Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my attribute being fired on all actions, including ones that don't have the attribute?

I have a controller in my web api. Let's call it TimeController.

I have a GET action and a PUT action. They look like this:

public class TimeController : ApiController
{
    [HttpGet]
    public HttpResponseMessage Get()
    {
        return Request.CreateResponse(HttpStatusCode.OK, DateTime.UtcNow);
    }

    [HttpPut]
    public HttpResponseMessage Put(int id)
    {
        _service.Update(id);
        return Request.CreateResponse(HttpStatusCode.OK);
    }
}

I also have a route config as follows:

routes.MapHttpRoute("DefaultApi", "{controller}/{id}", new { id = RouteParameter.Optional });

so I can access it in a restful manner.

Now I also want to version the GET action using a custom Route attribute. I'm using code very similar to what Richard Tasker talks about in this blog post.

(the difference being that I use a regular expression to get the version from the accept header. Everything else is pretty much the same)

So my controller now looks like this:

public class TimeController : ApiController
{
    private IService _service;

    public TimeController(IService service)
    {
        _service = service;
    }

    [HttpGet, RouteVersion("Time", 1)]
    public HttpResponseMessage Get()
    {
        return Request.CreateResponse(HttpStatusCode.Ok, DateTime.UtcNow);
    }

    [HttpGet, RouteVersion("Time", 2)]
    public HttpResponseMessage GetV2()
    {
        return Request.CreateResponse(HttpStatusCode.Ok, DateTime.UtcNow.AddDays(1));
    }

    [HttpPut]
    public HttpResponseMessage Put(int id)
    {
        _service.Update(id);
        return Request.CreateResponse(HttpStatusCode.OK);
    }
}

However, now when I try to access the PUT endpoint I'm getting a 404 response from the server. If I step through the code in debug mode, I can see that the RouteVersion attribute is being fired, even though I haven't decorated the action with it.

If I add the attribute to the PUT action with a version of 1, or I add the built in Route attribute like this: Route("Time") then it works.

So my question is: why is the attribute firing even though I haven't decorated the action with it?

Edit: Here is the code for the attribute:

public class RouteVersion : RouteFactoryAttribute
{
    private readonly int _allowedVersion;

    public RouteVersion(string template, int allowedVersion) : base(template)
    {
        _allowedVersion = allowedVersion;
    }

    public override IDictionary<string, object> Constraints
    {
        get
        {
            return new HttpRouteValueDictionary
            { 
                {"version", new VersionConstraint(_allowedVersion)}
            };
        }
    }
}

public class VersionConstraint : IHttpRouteConstraint
{
    private const int DefaultVersion = 1;
    private readonly int _allowedVersion;
    public VersionConstraint(int allowedVersion)
    {
        _allowedVersion = allowedVersion;
    }

    public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
    {
        if (routeDirection != HttpRouteDirection.UriResolution)
        {
            return true;
        }
        int version = GetVersionFromHeader(request) ?? DefaultVersion;
        return (version == _allowedVersion);
    }

    private int? GetVersionFromHeader(HttpRequestMessage request)
    {
        System.Net.Http.Headers.HttpHeaderValueCollection<System.Net.Http.Headers.MediaTypeWithQualityHeaderValue> acceptHeader = request.Headers.Accept;
        var regularExpression = new Regex(@"application\/vnd\.\.v([0-9]+)",
            RegexOptions.IgnoreCase);

        foreach (var mime in acceptHeader)
        {
            Match match = regularExpression.Match(mime.MediaType);
            if (match.Success)
            {
                return Convert.ToInt32(match.Groups[1].Value);
            }
        }
        return null; 
    }
}

Edit2: I think there is some confusion so I've updated the Put action to match the route config

like image 696
Piers Karsenbarg Avatar asked Apr 01 '16 08:04

Piers Karsenbarg


1 Answers

So my question is: why is the attribute firing even though I haven't decorated the action with it?

It is clear from both the way your question is phrased "when I try to access the PUT endpoint" and the fact that it matches the GET action (and then subsequently runs its constraint) that you have not issued a PUT request to the server. Most browsers are not capable of issuing a PUT request, you need a piece of code or script to do that.

Example

using (var client = new System.Net.WebClient())
{
    // The byte array is the data you are posting to the server
    client.UploadData(@"http://example.com/time/123", "PUT", new byte[0]);
}

Reference: How to make a HTTP PUT request?

like image 157
NightOwl888 Avatar answered Nov 16 '22 01:11

NightOwl888