ASP.NET Core多语言路由本地化实现技术咨询
Got it, let's tackle this problem of implementing Laravel-like localized route translations in ASP.NET Core. Since you already have the language route constraint and basic setup working, the key part now is using IApplicationModelConvention alongside route parameter transformers to rewrite route segments based on the active language. Here's a step-by-step breakdown tailored to your setup:
IApplicationModelConvention 1. Set Up Route Translation Resources
First, create localized resources to map original route segments to their translated counterparts. We'll use .resx files for this:
- Create a
Resourcesfolder in your project - Add
RouteTranslations.en.resx(base language):Name Value Product product Details details About about - Add
RouteTranslations.fr.resx(French translations):Name Value Product produit Details détails About à-propos
Register localization services in Program.cs:
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); builder.Services.AddHttpContextAccessor(); // Needed to access current request culture
2. Build Route Parameter Transformers
These transformers handle translating route segments when generating URLs (outbound) and converting translated segments back to original values for routing (inbound).
public class LocalizedControllerTransformer : IOutboundParameterTransformer, IInboundParameterTransformer { private readonly IStringLocalizer<RouteTranslations> _localizer; private readonly IHttpContextAccessor _httpContextAccessor; private readonly Dictionary<string, string> _reverseTranslationMap; public LocalizedControllerTransformer(IStringLocalizer<RouteTranslations> localizer, IHttpContextAccessor httpContextAccessor) { _localizer = localizer; _httpContextAccessor = httpContextAccessor; // Pre-build reverse map for inbound translation lookup _reverseTranslationMap = _localizer.GetAllStrings() .GroupBy(s => s.Value) .ToDictionary(g => g.Key, g => g.First().Name); } public string TransformOutbound(object value) { if (value == null) return null; // Strip "Controller" suffix from controller name var controllerName = value.ToString(); var baseName = controllerName.EndsWith("Controller") ? controllerName[0..^10] : controllerName; // Get active culture from request var httpContext = _httpContextAccessor.HttpContext; var culture = httpContext?.Request.RouteValues["culture"]?.ToString() ?? Thread.CurrentThread.CurrentCulture.Name; // Return translated segment using var cultureScope = new CultureInfo(culture); return _localizer[baseName].Value; } public object TransformInbound(string value) { // Convert translated segment back to original controller name if (_reverseTranslationMap.TryGetValue(value, out var originalName)) { return $"{originalName}Controller"; } // Fallback if translation not found return $"{value}Controller"; } } // Repeat similar logic for action segments if needed public class LocalizedActionTransformer : IOutboundParameterTransformer, IInboundParameterTransformer { private readonly IStringLocalizer<RouteTranslations> _localizer; private readonly IHttpContextAccessor _httpContextAccessor; private readonly Dictionary<string, string> _reverseTranslationMap; public LocalizedActionTransformer(IStringLocalizer<RouteTranslations> localizer, IHttpContextAccessor httpContextAccessor) { _localizer = localizer; _httpContextAccessor = httpContextAccessor; _reverseTranslationMap = _localizer.GetAllStrings() .GroupBy(s => s.Value) .ToDictionary(g => g.Key, g => g.First().Name); } public string TransformOutbound(object value) { if (value == null) return null; var actionName = value.ToString(); var httpContext = _httpContextAccessor.HttpContext; var culture = httpContext?.Request.RouteValues["culture"]?.ToString() ?? Thread.CurrentThread.CurrentCulture.Name; using var cultureScope = new CultureInfo(culture); return _localizer[actionName].Value; } public object TransformInbound(string value) { if (_reverseTranslationMap.TryGetValue(value, out var originalName)) { return originalName; } return value; } }
3. Implement IApplicationModelConvention
This convention will attach our transformers to controller and action route parameters at application startup.
public class LocalizedRouteConvention : IApplicationModelConvention { private readonly IServiceProvider _serviceProvider; public LocalizedRouteConvention(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public void Apply(ApplicationModel application) { // Resolve dependencies from service provider var localizer = _serviceProvider.GetRequiredService<IStringLocalizer<RouteTranslations>>(); var httpContextAccessor = _serviceProvider.GetRequiredService<IHttpContextAccessor>(); var controllerTransformer = new LocalizedControllerTransformer(localizer, httpContextAccessor); var actionTransformer = new LocalizedActionTransformer(localizer, httpContextAccessor); // Attach transformers to all controller and action route parameters foreach (var controller in application.Controllers) { foreach (var selector in controller.Selectors) { var controllerParam = selector.AttributeRouteModel.TemplateParameters .FirstOrDefault(p => p.Name == "controller"); if (controllerParam != null) { controllerParam.OutboundTransformer = controllerTransformer; controllerParam.InboundTransformer = controllerTransformer; } var actionParam = selector.AttributeRouteModel.TemplateParameters .FirstOrDefault(p => p.Name == "action"); if (actionParam != null) { actionParam.OutboundTransformer = actionTransformer; actionParam.InboundTransformer = actionTransformer; } } } } }
4. Register the Convention and Route Constraint
Update Program.cs to add your convention and existing language route constraint:
builder.Services.AddControllersWithViews(options => { // Register your existing language route constraint options.RouteOptions.ConstraintMap.Add("culture", typeof(LanguageRouteConstraint)); // Add the localized route convention options.Conventions.Add(serviceProvider => new LocalizedRouteConvention(serviceProvider)); }); // Configure request localization (adjust supported cultures as needed) builder.Services.Configure<RequestLocalizationOptions>(options => { var supportedCultures = new[] { "en", "fr" }; options.DefaultRequestCulture = new RequestCulture("en"); options.SupportedCultures = supportedCultures.Select(c => new CultureInfo(c)).ToList(); options.SupportedUICultures = supportedCultures.Select(c => new CultureInfo(c)).ToList(); });
5. Define Your Controllers with Localized Routes
Use standard route attributes with the culture constraint:
[Route("{culture:culture}/[controller]")] public class ProductController : Controller { [Route("[action]/{id:int}")] public IActionResult Details(int id) { // Generate localized URL: Url.Action("Details", "Product", new { culture = "fr" }) returns /fr/produit/détails/1 return View(); } } [Route("{culture:culture}/[controller]")] public class AboutController : Controller { public IActionResult Index() { // URL for French culture: /fr/à-propos return View(); } }
How It Works
- Inbound Routing: When a user visits
/fr/produit/détails/1, the inbound transformers convertproduitback toProductControlleranddétailsback toDetails, routing to the correct action. - Outbound URL Generation: When you call
Url.Actionor use tag helpers like<a asp-action="Details" asp-controller="Product" asp-route-culture="fr">, the outbound transformers translate route segments to the active language.
内容的提问来源于stack exchange,提问作者frank_lbt

