Returning data from my API and deserializing it in the client controller, the content seems to empty. I've verified that the API is returning valid data, and it the "count" in the response is accurate. However, the data when serialized is seems not be initialized.
Not quite sure where to look for what's causing the issue. My best theory is that the data is being deserialized before it's materialized due to the async, but that doesn't explain why the count is correct (2 records).
public async Task<IActionResult> Index()
{
var httpClient = _httpClientFactory.CreateClient("APIClient");
var request = new HttpRequestMessage(HttpMethod.Get, "/api/AppUsers");
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
var users = await JsonSerializer.DeserializeAsync<IList<AppUser>>(responseStream);
return View(new IndexViewModel(users));
}
}
else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized ||
response.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
return RedirectToAction("AccessDenied", "Authorization");
}
throw new Exception("Problem accessing the API");
}
API Method which is called
[HttpGet]
public IActionResult GetAppUsers()
{
var users = _appUserService.ListAppUsers();
return Ok(users);
}
Calling the API Method directly shows the 2 rows returned as expected
After some debugging, I noticed that the json structure returned from the API is having first letter in property name as lowercase, but the AppUser class have first letter UpperCase. Not sure why this happens, but that's the reason why the deserialization fails.
Try to deserialize to List instead to IList:
var users = await JsonSerializer.DeserializeAsync<List<AppUser>>(responseStream);
I think that you should remove ConfigureAwait(false) so you can remain in the same synchronizaton context.
For more info check this link: https://devblogs.microsoft.com/dotnet/configureawait-faq/
After noticing the properties changing from upper case to lower case on the JSON object, the solution will be to either force JSON to PascalCase as the default now is camelCase
https://codeopinion.com/asp-net-core-mvc-json-output-camelcase-pascalcase/
or I need to consider the camelCase on deserialization.
This issue stole several hours from my life :/
Hope this post can save some hours for somebody else.
One solution would be to disable the defaulting
builder.Services.AddControllers(options =>
{
options.ReturnHttpNotAcceptable = true;
})
.AddXmlSerializerFormatters()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null; //to avoid defaulting to camelCase
});
Related
HeyGuys
I'm working on a WebApi project that receives requests from clients and redirects these requests to other services that are not open for direct access.
By default, .Net serializes and deserializes the Json request parameters automatically, so I need to re-serialize them before calling the appropriate service. The same problem occurs when receiving the service response. I need to deserialize it before sending the response to the user; otherwise .Net framework will serialize it one more time, resulting in a "Json of Json" response.
I found this answer but it does not seem to work with .NetCore; so I tried to create my own ModelBinder that just reads the Json object and returns it.
class JsonUnformatterBinderProvider : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
return new JsonUnformatterBinder(new SimpleTypeModelBinder(context.Metadata.ModelType));
}
}
and
class JsonUnformatterBinder : IModelBinder
{
private readonly IModelBinder _fallbackBinder;
public JsonUnformatterBinder(IModelBinder fallbackBinder)
{
_fallbackBinder = fallbackBinder;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string currMethod = bindingContext.ActionContext.HttpContext.Request.Method;
if ("POST".Equals(currMethod) || "PUT".Equals(currMethod))
{
string strData = new StreamReader(bindingContext.ActionContext.HttpContext.Request.Body).ReadToEnd();
bindingContext.Result = ModelBindingResult.Success(strData);
return Task.CompletedTask;
}
return _fallbackBinder.BindModelAsync(bindingContext);
}
}
This code is very simple, it was my first attempt and it worked well for my purposes. However, I still get the "Json of Json" problem when I take the second service answer and returns back to the user.
I basically have no idea what I can do to overcome this, so any workaround is welcome here.
If you need just redirect a request without modification, you could read it from input stream directly and send it to inner service. You could also use such approach to read responce from inner service.
//1. Set empty parameter list in action then neither serializator nor model binder are not invoked.
public async Task<ContentResult> ProxyAction(/*empty parameter list*/)
{
var newUrl = #"https://stackoverflow.com";
var data = this.Request.Body;
using (var client = new HttpClient())
{
//2. Read request body from input stream.
var reader = new StreamReader(data);
var json = reader.ReadToEnd();
using (var content = new StringContent(json))
{
//3. Set correct content type
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(this.Request.ContentType);
//4. Post request to inner service
var response = await client.PostAsync(newUrl, content);
//5. Read response without deserialization
var innerResponse = await response.Content.ReadAsStringAsync();
var contentType = response.Content.Headers.ContentType.ToString();
var statusCode = response.StatusCode;
//6. Return inner response without serialization
var outerResponse = this.Content(innerResponse, contentType);
outerResponse.StatusCode = (int)statusCode;
return outerResponse;
}
}
}
What's the proper way to handle errors when using Pushstreamcontent?
I use Pushstreamcontent to stream data directly from database to a client.
On the client I use HttpCompletionOption.ResponseHeadersRead when recieving the result.
In the case the data is not available, I want to return a HttpStatusCode 404 (Not Found) for example.
Currently I only detect that there is no data, during the execution of the lambda (CopyBinaryValueToResponseStream).
At that point in time I cannot change the state of the HttpResponeMessage anymore.
So what is a proper way to handle such cases? I wanted to avoid an additional check in the database upfront, but right now that seems to be the only way to get it done?
[Route("{id}")]
public HttpResponseMessage GetImage(int id)
{
HttpResponseMessage resp = new HttpResponseMessage();
// do I need to check here first if the data is available?
// and return 404 if the data is not available
// resp.StatusCode = HttpStatusCode.NotFound
// or can I handle it later from within the lambda?
resp.Content = new PushStreamContent(async (responseStream, content, context) =>
{
// what if an error happens in this function? who do I get that error to the client?
await CopyBinaryValueToResponseStream(responseStream, id);
});
return resp;
}
You cannot fix it within the PushStreamContent action. By the time that action is executing, you have already started sending the response, and thus have already sent a 200. This is a drawback of PushStreamContent.
If you have some way to detect that the resource does not exist before streaming (for example, if some file does not exist), you can detect that first and return a 404, i.e. not using PushStreamContent at all in that case.
[Route("{id}")]
public HttpResponseMessage GetImage(int id)
{
HttpResponseMessage resp = new HttpResponseMessage();
if (File.Exists(#"c:\files\myfile.file"))
{
resp.StatusCode = HttpStatusCode.NotFound;
return resp;
}
// file exists - try to stream it
resp.Content = new PushStreamContent(async (responseStream, content, context) =>
{
// can't do anything here, already sent a 200.
await CopyBinaryValueToResponseStream(responseStream, id);
});
return resp;
}
After fetching data from the database, I serialize it into XML.
I then write that XML into a Redis caching instance as a string.
I want the endpoint to check if data exists in the cache and based on the result either return the data from the cache, or hit the DB, cache the data and then return that.
My code works just fine when executed synchronously:
Working Synchronous Code
[HttpGet]
public IHttpActionResult Test(int userId)
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
var data = Cache.StringGet("Cart:" + userId);
if (string.IsNullOrEmpty(data))
{
// Grab data from DB and format as XML
Cache.StringSet("Cart:" + userId, data, TimeSpan.FromHours(2));
}
response.Content = new StringContent(data);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
return new ResponseMessageResult(response);
}
Where everything goes bananas is when trying to make it Asynchronous.
Broken Async Code
( I included the smallest amount of code necessary to reproduce the issue )
[HttpGet]
public async Task<HttpResponseMessage> TestAsync(int userId)
{
var data = await Cache.StringGetAsync("Cart:" + userId);
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent("<test>Test</test>");
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
return response;
}
Note that in this example above I'm not even accessing the asynchronously loaded data. If I comment out the await line, things start working again. It only fails if a await is in the code.
The problem that occurs is that 50% of the time, requests to the endpoint just ... stall and never finish. Fiddler screenshot attached to highlight the issue.
Finally, if there is a easier way to skip media formatting and content negotiation, i'd be more than happy to change my approach.
I should add that the service that will consume this endpoint only supports XML, and it made no sense to me to deserialize and reserialize on every request.
Problem Solved!
It ended up being Azure Application Insights.
I guess it does not fully support async or has issues with async in combition with manually creating HttpResponseMessages.
Thank you all for the responses.
I have following web api method:
public IHttpActionResult Get(int id)
{
var person = _personRepository.GetPersonByID(id);
if (person == null)
return NotFound();
return Ok<Person>(person);
}
And following client calling method:
var data = default(IEnumerable<T>);
var response = _client.GetAsync(relativeUri).Result;
response.EnsureSuccessStatusCode(); // Throw on error code.
if (response.IsSuccessStatusCode)
{
//string dataString = response.Content.ReadAsStringAsync().Result;
data = response.Content.ReadAsAsync<IEnumerable<T>>().Result;
}
else
{
//return response status code/reason phrase
}
after ReadAsAsync call is finished, data variable indicates a collection of type T with count matching returned rows but all objects (elements) are having null valued properties or empty.
Actual values are not being populated in properties.
Fiddler shows the JSON string. Even, ReadAsStringAsAsync() method returns JSON string.
Is there a better way to parse JSON into T type using one of ReadAsXXXX() methods?
Thanks,
-Jignesh
I just wanted to update that I have found the root cause of the issue that I was facing.
The code sample that I mentioned is actually the correct way of calling web api methods.
In my case, JSON string was formatted in Camel Casing at server side and at client side, default formtting was used.
this is server side code which was causing the issue:
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
As soon as this was removed, it started working.
Clearly, ReadAsAsync method could not find the matching properties and therefore all values were being dropped.
Thanks,
-Jignesh
I have this line of code
var response = new HttpClient().PostAsJsonAsync(posturi, model).Result;
The Called WebAPI controller returns a bool to make sure the object was saved, but how do I return that bool response?
Continue to get from content:
var httpClient = new HttpClient();
var response = httpClient.PostAsJsonAsync(posturi, model).Result;
bool returnValue = response.Content.ReadAsAsync<bool>().Result;
But, this is really naive approach for quick way to get result. PostAsJsonAsync and ReadAsAsync is not designed to do like this, they are designed to support async await programming, so your code should be:
var httpClient = new HttpClient();
var response = await httpClient.PostAsJsonAsync(posturi, model);
bool returnValue = await response.Content.ReadAsAsync<bool>();
Also, instead of using a flag to check whether an object is saved or not, you should make use of HTTP codes by returning 200 OK to determine that saving is successfully.
The accepted answer is technically correct but blocks the current thread on calls to .Result. If you are using .NET 4.5 or higher, you should avoid that in almost all situations. Instead, use the equivalent asynchronous (non-blocking) version:
var httpClient = new HttpClient();
var response = await httpClient.PostAsJsonAsync(posturi, model);
bool returnValue = await response.Content.ReadAsAsync<bool>();
Note that the method containing the above code needs to be marked async, and should itself be awaited.
Since its an Async operation don't immediately do .Result as its wrong
Instead you need to do it async by doing this:
var httpClient = new HttpClient()
var task = httpClient.PostAsJsonAsync(posturi, model)
.ContinueWith( x => x.Result.Content.ReadAsAsync<bool>().Result);
// 1. GETTING RESPONSE - NOT ASYNC WAY
task.Wait(); //THIS WILL HOLD THE THREAD AND IT WON'T BE ASYNC ANYMORE!
bool response = task.Result
// 2. GETTING RESPONSE - TASK ASYNC WAY (usually used in < .NET 4.5
task.ContinueWith( x => {
bool response = x.Result
});
// 3. GETTING RESPONSE - TASK ASYNC WAY (usually used in >= .NET 4.5
bool response = await task;
NOTE: I just wrote them in here, so I didnt actually test them but more or less that's what you want.
I hope it helps!
I used HttpStatusCode to check the result.
public HttpStatusCode PostStaffPositions(Foo foo)
{
string uri = myapiuri;
using (HttpClient httpClient = new HttpClient())
{
var response = httpClient.PostAsJsonAsync(uri, foo).Result;
return response.StatusCode;
}
}
And then in Controller check it like this:
HttpStatusCode update = staffrest.PostStaffPositions(foo);
if (update == HttpStatusCode.OK)
{
//Update Succeed
}
else
{
//Update Failed
}
If you call the generic version, it should give you back the bool:
var response = new HttpClient().PostAsJsonAsync<bool>(posturi, model).Result;
At least according to the docs.
It's July 2021 and I'm using .net 5 (namely the .net core 5).
I did not see any generic methods above in System.Net.Http. Now the code looks like this (tested):
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://localhost:44330/api/Book/Add");
var response = client.PostAsJsonAsync(client.BaseAddress,
JsonSerializer.Serialize(_teamSummaries));
MessageBox.Show(#"Result is " + JsonSerializer.Serialize(response));
var returnValue = response.Result.Content.ReadAsStringAsync();
MessageBox.Show(#"Return value is " + returnValue.Result);
}
There are also ReadAsStringAsync, ReadAsByteArrayAsync, ReadAsStream, ReadFromJsonAsync, ReadFromJsonAsync<T> (this method returns Task<T>).
But from the text meaning "ReadFromJsonAsync", I think the T is not the bool mentioned above, but a class that contains the bool member. If you want to return something like book, give it a try.
On the other hands, since code on the server looks like this(.net 5):
[HttpPost]
[Route("Add")]
public async Task<ActionResult<IEnumerable<Book>>> Add(string value)
{
var all = await _dbCollection.FindAsync(Builders<Book>.Filter.Empty);
return Ok("Everything is ok.");
}
So, if we want to return true by bool, we should return Ok(...). If we want to return false by bool, we should return something else. There are more than 20 other types of results, which contains much more information rather than just "false".