Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
824 views
in Technique[技术] by (71.8m points)

asp.net mvc - Is there a way to have a RoutePrefix that starts with an optional parameter?

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

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

One way of achieving that is using multiple Route Attributes per Action:

[Route("bikes")]
[Route("{country}/bikes")]
public ActionResult Index()

To keep it DRY I'd prefer to use a RoutePrefix, but multiple Route Prefixes are not allowed:

[RoutePrefix("bikes")]
[RoutePrefix("{country}/bikes")] // <-- Error: Duplicate 'RoutePrefix' attribute    
public class BikesController : BaseController

    [Route("")]
    public ActionResult Index()

I've tried using just this Route Prefix:

[RoutePrefix("{country}/bikes")]
public class BikesController : BaseController

Result: /ca/bikes works, /bikes 404s.

I've tried making country optional:

[RoutePrefix("{country?}/bikes")]
public class BikesController : BaseController

Same result: /ca/bikes works, /bikes 404s.

I've tried giving country a default value:

[RoutePrefix("{country=us}/bikes")]
public class BikesController : BaseController

Same result: /ca/bikes works, /bikes 404s.

Is there another way to achieve my objective using Attribute Routing? (And yes, I know I can do this stuff by registering routes in RouteConfig.cs, but that's what not I'm looking for here).

I'm using Microsoft.AspNet.Mvc 5.2.2.

FYI: these are simplified examples - the actual code has an IRouteConstraint for the {country} values, like:

[Route("{country:countrycode}/bikes")]
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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



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



    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 :)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...