Is there a way to have a RoutePrefix that starts with an optional parameter?

前端 未结 4 1938
旧巷少年郎
旧巷少年郎 2020-12-20 16:26

I want to reach the Bikes controller with these URL\'s:

/bikes     // (default path for US)
/ca/bikes  // (path for Canada)

One way of achi

相关标签:
4条回答
  • 2020-12-20 16:59

    The best solution I've come across is detailed by NightOwl888 in response to the following question: ASP.NET MVC 5 culture in route and url. The code below is my trimmed down version of his post. It's working for me in MVC5.

    Decorate each controller with a single RoutePrefix, without a culture segment. When the application starts up, the custom MapLocalizedMvcAttributeRoutes method adds a localized route entry for each controller action.

    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            // Omitted for brevity
    
            MapLocalizedMvcAttributeRoutes(routes, "{culture}/", new { culture = "[a-z]{2}-[A-Z]{2}" });
        }
    
        static void MapLocalizedMvcAttributeRoutes(RouteCollection routes, string urlPrefix, object 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");
            var linkGenerationRouteType = Type.GetType("System.Web.Mvc.Routing.LinkGenerationRoute, System.Web.Mvc");
            FieldInfo subRoutesInfo = routeCollectionRouteType.GetField("_subRoutes", BindingFlags.NonPublic | BindingFlags.Instance);
            PropertyInfo entriesInfo = subRouteCollectionType.GetProperty("Entries");
            MethodInfo addMethodInfo = subRouteCollectionType.GetMethod("Add");
    
            var localizedRouteTable = new RouteCollection();
            var subRoutes = Activator.CreateInstance(subRouteCollectionType);
            Func<Route, RouteBase> createLinkGenerationRoute = (Route route) => (RouteBase)Activator.CreateInstance(linkGenerationRouteType, route);
    
            localizedRouteTable.MapMvcAttributeRoutes();
    
            foreach (var routeCollectionRoute in localizedRouteTable.Where(rb => rb.GetType().Equals(routeCollectionRouteType)))
            {
                // routeCollectionRoute._subRoutes.Entries
                foreach (RouteEntry routeEntry in (IEnumerable)entriesInfo.GetValue(subRoutesInfo.GetValue(routeCollectionRoute)))
                {
                    var localizedRoute = CreateLocalizedRoute(routeEntry.Route, urlPrefix, constraints);
                    var localizedRouteEntry = new RouteEntry(string.IsNullOrEmpty(routeEntry.Name) ? null : $"{routeEntry.Name}_Localized", localizedRoute);
                    // Add localized and default routes and subroute entries
                    addMethodInfo.Invoke(subRoutes, new[] { localizedRouteEntry });
                    addMethodInfo.Invoke(subRoutes, new[] { routeEntry });
                    routes.Add(createLinkGenerationRoute(localizedRoute));
                    routes.Add(createLinkGenerationRoute(routeEntry.Route));
                }
            }
            var routeEntries = Activator.CreateInstance(routeCollectionRouteType, subRoutes);
            routes.Add((RouteBase)routeEntries);
        }
    
        static Route CreateLocalizedRoute(Route route, string urlPrefix, object constraints)
        {
            var routeUrl = urlPrefix + route.Url;
            var routeConstraints = new RouteValueDictionary(constraints);
            // combine with any existing constraints
            foreach (var constraint in route.Constraints)
            {
                routeConstraints.Add(constraint.Key, constraint.Value);
            }
            return new Route(routeUrl, route.Defaults, routeConstraints, route.DataTokens, route.RouteHandler);
        }
    }
    
    0 讨论(0)
  • 2020-12-20 17:12

    I am a bit late to the party, but i have a working solution for this problem. Please find my detailed blog post on this issue here

    I am writing down summary below

    You need to create 2 files as given below

    • _3bTechTalkMultiplePrefixDirectRouteProvider.cs
    
    
        using System;
        using System.Collections.Generic;
        using System.Collections.ObjectModel;
        using System.Web.Http.Controllers;
        using System.Web.Http.Routing;
    
        namespace _3bTechTalk.MultipleRoutePrefixAttributes {
         public class _3bTechTalkMultiplePrefixDirectRouteProvider: DefaultDirectRouteProvider {
          protected override IReadOnlyList  GetActionDirectRoutes(HttpActionDescriptor actionDescriptor, IReadOnlyList  factories, IInlineConstraintResolver constraintResolver) {
           return CreateRouteEntries(GetRoutePrefixes(actionDescriptor.ControllerDescriptor), factories, new [] {
            actionDescriptor
           }, constraintResolver, true);
          }
    
          protected override IReadOnlyList  GetControllerDirectRoutes(HttpControllerDescriptor controllerDescriptor, IReadOnlyList  actionDescriptors, IReadOnlyList  factories, IInlineConstraintResolver constraintResolver) {
           return CreateRouteEntries(GetRoutePrefixes(controllerDescriptor), factories, actionDescriptors, constraintResolver, false);
          }
    
          private IEnumerable  GetRoutePrefixes(HttpControllerDescriptor controllerDescriptor) {
           Collection  attributes = controllerDescriptor.GetCustomAttributes  (false);
           if (attributes == null)
            return new string[] {
             null
            };
    
           var prefixes = new List  ();
           foreach(var attribute in attributes) {
            if (attribute == null)
             continue;
    
            string prefix = attribute.Prefix;
            if (prefix == null)
             throw new InvalidOperationException("Prefix can not be null. Controller: " + controllerDescriptor.ControllerType.FullName);
            if (prefix.EndsWith("/", StringComparison.Ordinal))
             throw new InvalidOperationException("Invalid prefix" + prefix + " in " + controllerDescriptor.ControllerName);
    
            prefixes.Add(prefix);
           }
    
           if (prefixes.Count == 0)
            prefixes.Add(null);
    
           return prefixes;
          }
    
    
          private IReadOnlyList  CreateRouteEntries(IEnumerable  prefixes, IReadOnlyCollection  factories, IReadOnlyCollection  actions, IInlineConstraintResolver constraintResolver, bool targetIsAction) {
           var entries = new List  ();
    
           foreach(var prefix in prefixes) {
            foreach(IDirectRouteFactory factory in factories) {
             RouteEntry entry = CreateRouteEntry(prefix, factory, actions, constraintResolver, targetIsAction);
             entries.Add(entry);
            }
           }
    
           return entries;
          }
    
    
          private static RouteEntry CreateRouteEntry(string prefix, IDirectRouteFactory factory, IReadOnlyCollection  actions, IInlineConstraintResolver constraintResolver, bool targetIsAction) {
           DirectRouteFactoryContext context = new DirectRouteFactoryContext(prefix, actions, constraintResolver, targetIsAction);
           RouteEntry entry = factory.CreateRoute(context);
           ValidateRouteEntry(entry);
    
           return entry;
          }
    
    
          private static void ValidateRouteEntry(RouteEntry routeEntry) {
           if (routeEntry == null)
            throw new ArgumentNullException("routeEntry");
    
           var route = routeEntry.Route;
           if (route.Handler != null)
            throw new InvalidOperationException("Direct route handler is not supported");
          }
         }
        }
    
    
    • 3bTechTalkRoutePrefix.cs
    
    
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Web;
        using System.Web.Http;
    
        namespace _3bTechTalk.MultipleRoutePrefixAttributes
        {
            [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
            public class _3bTechTalkRoutePrefix : RoutePrefixAttribute
            {
                public int Order { get; set; }
    
                public _3bTechTalkRoutePrefix(string prefix) : this(prefix, 0) { }
    
                public _3bTechTalkRoutePrefix(string prefix, int order) : base(prefix)
                {
                    Order = order;
                }        
            }
        }
    
    

    Once done, open WebApiConfig.cs and add this below given line

    
    config.MapHttpAttributeRoutes(new _3bTechTalkMultiplePrefixDirectRouteProvider());
    

    That's it, now you can add multiple route prefix in your controller. Example below

    
    
        [_3bTechTalkRoutePrefix("api/Car", Order = 1)]
        [_3bTechTalkRoutePrefix("{CountryCode}/api/Car", Order = 2)]
        public class CarController: ApiController {
         [Route("Get")]
         public IHttpActionResult Get() {
          return Ok(new {
           Id = 1, Name = "Honda Accord"
          });
         }
        }
    
    

    I have uploaded a working solution here

    Happy Coding :)

    0 讨论(0)
  • 2020-12-20 17:13

    You could use attribute routes with two ordered options.

    public partial class GlossaryController : Controller {
    
        [Route("~/glossary", Order = 2)]
        [Route("~/{countryCode}/glossary", Order = 1)]
        public virtual ActionResult Index()
        {
          return View();
        }
    }
    

    If you're planning to have region specific routes for all your pages you could add a route to the route config above the default. This will work only for views/controllers without attribute routes.

      routes.MapRoute(
         name: "Region",
         url: "{countryCode}/{controller}/{action}/{id}",
         defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
         constraints: new { countryCode = @"\w{2}" }
      );
    
    0 讨论(0)
  • 2020-12-20 17:23

    You're correct that you can't have multiple route prefixes, which means solving this particular use case is not going to be straight forward. About the best way I can think of to achieve what you want with the minimal amount of modifications to your project is to subclass your controller. For example:

    [RoutePrefix("bikes")]
    public class BikeController : Controller
    {
        ...
    }
    
    [RoutePrefix("{country}/bikes")]
    public class CountryBikeController : BikeController
    {
    }
    

    You subclassed controller will inherit all the actions from BikeController, so you don't need to redefine anything, per se. However, when it comes to generating URLs and getting them to go to the right place, you'll either need to be explicit with the controller name:

    @Url.Action("Index", "CountryBike", new { country = "us" }
    

    Or, if you're using named routes, you'll have to override your actions in your subclassed controller so you can apply new route names:

    [Route("", Name = "CountryBikeIndex")]
    public override ActionResult Index()
    {
        base.Index();
    }
    

    Also, bear in mind, that when using parameters in route prefixes, all of your actions in that controller should take the parameter:

    public ActionResult Index(string country = "us")
    {
        ...
    
    0 讨论(0)
提交回复
热议问题