11

Given this WebApi service:

[ActionName("KillPerson")]
[HttpPost]
public void KillPerson([FromBody] long id)
{
    // Kill
}

And this HttpClient PostAsync call:

var httpClient = new HttpClient { BaseAddress = new Uri(ClientConfiguration.ApiUrl) };
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var serializerSettings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
    Formatting = Formatting.Indented,
    ReferenceLoopHandling = ReferenceLoopHandling.Serialize
};
var serializedParameter = JsonConvert.SerializeObject(parameter, serializerSettings);
var httpContent = new StringContent(serializedParameter, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(serviceUrl, httpContent).ConfigureAwait(false);
response.EnsureSuccessStatusCode();

I would expect response.EnsureSuccessStatusCode(); to succede but it throws a 404 instead. The funny thing is that fiddler tells med that the webapi service is returning 204 as expected and when I debug it the KillPerson runs without issue.

Update: I Have determined that this only happens when the client code is within a PCL or Silverlight 5 project. The exact same code will give the expected 204 if I duplicate it in a Windows forms application. If i point the Windows Forms app to client code contained in a PCL it gives me the 404 Again.

Update2: This resolves the issue (though it bothers me no end that I should need to do it):

[ActionName("KillPerson")]
[HttpPost]
public HttpResponseMessage KillPerson([FromBody] long id)
{
    return this.Request.CreateResponse(HttpStatusCode.OK);
}

This reintroduces the 404 (fiddler still says 204 and non-silverlight client runs fine)

[ActionName("KillPerson")]
[HttpPost]
public HttpResponseMessage KillPerson([FromBody] long id)
{
    return this.Request.CreateResponse(HttpStatusCode.NoContent);
}

Update 3 (resolved): Finally figured this out. Seems you get a choice of using either browser or client HTTP handling in Silverlight. When using browser HTTP handling a lot of stuff is unsupported - including various response codes and headers. Adding these lines before calling HttpClient fixed it:

WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
WebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);
user2083690
  • 161
  • 1
  • 8
  • Does the encoding on the service match the client? 404 on the client suggest to me that when it was inflating the response, it didn't match what it was expecting. – Dr Rob Lang Jan 17 '14 at 11:04
  • Yes - this only happens with void (204) methods - if I change the KillPerson service to return a string it runs fine. – user2083690 Jan 17 '14 at 11:14
  • Given your update, this isn't particularly useful but I got correct functionality on a console app: (returning 204) https://gist.github.com/brainwipe/8472430 Makes me think that there might be an AppDomain setting or encoding that is set globally somewhere. – Dr Rob Lang Jan 17 '14 at 12:11
  • Aren't POST requests give you a 201 (Created)? – trinaldi Jan 17 '14 at 17:55
  • Unfortunately, this isn't the only time we've seen weird behavior of HttpClient in Silverligt at our shop. There is similar trouble (inconsistent behavior with console or WinForms apps) for WS-Trust transactions as well. Very strange and murky because MSFT isn't spending any time fixing SL5. – jbeldock Jan 25 '14 at 20:30

2 Answers2

5

Finally figured this out. Seems you get a choice of using either browser or client HTTP handling in Silverlight. When using browser HTTP handling a lot of stuff is unsupported - including various response codes and headers. Adding these lines before calling HttpClient fixed it:

WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
WebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);
user2083690
  • 161
  • 1
  • 8
1

Adding the HttpResponseMessage returntype to the method is the documented way of doing this, so the solution you have found is the best really.

But if you don't want to change all your void methods that way, an alternative could be to add a delegating handler to change the response code on the fly - something like this:

public class ResponseHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var response = base.SendAsync(request, cancellationToken);

        if (request.Method == HttpMethod.Post)
        {
            response.Result.StatusCode = response.Result.IsSuccessStatusCode ? System.Net.HttpStatusCode.OK : response.Result.StatusCode;
        }
        return response;
   }
}

Note: This will change the statuscode for all your post methods (when successful). You would have to add some code if you only want it done for specific routes/methods (if you need different post methods to be treated differently).

CarllDev
  • 1,294
  • 2
  • 19
  • 34