0

I have a Web API project that has a few dozen RESTful methods, split about evenly between GETs, POSTs and PUTs. The system uses Entity Framework objects and Newtonsoft's JSON from Nuget (version 9.0.1).

Something I've done recently has suddenly broken all the POSTs and PUTs. I find that the [FromBody] objects I'm POST/PUTting arrive as null.

So my "Update User" method looks like so...

    [HttpPut]
    public IHttpActionResult Put([FromBody] User user)

...but "user" always arrives null. Likewise if I do this...

  var obj = Request.Content.ReadAsAsync<object>().Result;

...then obj is null.

But if I do this...

var jsonString = Request.Content.ReadAsStringAsync().Result;

...then I get the expected JSON. (But for my architecture, I don't want the JSON, I want the object.)

From what I understand, this is the kind of behavior that I'd expect if something has already read the Request.Content. The Content stream is non-rewindable, and gets set to the last byte; but .ReadAsStringAsync() and .ReadAsByteArrayAsync() get around this by (I presume) copying the stream and handling it themselves.

I call this from a WPF application using HttpClient. Sample call...

using (HttpClient http = API.GetHttpClient())
{
  string url = string.Format("{0}/User", thisApp.WebAPI_BaseUrl);
  RILogManager.Default.SendString("url", url);

  JsonMediaTypeFormatter formatter = new JsonMediaTypeFormatter() ;
  formatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

  HttpResponseMessage response;

  response = http.PutAsync<User>(url, user, formatter, "application/json").Result;
  ...

My "API.GetHttpClient()" routine looks like this. You can see I'm doing some JWT work on the client-side with a DelegateHandler, but I don't think that's pertinent here; it doesn't touch the outgoing request, just the incoming response.

 public static HttpClient GetHttpClient(bool WithAuthToken = true)
 {
     App thisApp = (App)System.Windows.Application.Current;

     //HttpClient http = new HttpClient();
     HttpClient http = HttpClientFactory.Create(new JWTExpirationHandler());

     http.BaseAddress = new Uri(thisApp.WebAPI_BaseUrl);
     http.DefaultRequestHeaders.Accept.Clear();
     http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

     if(WithAuthToken)
         http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", JWT);

     return http;

 }

I've confirmed that the incoming request is Content-type of "application/json" and UTF-8.

I have two DelegatingHandlers on the web server, but they don't seem to be the problem, because when I do the ReadAsAsync() at the very top of the first one, it's also null; and ReadAsStringAsync() returns the JSON.

So...what am I doing wrong? Again, this was working great, and something changed and all my POSTs and PUTs broke this way.

I saw some links that said to remove [Serializable] from my classes--but these are Entity Framework classes that I use in many places, and I don't want to do that.

Finally...when I call this Update PUT through Postman, it works.

UPDATE
Comparing the HttpRequests from my own client, and from Postman, I see that the former are "chunked" and the latter are not. This seems an important clue. I see some other folks dealing with the same:

ASP.NET Web Api - the framework is not converting JSON to object when using Chunked Transfer Encoding

I cannot find any clear indication of how to turn chunking off. I do see that there's a property to turn off chunking, but doing that doesn't help.

Question: What is triggering the "choice" to chunk? Is it the client choosing to do this, or the Controller, or some negotiation between the two?

Community
  • 1
  • 1
TomK
  • 523
  • 2
  • 7
  • 18
  • Does the User object your use in your WPF app match the one in your WebApi endpoint? You say its something you've recently done. have you updated the model? As a side point, I'd really strongly recommend not send your EF Entity Model out from the Api Controller. Map it to a simple Dto and send that, here is a good blog on how to do that..http://codethug.com/2015/02/13/web-api-deep-dive-dto-transformations-and-automapper-part-5-of-6/ Whilst this might not be your issue it is safer, easier to handle and gives you a clearer picture on the data you're dealing with – MartinM Feb 13 '17 at 11:30
  • Thanks, @MartinMilsom. The User object is the same. I arranged my solution so my .edmx file is in one project and its .tt file is in a "Common" project that all the others refer to. So changing the .edmx changes the objects, but all projects in the solution share the same User/other objects. – TomK Feb 13 '17 at 14:24
  • I see. However, as you are sending the Entity out over the Api, a change to it could still cause issues. For example, if a property is added that the json/xml serialiser does not know how to de-serialise, or,even if you'd added some logic to your constructor, or made the constructor private etc etc. these are just examples - but there is lots that can go wrong. It might be worth trying to deserialise the json string to your type manually - to see if any errors show. JsonConvert.DeserializeObject(jsonString); – MartinM Feb 13 '17 at 14:39
  • I'll look into generic DTOs, but the EF objects have worked fine for months. And the kicker is: It works if I fire the PUT from Postman; then the incoming "user" parameter is a fully-populated non-null User object. I even got the JSON for Postman from doing .ReadAsStringAsync! So I think the problem is somewhere in the _request_, not the object per se. To compare the two HttpRequests I logged them using ReflectionSoft's logging. The Postman's has a RawData property full of JSON, and Content_Length=663. The other has no RawData property and Content_Length=0. – TomK Feb 13 '17 at 14:39
  • Ah I see what youre saying about the request, I think you're probably right about that. What I mentioned in the last comment may still be worth it though- I'd say there is still likely a problem (de)serialising that json into your entity - its just coming from the sender's side, not the receiver's – MartinM Feb 13 '17 at 14:46
  • @Thanks again. I appreciate your brain/time on this! I did try JsonConvert.DeserializeObject(jsonString); and it works fine. I was hoping I'd see some useful error, but it deserializes into my object with no troubles. So I could use this workaround if I absolutely must. But it's a hack. I should be able to send my objects, and _have_, for months. And I imagine all the problems named could happen for a DTO too. I've not changed the EF constructors. I've added an EntityState property to each object (i.e, added/deleted/modified). That's also worked for months. – TomK Feb 13 '17 at 14:48
  • I saw a difference in the HttpRequests where the failing one was Transfer-Encoding: chunked. So I set request.Headers.TransferEncodingChunked = false; ...on the outgoing request, but it still comes in as Transfer-Encoding: Chunked. The outgoing request.Headers actually has two properties, .TransferEncoded (=null) and .TransferEncodingChunked (=false). – TomK Feb 13 '17 at 14:56
  • Efforts to turn off the chunking are also failing. [Tried this](http://stackoverflow.com/a/12539775/283160) and it didn't help. – TomK Feb 14 '17 at 01:12
  • I've just realised (and should of done sooner) on the request you are setting the json formatter manually and not using default. Assuming that you need to set the loophandling to ignore, you'd have to do this on receiving the request too. have you done that? I.e. you'd need to override/change the default json serializer. – MartinM Feb 14 '17 at 09:25
  • Failing that, it might be worth posting the entity object, to see if there are any more clues! – MartinM Feb 14 '17 at 09:39
  • I will try the loop handling thing but I suspect that won't change things. I added the loop handling only on the client side long ago, and it worked fine, on all my various POSTs and PUTs. Suddenly, they're all broken together, everywhere. And it still receives my objects fine if I send thru Postman, where I certainly _can't_ set loop handling. -- And I can post the entity object, but because it's happening to all of them I'd have to post all of them. If feels like something global happened that I have to undo. – TomK Feb 15 '17 at 18:17

1 Answers1

0

That was a long haul.

Don't know how it got there, but in my .csproj I had this:

<Reference Include="System.Net.Http, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
  <HintPath>..\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll</HintPath>
  <Private>True</Private>
</Reference>

Rather than this:

<Reference Include="System.Net.Http" />

And in my App.config I had this:

  <dependentAssembly>
    <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
  </dependentAssembly>
  <dependentAssembly>
    <assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
  </dependentAssembly>

...rather than...not this. I didn't need this stuff. I just took it out.

TomK
  • 523
  • 2
  • 7
  • 18