.NET core Razor 中基于 URL 的本地化 - 如何操作?

问题描述 投票:0回答:1

我正在构建一个 .NET 8.0 Razor Page 站点,其中使用如下 URL 进行本地化/国际化:

example.org/us // United States
example.org/fr // France
example.org/pt // Portugal
example.org/se // Sweden
...

example.org/us/contact  // contact page for USA (text is displayed in English)
example.org/fr/contact  // contact page for France (text is displayed in French)

即URL 包含国家/地区的 ISO 3166-1 alpha-2 代码(例如 us、fr、pt、se 等)。文本来自标准资源文件 (resx)。

我的大部分实现都有效,但我有一个小问题,即不存在的页面返回

StatusCode 200
而不是
404
。我用 Google 搜索过,查看了其他示例,但它们要么已过时/不再工作,要么仅适用于 MVC。这个问题是针对使用
@page "/..."
指令的 Razor Pages。

我需要有关实施的帮助和意见。

我所有的 Razor 页面的页面路由中都有

{lang}
。联系页面路径将为
/{lang}/contact
{lang}
指 ISO 3166-1 alpha-2 代码。

以下代码将

{lang}
参数添加到 Web 应用程序中的所有页面:

public class CultureTemplatePageRouteModelConvention: IPageRouteModelConvention
{
    public void Apply(PageRouteModel model)
    {
        foreach (var selector in model.Selectors)
        {
            var template = selector.AttributeRouteModel.Template;

            if (template.StartsWith("MicrosoftIdentity")) continue;  // Skip MicrosoftIdentity pages

            // Prepend {lang}/ to the page routes allow for route-based localization
            selector.AttributeRouteModel.Order = -1;
            selector.AttributeRouteModel.Template = AttributeRouteModel.CombineTemplates("{lang}", template);
        }
    }
}

...在

program.cs
中实例化为:

builder.Services.AddRazorPages(options =>
{
    // decorate all page routes with {lang} e.g. @page "/{lang}..."
    options.Conventions.Add(new CultureTemplatePageRouteModelConvention());
});

为了设置所请求页面的实际文化,我实现了自定义

RequestCultureProvider
,其中包含以下内容:

public class CustomRouteDataRequestCultureProvider : RequestCultureProvider
{
    public SupportedAppLanguages SupportedAppLanguages;
    public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
    {

        var lang = (string)httpContext.GetRouteValue("lang");
        var urlCulture = httpContext.Request.Path.Value.Split('/')[1];

        string[] container = [lang, urlCulture];
        
        var culture = SupportedAppLanguages.Dict.Values.SingleOrDefault(langInApp => container.Contains(langInApp.Icc) );

        if (culture != null)
        {
            return Task.FromResult(new ProviderCultureResult(culture.Culture));
        }

        // if no match, return 404
        httpContext.Response.StatusCode = StatusCodes.Status404NotFound;
        return Task.FromResult(new ProviderCultureResult(Options.DefaultRequestCulture.Culture.TwoLetterISOLanguageName));
    }
}

...通过

program.cs
实例化:

// get all languages supported by app via `appsettings.json`:
var supportedAppLanguages = builder.Configuration.GetSection("SupportedAppLanguages").Get<SupportedAppLanguages>();
var supportedCultures = supportedAppLanguages.Dict.Values.Select(langInApp => new CultureInfo(langInApp.Culture)).ToList();

builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    options.DefaultRequestCulture = new RequestCulture(culture: "en-us", uiCulture: "en-us");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;
    options.FallBackToParentCultures = true;

    options.RequestCultureProviders.Clear();
    options.RequestCultureProviders.Insert(0, new CustomRouteDataRequestCultureProvider() { Options = options, SupportedAppLanguages = supportedAppLanguages });
});

为了防止用户输入奇怪的区域设置/国家/地区代码(并混淆搜索引擎/防止糟糕的 SEO),我创建了一个 MiddlewareFilter 来处理此类情况:

public class RouteConstraintMiddleware(RequestDelegate next, SupportedAppLanguages supportedAppLanguages) 
{
    public async Task Invoke(HttpContext context)
    {

        // The context.Response.StatusCode for pages like example.org/us/non-existing is 200 and not the correct 404.
        // How can I implement a proper check?
        if (context.Response.StatusCode == StatusCodes.Status404NotFound) return; 
        if (string.IsNullOrEmpty(context?.GetRouteValue("lang")?.ToString())) return;

        // check for a match 
        var lang = context.GetRouteValue("lang").ToString();
        var supported = supportedAppLanguages.Dict.Values.Any(langInApp => lang == langInApp.Icc);
       
        if (!supported)
        {
            context.Response.StatusCode = StatusCodes.Status404NotFound;
            return;
        }

        await next(context);
    }
}

应用程序的当前状态:

example.org/us       // 200. (correct)
example.org/fr       // 200. (correct)
example.org/nonsense/contact // 404. (correct)
example.org/nonsense         // 404. (correct)
example.org/us/non-existing  // 200. (wrong, must return 404)

我该如何解决这个问题?这也是一个很好的实现吗(对我来说似乎有点笨拙)? 能否通过更好的方式解决?此处提供了简化的 Github 重现:https://github.com/shapeh/TestLocalization

希望大家指点。

c# asp.net-core razor-pages asp.net-core-localization iso-3166
1个回答
0
投票

问题是由您的

RouteConstraintMiddleware

引起的

这条线会缩短你的中间件pineline

if (string.IsNullOrEmpty(context?.GetRouteValue("lang")?.ToString())) return;

如果没有

await next(context);
,它不会进入下一个中间件,在该中间件中你会得到 404 响应

在我看来,你可以尝试在你的

{lang}
部分添加一个RouteConstraint,默认的Route中间件会为你处理它,这样你就不需要添加另一个中间件

一个最小的例子:

public class CultureConstraint : IRouteConstraint
{
   
    public bool Match(
        HttpContext? httpContext, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.TryGetValue(routeKey, out var routeValue))
        {
            return false;
        }

        var supportedAppLanguages = httpContext.RequestServices.GetService<IConfiguration>().GetSection("SupportedAppLanguages").Get<SupportedAppLanguages>();
        


        var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);

        

        return supportedAppLanguages.Dict.Values.Select(x=>x.Icc).Contains(routeValueString);
    }
}

注册约束:

builder.Services.AddRouting(options =>
{
    options.LowercaseUrls = true;
    options.AppendTrailingSlash = false;
    options.ConstraintMap.Add("cultureconstraint", typeof(CultureConstraint));
});

修改

CultureTemplatePageRouteModelConvention

public class CultureTemplatePageRouteModelConvention: IPageRouteModelConvention
{
    public void Apply(PageRouteModel model)
    {
        foreach (var selector in model.Selectors)
        {
            var template = selector.AttributeRouteModel.Template;

            if (template.StartsWith("MicrosoftIdentity")) continue;  // Skip MicrosoftIdentity pages

            // Prepend {lang}/ to the page routes allow for route-based localization
            selector.AttributeRouteModel.Order = -1;
            selector.AttributeRouteModel.Template = AttributeRouteModel.CombineTemplates("{lang:cultureconstraint}", template);
        }
    }
}

结果:

© www.soinside.com 2019 - 2024. All rights reserved.