web-dev-qa-db-fra.com

Culture ASP.NET MVC 5 en route et url

J'ai traduit mon site Web MVC, qui fonctionne très bien. Si je sélectionne une autre langue (néerlandais ou anglais), le contenu est traduit. Cela fonctionne parce que je mets la culture dans la session.

Maintenant, je veux montrer la culture sélectionnée (= culture) dans l'URL. S'il s'agit de la langue par défaut, il ne doit pas être affiché dans l'URL. Si ce n'est pas la langue par défaut, il doit l'indiquer dans l'URL.

par exemple.:

Pour la culture par défaut (néerlandais):

site.com/foo
site.com/foo/bar
site.com/foo/bar/5

Pour la culture autre que celle par défaut (anglais):

site.com/en/foo
site.com/en/foo/bar
site.com/en/foo/bar/5

Mon problème c'est que je vois toujours ceci:

site.com/nl/foo/bar/5 même si je cliquais sur l'anglais (voir _Layout.cs). Mon contenu est traduit en anglais mais le paramètre route de l'URL reste sur "nl" au lieu de "en".

Comment puis-je résoudre ceci ou qu'est-ce que je fais mal?

J'ai essayé dans le global.asax de définir le RouteData mais ne m'a pas aidé.

  public class RouteConfig
  {
    public static void RegisterRoutes(RouteCollection routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
      routes.IgnoreRoute("favicon.ico");

      routes.LowercaseUrls = true;

      routes.MapRoute(
        name: "Errors",
        url: "Error/{action}/{code}",
        defaults: new { controller = "Error", action = "Other", code = RouteParameter.Optional }
        );

      routes.MapRoute(
        name: "DefaultWithCulture",
        url: "{culture}/{controller}/{action}/{id}",
        defaults: new { culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional },
        constraints: new { culture = "[a-z]{2}" }
        );// or maybe: "[a-z]{2}-[a-z]{2}

      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional }
      );
    }

Global.asax.cs:

  protected void Application_Start()
    {
      MvcHandler.DisableMvcResponseHeader = true;

      AreaRegistration.RegisterAllAreas();
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
    }

    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
      if (HttpContext.Current.Session != null)
      {
        CultureInfo ci = (CultureInfo)this.Session["Culture"];
        if (ci == null)
        {
          string langName = "nl";
          if (HttpContext.Current.Request.UserLanguages != null && HttpContext.Current.Request.UserLanguages.Length != 0)
          {
            langName = HttpContext.Current.Request.UserLanguages[0].Substring(0, 2);
          }
          ci = new CultureInfo(langName);
          this.Session["Culture"] = ci;
        }

        HttpContextBase currentContext = new HttpContextWrapper(HttpContext.Current);
        RouteData routeData = RouteTable.Routes.GetRouteData(currentContext);
        routeData.Values["culture"] = ci;

        Thread.CurrentThread.CurrentUICulture = ci;
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
      }
    }

_Layout.cs (où je laisse l'utilisateur changer de langue)

// ...
                            <ul class="dropdown-menu" role="menu">
                                <li class="@isCurrentLang("nl")">@Html.ActionLink("Nederlands", "ChangeCulture", "Culture", new { lang = "nl", returnUrl = this.Request.RawUrl }, new { rel = "alternate", hreflang = "nl" })</li>
                                <li class="@isCurrentLang("en")">@Html.ActionLink("English", "ChangeCulture", "Culture", new { lang = "en", returnUrl = this.Request.RawUrl }, new { rel = "alternate", hreflang = "en" })</li>
                            </ul>
// ...

CultureController: (= où j'ai défini la session que j'utilise dans GlobalAsax pour changer CurrentCulture et CurrentUICulture)

public class CultureController : Controller
  {
    // GET: Culture
    public ActionResult Index()
    {
      return RedirectToAction("Index", "Home");
    }

    public ActionResult ChangeCulture(string lang, string returnUrl)
    {
      Session["Culture"] = new CultureInfo(lang);
      if (Url.IsLocalUrl(returnUrl))
      {
        return Redirect(returnUrl);
      }
      else
      {
        return RedirectToAction("Index", "Home");
      }
    }
  }
49
juFo

Cette approche présente plusieurs problèmes, mais cela se résume à un problème de flux de travail.

  1. Vous avez un CultureController dont le seul but est de rediriger l'utilisateur vers une autre page du site. Gardez à l'esprit que RedirectToAction enverra une réponse HTTP 302 au navigateur de l'utilisateur, qui lui indiquera de rechercher le nouvel emplacement sur votre serveur. C'est un aller-retour inutile sur le réseau.
  2. Vous utilisez l'état de session pour stocker la culture de l'utilisateur lorsqu'elle est déjà disponible dans l'URL. L'état de la session est totalement inutile dans ce cas.
  3. Vous lisez le HttpContext.Current.Request.UserLanguages de l'utilisateur, ce qui peut être différent de la culture demandée dans l'URL.

La troisième question est principalement due à une vision fondamentalement différente entre Microsoft et Google sur la façon de gérer la mondialisation.

Selon Microsoft (à l'origine), la même URL devait être utilisée pour toutes les cultures et le UserLanguages du navigateur devait déterminer la langue d'affichage du site Web.

Selon Google, chaque culture doit être hébergée sur une URL différente . Cela a plus de sens si vous y réfléchissez. Il est souhaitable que chaque personne qui trouve votre site Web dans les résultats de recherche (SERP) puisse rechercher le contenu dans sa langue maternelle.

La mondialisation d'un site Web doit être considérée comme un contenu plutôt que comme une personnalisation - vous diffusez une culture vers un groupe de les gens, pas une personne individuelle. Par conséquent, l'utilisation de fonctionnalités de personnalisation d'ASP.NET telles que l'état de session ou les cookies pour implémenter la globalisation n'a généralement aucun sens. Ces fonctionnalités empêchent les moteurs de recherche d'indexer le contenu de vos pages localisées.

Si vous pouvez envoyer l'utilisateur à une culture différente en le routant simplement vers une nouvelle URL, vous vous inquiétez beaucoup moins - vous n'avez pas besoin d'une page séparée pour que l'utilisateur sélectionne sa culture, il vous suffit d'inclure un lien dans l'en-tête. ou footer pour changer la culture de la page existante et tous les liens basculeront automatiquement vers la culture choisie par l'utilisateur (car MVC réutilise automatiquement les valeurs de route de la requête en cours ).

Résoudre les problèmes

Tout d’abord, supprimez le CultureController et le code dans le Application_AcquireRequestState méthode.

CultureFilter

Maintenant, puisque la culture est une préoccupation transversale, définir la culture du fil actuel devrait être fait dans un IAuthorizationFilter. Cela garantit que la culture est définie avant que ModelBinder ne soit utilisé dans MVC.

using System.Globalization;
using System.Threading;
using System.Web.Mvc;

public class CultureFilter : IAuthorizationFilter
{
    private readonly string defaultCulture;

    public CultureFilter(string defaultCulture)
    {
        this.defaultCulture = defaultCulture;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var values = filterContext.RouteData.Values;

        string culture = (string)values["culture"] ?? this.defaultCulture;

        CultureInfo ci = new CultureInfo(culture);

        Thread.CurrentThread.CurrentCulture = ci;
        Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(ci.Name);
    }
}

Vous pouvez définir le filtre globalement en l'enregistrant en tant que filtre global.

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new CultureFilter(defaultCulture: "nl"));
        filters.Add(new HandleErrorAttribute());
    }
}

Sélection de la langue

Vous pouvez simplifier la sélection de la langue en créant un lien vers la même action et le même contrôleur pour la page actuelle et en l'incluant en tant qu'option dans l'en-tête ou le pied de page de votre _Layout.cshtml.

@{ 
    var routeValues = this.ViewContext.RouteData.Values;
    var controller = routeValues["controller"] as string;
    var action = routeValues["action"] as string;
}
<ul>
    <li>@Html.ActionLink("Nederlands", @action, @controller, new { culture = "nl" }, new { rel = "alternate", hreflang = "nl" })</li>
    <li>@Html.ActionLink("English", @action, @controller, new { culture = "en" }, new { rel = "alternate", hreflang = "en" })</li>
</ul>

Comme mentionné précédemment, tous les autres liens de la page se verront automatiquement transmettre une culture du contexte actuel, de sorte qu'ils resteront automatiquement dans la même culture. Il n'y a aucune raison de transmettre la culture explicitement dans ces cas.

@ActionLink("About", "About", "Home")

Avec le lien ci-dessus, si l'URL actuelle est /Home/Contact, le lien généré sera /Home/About. Si l'URL actuelle est /en/Home/Contact, le lien sera généré comme /en/Home/About.

Culture par défaut

Enfin, nous arrivons au coeur de votre question. La raison pour laquelle votre culture par défaut n'est pas générée correctement est que le routage est une carte bidirectionnelle et que vous mettiez en correspondance une demande entrante ou génériez une URL sortante, la première correspondance l'emporte toujours. Lors de la construction de votre URL, la première correspondance est DefaultWithCulture.

Normalement, vous pouvez résoudre ce problème simplement en inversant l'ordre des itinéraires. Cependant, dans votre cas, cela entraînerait l’échec des itinéraires entrants.

Ainsi, l’option la plus simple dans votre cas est de construire un contrainte de route personnalisée pour gérer le cas particulier de la culture par défaut lors de la génération de l’URL. Vous renvoyez simplement false lorsque la culture par défaut est fournie et le framework de routage .NET ignorera la route DefaultWithCulture et passera à la route enregistrée suivante (dans ce cas, Default).

using System.Text.RegularExpressions;
using System.Web;
using System.Web.Routing;

public class CultureConstraint : IRouteConstraint
{
    private readonly string defaultCulture;
    private readonly string pattern;

    public CultureConstraint(string defaultCulture, string pattern)
    {
        this.defaultCulture = defaultCulture;
        this.pattern = pattern;
    }

    public bool Match(
        HttpContextBase httpContext, 
        Route route, 
        string parameterName, 
        RouteValueDictionary values, 
        RouteDirection routeDirection)
    {
        if (routeDirection == RouteDirection.UrlGeneration && 
            this.defaultCulture.Equals(values[parameterName]))
        {
            return false;
        }
        else
        {
            return Regex.IsMatch((string)values[parameterName], "^" + pattern + "$");
        }
    }
}

Tout ce qui reste à faire est d'ajouter la contrainte à votre configuration de routage. Vous devez également supprimer le paramètre par défaut pour la culture dans la route DefaultWithCulture, car vous souhaitez qu'il corresponde uniquement lorsqu'il existe une culture fournie dans l'URL. La route Default doit quant à elle avoir une culture car il n’ya aucun moyen de la transmettre via l’URL.

routes.LowercaseUrls = true;

routes.MapRoute(
  name: "Errors",
  url: "Error/{action}/{code}",
  defaults: new { controller = "Error", action = "Other", code = UrlParameter.Optional }
  );

routes.MapRoute(
  name: "DefaultWithCulture",
  url: "{culture}/{controller}/{action}/{id}",
  defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
  constraints: new { culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]{2}") }
  );

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional }
);

AttributeRouting

REMARQUE: Cette section ne s'applique que si vous utilisez MVC 5. Vous pouvez l'ignorer si vous utilisez une version précédente.

Pour AttributeRouting, vous pouvez simplifier les choses en automatisant la création de 2 itinéraires différents pour chaque action. Vous devez modifier un peu chaque route et les ajouter à la même structure de classe que celle utilisée par MapMvcAttributeRoutes. Malheureusement, Microsoft a décidé de rendre les types internes, il est donc nécessaire que Reflection les instancie et les remplisse.

RouteCollectionExtensions

Ici, nous utilisons simplement la fonctionnalité intégrée de MVC pour analyser notre projet et créer un ensemble de routes, puis insérons un préfixe d’URL de route supplémentaire pour la culture et le CultureConstraint avant d’ajouter les instances à notre RouteTable MVC.

Une route distincte est également créée pour résoudre les URL (de la même façon que AttributeRouting).

using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Mvc.Routing;
using System.Web.Routing;

public static class RouteCollectionExtensions
{
    public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, object constraints)
    {
        MapLocalizedMvcAttributeRoutes(routes, urlPrefix, new RouteValueDictionary(constraints));
    }

    public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, 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
                        var linkGenerationRoute = CreateLinkGenerationRoute(route);
                        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);
    }
}

Ensuite, il suffit d'appeler cette méthode à la place de MapMvcAttributeRoutes.

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        // Call to register your localized and default attribute routes
        routes.MapLocalizedMvcAttributeRoutes(
            urlPrefix: "{culture}/", 
            constraints: new { culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]{2}") }
        );

        routes.MapRoute(
            name: "DefaultWithCulture",
            url: "{culture}/{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
            constraints: new { culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]{2}") }
        );

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}
125
NightOwl888

Correctif de culture par défaut

Incredible post par NightOwl888. Il manque cependant quelque chose - les itinéraires d'attributs de génération d'URL normaux (non localisés), qui sont ajoutés par réflexion, nécessitent également un paramètre de culture par défaut, sinon vous obtenez un paramètre de requête dans l'URL.

? culture = nl

Afin d'éviter cela, ces modifications doivent être effectuées:

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);
        }
    }
}

Et pour attribuer l'enregistrement des routes:

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

Meilleure solution

Et en fait, après un certain temps, j’ai eu besoin d’ajouter une traduction d’URL, j’en ai donc creusé plus, et il apparaît qu’il n’est pas nécessaire de procéder au piratage par réflexion décrit. Les gars d'ASP.NET y ont pensé. Il existe une solution beaucoup plus propre. Vous pouvez plutôt étendre un DefaultDirectRouteProvider comme ceci:

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<RouteEntry> GetActionDirectRoutes(
                ActionDescriptor actionDescriptor,
                IReadOnlyList<IDirectRouteFactory> factories,
                IInlineConstraintResolver constraintResolver)
    {
        var originalEntries = base.GetActionDirectRoutes(actionDescriptor, factories, constraintResolver);
        var finalEntries = new List<RouteEntry>();

        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);
    }
}

Il existe une solution basée sur cela, y compris la traduction d’URL ici: https://github.com/boudinov/mvc-5-routing-localization

10
Plamen Boudinov