ASP.NET Core 7.0 Razor Pages – 创建动态路由

huangapple go评论145阅读模式
英文:

ASP.NET Core 7.0 Razor Pages - Create dynamic routes

问题

背景故事关于我的项目

我试图在我的网站上添加多种语言。为此,我在URL路径中使用区域设置来区分不同的语言。默认网站(英语)位于http://localhost:5000/,而德语页面位于http://localhost:5000/de-de/。我已经实现了在会话中存储的区域设置。

用户可以通过表单在网站上更改他们的区域设置。如果用户将他们的语言设置为德语,并尝试访问http://localhost:5000/,它会重定向到http://localhost:5000/de-de上的德语页面。这对于将用户重定向到正确的路径非常有效。这里没有问题。

这也适用于例如用户将其区域设置设置为德语并尝试输入页面:http://localhost:5000/Pictures,它会将他们重定向到http://localhost:5000/de-de/Pictures。一切都没有问题。

目前,我还没有为特定区域设置创建任何页面。我基本上只是将它设置为重定向到起始页面作为备用。由于/de-de/实际上不存在于网站中,它只是使用app.UseStatusCodePagesWithReExecute("/");在我的Program.cs文件中加载起始页面。

我的问题

由于目前我只是将用户重定向到备用页面(因为在我的Pages文件夹中没有de-de文件夹),它只会加载英语起始页面的内容。所以如果我访问http://localhost:5000/de-de/Pictures,我实际上看到的是http://localhost:5000/的内容。这不好。

假设我在Pages文件夹中有两个页面。它们分别称为Index.cshtmlPictures.cshtml。我可以使用默认区域设置(英语)访问以下URL:http://localhost:5000/http://localhost:5000/Pictures

**但是我如何设置它,以便它将加载相应的页面,不包括URL中的区域设置?**换句话说,如果用户将其区域设置设置为德语并访问http://localhost:5000/de-de/Pictures,我如何使它加载来自http://localhost:5000/Pictures的HTML?

我还需要使这对我以后创建的任何页面起作用,例如/Videos/About等等。它需要是动态的。

不要担心它的内容是否为英文。我以后可以解决这个问题。我只是想实现的是,不创建Pages\de-de子目录,它应该自动加载页面的内容,不包括URL中的区域设置。

将来,我希望将20多种语言添加到网站中。我不想有20多个包含相同HTML文件的文件夹。例如,文件夹和文件结构如下:

Pages
	Index.cshtml
	Pictures.cshtml
	/de-de/
		Index.cshtml
		Pictures.cshtml
	/es-es/
		Index.cshtml
		Pictures.cshtml
	/fr-fr/
		Index.cshtml
		Pictures.cshtml

上面的文件夹结构示例将很难维护。我只想在我的Pages目录中有两个文件(Index.cshtmlPictures.cshtml)。并路由所有其他区域设置以加载这些文件。因此,如果我更改了Pictures.cshtml文件,它将对所有区域设置进行更新。

我的中间件

我设置了一个名为LocaleMiddleware的中间件,它检查名为"locale"的会话变量并相应地重定向用户。我不确定中间件内是否可以执行任何操作。所有中间件只是将用户重定向到正确的URL。

我需要解决的问题是使该URL加载不包含其中的区域设置的页面内容(HTML)。

public class LocaleMiddleware
{
    private readonly RequestDelegate _next;

    public LocaleMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
		// 排除/api和/auth路径的重定向
        if (context.Request.Path.StartsWithSegments("/api") || context.Request.Path.StartsWithSegments("/auth"))
        {
            await _next(context);
        }
        else
        {
            // URL的正则表达式模式([xx-yy])
            string pattern = @"/([a-z]{2})-([a-z]{2})";

            // 从会话中获取区域设置
            string? locale = context.Session.GetString("locale");

            // 如果没有区域设置,或者区域设置为默认值"en-us"
			// 从URL中删除任何潜在的区域设置并将它们重定向到"root"路径
			// 例如/de-de/Pictures将重置为/Pictures
            if (String.IsNullOrEmpty(locale) || locale == "en-us")
            {
                if (Regex.IsMatch(context.Request.Path, pattern))
                {
                    // 重定向到不包含区域设置的页面
                    string newPath = Regex.Replace(context.Request.Path, pattern, "");
                    if (String.IsNullOrEmpty(newPath)) newPath = "/";
                    context.Response.Redirect($"{newPath}");
                    return;
                }
            }

            // 如果设置了区域设置且不是默认的"en-us",并且区域设置当前不在URL中
			// 那么它应该将它们重定向到正确的区域设置URL路径
			// 例如,如果用户将区域设置设置为"de-de"并加载/Pictures,它将重定向到/de-de/Pictures
            if (!String.IsNullOrEmpty(locale) && locale != "en-us" && !context.Request.Path.ToString().Contains(locale))
            {
                // 重定向到区域设置路径
                string oldPath = Regex.Replace(context.Request.Path, pattern, "");
                context.Response.Redirect($"/{locale}{oldPath}");
                return;
            }

            await _next(context);
        }
    }


<details>
<summary>英文:</summary>

## Background story about my project
I am trying to add multiple languages to my website. To do that I use locales in the path of the URL to differentiate the various languages. The default website (in English) is located at `http://localhost:5000/` meanwhile the German page is located at `http://localhost:5000/de-de/`. I have made it possible to set a locale that is stored on the session.

Users can change their locale on the website through a form. If a user has set their language to German and they try to visit `http://localhost:5000/` it will redirect to the German page at `http://localhost:5000/de-de`. This works great for redirecting the user to the correct path. Nothing wrong here.

This also works if for example the user has set their locale to German and they try to enter the page: `http://localhost:5000/Pictures` it will redirect them to `http://localhost:5000/de-de/Pictures`. No issues what-so-ever.

At the moment, I have not created any pages for the specific locales. What I have basically done is just set it to redirect to the start page as a fallback. Since `/de-de/` doesn&#39;t actually exist in the website, it just loads the start page using: `app.UseStatusCodePagesWithReExecute(&quot;/&quot;);` in my `Program.cs` file.

## My question
Since I am just redirecting thee user to a fallback page at the moment (due to the fact that there is no `de-de` folder in my `Pages` folder), it only loads the content of the English start page. So if I visit `http://localhost:5000/de-de/Pictures` I am actually seeing the content of `http://localhost:5000/`. This is not good.

Suppose I have two pages in my `Pages` folder. They are called `Index.cshtml` and `Pictures.cshtml`. I can visit them using the default locale (English) at the following URLs: `http://localhost:5000/` and `http://localhost:5000/Pictures`.

**But how can I set it so that it will load the corresponding page, excluding the locale in the URL?** In other words, if a user has set their locale to German and visits `http://localhost:5000/de-de/Pictures`, how can I make it load the HTML from `http://localhost:5000/Pictures`?

I also need this to work for any future pages I created, e.g. `/Videos` or `/About` and so on. It needs to be dynamic.

Do not worry about it being content in English. I can sort that out later. What I just want to achieve is that, without creating a subdirectory at `Pages\de-de`, it should automatically just load the content of the page, without the locale in the URL.

In the future, I want to add 20+ more languages to the website. I do not want to have 20+ folders, each containing the same HTML file. E.g. a folder and file structure like so:

Pages
Index.cshtml
Pictures.cshtml
/de-de/
Index.cshtml
Pictures.cshtml
/es-es/
Index.cshtml
Pictures.cshtml
/fr-fr/
Index.cshtml
Pictures.cshtml


The above example of a folder structure would be difficult to maintain. I simply want two files (`Index.cshtml` and `Pictures.cshtml`) inside my `Pages` directory. And route all other locales to load those files. So if I make a change to the `Pictures.cshtml` file it will update for all locales.
## My middleware
I&#39;ve set up a middleware called `LocaleMiddleware` which checks for the session variable called `&quot;locale&quot;` and redirects the user accordingly. I&#39;m not sure if there&#39;s anything that can be done inside the middleware. All the middleware does is to redirect the user to the correct URL.
The problem I need to resolve is to make that URL load the page content (HTML) from the path without locale inside it.

public class LocaleMiddleware
{
private readonly RequestDelegate _next;

public LocaleMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Exclude redirects for &quot;/api&quot; and &quot;/auth&quot; paths
if (context.Request.Path.StartsWithSegments(&quot;/api&quot;) || context.Request.Path.StartsWithSegments(&quot;/auth&quot;))
{
await _next(context);
}
else
{
// Regex pattern for URL ([xx-yy])
string pattern = @&quot;\/([a-z]{2})-([a-z]{2})&quot;;
// Get locale from session
string? locale = context.Session.GetString(&quot;locale&quot;);
// If no locale, or if locale is default &quot;en-us&quot;
// Remove any potential locales from URL and redirect them to &quot;root&quot; path
// E.g. /de-de/Pictures will be reset to /Pictures
if (String.IsNullOrEmpty(locale) || locale == &quot;en-us&quot;)
{
if (Regex.IsMatch(context.Request.Path, pattern))
{
// Redirect to page without locale in URL
string newPath = Regex.Replace(context.Request.Path, pattern, &quot;&quot;);
if (String.IsNullOrEmpty(newPath)) newPath = &quot;/&quot;;
context.Response.Redirect($&quot;{newPath}&quot;);
return;
}
}
// If locale is set and is not default &quot;en-us&quot; and locale is not currently in the URL
// Then it should redirect them to the correct locale URL path
// E.g. if the user has set locale to &quot;de-de&quot; and loads /Pictures it will redirect to /de-de/Pictures
if (!String.IsNullOrEmpty(locale) &amp;&amp; locale != &quot;en-us&quot; &amp;&amp; !context.Request.Path.ToString().Contains(locale))
{
// Redirect to locale path
string oldPath = Regex.Replace(context.Request.Path, pattern, &quot;&quot;);
context.Response.Redirect($&quot;/{locale}{oldPath}&quot;);
return;
}
await _next(context);
}
}

}

public static class RequestLocaleMiddlewareExtensions
{
public static IApplicationBuilder UseLocaleMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<LocaleMiddleware>();
}
}


## Tl/dr:
Basically, if a user has set their locale to `de-de`, I want the URL to be `http://localhost:5000/de-de/Pictures` but load the HTML content from `http://localhost:5000/Pictures`. And for all future paths as well, e.g. `/Videos`, `/Contact`.
In other words the logic would be:
  1. Check if the URL path matches Regex pattern for locales [xx-yy]
  2. If it does, setup a router to load HTML from the path EXCLUDING the locale

Where and how can I do this? In which files do I setup routing for that? And it needs to be dynamic.
</details>
# 答案1
**得分**: 1
在ASP.NET Core中有内置的本地化支持,您可以查看与文档[1]相关的内容。
根据文档和Mike Brind的文章,以下是一个最小示例:
在CultureTemplatePageRouteModelConvention.cs文件中:
```csharp
public class CultureTemplatePageRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
//modify -1 to 1 here
Order = 1,
Template = AttributeRouteModel.CombineTemplates("{culture?}", selector.AttributeRouteModel.Template),
}
});
}
}
}

在Program.cs文件中:

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

builder.Services.AddLocalization();
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    var supportedCultures = new[] { "en-us", "zh-cn" };
    options.SetDefaultCulture(supportedCultures[0])
        .AddSupportedCultures(supportedCultures)
        .AddSupportedUICultures(supportedCultures);
    options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider());
});

在PageModel.cs文件中:

public class MyPageModel : PageModel
{
    private readonly IStringLocalizer<MyPageModel> _localizer;

    public MyPageModel(IStringLocalizer<MyPageModel> localizer) 
    {
        _localizer = localizer;
    }
    
    [FromRoute]
    public string? culture { get; set; }

    public string? Hi { get; set; }
    public void OnGet()
    {
        Hi = _localizer["Hi"];
        if(!String.IsNullOrEmpty(culture))
        {
            Response.Cookies.Append(
         CookieRequestCultureProvider.DefaultCookieName,
         CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
         new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) });
        }
    }
}

在页面中:

@Model.Hi;

资源文件和其他相关部分请参考原文档1

英文:

There's build-in support for localization in asp.net core ,you could check the document related

based on the doc and Mike Brind's article,

a minimal example here:

public class CultureTemplatePageRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i &lt; selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
//modify -1 to 1 here
Order = 1,
Template = AttributeRouteModel.CombineTemplates(&quot;{culture?}&quot;, selector.AttributeRouteModel.Template),
}
});
}
}
}

in Program.cs:

builder.Services.AddRazorPages(options=&gt; {
options.Conventions.Add(new CultureTemplatePageRouteModelConvention()); 
});
builder.Services.AddLocalization();
builder.Services.Configure&lt;RequestLocalizationOptions&gt;(options =&gt;
{
var supportedCultures = new[] { &quot;en-us&quot;, &quot;zh-cn&quot; };
options.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider());
});

....

app.UseRouting();
app.UseRequestLocalization();

PageModel:

public class MyPageModel : PageModel
{
private readonly IStringLocalizer&lt;MyPageModel&gt; _localizer;
public MyPageModel(IStringLocalizer&lt;MyPageModel&gt; localizer) 
{
_localizer = localizer;
}
[FromRoute]
public string? culture { get; set; }
public string? Hi { get; set; }
public void OnGet()
{
Hi = _localizer[&quot;Hi&quot;];
if(!String.IsNullOrEmpty(culture))
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) });
}
}
}

Page:

@Model.Hi;

Resource files:
ASP.NET Core 7.0 Razor Pages – 创建动态路由

MyProject:

ASP.NET Core 7.0 Razor Pages – 创建动态路由

Result:

ASP.NET Core 7.0 Razor Pages – 创建动态路由

Modify resource file:

ASP.NET Core 7.0 Razor Pages – 创建动态路由

huangapple
  • 本文由 发表于 2023年7月31日 22:48:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/76804763.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定