ASP.net 5 Web API Post CreatedAtRoute always returns 500 Internal Server Error - asp.net-web-api

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.

Related

ASP.NET 5, MVC 6, API response status

I have a controller in my MVC 6 API project. There is a post method and what I want is to validate posted value and return some error back to a client if data are not valid.
[HttpPost]
public void Post([FromBody]PostedHistoricalEvent value)
{
if (!IsHistoricalEventValid(value))
{
//return error status code
}
}
Now I wonder why Post method in the default template does not have any returning type but void and how then one should return an error http status code with some message?
An action method that has no return type (void) will return an EmptyResult, or an 200 OK without a response body.
If you want to alter the response, then you can either change the return type and return an HttpStatusCodeResult:
public IActionResult Post(...)
{
// ...
return new HttpStatusCodeResult(400);
}
Or set it on the Controller.Response:
public void Post(...)
{
// ...
Response.StatusCode = 400;
}

Update data with Microsoft.AspNet.WebApi

Hey i am having a big trouble updating data in my client side REST application.
I made a Web API controller.
// PUT: api/Contacts/5
[ResponseType(typeof(void))]
public IHttpActionResult PutContact(Contact contact, int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != contact.ContactId)
{
return BadRequest();
}
_contactService.Update(contact);
return StatusCode(HttpStatusCode.NoContent);
}
And also client side service method:
public async Task<T> PutData<T>(T data, int dataId)
{
HttpResponseMessage resp = await this._client.PutAsJsonAsync(_serviceUrl + "/" + dataId, data);
resp.EnsureSuccessStatusCode();
return await resp.Content.ReadAsAsync<T>();
}
Service URL shows in debug mode that i goes to endpoint:
http://localhost:21855/api/Contacts/8
But it does not even go to breakpoint when i debug my server controller PutContact method.
What i am doint wrong? I need to update the data but i cant, because my client-side application won't even go to servers breakpoint on debug mode!!!
It gives me an error response 405 : Method not allowed
You can't have two different body parameters in the same method.
What you need to do is to set the id parameter to come from the URI and the Contact parameter from the body, like this:
public IHttpActionResult PutContact([FromBody]Contact contact, [FromUri]int id)
{
// method code
}
BTW, I suppose you have a GET method in your controller which looks like this:
public IHttpActionResult GetContact(int id)
{
// method code
return Contact; // pseudo-code
}
The error you getting comes from the fact that the system is not really calling your PUT method but the GET one (the system is ignoring the Contact parameter for the reason I expressed before): calling a GET method with a PUT verb results in a 405 Method Not Allowed exception.

How to use ASP.Net 5 CreatedAtAction

I'm trying to do an HttpPost to create an entity and I want to return a 201 created with the location header set to https://mysite/api/entity/1. I can't figure out how to do this while returning an IActionResult.
Calling it like this
return CreatedAtAction("Get", new { id = entity.Id }, "/{id}");
I get https://mysite/api/entity?id=11
I can't figure out how to get what I want.
I assign a route name to the Get action and reference it by name to make this work. For example:
// GET api/values/5
[HttpGet("{id}", Name="GetAction")]
public string Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public IActionResult Post([FromBody]string value)
{
return this.CreatedAtRoute(
"GetAction", new { id = 5 }, new { id = 5 });
}

Post Scalar data type using HttpClient.PostAsJsonAsync

I am invoking ASP .Net Web API using HttpClient and invoke actions successfully. Also I am able to POST custom object into action as well.
Now problem I am facing is, not able to post scalar data type like Integer,String etc...
Below is my controller and application code that invokes action
// Test application that invoke
[Test]
public void RemoveCategory()
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage();
HttpResponseMessage response = client.PostAsJsonAsync<string>("http://localhost:49931/api/Supplier/RemoveCategory/", "9").Result;
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
}
// Controller and Action in Web API
public class SupplierController : ApiController
{
NorthwindEntities context = new NorthwindEntities();
[HttpPost]
public HttpResponseMessage RemoveCategory(string CategoryID)
{
try
{
int CatId= Convert.ToInt32(CategoryID);
var category = context.Categories.Where(c => c.CategoryID == CatId).FirstOrDefault();
if (category != null)
{
context.Categories.DeleteObject(category);
context.SaveChanges();
return Request.CreateResponse(HttpStatusCode.OK, "Delete successfully CategoryID = " + CategoryID);
}
else
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, "Invalid CategoryID");
}
}
catch (Exception _Exception)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, _Exception.Message);
}
}
When I Post custome object that represent "Category" table in Northwind database all things working properly but I am not able to post scalar data like Integer and String
When I am post string data type I am getting following exception
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:49931/api/Supplier/RemoveCategory/'.","MessageDetail":"No action was found on the controller 'Supplier' that matches the request."}
Can anyone guide me?
You will have to mark your CategoryID parameter as [FromBody]:
[HttpPost]
public HttpResponseMessage RemoveCategory([FromBody] string CategoryID)
{ ... }
By default, simple types such as string will be model bound from the URI.

httpmessagehandler - reading content

I created a message handler which will log the request and the response. ideally I want to
public class LoggingMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
LogRequest(request);
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
LogResponse(response);
return response;
});
}
private void LogRequest(HttpRequestMessage request)
{
var writer = request.GetConfiguration().Services.GetTraceWriter();
var content = request.Content;
(content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
writer.Trace(request, "request", System.Web.Http.Tracing.TraceLevel.Info, t =>
{
t.Message = x.Result;
});
});
}
private void LogResponse(HttpResponseMessage response)
{
var request = response.RequestMessage;
var writer = request.GetConfiguration().Services.GetTraceWriter();
var content = response.Content;
(content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
writer.Trace(request, "response", System.Web.Http.Tracing.TraceLevel.Info, t =>
{
t.Status = response.StatusCode;
t.Message = x.Result;
});
});
}
}
and here is my client code.
public ActionResult Index()
{
var profile = Client.GetAsync("Vendor").Result.EnsureSuccessStatusCode().Content.ReadAsAsync<VendorProfileModel>().Result;
return View(profile);
}
Logging appears to be working. However, when this handler is registered my client code returns an empty object. If I remove this handler the model is successfully read from the response and displayed on screen.
Is there a way to read the content and display the results on the client?
after a few more days for digging around on the net I finally found the root problem and a solution. First the problem:
everything in webapi is async
my action uses Controller.User which in turn is calling Thread.CurrentPrinciple
I am using ITraceWriter as my logging abstraction
apparently there is a bug in the ITraceWriter mechanicism where the current profile is not propagated across threads. therefore, i loose the principle when i get to my controller action. therefore, my query returns an empty result, rather than a fully populated result.
solution: don't use ITraceWriter to log messages. It would have been nice to use the built in mechanics, but that doesn't work. here is the link to the same issue which provides more detail/context.
https://aspnetwebstack.codeplex.com/workitem/237

Resources