8

I searched a lot before putting the questions here but the more I search the more confused I get.

So I have created an handler and I am trying to get the route like this:

public class ExecutionDelegatingHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (securityAuthority.VerifyPermissionToExecute(request.GetRouteData().Route.RouteTemplate, request.Headers))
        {
            return base.SendAsync(request, cancellationToken);
        }
        else
        {
            httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
        }
    }
 }

GetRouteData returns null so I can't get to the RouteTemplate property but I can see the route there in a list deep in the stack. I found so many different ways which one can use to get the route, but those methods evaluate to null as well. I am a bit lost on how to get something so simple done. I am using self host for development but will use IIS for deployment.

UPDATE 1

I forgot to put here what else I had tried:

//NULL
request.GetRouteData(); 
//EMPTY
request.GetRequestContext().Configuration.Routes.GetRouteData(request).Route.RouteTemplate;
//EMPTY
request.GetConfiguration().Routes.GetRouteData(request).Route.RouteTemplate;

The route works just fine, but strangely if I try to get the controller to service that request I get a 404... if I just step over that I will get to the controller just fine.

HttpControllerDescriptor httpControllerDescriptor = request.GetRequestContext().Configuration.Services.GetHttpControllerSelector().SelectController(request);
IHttpController httpController = httpControllerDescriptor.CreateController(request);

I am using autofac to discover all the routes which I am defining just like:

[Route("queries/organization/clients")]
[HttpGet]
public ClientInitialScreenModel GetClients()
{
    return OrganizationModelsBuilder.GetClientInitialScreen();
}

UPDATE 2

If I GetRouteData gets called after the line above, I am able to get the route template:

base.SendAsync(request, cancellationToken);

var routeData = request.GetRouteData();

So maybe I misunderstood the whole picture and I cant get the route template before the handler that resolves which controller to execute for the request does its work... is that the case?

For reference this is the handler I am working on:

public class ExecutionDelegatingHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var securityAuthority = (ISecurityAuthority) request.GetDependencyScope().GetService(typeof (ISecurityAuthority));
        var configuration = (IWebApiConfiguration)request.GetDependencyScope().GetService(typeof(IWebApiConfiguration));
        var tsc = new TaskCompletionSource<HttpResponseMessage>();
        var httpResponseMessage = new HttpResponseMessage();

        if (request.RequestUri.AbsolutePath.Equals(configuration.CommandGatewayUrl, StringComparison.InvariantCultureIgnoreCase))
        {
            var apiMessage = JsonConvert.DeserializeObject<ApiCommandEnvelope>(request.Content.ReadAsStringAsync().Result);

            if (securityAuthority != null && !securityAuthority.VerifyPermissionToExecute(apiMessage, request.Headers))
            {
                httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
            }
            else
            {
                var messageProcessor = (IWebApiMessageProcessor)request.GetDependencyScope().GetService(typeof(IWebApiMessageProcessor));
                var reponse = messageProcessor.HandleRequest(apiMessage);

                httpResponseMessage.StatusCode = (HttpStatusCode) reponse.StatusCode;

                if (!string.IsNullOrEmpty(reponse.Content))
                {
                    httpResponseMessage.Content = new StringContent(reponse.Content);
                }
            }
        }
        else
        {
            if (securityAuthority != null && !securityAuthority.VerifyPermissionToExecute(request.GetRouteData().Route.RouteTemplate, request.Headers))
            {
                httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
            }
            else
            {
                return base.SendAsync(request, cancellationToken);
            }
        }

        tsc.SetResult(httpResponseMessage);

        return tsc.Task;
    }

UPDATE 3

The code runs fine in a non self hosting environment, so this is more like a self host issue.

MeTitus
  • 3,390
  • 2
  • 25
  • 49

5 Answers5

16

The Web Api still has a lot to improve. It was tricky to find a way to get this working and I just hope this saves other guys from spending all the time I did.

 var routeTemplate = ((IHttpRouteData[]) request.GetConfiguration().Routes.GetRouteData(request).Values["MS_SubRoutes"])
                                    .First().Route.RouteTemplate;
MeTitus
  • 3,390
  • 2
  • 25
  • 49
  • Haven't tested that one, but that would be strange to say the least, but anyway, a lot of things aren't really done properly, which I just hope ASP.Net vNext fixes. – MeTitus Jul 09 '15 at 00:47
  • Sorry I missed the answser. Your code works, I was referring to the other answer of @Shivan ahmady. – Hugo Hilário Jul 09 '15 at 16:05
  • Ok, cause I was finding it really strange why my code wouldn't work on a base controller. – MeTitus Jul 09 '15 at 16:07
  • Works great for me; very useful to add to a DelegatingHandler to log all the routes that are in active usage, so you can make data-driven decisions about which API endpoints can be obsoleted without causing too many breaking changes. – David Keaveny Jul 01 '16 at 00:00
  • Yep, that what I do as well. I also do access validation using delegates.. A role is composed of multiples routes and this way I can control the access to each route. – MeTitus Jul 01 '16 at 13:01
  • @taras.roshko LOL... I think this is all solved in the new ASP.net core – MeTitus Oct 05 '16 at 21:50
  • @Marco btw it does not work throughout entire request, i.e. If you resolve this at the very beginning - it's does resolve, but during action run it just won't so you have to cache it somewhere which makes it even more gross. Checking out ASP.NET Core now.. – illegal-immigrant Oct 05 '16 at 22:11
  • If that's the case, because I haven't tried it out, just add a new request key and add it to the request object. – MeTitus Oct 05 '16 at 22:19
4

I had a similar issue, but was able to get the route inside the message handler by the following:

request.GetConfiguration().Routes.GetRouteData(request).Route.RouteTemplate;

Shivan A.
  • 769
  • 6
  • 5
4

The answer from Marco (shown below) is correct so long as there isn't more than one route defined with the same HttpMethod. The .First() will grab the 1st route defined in that specific ApiController, but this doesn't ensure it grabs the correct one. If you use the ControllerContext to get the Route, you can be sure you've got the exact endpoint you want.

Marco's:

var routeTemplate = ((IHttpRouteData[])request.GetConfiguration()
                     .Routes.GetRouteData(request).Values["MS_SubRoutes"])
                     .First().Route.RouteTemplate;

The code:

((IHttpRouteData[])request.GetConfiguration()
                     .Routes.GetRouteData(request).Values["MS_SubRoutes"])

actually returns a collection of IHttpRouteData, and it contains a record for each endpoint which has the same HttpMethod (Post, Get, etc)... The .First() doesn't guarantee you get the one you want.

Guaranteed To Grab Correct Endpoint's RouteTemplate:

public static string GetRouteTemplate(this HttpActionContext actionContext)
{
    return actionContext.ControllerContext.RouteData.Route.RouteTemplate;
} 

I used an extension method so to call this you'd do:

var routeTemplate = actionContext.GetRouteTemplate();

This will assure that you get the specific RouteTemplate from the endpoint making the call.

SharpC
  • 6,974
  • 4
  • 45
  • 40
0

I think you can get route Data from request.Properties property and easy to unit test.

/// <summary>
    /// Gets the <see cref="System.Web.Http.Routing.IHttpRouteData"/> for the given request or null if not available.
    /// </summary>
    /// <param name="request">The HTTP request.</param>
    /// <returns>The <see cref="System.Web.Http.Routing.IHttpRouteData"/> or null.</returns>
    public static IHttpRouteData GetRouteData(this HttpRequestMessage request)
    {
        if (request == null)
        {`enter code here`
            throw Error.ArgumentNull("request");
        }

        return request.GetProperty<IHttpRouteData>(HttpPropertyKeys.HttpRouteDataKey);
    }

    private static T GetProperty<T>(this HttpRequestMessage request, string key)
    {
        T value;
        request.Properties.TryGetValue(key, out value);
        return value;
    }

Reference link of code

Ankit Rana
  • 383
  • 6
  • 24
0
var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template ?? "Unknown";
Thomas Eyde
  • 3,820
  • 2
  • 25
  • 32