The Json data in ASP.NET Core Web API response is being encoded - asp.net-core-webapi

I have an ASP.NET Core 6 Web API controller. I am trying to return a mix of JSON strings along with plain C# objects as the result of one of controller's web methods. But I am facing a few problems.
The following web method can produce the problems I am facing:
[HttpGet("GetMixedJson")]
public IActionResult GetMixedJson()
{
string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
string json = JsonConvert.SerializeObject(Summaries, Formatting.Indented);
JObject jsonObject = new JObject();
jsonObject["thisFiledValueWillBeMissing"] = "JObject won't serialize :(";
var resultObject = new
{
PlainCSharpFiled = "plain c# fields showing fine",
jsonObject = jsonObject,
TunneledJsonString = json
};
return new JsonResult(resultObject);
}
It produces the following JSON response:
{
"plainCSharpFiled": "plain c# fields showing fine",
"jsonObject": {
"thisFiledValueWillBeMissing": []
},
"tunneledJsonString": "[\r\n \u0022Freezing\u0022,\r\n \u0022Bracing\u0022,\r\n \u0022Chilly\u0022,\r\n \u0022Cool\u0022,\r\n \u0022Mild\u0022,\r\n \u0022Warm\u0022,\r\n \u0022Balmy\u0022,\r\n \u0022Hot\u0022,\r\n \u0022Sweltering\u0022,\r\n \u0022Scorching\u0022\r\n]"
}
Problem #1: JObject jsonObject value is missing
Problem #2: The json data in TunneledJsonString is encoded, hence it is corrupted in the result, not usable.
Please note that I simplified the code. In my project, the actual objects data fields are coming from variety of sources and they are bigger. The more I save on memory cost the better.
What are my options to fix the problems #1 and #2?

in .net 6 web api projects,serialize and deserialize operations are based on System.Text.Json
You could add Newtonsoft JSON format support with this package:
Microsoft.AspNetCore.Mvc.NewtonsoftJson
and configure as below :
builder.Services.AddControllers().AddNewtonsoftJson();
The result:
object Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
string json = JsonConvert.SerializeObject(Summaries);

Related

Web API PUT,DELETE, POST methods via URI

I am very new to the whole consept of API's. So far, I managed to build a web api that has GET,POST,PUT and DELETE methods.
Now, from an ASP.NET project, I try to finally use my web api.
Here's what I do for GET method:
string info = new WebClient() { }.DownloadString("https://mywebapisite.com/item/" + id);
Item item = JsonConvert.DeserializeObject<Item>(info);
This functions all fine. As you can see, all the GET method needs is an id.
However, for the POST method, I have no clue what to do.
I can create a new Item instance, but don't know what to do with it.
By the way, I also used ASP.NET to make my web.api.
There is a built-in feature in ASP.NET 5 called Swagger. It can perform all the tasks very succesfully. Is there like a code-behind for what Swagger does.
PS: I know that this question must be very common and basic. If you could refer me to another question in stackoverflow or simply tell me what to search on google I would appreciate it. (As you may guess, I don't even know what to search for)
pseudo code to consume post request in C#
var requestObj = GetDummyDataTable();
using (var client = new HttpClient())
{
// Setting Base address.
client.BaseAddress = new Uri("https://localhost:8080/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = new HttpResponseMessage();
// HTTP POST
response = await client.PostAsJsonAsync("api/product", requestObj).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
// Reading Response.
string result = response.Content.ReadAsStringAsync().Result;
var responseObj = JsonConvert.DeserializeObject<DataTable>(result);
}
}
You can refer the following code to call the API using HttpClient:
////using System.Net.Http.Headers;
////using System.Text;
using (var client = new HttpClient())
{
var requesturi = "https://localhost:7110/api/ToDo/relativeAddress";
var item = new TestUserViewModel()
{
Name = "John Doe",
Age = 33
};
////using System.Text.Json; // use JsonSerializer.Serialize method to convert the object to Json string.
StringContent content = new StringContent(JsonSerializer.Serialize(item), Encoding.UTF8, "application/json");
//HTTP POST
var postTask = client.PostAsync(requesturi, content);
postTask.Wait();
var result = postTask.Result;
if (result.IsSuccessStatusCode)
{
var Content = await postTask.Result.Content.ReadAsStringAsync();
return RedirectToAction("Privacy");
}
}
The API method like this:
[Route("api/[controller]")]
[ApiController]
public class TodoController : ControllerBase
{
[HttpPost]
[Route("relativeAddress")]
public string GetAddress([FromBody] TestUserViewModel testUser)
{
return "Address A";
}
And the result like this:
You can also refer this link to set the Content-Type.
You seem a little bit lost, and I get it. Api learning path is kinda weird, I recommend you watch a tutorial (My favorite https://www.youtube.com/playlist?list=PLLWMQd6PeGY0bEMxObA6dtYXuJOGfxSPx)
But if you need code asap, you could refer the following code.
Ps: The others answers are really good!
using System.Net.Http;
using System.Net.Http.Headers;
public class ApiHelper
{
public HttpClient ApiClient { get; set; }
public void InitializeClient()
{
ApiClient = new HttpClient();
ApiClient.BaseAddress = new Uri("https://mywebapisite.com/");
ApiClient.DefaultRequestHeaders.Accept.Clear();
ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task PostSomething(FormUrlEncodedContent data)
{
using (HttpResponseMessage response = await ApiClient.PostAsync("/item",data)
{
var result = await response.Content.ReadAsAsync<string>();
}
}
}

Cannot deserialize the current JSON array - Auth0 Management API Endpoint - despite using online POCO generators

I am working with Auth0 Management API endpoints, the issue with this one.
https://auth0.com/docs/api/management/v2?_ga=2.197148647.957265821.1601726170-1678439180.1588036522#!/Users/get_users
Here is my rest code.
var client = new RestClient(tempapiendpoint);
var request = new RestRequest(Method.GET);
request.AddHeader(header, bearerstring);
request.AddParameter(specificfieldname,specificfields);
request.AddParameter(includefieldname, includetrueorfalse);
IRestResponse response = await client.ExecuteAsync(request);
var myDeserializedClass = JsonConvert.DeserializeObject<Root>(response.Content);
I have the following response.
[
{
"email": "somevalue",
"name": "somevalue",
"nickname": "somevalue",
"user_id": "somevalue"
},
{
"email": "somevalue",
"name": "somevalue",
"nickname": "somevalue",
"user_id": "somevalue"
},
{
"email": "somevalue",
"name": "somevalue",
"nickname": "somevalue",
"user_id": "somevalue"
}
]
At this point, I use an online class generator, such as, https://json2csharp.com/
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class MyArray {
[JsonProperty("email")]
public string Email;
[JsonProperty("name")]
public string Name;
[JsonProperty("nickname")]
public string Nickname;
[JsonProperty("user_id")]
public string UserId;
}
public class Root {
[JsonProperty("MyArray")]
public List<MyArray> MyArray;
}
and everytime, I get the same error.
Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'RandomStuffGeneratorPrivate.POCO.Root' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path '', line 1, position 1.
A few more things I have tried.
I have tried another source for class generation, https://app.quicktype.io/. I get the exact same error.
I have checked the json for validity. it is in the correct format.
even if hand map it, this is a straight forward json.
Further, I noticed that the name of the user class and the collection name are both the same. So, I changed the name for the user class. (it never gave any errors while debugging but I changed it anyway). no change in error
When generating classes, I am taking the json string directly from the live response during debugging, from IRestResponse response.content, just in case the online API documentation is making a mistake.
i have looked at other stack questions, and in those cases, I noticed that there was some mistake related to not having a list. Here, I definitely have mapped (the online generators wont make such a mistake) the returning array to a list.
Looks to me that you are deserializing to the wrong class, try using : JsonConvert.DeserializeObject<MyArray[]>(myJsonResponse)
(looks like Thomas has already provided a solution which i also discovered on my own. Posting some additional details here.)
Ultimately, this seems to have worked for me.
var myDeserializedClass = JsonConvert.DeserializeObject<List<SingleUser>>(response.Content);
I think, the issue was, I was looking for a solution, that would me something like this.
myDeserializedClass
and, I could use it, like this,
var nameOfPerson = myDeserializedClass.MyArray[0].Name;
But, i think that is not how JSON works. Perhaps, if there were other fields in the JSON body, with a mix and match of collection and non-collection values. Since, this was a complete collection, the outer layer gets eliminated or something.
Further, Quicktype class generator clearly says this at the top.
// <auto-generated />
//
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
// using RandomStuffGeneratorPrivate.POCO;
//
// var allUsers445 = AllUsers445.FromJson(jsonString);
and, the function is defined like this.
public static List<AllUsers445> FromJson(string json) => JsonConvert.DeserializeObject<List<AllUsers445>>(json, RandomStuffGeneratorPrivate.POCO.Converter.Settings);

dotnet core webapi calling .net webapi2

I am calling a .NET WebApi2 endpoint from a dotnet core webapi. When I debug into the .NET WebApi2 POST endpoint, my value is always null. Is this not possible to do?
When I call the GET endpoint with an ID, the ID is passed with no issues.
I have used both Postman and Fiddler to debug. Whenever I pass my JSON object from Postman to the .NET WebApi2 POST endpoint, my value is populated.
Beyond frustrated as this seems pretty simple. :,(
Updated to include code
dotnet core web api (calling from Postman)
[HttpPost]
public async Task PostAsync([FromBody] string value)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var jsonObject = new JObject();
jsonObject.Add("text", "Rich");
var response = await client.PostAsJsonAsync("http://localhost:54732/api/Rich", jsonObject);
var responseResult = response.Content.ReadAsStringAsync().Result;
}
.NET WebApi2 (JObject is always null)
// POST: api/Rich
public void Post(JObject value)
{
}
This boils down to using JObject basically. For your older Web Api action, JObject works merely because you're posting JSON, and JObject is a dynamic. However, that is an entirely incorrect approach. You should be binding to a concrete class that represents the JSON being posted. That said, you may or may not be able to change anything there, and its not technically the source of your current issue.
The actual source is that you're attempting to send a JObject, which is not doing what you think it is. Again, JObject is a dynamic. It has accessors to parse and access the underlying JSON, but it does not actually expose the members of that JSON object directly. As a result, if you attempt to serialize it, you won't get anything usable from it. Passing it to PostAsJsonAsync causes it to be serialized.
What you actually need is something like:
var jsonObject = new { text = "Rich" };
Then, what you're passing to PostAsJsonAsync will be an anonymous object with actual members that can be serialized.
My "REAL" issue turned out to be Transfer-Encoding: chunked was being sent in the request header.
Here is my corrected code (dotnet core web api):
public async Task PostAsync([FromBody] JObject value)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new
MediaTypeWithQualityHeaderValue("application/json"));
var jsonObject = new { variable1 = "Rich" };
var json = JsonConvert.SerializeObject(jsonObject);
var content = new StringContent(json, Encoding.UTF8, "application/json");
content.Headers.ContentLength = json.Length;
var response = await client.PostAsync("http://localhost:54732/api/Rich", content);
var responseResult = response.Content.ReadAsStringAsync().Result;
}
Here is my .NET WebApi2 code:
public IHttpActionResult Post([FromBody]RichTest value)
{
return Ok(value.variable1 + " done");
}
public class RichTest
{
public string variable1 { get; set; }
}
When I set the content.Headers.ContentLength, the Transfer-Encoding: chunked is removed. Now my code is working!!
I am still curious why the original PostAsJsonAsync does not work...

Items count in OData v4 WebAPI response

How to return number of items in OData v4 HTTP response?
I need this number to pagination, so it should be number of items after filtering, but before 'skip' and 'top'.
I already tried passing '$inlinecount=allpages' and '$count=true' parameters in query options in url (https://damienbod.wordpress.com/2014/06/13/web-api-and-odata-v4-queries-functions-and-attribute-routing-part-2/ - "Example of $count"), but my responses from WebAPI always have only query results (collection) - whole response looks like:
[
{
"Name":"name1",
"age":5
},
{
"Name":"name2",
"age":15
}
]
There is nothing like "odata.count" in the response.
I also tried returning PageResult instead of IQueryable in my WebAPI controller action (like described here: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#server-paging), but Request.GetInlineCount() is deprecated and its value is always null.
Any ideas?
[Update] I just found the same problem here: WebApi with Odata NextPage and Count not appearing in the JSON response and I removed [EnableQuery] attribute and now my response looks like:
{
"Items":
[
{
"Name":"name1",
"age":5
},
{
"Name":"name2",
"age":15
}
],
"NextPageLink":null,
"Count":null
}
But still "Count" is always null. :(
Edit: After debugging and searching for count value in Request properties in my controller, I found out that correct Count value is in property named "System.Web.OData.TotalCount". So right now I exctract this value from that request property and my controller looks like that:
public PageResult<People> Get(ODataQueryOptions<People> queryOptions)
{
var query = _context.People.OrderBy(x => x.SomeProperty);
var queryResults = (IQueryable<People>)queryOptions.ApplyTo(query);
long cnt = 0;
if (queryOptions.Count != null)
cnt = long.Parse(Request.Properties["System.Web.OData.TotalCount"].ToString());
return new PageResult<People>(queryResults, null, cnt);
}
And it works fine, but I still don't know why I have to use workarounds like that.
For future reference (OData v4):
First of all $inlinecount it's not supported in OData v4 so you should use $count=true instead.
Second, if you have a normal ApiController and you return a type like IQueryable<T> this is the way you can attach a count property to the returned result:
using System.Web.OData;
using System.Web.OData.Query;
using System.Web.OData.Extensions;
//[EnableQuery] // -> If you enable globally queries does not require this decorator!
public IHttpActionResult Get(ODataQueryOptions<People> queryOptions)
{
var query = _peopleService.GetAllAsQueryable(); //Abstracted from the implementation of db access. Just returns IQueryable<People>
var queryResults = (IQueryable<People>)queryOptions.ApplyTo(query);
return Ok(new PageResult<People>(queryResults, Request.ODataProperties().NextLink, Request.ODataProperties().TotalCount));
}
Note:
OData functionality does not supported by ApiControllers so you
cannot have things like count or $metadata. If you choose to
use simple ApiController the way above is the one you should use
to return a count property.
For a full support of OData functionality you should implement a ODataController the following way:
PeopleController.cs
using System.Web.OData;
using System.Web.OData.Query;
public class PeopleController : ODataController
{
[EnableQuery(PageSize = 10, AllowedQueryOptions = AllowedQueryOptions.All)]
public IHttpActionResult Get()
{
var res = _peopleService.GetAllAsQueryable();
return Ok(res);
}
}
App_Start \ WebApiConfig.cs
public static void ConfigureOData(HttpConfiguration config)
{
//OData Models
config.MapODataServiceRoute(routeName: "odata", routePrefix: null, model: GetEdmModel(), batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder
{
Namespace = "Api",
ContainerName = "DefaultContainer"
};
builder.EntitySet<People>("People").EntityType.HasKey(item => item.Id); //I suppose the returning list have a primary key property(feel free to replace the Id key with your key like email or whatever)
var edmModel = builder.GetEdmModel();
return edmModel;
}
Then you access your OData Api this way (example):
encoded uri:
http://localhost:<portnumber>/People/?%24count=true&%24skip=1&%24top=3
decoded:
http://localhost:<portnumber>/People/?$count=true&$skip=1&$top=3
References:
How to Use Web API OData to Build an OData V4 Service without Entity Framework
Web API OData V4 Pitfalls
Create an OData v4 Endpoint Using ASP.NET Web API 2.2
This can also be achieved by an action filter:
/// <summary>
/// Use this attribute whenever total number of records needs to be returned in the response in order to perform paging related operations at client side.
/// </summary>
public class PagedResultAttribute: ActionFilterAttribute
{
/// <summary>
///
/// </summary>
/// <param name="actionExecutedContext"></param>
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
if (actionExecutedContext.Response != null)
{
dynamic responseContent=null;
if (actionExecutedContext.Response.Content != null)
responseContent = actionExecutedContext.Response.Content.ReadAsAsync<dynamic>().Result;
var count = actionExecutedContext.Response.RequestMessage.ODataProperties().TotalCount;
var res = new PageResult<dynamic>() {TotalCount=count,Items= responseContent };
HttpResponseMessage message = new HttpResponseMessage();
message.StatusCode = actionExecutedContext.Response.StatusCode;
var strMessage = new StringContent(JsonConvert.SerializeObject(res), Encoding.UTF8, "application/json");
message.Content = strMessage;
actionExecutedContext.Response = message;
}
}
}
And the custom PageResult class is:
public class PageResult<T>
{
public long? TotalCount { get; set; }
public T Items { get; set; }
}
Usage:
[PagedResult]
[EnableQuery()]
Will you please take a look at the sample service TripPin web api implementation at https://github.com/OData/ODataSamples/blob/master/Scenarios/TripPin. You can follow the code in Airports controller and the service with the code http://services.odata.org/TripPinWebApiService/Airports?$count=true can return the count correctly.
That's what I am using with oData v4:
Request.ODataProperties().NextLink,
Request.ODataProperties().TotalCount
If you are using OData conventional routing, $odata.count is not returned when your routes are not known to odata. Add 'app.UseODataRouteDebug();' to your ConfigureServices-method and then invoke 'https://localhost:5001/$odata'. If your route is not in the OData-route table, your route is not known to OData and you are not using correct naming conventions for your controller and EDM-type to be included in OData conventional routing.

How to return dynamic List from WCF HTTP Service

public List<dynamic> GetDynamicResult()
{
List<dynamic> lstDynamic = new List<dynamic>();
lstDynamic.Add(new { ID = "1", Name = "ABC" });
lstDynamic.Add(new { ID = "1", Name = "XYZ" });
return lstDynamic;
}
When i return List from WCF to ASP.NET web App using
List<dynamic> lstDynamic = objClient.GetDynamicResult();
I get this error: The underlying connection was closed: The connection was closed unexpectedly
WCF needs to define a DataContract (serialization is used) which is sent by wire. Using dynamic types conflict with this. You can use string combine with (de)serialization instead.

Resources