1
votes

I have an API controller that supports DELETE, GET, POST and PUT operations for an user object. The GET-one operation can retrieve a user account by their ID. But I also want to get the user by their UserName. I created separate methods for each and it seems to work fine as I can get the expected data both ways.

// GET: api/UserAccounts/5
[HttpGet]
[ResponseType(typeof(UserAccount))]
[Route("~/api/UserAccounts/{id:int}")]
public async Task<IHttpActionResult> GetUserAccount(int id) { ...  }

// GET: api/UserAccounts/JohnDoe
[HttpGet]
[ResponseType(typeof(UserAccount))]
[Route("~/api/UserAccounts/{userName}")]
public async Task<IHttpActionResult> GetUserAccount(string userName) { ... }

// DELETE: api/UseAccounts/5
[HttpDelete]
[ResponseType(typeof(UserAccount))]
public async Task<IHttpActionResult> DeleteUserAccount(int id) { ... }

// GET: api/UserAccounts
[HttpGet]
public IQueryable<UserAccount> GetUserAccounts() { ... }

// POST: api/UserAccounts
[HttpPost]
[ResponseType(typeof(UserAccount))]
public async Task<IHttpActionResult> PostUserAccount(UserAccount userAccount) { ... }

// PUT: api/UserAccounts/5
[HttpPut]
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutUserAccount(int id, UserAccount userAccount)

The problem is that whenever I attempt to do a DELETE, POST or PUT operation, I get a 405 - Method Not allowed response. If I comment out the GetUser(string) method, they all work.

I looked through a bunch of articles and documentation and I saw something about using the RouteOrder property on the Route attribute, but that doesn't make any difference.

My WebApiConfig has this code:

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

Since I am using attribute routing for my GetUser methods I would think this should be working. I'm using the default routes for the other methods/verbs.

Surely there is a way to make this work, right?

UPDATE:

I'm still experiencing this issue. I removed the GET (string) operation and removed all the attribute routes since the other operations use the DefaultApi route. All the standard operations work. However, if I introduce a GET (string) operation, only the GET-all operation works. The GET (int) version returns no response, presumably because the API cannot determine which GET overload to use. All other operations return 405 Method Not Allowed. To get around this I added an attribute route to the GET (int) and GET (string) methods and those now work, but the DELETE, POST and PUT operations return 405 Method Not Allowed.

If I add attribute routes to all the methods, everything works except the POST operation. In that case I get 405 Method Not Allowed. Here are the methods signatures with the attribute routes:

[HttpDelete]
[ResponseType(typeof(UserAccount))]
[Route("~/api/UserAccounts/{id:int}")]
public async Task<IHttpActionResult> DeleteUserAccount(int id)

[HttpGet]
[ResponseType(typeof(UserAccount))]
[Route("~/api/UserAccounts/{id:int}")]
public async Task<IHttpActionResult> GetUserAccount(int id)

[HttpGet]
[ResponseType(typeof(EnrollmentApiAccountDto))]
[Route("~/api/UserAccounts/{userName}")]
public async Task<IHttpActionResult> GetUserAccount(string userName)

[HttpGet]
[Route("~/api/UserAccounts")]
public IQueryable<UserAccount> GetUserAccounts()

[HttpPost]
[ResponseType(typeof(UserAccount))]
[Route("~/api/UserAccounts")]
public async Task<IHttpActionResult> PostUserAccount(UserAccount userAccount)

[HttpPut]
[ResponseType(typeof(void))]
[Route("~/api/UserAccounts/{id:int}")]
public async Task<IHttpActionResult> PutUserAccount(int id, UserAccount userAccount)

There is no constraint on the GET (string) attribute route. I can't use {username:alpha} because the username might contain digits.

I need to support the GET by userName operation because I need it for user authentication. But unless I can figure this out, I may have to create a different controller just to support that action. I prefer not to.

1
You have [HttpGet] as an attribute. Don't you need either a) the rest of the http verb attributes, or b) no verb attributes (which would allow PATCH as well)?gunr2171
I have all the attributes on my methods. They are actually optional but I like adding them to reduce any ambiguity,DesertFoxAZ
Please post the other methods - POST, PUT, etc.Big Daddy
Code for other methods are aboveDesertFoxAZ
Try copying the [Route(...)] attribute to all your other methods. I have a feeling that actions that don't have a route attribute are using the "DefaultApi" route.gunr2171

1 Answers

0
votes

I didn't mention this before, but there are two clients using this API controller.

The first is an ASP.NET web forms application. With all the methods decorated with route attributes, all operations work as expected. In the case of the POST operation, the route it is posting to is /api/UserAccounts. This is what I expected as the ID argument is not necessary.

The second is an AngularJS application. The way the resource is configured, the ID parameter is sent for POST's, meaning the route being posted to is /api/UserAccounts/0. This seems to be the problem. (Oddly a GET request doesn't pass the ID when I want to get all records, only when I want one record.)

I noticed this using Fiddler to inspect the request/response. For some reason, Fiddler was not able to capture the traffic between the web forms application and the API, but I know the code is composing the POST request without the ID parameter because it is not necessary.

Therefore, I then used Fiddler to compose a new POST request to /api/UserAccounts using the same payload that failed before, and when I submitted it, the PostUserAccount method controller executed and created the database record. This is what I expected.

However it must be noted that I have many other controllers that are called by my AngularJS application using this pattern of route (appending the ID of 0 for POST requests) and there is no problem. Apparently the addition of an overload for the GET operation that takes a string argument messes this up. I now need to figure out how to get my AngularJS application to not pass the ID argument for POST methods. I'll create another question to deal with that issue.