C# Web API 405 method not routing to my PUT, always hitting my GET - asp.net-web-api

I have been working with Web API for well over a year, and haven't run into this problem before. I am really at my witt's end after spending hours googling, and looking at stack overflow posts etc. I am wondering if I just have some brain fart thing going on.
I have a controller where I want a get and a put against the same route:
[Route("api/strings")]
[HttpGet]
public IHttpActionResult GetStrings()
{
try
{
var siteStrings = svc.GetSiteStrings(_appId);
return Ok(new { strings = siteStrings });
}
catch(Exception)
{
return InternalServerError();
}
}
[HttpPut]
[AcceptVerbs("PUT")]
[Route("api/strings")]
public IHttpActionResult PutString(String key, String text)
{
//TODO: add authorization to this one.
try
{
if (svc.UpdateString(key, text, _appId))
{
return Ok();
}
return InternalServerError();
}
catch (Exception)
{
return InternalServerError();
}
}
My routing is just the default out of the box routing as can be seen here:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
After a seeming eternity doing configs over and over based on so many other stack overflow questions about 405 errors, I realized that I can verify that mine is trying to route my PUT method to my GET's endpoint. However if I change the route on my put to something else, I always get a 404. I can't figure out why it is putting all my puts or posts through to my GET verb and thus saying that the action is not allowed on my route. I am sure I am just missing something trivial at this point but mental fatigue and tired eyes are just holding me back. Anyone see something stupid I am doing wrong?

All I had to do was create a class that my put has to model bind to rather than two parameters, and the routing started working. That is surprising, but I just had to do the following and it works:
public class temp
{
public String key { get; set; }
public String text { get; set; }
}
[HttpPut]
[AcceptVerbs("PUT")]
[Route("api/strings/")]
public IHttpActionResult PutString(temp data)
{
//TODO: add authorization to this one.
try
{
if (svc.UpdateString(data.key, data.text, _appId))
{
return Ok();
}
return InternalServerError();
}
catch (Exception)
{
return InternalServerError();
}
}
Obviously I won't keep that temp class there, but it was a quick stab in the dark to see if the put would route correctly that way. Sure enough it did. Must be something in the model binding specs for Web API that I wasn't aware of.

Related

ASP.net 5 Web API Post CreatedAtRoute always returns 500 Internal Server Error

The database works. It does actually insert the new record, but when I use CreatedAtRoute(), I always get a 500 back from the client. Why?
My controller's Get:
[Route("api/[controller]")]
public class IngredientController : Controller
{
private SimpleCookbookDbContext db { get; set; }
public IngredientController(SimpleCookbookDbContext context)
{
db = context;
}
// GET: api/values
[HttpGet]
public async Task<IEnumerable<Ingredient>> Get()
{
return await db.Ingredients.ToListAsync();
}
// GET api/values/5
[HttpGet("{id}", Name = "GetIngredient")]
public async Task<Ingredient> Get(int id)
{
return await db.Ingredients.SingleOrDefaultAsync(i => i.Id == id);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]Ingredient ingredient)
{
try
{
var res = await IM.CreateAsync(ingredient);
if (!res.Success)
{
return HttpBadRequest(res.Errors);
}
}
catch(Exception)
{
return new HttpStatusCodeResult((int)HttpStatusCode.InternalServerError);
}
return CreatedAtRoute("GetIngredient", new { controller="Ingredient", id = ingredient.Id });
}
}
I tried debugging this. Yes, it would return the HttpBadRequest if the ingredient I'm trying to insert already exists.
I tried putting a breakpoint inside the catch block, and I'm not getting there, so I assume there was no error from the database.
The record does get inserted to the database. I do get to the line return CreatedAtRoute(...); but I get a 500 back. (I set a breakpoint there, too).
Now, I'm using fiddler. My request is this:
POST /api/ingredient HTTP/1.1
Host: localhost:55303
Content-Type: application/json;charset=utf-8
{"id":0, "name": "rosemary", "description": "rosemary"}
I also removed the double quotes on the property names, and I still get the same 500.
I do have camel-casing resolved at the Startup:
services.AddMvc().AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
I think I showed all relevant code. If you need more, please let me know.
UPDATE
CreatedAtRoute has an overload taking in three parameters:
return CreatedAtRoute("GetIngredient", new { controller="Ingredient", id = ingredient.Id }, ingredient);
The last parameter is an object, which you could create dynamically or pass back your entire entity, depending on what you want to expose back.
It's strange how there's a 2-parameter variant that would result in a strange 500 response.

Post or Put multiple items in a single request

Is it possible to Post or Put more than a single item at a time, in a single request?
From
GET /api/books Get all books.
POST /api/books Create a new book.
PUT /api/books/{id} Update an existing book.
To
POST /api/books Create books.
PUT /api/books Update books.
Let's say you have a class called Book, defined as:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
Now we have a simple Web.Api controller with a POST method:
public class BooksController : ApiController
{
[HttpPost]
[Route("books")]
public HttpResponseMessage PostBooks([FromBody] IEnumerable<Book> books)
{
return Request.CreateResponse(HttpStatusCode.Created);
}
}
The method doesn't do anything with the data, it's purely so it compiles and runs. My first attribute indicates this is a POST, the second attribute determines the route, I'm using attribute based routing because I just like it more, but you can roll your own routing rules.
Now the PostBooks method takes in a parameter of type Ienumerable<Book>, not just a single parameter of type Book.
When I now stand up this little endpoint, and hit it with the following url:
http://localhost:port/books
And specify the request to be a POST and supply the following payload in the body of the request:
[
{
"Title":"This is a book",
"Author":"Joe Bloggs"
},
{
"Title":"This is a book: The reckoning",
"Author":"Joe Bloggs"
}
]
My breakpoint is hit and Web.API managed to deserialize my payload into the books parameter when it comes into the PostBooks method:
The same applies for a PUT, the only thing that needs changing is the attribute.
Finally I use like below's code.
// Post Multi
// POST: api/books
[Route("api/books")]
[ResponseType(typeof(IEnumerable<Book>))]
public IHttpActionResult Postbooks(IEnumerable<Book> books)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
foreach (var item in books)
{
db.Book.Add(item);
}
try
{
db.SaveChanges();
}
catch (DbUpdateException)
{
throw;
}
return Ok(books);
}

ASP.NET Web API Action Routing

After a lengthy discussion in terms of how our API would need to behave due to limitations of the backend design, I'd like to have the following possibilities:
1. /students/251/employment <--- allow GET, PUT, POST
2. /students/251/employment/jobs <--- allow GET only
3. /students/251/employment/jobs/435 <--- allow all verbs
4. /students/251/employment/internships <--- allow GET only
5. /students/251/employment/internships/664 <--- allow all verbs
These cases are working for GET requests. I'm struggling when I try to do a PUT request for case #1 and #3:
Case #1 Error
No HTTP resource was found that matches the request URI '/students/251/employment/221'.,
No action was found on the controller 'Employment' that matches the name '221'.
Case #3 Error
The requested resource does not support http method 'PUT'.
Here's an abridged version of my controller methods:
public ApiEmploymentGetResult Get(long id) {
// code omitted
}
[HttpGet]
public IEnumerable<ApiJob> Jobs(long id) {
// code omitted
}
[HttpGet]
public IEnumerable<ApiOwnVenture> OwnVenture(long id) {
// code omitted
}
public void Put(long id, MyModel model) {
// breaks before getting here
}
My routing looks like this, but I'm not sure it's quite right, even though the GETs are working.
context.Routes.MapHttpRoute(
name: "V1/EmploymentApi",
routeTemplate: "api/v1/Employment/{action}/{jobId}",
defaults: new { controller = "Employment", jobId = RouteParameter.Optional, action = "Get" }
);
Case #1 seems to be conflicting due to the framework expecting an action rather than the 221. I'd like to be able to get all of these cases working.
You may want to look at Attribute Routing (Web API 1 and Web API 2).
public class StudentsController : ApiController
{
[HttpPut]
[Route("students/{studentId}/employment")]
public void UpdateStudentEmployment(int studentId) { ... }
[HttpPut]
[Route("students/{studentId}/employment/jobs/{jobId}")]
public void UpdateStudentEmploymentJob(int studentId, int jobId) { ... }
}

How To Pass formdata parameters into ASP.NET WebAPI without creating a record structure

I have data coming into my form that looks like the image below (sessionsId: 1367,1368).
I've create c# in my webapi controller that works as below. when I've tried ot just make use SessionIds as the parameter (or sessionIds) by saying something like PostChargeForSessions(string SessionIds) either null gets passed in or I get a 404.
What is the proper way to catch a form parameter like in my request without declaring a structure.
(the code below works, but I'm not happy with it)
public class ChargeForSessionRec
{
public string SessionIds { get; set; }
}
[HttpPost]
[ActionName("ChargeForSessions")]
public HttpResponseMessage PostChargeForSessions(ChargeForSessionRec rec)
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, new ShirtSizeReturn()
{
Success = true,
//Data = shirtSizeRecs
});
return response;
}
You can declare the action method like this.
public HttpResponseMessage Post(string[] sessionIds) { }
If you don't want to define a class, the above code is the way to go. Having said that, the above code will not work with the request body you have. It must be like this.
=1381&=1380

WebApi in MVC3, cannot hit my API Controller (500 internal error)

I have the below route mapped in my AreaRegistration:
public override void RegisterArea(AreaRegistrationContext context)
{
if (context != null)
{
context.Routes.MapHttpRoute(
name: "API_Default",
routeTemplate: "Areas/Test/AIO/api/{controller}/{id}",
defaults: new
{
id = RouteParameter.Optional
});
When I look at the Global.asax file, I can see the HttpRoute is being Registered, and is listed in the RouteTable.Routes as a {System.Web.Http.WebHost.Routing.HttpWebRoute}.
Problem is, when I go to the url... https://myRoot/Areas/Test/AIO/api/AioApi/test or https://myRoot/Areas/Test/AIO/api/AioApi, it's giving me a 500 internal server error.
I'm not sure how to view the actual error, when stepping thru the code I cannot see anything after it leaves Application_BeginRequest.
My controller code:
public class AioApiController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Any insight as to why I cannot hit the API controller? I can hit my regular MVC controller in the same context.
Any help is greatly appreciated!
One thing I found out with WebApi is that there are two Route collections: System.Web.Routing.RouteCollection and System.Web.Http.HttpRouteCollection. I believe (but can't remember) that you need to use the latter in order for your ApiController derivations to work properly (luckily the syntax is the same).

Resources