Wednesday, April 13, 2016

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

Leave a Comment

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

4 Answers

Answers 1

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?

Answers 2

I think its because of your action signature in combination with the default route

In your default route you specify the Id attribute as optional, however in your action you use the parameter days, in this case the framework can't resolve it. you either have to add it as a query string parameter eg:

?days={days} 

Or change the signature to accept id as input.

Since it can't resove the action with days in the url it will return a 404

Personally i don't use the default routes and always use Attribute routing to prevent this kinda behavior

Answers 3

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

Any controller methods that do not have a route attribute use convention-based routing. That way, you can combine both types of routing in the same project.

Please see this link : attribute-routing-in-web-api-2

Also as method is not decorated with route attribute, When the Web API framework receives an HTTP request, it tries to match the URI against one of the route templates in the routing table. If no route matches, the client receives a 404 error. That is why you are getting 404

Please see this one as well : Routing in ASP.NET Web API

Answers 4

using (var client = new System.Net.WebClient()) {

client.UploadData(@"http://example.com/time/123", "PUT", new byte[0]); 

}

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment