While the approach of setting the culture for the request relies on setting query string values to generate locale-specific URLs works, it is not the recommended approach. Google recommend a number of other approaches, one of which involves making use of sub-directories for locale-specific versions of the site content. Sub-directories should be named for the culture code that each version represents, so you end up with URL segments named after the specific culture:
This is all well and good, but it will result in an application with a LOT of duplication, effectively a completely new version for each supported culture in each sub-directory. ASP.NET Core is a lot more clever than that. It supports the notion of route data - application data passed as a segment in the URL. ASP.NET Core also provides a request culture provider that works with route data - the
RouteDataRequestCultureProvider. This article builds on the application introduced in the previous articles to demonstrate how to make best use of this component.
The application in this article is the same one that has featured in the previous articles. It's built using the standard Razor Pages 3.1 project template with no authentication. Many of the concepts in this article were originally introduced in the previous articles, so you should read those first if you haven't already.
You need to add a route data item representing the culture to all routes. You could apply this as a route template to each page individually, but that approach is not really scalable. A better approach is to use a PageRouteModelConvention to add additional route templates to those that are generated by default for all pages in the application. The following
PageRouteModelConvention creates a new attribute route for each existing route by combining a placeholder for a route data item named
culture with the existing attribute route. The
Order property of the
AttributeRouteModel is set to
-1 to ensure that it is processed first:
By convention, all RequestCultureProviders look for items with a key of
ui-culture by default to establish the culture of the current request. You can change this through configuration.
PageRouteModelConvention is registered in
ConfigureServices as part of RazorPagesOptions:
If you run the application at this stage, you can test that the new attribute routes are working by manually inserting a culture code into the
Routes are resolved correctly, but the culture for the request is not affected by the presence of the
culture route data value. The
RouteDataRequestCultureProvider needs to be registered as part of the
The code above is from the first article in the series, with the addition of the final line which registers the route data request culture provider, and inserts it as the first request provider to be used for culture matching. Now if you run the application again, you should see that request provider working:
Generating Links With Tag Helpers
The links at the top of the page are generated by anchor tag helpers in the layout page. Prior to Razor Pages 2.2, anchor tag helpers would make use of ambient route values to generate URLs. If the target page and the current request share the same route value within their attribute route, values from the current request are automatically reused within URL generation. This is very useful when every page shares the
culture route value as in this example. However, this behaviour also has some unwanted side effects, and was largely removed with the introduction of endpoint routing in ASP.NET Core 2.2. Ambient route values are now only used if the target page specified by the
asp-page attribute in the anchor tag is the same as the current request. You can see this by navigating to the Contact page, selecting a culture (French, in my case) and looking at the generated HTML for the links:
Notice that only the contact page's anchor tag includes the ambient route value for culture. It is not included in the links generated for the home page or the privacy page. The culture route value needs to be added explicitly:
Now all links include the culture:
However, just like the prospect of manually generating route templates for each page, this is equally unscalable for large applications. What you can do instead is to implement your own anchor tag helper which derives from the framework version and is responsible for adding the culture route value as demonstrated in this Github issue.
The code for the tag helper follows:
Then you need to remove the existing anchor tag helper from the application, which is achieved by applying the removeTagHelper directive in _ViewImports
Finally, if you have been following the entire series of articles that look at localisation in Razor Pages, you may want to alter the client side code in the culture switcher ViewComponent introduced in the first article. It currently works with the query string request culture provider, so it requires amending to work with a URL segment rather than a query string value:
RouteDataRequestCultureProvider is an essential component if you want to follow the recommendations for working with optimal locale-specific URLs in a Razor Pages application. It is not registered as one of the default request culture providers so it needs to be configured separately. This article has also shown how to use a PageRouteModelConvention to centrally manage locale-specific routing across an application, and how to circumvent the removal of ambient route values in ASP.NET Core 2.2 and still use an anchor tag helper to generate routes that incorporate the current culture for all pages in the application.