Valeklosse
Valeklosse

Reputation: 1017

Web Api Routing Handler Change Route Data

Within web api route I have created the following route and route handler.

Im trying to intercept the request and change some of the route data before it hits the actual method.

However the below doesnt quite work as I expected, when running and making requests to the api, it returns a 404 No route providing a controller name was found to match request.

Is this actually possible to change the route data? I feel I am close to a solution but missing something.

Below is my code:

public class CustomHander : DelegatingHandler {
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {

        var routeData = request.GetRouteData();
        var subroutes = (IEnumerable<IHttpRouteData>)routeData.Values["MS_SubRoutes"];
        var route = subroutes.First();

        var msSubRoutes = new HttpRouteValueDictionary();

        var httpRouteData = new HttpRouteData(route.Route);

        foreach (var r in route.Values) {
            httpRouteData.Values.Add(r.Key, r.Value.ToString().Replace("|", "/"));
        }

        msSubRoutes.Add("MS_SubRoutes", httpRouteData);
        var newRoute = new HttpRouteData(routeData.Route, msSubRoutes);

        request.SetRouteData(newRoute);
        var test = request.GetRouteData();

        var resp = await base.SendAsync(request, cancellationToken);
        return resp;
    }
}

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional, controller = "Vehicles" }
        );

        config.MessageHandlers.Add(new CustomHander());
    }
}

If i remove the line of code below, then it works as normal, but obviously without change the route data.

request.SetRouteData(newRoute);

Example of Action Result on controller:

[Route("api/Makes")]
[HttpGet]
public IHttpActionResult Makes()
{
    using (var db = new eCatEntities())
    {
        var makes = db.VEHICLEs.Select(p => new { TEXT = p.VEHI_MAKE }).Distinct().OrderBy(p => p.TEXT);

        return Json(makes.ToList());
    }
}

[Route("api/Models/{make}")]
[HttpGet]
public IHttpActionResult Models(string make)
{
    using (var db = new eCatEntities())
    {
        var models = db.VEHICLEs.Where(p => p.VEHI_MAKE == make.Replace("|", "/")).Select(p => new { TEXT = p.VEHI_MODEL, p.VEHI_ALPHASORT }).Distinct().OrderBy(p => p.VEHI_ALPHASORT);

        return Json(models.ToList());
    }
}

An example url is:

http://domain:port/api/Makes
http://domain:port/api/Models/Jaguar|Daimler

Upvotes: 3

Views: 4581

Answers (2)

Mark Nadig
Mark Nadig

Reputation: 5136

I was able to achieve this with System.Web.Http.Filters ActionFilterAttribute.

Your route will look like this:

[Route("api/Models/{make}"), MakeWebApiFilter]
[HttpGet]

and the filter like this:

using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

public class MakeWebApiFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        object data = "";
        if(actionContext.ActionArguments.TryGetValue("make", out data))
        {
            data = data.Replace("|", "/");;
            actionContext.ActionArguments["make"] = data;
        }
    }
}

I was running into this today and found many examples for MVC, but not WebAPI until I found the answer here https://damienbod.com/2014/01/04/web-api-2-using-actionfilterattribute-overrideactionfiltersattribute-and-ioc-injection/

Upvotes: 0

NightOwl888
NightOwl888

Reputation: 56909

The extension point for extending routing is the IHttpRoute (or its default concrete implementation HttpRoute). A handler is a lower-level object that doesn't have the ability to generate outgoing routes (something you are definitely going to need to fix if you are putting / in your route values).

public class ReplacePipeHttpRoute : System.Web.Http.Routing.HttpRoute
{
    public ReplacePipeHttpRoute(string routeTemplate, object defaults)
        : base(routeTemplate, new HttpRouteValueDictionary(defaults))
    { }

    // OPTIONAL: Add additional overloads for constraints, dataTokens, and handler

    // Converts | to / on incoming route
    public override IHttpRouteData GetRouteData(string virtualPathRoot, System.Net.Http.HttpRequestMessage request)
    {
        var routeData = base.GetRouteData(virtualPathRoot, request);

        // RouteData will be null if the URL or constraint didn't match
        if (routeData != null)
        {
            var newValues = new HttpRouteValueDictionary();
            foreach (var r in routeData.Values)
            {
                newValues.Add(r.Key, r.Value.ToString().Replace("|", "/"));
            }
            routeData = new HttpRouteData(this, newValues);
        }

        return routeData;
    }

    // Converts / to | on outgoing route (URLs that are generated from WebApi)
    public override IHttpVirtualPathData GetVirtualPath(System.Net.Http.HttpRequestMessage request, IDictionary<string, object> values)
    {
        var newValues = new Dictionary<string, object>();
        if (values != null)
        {
            foreach (var r in values)
            {
                // Encode pipe as outlined in: http://www.tutorialspoint.com/html/html_url_encoding.htm
                newValues.Add(r.Key, r.Value.ToString().Replace("/", "%7c"));
            }
        }

        return base.GetVirtualPath(request, newValues);
    }
}

Usage

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.Add(
            name: "DefaultApi",
            route: new ReplacePipeHttpRoute(
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional, controller = "Vehicles" })
        );
    }
}

NOTE: You might want to reconsider using a pipe character in your URLs.

Upvotes: 1

Related Questions