1

I am coding a C# forms application to post some data to a web service. The object being posted is correctly added to the database, but the client is not receiving a SuccessStatusCode of true.

Here is the webservice function:

[Route("Postitem")]
[ResponseType(typeof(Item))]
public async Task<IHttpActionResult> PostItem(Item item)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    db.items.Add(item);
    await db.SaveChangesAsync();

    var data = CreatedAtRoute("DefaultApi", new { id = item.Id }, item);
    return data;
}

Here is the client PostItem function:

public async Task PostItem()
{
    var item = new Item(1, 0, "Posted Item Name 6", "Posted Item Data");

    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(baseAddress);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        var response = await client.PostAsJsonAsync("api/Postitem", item);
        if (response.IsSuccessStatusCode)
        {

        }
    }
}

The response.IsSuccessStatusCode has the value of false.

Here is the string value of the response:

"StatusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:\r\n{\r\n Pragma: no-cache\r\n Cache-Control: no-cache\r\n Date: Tue, 26 Jan 2016 03:37:09 GMT\r\n Server: Microsoft-IIS/10.0\r\n X-AspNet-Version: 4.0.30319\r\n X-Powered-By: ASP.NET\r\n Content-Length: 1174\r\n Content-Type: application/json; charset=utf-8\r\n Expires: -1\r\n}"

How can the client correctly determine if the object was posted correctly?

Simon
  • 7,991
  • 21
  • 83
  • 163
  • Not really sure what you are looking for - your service should not throw exception in case of success... There are plenty of resources on how to debug ASP.Net sites - so clearly you are not looking for tutorial... – Alexei Levenkov Jan 26 '16 at 03:50
  • No exceptions are being thrown, the data is added successfully, yet the client is not receiving a success status code. – Simon Jan 26 '16 at 03:52
  • i think it's return OK(data) – Muckeypuck Jan 26 '16 at 03:53
  • StatusCode: 500 is still being returned with OK(data). – Simon Jan 26 '16 at 03:55

1 Answers1

2

Use process of elimination...if one statement succeeds, but the method doesn't return after that, then what could it be? I'm guessing your CreatedAtRoute is throwing the 500 since your item was added to your database, but the method did not execute successfully.

Perhaps do as this answer suggests and try:

var data = CreatedAtRoute("DefaultApi", new { controller = "controllername", id = item.Id }, item);
return data;

Obviously, replace "controllername" with the name of your controller. However route attributes don't interact with the whole "DefaultApi" thing very well since I believe they are added under a different route name. You may actually want to try something like this and add a Name property to your RouteAttribute. This will create an explicit routeName that you can use in CreatedAtRoute as the first argument.

There is, however, a problem. Based on your naming convention (you called your route "Postitem", you are not getting the point of CreatedAtRoute. This function is meant to facilitate RESTful services. Your service is not restful. You should instead name your route "item" and have a corresponding GetItem method with the same route. One of them accepts an HTTP POST (your PostItem) and one accepts an HTTP GET. CreatedAtRoute is meant to help the calling function know the URL it should call in order to

If you don't want to go the restful route, you can ditch CreatedAtRoute altogether and just do this:

[Route("Postitem")]
[ResponseType(typeof(Item))]
public async Task<IHttpActionResult> PostItem(Item item)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    db.items.Add(item);
    await db.SaveChangesAsync();

    return this.Ok(new { id = item.Id });
}

General debugging note

You likely could solve this problem yourself if you actually look at the response from the controller. Use something like this or this. Your message you posted says its 1174 bytes long. What do you want to bet that it contains a JSON-formatted exception that would tell you exactly what went wrong?

General API note

I noticed that you are directly passing entities around (you add your item directly to your database). This is very bad, especially with navigation properties (they cause infinite loops in serializers). I would suggest having a separate model for your API and your DB. Make the thing that your method takes in able to transform itself into a database item and vice versa.

EDIT: An example of reading JSON

First, declare a class somewhere that looks like this:

[DataContract] //found in System.Runtime.Serializatino
public class ItemResult
{
    [DataMember(Name = "id")] //Same place as DataContractAttribute
    public int Id { get; set; }
}

This class represents the response from your service. Next, in your client class (the place where you declare PostItem...not the action method, the client method), declare the following:

private static readonly JsonSerializer serializer = new JsonSerializer();

This is from the very popular JSON.Net library. Install it via nuget if you don't have it already.

Next, this is what your PostItem needs to look like:

public async Task<ItemResult> PostItem()
{
    var item = new Item(1, 0, "Posted Item Name 6", "Posted Item Data");

    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(baseAddress);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        using (var response = await client.PostAsJsonAsync("api/Postitem", item))
        using (var rs = await response.Content.ReadAsStreamAsync())
        using (var sr = new StreamRead(rs))
        using (var jr = new JsonTextReader(sr))
        {
            if (response.IsSuccessStatusCode)
            {
                return serializer.Deserialize<ItemResult>(jr);
            }
            else
            {
                //deserialize as something else...an error message perhaps?
            }
        }

    }
}

Here is an explanation of what happens:

  1. Make the request via POST by calling PostAsJsonAsync
  2. Use the content from the response and get a stream which will contain the content the server sends back. This is response.Content.ReadAsStreamAsync.
  3. Wrap that stream in a stream reader (part of System.IO)
  4. Wrap that stream in a json text reader (part of Newtonsoft.JSON (JSON.Net's namespace))
  5. Check to see if the status code was a success (if you want to automatically throw an exception if there's an error, then call response.EnsureSuccessStatusCode instead.
  6. Use the previously declared serializer object to deserialize the JSON object returned by the server into the ItemResponse class.
Community
  • 1
  • 1
Los Frijoles
  • 4,771
  • 5
  • 30
  • 49
  • Thanks, I have ditched the CreatedAtRoute, but how on the client is the best way to get the id value that is being returned? – Simon Jan 26 '16 at 04:01
  • `this.Ok(new { id = item.Id })` will pass back a JSON document. You need to open the response stream and read it after the post completes. Would you like an example? – Los Frijoles Jan 26 '16 at 04:03