ASP.NET MVC 5 culture in route and url

后端 未结 2 962
梦毁少年i
梦毁少年i 2020-11-22 00:08

I\'ve translated my mvc website, which is working great. If I select another language (Dutch or English) the content gets translated. This works because I set the culture in

2条回答
  •  执笔经年
    2020-11-22 00:27

    Default culture fix

    Incredible post by NightOwl888. There is something missing though - the normal(not localized) URL-generation attribute routes, which get added through reflection, also need a default culture parameter, otherwise you get a query parameter in the URL.

    ?culture=nl

    In order to avoid this, these changes must be made:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Mvc.Routing;
    using System.Web.Routing;
    
    namespace Endpoints.WebPublic.Infrastructure.Routing
    {
        public static class RouteCollectionExtensions
        {
            public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, object defaults, object constraints)
            {
                MapLocalizedMvcAttributeRoutes(routes, urlPrefix, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints));
            }
    
            public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, RouteValueDictionary defaults, RouteValueDictionary constraints)
            {
                var routeCollectionRouteType = Type.GetType("System.Web.Mvc.Routing.RouteCollectionRoute, System.Web.Mvc");
                var subRouteCollectionType = Type.GetType("System.Web.Mvc.Routing.SubRouteCollection, System.Web.Mvc");
                FieldInfo subRoutesInfo = routeCollectionRouteType.GetField("_subRoutes", BindingFlags.NonPublic | BindingFlags.Instance);
    
                var subRoutes = Activator.CreateInstance(subRouteCollectionType);
                var routeEntries = Activator.CreateInstance(routeCollectionRouteType, subRoutes);
    
                // Add the route entries collection first to the route collection
                routes.Add((RouteBase)routeEntries);
    
                var localizedRouteTable = new RouteCollection();
    
                // Get a copy of the attribute routes
                localizedRouteTable.MapMvcAttributeRoutes();
    
                foreach (var routeBase in localizedRouteTable)
                {
                    if (routeBase.GetType().Equals(routeCollectionRouteType))
                    {
                        // Get the value of the _subRoutes field
                        var tempSubRoutes = subRoutesInfo.GetValue(routeBase);
    
                        // Get the PropertyInfo for the Entries property
                        PropertyInfo entriesInfo = subRouteCollectionType.GetProperty("Entries");
    
                        if (entriesInfo.PropertyType.GetInterfaces().Contains(typeof(IEnumerable)))
                        {
                            foreach (RouteEntry routeEntry in (IEnumerable)entriesInfo.GetValue(tempSubRoutes))
                            {
                                var route = routeEntry.Route;
    
                                // Create the localized route
                                var localizedRoute = CreateLocalizedRoute(route, urlPrefix, constraints);
    
                                // Add the localized route entry
                                var localizedRouteEntry = CreateLocalizedRouteEntry(routeEntry.Name, localizedRoute);
                                AddRouteEntry(subRouteCollectionType, subRoutes, localizedRouteEntry);
    
                                // Add the default route entry
                                AddRouteEntry(subRouteCollectionType, subRoutes, routeEntry);
    
    
                                // Add the localized link generation route
                                var localizedLinkGenerationRoute = CreateLinkGenerationRoute(localizedRoute);
                                routes.Add(localizedLinkGenerationRoute);
    
                                // Add the default link generation route
                                //FIX: needed for default culture on normal attribute route
                                var newDefaults = new RouteValueDictionary(defaults);
                                route.Defaults.ToList().ForEach(x => newDefaults.Add(x.Key, x.Value));
                                var routeWithNewDefaults = new Route(route.Url, newDefaults, route.Constraints, route.DataTokens, route.RouteHandler);
                                var linkGenerationRoute = CreateLinkGenerationRoute(routeWithNewDefaults);
                                routes.Add(linkGenerationRoute);
                            }
                        }
                    }
                }
            }
    
            private static Route CreateLocalizedRoute(Route route, string urlPrefix, RouteValueDictionary constraints)
            {
                // Add the URL prefix
                var routeUrl = urlPrefix + route.Url;
    
                // Combine the constraints
                var routeConstraints = new RouteValueDictionary(constraints);
                foreach (var constraint in route.Constraints)
                {
                    routeConstraints.Add(constraint.Key, constraint.Value);
                }
    
                return new Route(routeUrl, route.Defaults, routeConstraints, route.DataTokens, route.RouteHandler);
            }
    
            private static RouteEntry CreateLocalizedRouteEntry(string name, Route route)
            {
                var localizedRouteEntryName = string.IsNullOrEmpty(name) ? null : name + "_Localized";
                return new RouteEntry(localizedRouteEntryName, route);
            }
    
            private static void AddRouteEntry(Type subRouteCollectionType, object subRoutes, RouteEntry newEntry)
            {
                var addMethodInfo = subRouteCollectionType.GetMethod("Add");
                addMethodInfo.Invoke(subRoutes, new[] { newEntry });
            }
    
            private static RouteBase CreateLinkGenerationRoute(Route innerRoute)
            {
                var linkGenerationRouteType = Type.GetType("System.Web.Mvc.Routing.LinkGenerationRoute, System.Web.Mvc");
                return (RouteBase)Activator.CreateInstance(linkGenerationRouteType, innerRoute);
            }
        }
    }
    

    And to attribute routes registration:

        RouteTable.Routes.MapLocalizedMvcAttributeRoutes(
            urlPrefix: "{culture}/",
            defaults: new { culture = "nl" },
            constraints: new { culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]{2}") }
        );
    

    Better solution

    And actually, after some time, I needed to add url translation, so i digged in more, and it appears there is no need to do the reflection hacking described. The ASP.NET guys thought about it, there is much cleaner solution - instead you can extend a DefaultDirectRouteProvider like this:

    public static class RouteCollectionExtensions
    {
        public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string defaultCulture)
        {
            var routeProvider = new LocalizeDirectRouteProvider(
                "{culture}/", 
                defaultCulture
                );
            routes.MapMvcAttributeRoutes(routeProvider);
        }
    }
    
    class LocalizeDirectRouteProvider : DefaultDirectRouteProvider
    {
        ILogger _log = LogManager.GetCurrentClassLogger();
    
        string _urlPrefix;
        string _defaultCulture;
        RouteValueDictionary _constraints;
    
        public LocalizeDirectRouteProvider(string urlPrefix, string defaultCulture)
        {
            _urlPrefix = urlPrefix;
            _defaultCulture = defaultCulture;
            _constraints = new RouteValueDictionary() { { "culture", new CultureConstraint(defaultCulture: defaultCulture) } };
        }
    
        protected override IReadOnlyList GetActionDirectRoutes(
                    ActionDescriptor actionDescriptor,
                    IReadOnlyList factories,
                    IInlineConstraintResolver constraintResolver)
        {
            var originalEntries = base.GetActionDirectRoutes(actionDescriptor, factories, constraintResolver);
            var finalEntries = new List();
    
            foreach (RouteEntry originalEntry in originalEntries)
            {
                var localizedRoute = CreateLocalizedRoute(originalEntry.Route, _urlPrefix, _constraints);
                var localizedRouteEntry = CreateLocalizedRouteEntry(originalEntry.Name, localizedRoute);
                finalEntries.Add(localizedRouteEntry);
                originalEntry.Route.Defaults.Add("culture", _defaultCulture);
                finalEntries.Add(originalEntry);
            }
    
            return finalEntries;
        }
    
        private Route CreateLocalizedRoute(Route route, string urlPrefix, RouteValueDictionary constraints)
        {
            // Add the URL prefix
            var routeUrl = urlPrefix + route.Url;
    
            // Combine the constraints
            var routeConstraints = new RouteValueDictionary(constraints);
            foreach (var constraint in route.Constraints)
            {
                routeConstraints.Add(constraint.Key, constraint.Value);
            }
    
            return new Route(routeUrl, route.Defaults, routeConstraints, route.DataTokens, route.RouteHandler);
        }
    
        private RouteEntry CreateLocalizedRouteEntry(string name, Route route)
        {
            var localizedRouteEntryName = string.IsNullOrEmpty(name) ? null : name + "_Localized";
            return new RouteEntry(localizedRouteEntryName, route);
        }
    }
    

    There is a solution based on this, including url translation here: https://github.com/boudinov/mvc-5-routing-localization

提交回复
热议问题