Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpRouteBuilder - Where did it go and Why?

I upgraded my nuget package for the Web API 2 from the RC1 to 5.0.0, and was dumbfounded to find that HttpRouteBuilder, which used to be accessible, was made internal. Along with that, there is no longer an overload for HttpConfiguration.MapHttpAttributeRoutes that takes HttpRouteBuilder as an argument. Why?

I was using that, and it solves a major problem in my project. What do I use instead?

Background: I am writing a server that uses Attribute Routing for Web API 2. I implemented a class that inherited from HttpRouteBuilder so that I could inject a couple extra path segments to every URI. For example, if the default route builder ended up creating a route for //myserver/user/update, my route builder would modify that route to //myserver/{instance}/user/update. I wanted this done automatically so that I didn't have to stick that in every single of my hundreds of HttpGet, HttpPost, etc. attributes. So now how do I handle that with this major change?

like image 745
Nathan A Avatar asked Oct 22 '13 22:10

Nathan A


Video Answer


1 Answers

That internalling broke something I was working on as well.

A change set made on August 21st 2013 made this api alteration to fix this issue. According to that issue the only reason functionality was removed was to make Web Api closer to MVC's api. Not a particularly good justification in my opinion.

To resolve my issues I implemented a custom IHttpActionSelector derived from ApiControllerActionSelector. I hope it is not going to be my final solution since it really is far too much code for a simple thing. This approach should work for your problem too.

In my project each route needs to be modified according to which assembly it was found in. In following simplified code every route is prefixed with /Api (before a controller's RoutePrefixAttribute if present).

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

The 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 functionality just substitute the IHttpActionSelector service with PrefixWithApiControllerActionSelector in the config.

If you find a cleaner way of doing things please post your solution!

like image 180
sh54 Avatar answered Sep 22 '22 13:09

sh54