在Razor + Blazor组件中使用URL路径进行定位



我想建立一个ASP。. NET Razor应用程序,包含Razor页面和一些Blazor组件,站点内容根据URL中的语言进行本地化。

例如,/en/home/fr/home将有一个基于语言呈现内容的后台页面。

实现这个的方法是什么?

AspNetCore.Mvc.Localization有我们需要的。

_ViewImports.cshtml中,我们可以注入一个IViewLocalizer,它将为相应的页面抓取.resx文件。

@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer

现在Localizer可以在我们所有的页面中使用。

例如:Index.cshtml

@page
@model IndexModel
@{
ViewData["Title"] = @Localizer["Title"];
}
<h1>@Localizer["Header"]</h1>
<section>
<p>@Localizer["Welcome", User.Identity.Name]</p>
@Localizer["Learn"]
<a asp-page="Page1">@Localizer["SomePage"]</a>
<a asp-page="Dogs/Index">@Localizer["LinkDogs"]</a>
</section>

一旦创建了resx文件,页面标题、页眉和内容就被本地化了。

需要创建Resources/Pages/Index.resxResources/Pages/Index.fr.resx。有一个VSCode扩展可用于此,因为这些文件只是丑陋的XML。

字符串可以参数化。在Index.cshtml的例子中,"Welcome"="Howdy {0}"@Localizer["Welcome", User.Identity.Name]引用,用户名将被替换为{0}

Startup.cs中,我们还需要添加一些设置。

services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
}); // new
services.AddRazorPages()
.AddRazorRuntimeCompilation()
.AddViewLocalization(); // new
services.AddServerSideBlazor();

但是这只允许访问.cshtml文件中的Localizer。我们的页面看起来仍然像/home而不是/en/home

为了解决这个问题,我们将添加一个IPageRouteModelConvention修改页面模板,将{culture}我们所有的页面。

Startup.cs中,我们需要在razor配置时添加约定。

services.AddRazorPages(options =>
{
options.Conventions.Add(new CultureTemplatePageRouteModelConvention());
})

我在Middleware/文件夹下创建了CultureTemplatePageRouteModelConvention.cs,但是你可以把它放在任何地方(不确定它是否"技术上")。中间件?).

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.Extensions.Logging;
namespace app.Middleware
{
public class CultureTemplatePageRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
// For each page Razor has detected
foreach (var selector in model.Selectors)
{
// Grab the template string
var template = selector.AttributeRouteModel.Template;
// Skip the MicrosoftIdentity pages
if (template.StartsWith("MicrosoftIdentity")) continue;
// Prepend the /{culture?}/ route value to allow for route-based localization
selector.AttributeRouteModel.Template = AttributeRouteModel.CombineTemplates("{culture?}", template);
}
}
}
}

现在去/en/home应该解决,/home不应该。但如果你去/fr/home,你会注意到它仍然使用英文resx文件。这是因为区域性没有基于URL进行更新。

要解决这个问题,需要对Startup.cs进行更多的修改。

Configure方法中,我们将添加
app.UseRequestLocalization();

ConfigureServices下,我们将配置请求本地化选项。这将包括添加一个RequestCultureProvider,用于确定每个请求的Culture

services.Configure<RequestLocalizationOptions>(options =>
{
options.SetDefaultCulture("en");
options.AddSupportedCultures("en", "fr");
options.AddSupportedUICultures("en", "fr");
options.FallBackToParentCultures = true;
options.RequestCultureProviders.Remove(typeof(AcceptLanguageHeaderRequestCultureProvider));
options.RequestCultureProviders.Insert(0, new Middleware.RouteDataRequestCultureProvider() { Options = options });
});

它使用一个扩展方法来删除默认的接受语言头区域性提供程序

using System;
using System.Collections.Generic;
using System.Linq;
namespace app.Extensions
{
public static class ListExtensions {
public static void Remove<T>(this IList<T> list, Type type)
{
var items = list.Where(x => x.GetType() == type).ToList();
items.ForEach(x => list.Remove(x));
}
}
}

更重要的是,我们需要创建刚刚添加到列表中的RouteDataRequestCultureProvider

Middleware/RouteDataRequestCultureProvider.cs

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
namespace app.Middleware
{
public class RouteDataRequestCultureProvider : RequestCultureProvider
{
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
string routeCulture = (string)httpContext.Request.RouteValues["culture"];
string urlCulture = httpContext.Request.Path.Value.Split('/')[1];
// Culture provided in route values
if (IsSupportedCulture(routeCulture))
{
return Task.FromResult(new ProviderCultureResult(routeCulture));
}
// Culture provided in URL
else if (IsSupportedCulture(urlCulture))
{
return Task.FromResult(new ProviderCultureResult(urlCulture));
}
else
// Use default culture
{
return Task.FromResult(new ProviderCultureResult(DefaultCulture));
}
}
/**
* Culture must be in the list of supported cultures
*/
private bool IsSupportedCulture(string lang) =>
!string.IsNullOrEmpty(lang)
&& Options.SupportedCultures.Any(x =>
x.TwoLetterISOLanguageName.Equals(
lang,
StringComparison.InvariantCultureIgnoreCase
)
);
private string DefaultCulture => Options.DefaultRequestCulture.Culture.TwoLetterISOLanguageName;
}
}

注意,我们检查RouteValues["culture"]在这个提供程序中,当该值实际上还没有出现。这是因为我们需要另一个中间件来让Blazor正常工作。但是现在,至少我们的页面将从URL中应用正确的文化,这将允许/fr/使用正确的Index.fr.resx而不是Index.resx

另一个问题是,asp-page标记帮助器不能工作,除非您还指定asp-route-culture与用户的当前区域性。这很糟糕,所以我们将重写标记帮助器,每次只复制区域性。

Inside_ViewImports.cshtml

@* Override anchor tag helpers with our own to ensure URL culture is persisted *@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.AnchorTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, app

,在TagHelpders/CultureAnchorTagHelper.cs下面加上

using System;
using app.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

// https://stackoverflow.com/a/59283426/11141271
// https://stackoverflow.com/questions/60397920/razorpages-anchortaghelper-does-not-remove-index-from-href
// https://talagozis.com/en/asp-net-core/razor-pages-localisation-seo-friendly-urls
namespace app.TagHelpers
{
[HtmlTargetElement("a", Attributes = ActionAttributeName)]
[HtmlTargetElement("a", Attributes = ControllerAttributeName)]
[HtmlTargetElement("a", Attributes = AreaAttributeName)]
[HtmlTargetElement("a", Attributes = PageAttributeName)]
[HtmlTargetElement("a", Attributes = PageHandlerAttributeName)]
[HtmlTargetElement("a", Attributes = FragmentAttributeName)]
[HtmlTargetElement("a", Attributes = HostAttributeName)]
[HtmlTargetElement("a", Attributes = ProtocolAttributeName)]
[HtmlTargetElement("a", Attributes = RouteAttributeName)]
[HtmlTargetElement("a", Attributes = RouteValuesDictionaryName)]
[HtmlTargetElement("a", Attributes = RouteValuesPrefix + "*")]
public class CultureAnchorTagHelper : AnchorTagHelper
{
private const string ActionAttributeName = "asp-action";
private const string ControllerAttributeName = "asp-controller";
private const string AreaAttributeName = "asp-area";
private const string PageAttributeName = "asp-page";
private const string PageHandlerAttributeName = "asp-page-handler";
private const string FragmentAttributeName = "asp-fragment";
private const string HostAttributeName = "asp-host";
private const string ProtocolAttributeName = "asp-protocol";
private const string RouteAttributeName = "asp-route";
private const string RouteValuesDictionaryName = "asp-all-route-data";
private const string RouteValuesPrefix = "asp-route-";
private readonly IHttpContextAccessor _contextAccessor;
public CultureAnchorTagHelper(IHttpContextAccessor contextAccessor, IHtmlGenerator generator) :
base(generator)
{
this._contextAccessor = contextAccessor;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var culture = _contextAccessor.HttpContext.Request.GetCulture();
RouteValues["culture"] = culture;
base.Process(context, output);
}
}
}

使用扩展方法从HttpRequest

获取当前区域性
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
namespace app.Extensions
{
public static class HttpRequestExtensions
{
public static string GetCulture(this HttpRequest request)
{
return request.HttpContext.Features.Get<IRequestCultureFeature>()
.RequestCulture.Culture.TwoLetterISOLanguageName;
}
}
}

要确保当前上下文的依赖注入工作,我们需要修改Startup.cs

// Used by the culture anchor tag helper
services.AddHttpContextAccessor();

现在我们可以使用标签帮助器,而不会出错。

的例子:

<a asp-page="Page1">@Localizer["SomePage"]</a>

正常页面工作后,现在我们可以翻译Blazor组件了。

_Imports.razor内部,我们将添加

@using Microsoft.Extensions.Localization

myComponent.razor中,我们将添加

@inject IStringLocalizer<myComponent> Localizer

现在我们可以使用<h1>@Localizer["Header"]</h1>就像在我们的正常页面。但现在有另一个问题:我们的Blazor组件没有正确设置他们的文化。组件将/_blazor视为它们的URL,而不是页面的URL。注释掉_Layout.cshtml<head>元素中的<base href="~/">,使Blazor尝试击中/en/_blazor而不是/_blazor。这将得到一个404,但我们会解决这个问题。

Startup.cs内部,我们将注册另一个中间件。

app.Use(new BlazorCultureExtractor().Handle);

这叫app.UseEndpoints之前应该和app.UseRequestLocalization()电话。

Middleware/BlazorCultureExtractor.cs

using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using app.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
namespace app.Middleware
{
public class BlazorCultureExtractor
{
private readonly Regex BlazorRequestPattern = new Regex("^/(.*?)(/_blazor.*)$");
public async Task Handle(HttpContext context, Func<Task> next)
{
var match = BlazorRequestPattern.Match(context.Request.Path.Value);
// If it's a request for a blazor endpoint
if (match.Success)
{
// Grab the culture from the URL and store it in RouteValues
// This allows IStringLocalizers to use the correct culture in Blazor components
context.Request.RouteValues["culture"] = match.Groups[1].Value;
// Remove the /culture/ from the URL so that Blazor works properly
context.Request.Path = match.Groups[2].Value;
}
await next();
}
}
}

中间件将检查路由是否试图到达/en/_blazor,将RouteValues["culture"]的值设置为en,并在进一步处理之前将路径重写为/_blazor。这就把lang放到了我们的RequestCultureProvider使用的路由值中,同时也修复了blazor试图命中我们的本地化路由的404。

_Layout.cshtml中我也使用

<script src="~/_framework/blazor.server.js"></script>"

确保对blazor脚本的请求到达正确的路径,而不是到达/en/_framework/...。注意src属性前面的~/

闭幕词

如果你想要纯基于url的本地化,而不是微软提倡的奇怪的cookie的东西,那么这是很多工作。

我没有在Blazor页面中寻找这个问题。,我只是坚持使用组件现在。

<component>
@(await Html.RenderComponentAsync<MyCounterComponent>(RenderMode.Server))
</component>

相关内容

  • 没有找到相关文章

最新更新