c# - httpget - asp net core post route



ASP.NET MVC 5-Kultur in Route und URL (2)

Ich habe meine mvc-Website übersetzt, was großartig funktioniert. Wenn ich eine andere Sprache wähle (Niederländisch oder Englisch), wird der Inhalt übersetzt. Dies funktioniert, weil ich die Kultur in der Sitzung festgelegt habe.

Jetzt möchte ich die ausgewählte Kultur (= Kultur) in der URL anzeigen. Wenn es die Standardsprache ist, sollte sie nicht in der URL angezeigt werden, nur wenn es nicht die Standardsprache ist, sollte sie in der URL angezeigt werden.

z.B:

Für Standardkultur (Niederländisch):

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

Für eine nicht standardmäßige Kultur (Englisch):

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

Mein Problem ist, dass ich das immer sehe:

site.com/nl / foo / bar / 5, auch wenn ich auf Englisch geklickt habe (siehe _Layout.cs). Mein Inhalt ist auf Englisch übersetzt, aber der Routenparameter in der URL bleibt auf "nl" anstelle von "en".

Wie kann ich das lösen oder was mache ich falsch?

Ich habe in der global.asax versucht, die RouteData zu setzen, aber es hilft nicht.

  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 (wo ich den Benutzer die Sprache ändern lasse)

// ...
                            <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: (= hier lege ich die Sitzung fest, die ich in GlobalAsax verwende, um CurrentCulture und CurrentUICulture zu ändern)

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

Standard-Kulturfix

Unglaublicher Beitrag von NightOwl888. Es fehlt jedoch etwas - die normalen (nicht lokalisierten) URL-Generierungsattributrouten, die durch Reflektion hinzugefügt werden, benötigen ebenfalls einen Standardkulturparameter, ansonsten erhalten Sie einen Abfrageparameter in der URL.

Kultur = nl

Um dies zu vermeiden, müssen folgende Änderungen vorgenommen werden:

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

Und um die Routenregistrierung zuzuordnen:

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

Bessere Lösung

Tatsächlich musste ich nach einiger Zeit eine URL-Übersetzung hinzufügen, also habe ich mehr recherchiert, und es scheint, dass es keine Notwendigkeit gibt, das beschriebene Reflection-Hacking durchzuführen. Die ASP.NET-Leute haben darüber nachgedacht, es gibt eine viel sauberere Lösung - stattdessen können Sie einen DefaultDirectRouteProvider folgendermaßen erweitern :

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

Darauf basierend gibt es eine Lösung, einschließlich der URL-Übersetzung hier: https://github.com/boudinov/mvc-5-routing-localization


Es gibt mehrere Probleme mit diesem Ansatz, aber es läuft darauf hinaus, ein Workflow-Problem zu sein.

  1. Sie haben einen CultureController dessen einziger Zweck darin besteht, den Benutzer auf eine andere Seite auf der Site umzuleiten. Beachten Sie, dass RedirectToAction eine HTTP 302-Antwort an den Browser des Benutzers sendet, die ihn auffordert, den neuen Speicherort auf Ihrem Server zu suchen. Dies ist eine unnötige Rundreise durch das Netzwerk.
  2. Sie verwenden den Sitzungsstatus, um die Kultur des Benutzers zu speichern, wenn diese bereits in der URL verfügbar ist. Der Sitzungsstatus ist in diesem Fall völlig unnötig.
  3. Sie lesen die HttpContext.Current.Request.UserLanguages des Benutzers, die sich möglicherweise von der in der URL angeforderten Kultur unterscheiden.

Das dritte Problem ist in erster Linie auf eine grundlegend andere Sichtweise von Microsoft und Google im Umgang mit der Globalisierung zurückzuführen.

UserLanguages (ursprünglicher) Ansicht von Microsoft sollte für jede Kultur dieselbe URL verwendet werden und in den UserLanguages des Browsers festgelegt werden, welche Sprache auf der Website angezeigt werden soll.

Nach Ansicht von Google sollte jede Kultur unter einer anderen URL gehostet werden . Das macht mehr Sinn, wenn Sie darüber nachdenken. Es ist wünschenswert, dass jede Person, die Ihre Website in den Suchergebnissen (SERPs) findet, nach Inhalten in ihrer Muttersprache suchen kann.

Die Globalisierung einer Website sollte eher als Inhalt denn als Personalisierung betrachtet werden - Sie senden eine Kultur an eine Gruppe von Menschen, nicht an eine einzelne Person. Daher ist es in der Regel nicht sinnvoll, Personalisierungsfunktionen von ASP.NET wie den Sitzungsstatus oder Cookies für die Implementierung der Globalisierung zu verwenden. Diese Funktionen verhindern, dass Suchmaschinen den Inhalt Ihrer lokalisierten Seiten indizieren.

Wenn Sie den Benutzer zu einer anderen Kultur senden können, indem Sie ihn einfach an eine neue URL weiterleiten, besteht weitaus weniger Grund zur Sorge: Sie benötigen keine separate Seite, auf der der Benutzer seine Kultur auswählen kann. Fügen Sie einfach einen Link in den Header ein oder Fußzeile, um die Kultur der vorhandenen Seite zu ändern, und dann werden alle Links automatisch auf die vom Benutzer gewählte Kultur umgeschaltet (da MVC Routenwerte aus der aktuellen Anforderung automatisch wiederverwendet ).

Beheben der Probleme

CultureController den CultureController und den Code in der Application_AcquireRequestState Methode.

CultureFilter

Da Kultur ein Querschnittsthema ist, sollte das Festlegen der Kultur des aktuellen Threads in einem IAuthorizationFilter . Dadurch wird sichergestellt, dass die Kultur festgelegt wird, bevor ModelBinder in MVC verwendet wird.

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

Sie können den Filter global festlegen, indem Sie ihn als globalen Filter registrieren.

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

Sprachauswahl

Sie können die Sprachauswahl vereinfachen, indem Sie für die aktuelle Seite eine Verknüpfung zu derselben Aktion und demselben Controller herstellen und diese als Option in die Kopf- oder Fußzeile der Seite in Ihrer _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>

Wie bereits erwähnt, wird allen anderen Links auf der Seite automatisch eine Kultur aus dem aktuellen Kontext übergeben, sodass sie automatisch in derselben Kultur bleiben. In diesen Fällen gibt es keinen Grund, die Kultur explizit weiterzugeben.

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

Wenn die aktuelle URL unter dem obigen Link /Home/Contact lautet, wird der Link /Home/About generiert. Wenn die aktuelle URL /en/Home/Contact lautet, wird der Link als /en/Home/About generiert.

Standardkultur

Schließlich kommen wir zum Kern Ihrer Frage. Der Grund, warum Ihre Standardkultur nicht korrekt generiert wird, ist, dass das Routing eine bidirektionale Zuordnung ist und unabhängig davon, ob Sie einer eingehenden Anfrage entsprechen oder eine ausgehende URL generieren, immer die erste Übereinstimmung gewinnt. Beim DefaultWithCulture Ihrer URL lautet die erste Übereinstimmung DefaultWithCulture .

Normalerweise können Sie dies einfach beheben, indem Sie die Reihenfolge der Routen umkehren. In Ihrem Fall würde dies jedoch dazu führen, dass die eingehenden Routen fehlschlagen.

In Ihrem Fall ist es daher am einfachsten, eine benutzerdefinierte Routenbeschränkung zu erstellen, um den Sonderfall der Standardkultur beim Generieren der URL zu behandeln. Sie geben einfach false zurück, wenn die Standardkultur angegeben wird, und das .NET-Routingframework überspringt die DefaultWithCulture Route und wechselt zur nächsten registrierten Route (in diesem Fall 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 + "$");
        }
    }
}

Sie müssen lediglich die Einschränkung zu Ihrer Routing-Konfiguration hinzufügen. Sie sollten auch die Standardeinstellung für die Kultur in der DefaultWithCulture Route DefaultWithCulture , da diese nur übereinstimmen soll, wenn in der URL ohnehin eine Kultur angegeben ist. Die Default sollte andererseits eine Kultur haben, da es keine Möglichkeit gibt, sie über die URL zu übergeben.

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

HINWEIS: Dieser Abschnitt gilt nur, wenn Sie MVC 5 verwenden. Sie können diesen Abschnitt überspringen, wenn Sie eine frühere Version verwenden.

Für AttributeRouting können Sie die Dinge vereinfachen, indem Sie die Erstellung von 2 verschiedenen Routen für jede Aktion automatisieren. Sie müssen jede Route ein wenig optimieren und sie derselben Klassenstruktur hinzufügen, die MapMvcAttributeRoutes verwendet. Leider hat Microsoft beschlossen, die Typen intern zu machen, sodass Reflection zum Instanziieren und Auffüllen erforderlich ist.

RouteCollectionExtensions

Hier verwenden wir nur die integrierte Funktionalität von MVC, um unser Projekt zu scannen und einen Satz von Routen zu erstellen. Anschließend fügen wir ein zusätzliches Routen-URL-Präfix für die Kultur und die CultureConstraint bevor wir die Instanzen zu unserer MVC-Routentabelle hinzufügen.

Es gibt auch eine separate Route, die zum Auflösen der URLs erstellt wird (genau wie bei 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);
    }
}

In diesem MapMvcAttributeRoutes Sie nur diese Methode anstelle von 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 }
        );
    }
}




asp.net-mvc-routing