I'm converting a .Net 4.7 application to .Net Core 3.1. I am updating the localization part. I followed some examples like localization-in-asp-net-core-3-1-mvc.
It works fine but I didn't find a way to make the UrlHelper.Action working without precise the culture. I would like to set automatically the culture parameter. It should come from userclaims, or previous request culture, or default culture.
For example, if the URL is "/Home/Contact", the UrlHelper or HtmlHelper generated a link will be /Home/About. If the current URL is "/en/Home/Contact", the link will be generated as "/en/Home/About". If the user is authenticated it should be "/userCulture/Home/About".
But I can not force my route template only "{culture=en}/{controller=Home}/{action=Welcome}/{id?}" because root url must accessible and the API urls should stay lile "api/somestufff".
Startup.cs :
var supportedCultures = CultureHelper.Cultures.Select(a => new CultureInfo(a)).ToArray();
var requestLocalizationOptions = new RequestLocalizationOptions();
requestLocalizationOptions.SupportedCultures = supportedCultures;
services.AddLocalization();
services.Configure<RequestLocalizationOptions>(options =>
{
options.DefaultRequestCulture = new RequestCulture("fr", "fr");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders.Insert(0, new RouteValueRequestCultureProvider() { Options = requestLocalizationOptions });
});
//......................................
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "culture-route", pattern: "{culture=en}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
});
Controller :
[Route("api/somestufff")]
public async Task<IActionResult> Somestufff()
{ ... }
[Route("{culture:length(2)}/items/{number:int}/{permalink?}")]
public async Task<IActionResult> DisplayItem(int number, string permalink)
{ ... }
Razor Page :
Url.Action("DisplayItem", "MyController",new { culture = ViewBag.Culture as string, number = 123, permalink = "permalink1235" })
// => /en/items/123/permalink1235 OK
Url.Action("DisplayItem", "MyController",new { number = 123, permalink = "permalink1235" })
// => /en/MyController/DisplayItem?number=123&permalink=permalink1235 KO
Is there a way to make Urlhelper add the culture from the current context if it's missing?
Since you are in .NET Core, you should replace the Url.Action
methods by using Tag helpers.
By using tag helpers, you can use something like this:
<a asp-controller="MyController" asp-action="DisplayItem" asp-route-number="123">Click</a>
This obviously doesn't solve the culture
in your route, which we will solve in a minute.
The current tag holder that processes the asp-
attributes on the a
tag is done by the AnchorTagHelper
class in Microsoft.AspNetCore.Mvc.TagHelpers
.
We can inherit that class and add functionality about handling the culture
.
CultureAnchorTagHelper
in your MVC project under /TagHelpers
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace YourMvcName.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
{
public CultureAnchorTagHelper(IHttpContextAccessor contextAccessor, IHtmlGenerator generator) :
base(generator)
{
this.contextAccessor = contextAccessor;
}
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 const string Href = "href";
private readonly IHttpContextAccessor contextAccessor;
private readonly string defaultRequestCulture = "en";
public override void Process(TagHelperContext context, TagHelperOutput output)
{
// Get the culture from the route values
var culture = (string)contextAccessor.HttpContext.Request.RouteValues["culture"];
// Set the culture in the route values if it is not null
if (culture != null && culture != defaultRequestCulture)
{
// Remove the 'href' just in case
output.Attributes.RemoveAll("href");
RouteValues["culture"] = culture;
}
// Call the base class for all other functionality, we've only added the culture route value.
// Because the route has the `{culture=en}`, the tag helper knows what to do with that route value.
base.Process(context, output);
}
}
}
Shared/_ViewImports.cshtml
to remove the current AnchorTagHelper
, and use our newly created tag helper as follows:@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.AnchorTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, YourMvcName
With all the changed applied, the culture will be filled in when you use the tag helper.