This internally broke what I was working on.
A set of changes made on August 21, 2013 made this api change to fix this issue . According to this problem, the only reason the functionality was removed was to make Web Api closer to MVC api. In my opinion, not a good excuse.
To solve my problems, I implemented a custom IHttpActionSelector derived from ApiControllerActionSelector . Hope this will not be my final decision, as it is actually too much code for a simple thing. This approach should also work for your problem.
In my project, each route should be changed depending on which assembly was found. In the following simplified code, each route has the /Api prefix (before the RoutePrefixAttribute controller, if any).
Actual IHttpActionSelector :
public class PrefixWithApiControllerActionSelector : WrappingApiControllerActionSelector { protected override HttpActionDescriptor WrapHttpActionDescriptor(HttpActionDescriptor actionDescriptor) { if (actionDescriptor is ReflectedHttpActionDescriptor) return new PrefixWithApiReflectedHttpActionDescriptor((ReflectedHttpActionDescriptor)actionDescriptor); return actionDescriptor; } } public abstract class WrappingApiControllerActionSelector : ApiControllerActionSelector { protected abstract HttpActionDescriptor WrapHttpActionDescriptor(HttpActionDescriptor actionDescriptor); public override ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor) { return base.GetActionMapping(controllerDescriptor).SelectMany(grouping => { return grouping.Select(actionDescriptor => new KeyValuePair<string, HttpActionDescriptor>(grouping.Key, WrapHttpActionDescriptor(actionDescriptor))); }).ToLookup(_ => _.Key, _ => _.Value); } }
The part that changes the route:
public class PrefixWithApiHttpRouteInfoProvider : WrappedHttpRouteInfoProvider { public PrefixWithApiHttpRouteInfoProvider(IHttpRouteInfoProvider infoProvider, HttpControllerDescriptor controllerDescriptor) : base(infoProvider, controllerDescriptor) { } public override string Template { get { var parts = new List<string>(); parts.Add("Api"); var prefix = ControllerDescriptor.GetCustomAttributes<RoutePrefixAttribute>().FirstOrDefault(); if (prefix != null && !string.IsNullOrEmpty(prefix.Prefix)) { parts.Add(prefix.Prefix); } if (!string.IsNullOrEmpty(InfoProvider.Template)) { parts.Add(InfoProvider.Template); } var route = "~/" + string.Join("/", parts); if (route.Length > 2 && route.EndsWith("/", StringComparison.Ordinal)) { route = route.Substring(0, route.Length - 1); } return route; } } } public abstract class WrappedHttpRouteInfoProvider : IHttpRouteInfoProvider { private readonly IHttpRouteInfoProvider _infoProvider; private readonly HttpControllerDescriptor _controllerDescriptor; protected WrappedHttpRouteInfoProvider(IHttpRouteInfoProvider infoProvider, HttpControllerDescriptor controllerDescriptor) { _infoProvider = infoProvider; _controllerDescriptor = controllerDescriptor; } public virtual string Name { get { return InfoProvider.Name; } } public virtual string Template { get { return _infoProvider.Template; } } public virtual int Order { get { return InfoProvider.Order; } } protected HttpControllerDescriptor ControllerDescriptor { get { return _controllerDescriptor; } } protected IHttpRouteInfoProvider InfoProvider { get { return _infoProvider; } } }
Glue:
public class PrefixWithApiReflectedHttpActionDescriptor : WrappedReflectedHttpActionDescriptor { public PrefixWithApiReflectedHttpActionDescriptor(ReflectedHttpActionDescriptor descriptor) : base(descriptor) {} public override Collection<T> GetCustomAttributes<T>(bool inherit) { if (typeof(T) == typeof(IHttpRouteInfoProvider)) { var attributes = Descriptor.GetCustomAttributes<T>(inherit).Cast<IHttpRouteInfoProvider>().Select(_ => new PrefixWithApiHttpRouteInfoProvider(_, Descriptor.ControllerDescriptor)); return new Collection<T>(attributes.Cast<T>().ToList()); } return Descriptor.GetCustomAttributes<T>(inherit); } public override Collection<T> GetCustomAttributes<T>() { if (typeof(T) == typeof(IHttpRouteInfoProvider)) { var attributes = Descriptor.GetCustomAttributes<T>().Cast<IHttpRouteInfoProvider>().Select(_ => new PrefixWithApiHttpRouteInfoProvider(_, Descriptor.ControllerDescriptor)); return new Collection<T>(attributes.Cast<T>().ToList()); } return Descriptor.GetCustomAttributes<T>(); } } public abstract class WrappedReflectedHttpActionDescriptor : ReflectedHttpActionDescriptor { private readonly ReflectedHttpActionDescriptor _descriptor; protected WrappedReflectedHttpActionDescriptor(ReflectedHttpActionDescriptor descriptor) : base(descriptor.ControllerDescriptor, descriptor.MethodInfo) { _descriptor = descriptor; } public override HttpActionBinding ActionBinding { get { return Descriptor.ActionBinding; } set { Descriptor.ActionBinding = value; } } public override Collection<T> GetCustomAttributes<T>(bool inherit) { return Descriptor.GetCustomAttributes<T>(inherit); } public override Collection<T> GetCustomAttributes<T>() { return Descriptor.GetCustomAttributes<T>(); } public override Collection<System.Web.Http.Filters.FilterInfo> GetFilterPipeline() { return Descriptor.GetFilterPipeline(); } public override Collection<System.Web.Http.Filters.IFilter> GetFilters() { return Descriptor.GetFilters(); } public override System.Collections.Concurrent.ConcurrentDictionary<object, object> Properties { get { return Descriptor.Properties; } } public override IActionResultConverter ResultConverter { get { return Descriptor.ResultConverter; } } public override Collection<HttpMethod> SupportedHttpMethods { get { return Descriptor.SupportedHttpMethods; } } public override Collection<HttpParameterDescriptor> GetParameters() { return Descriptor.GetParameters(); } public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken) { return Descriptor.ExecuteAsync(controllerContext, arguments, cancellationToken); } public override string ActionName { get { return Descriptor.ActionName; } } public override Type ReturnType { get { return Descriptor.ReturnType; } } protected ReflectedHttpActionDescriptor Descriptor { get { return _descriptor; } } }
To use this function, simply replace the IHttpActionSelector service with PrefixWithApiControllerActionSelector in config.
If you find a cleaner way to do something, submit your decision!