I have a WebApi with several controllers which return different results. For example, one controller returned an IEnumerable<Foo>, another a Bar, another an IEnumerable of IEnumerable etc., and all I had to do was:
return Ok(thething)
and everything worked fine, even complicated nested objects were serialized with no problem.
Now, The client asked that all results be returned in a Wrapper:
public class Wrapper
{
public bool Success { get; set; }
public int ErrorCode { get; set; }
public String ErrorMessage { get; set; }
public String Referrer { get; set; }
public Object Payload { get; set; }
}
Thought it would be trivial, but when I try to return it from the controller:
return Ok(new Wrapper { Success = true, Referrer = "me", Payload = thething)
I get a serialization error:
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
The inner exception message is:
'System.Linq.Enumerable+WhereSelectListIterator2[[EPiServer.Find.Api.SearchHit1[[DGTNext.Api.Data.Entities.ProductSummary,
DGTNext.Api.Entities, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null]], EPiServer.Find, Version=9.6.0.3185,
Culture=neutral,
PublicKeyToken=8fe83dea738b45b7],[DGTNext.Api.Data.Entities.ProductSummary,
DGTNext.Api.Entities, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null]]' with data contract name
'ArrayOfProductSummary:http://schemas.datacontract.org/2004/07/DGTNext.Api.Data.Entities'
is not expected. Consider using a DataContractResolver if you are
using DataContractSerializer or add any types not known statically to
the list of known types - for example, by using the KnownTypeAttribute
attribute or by adding them to the list of known types passed to the
serializer.
What am I doing wrong? Why did the Ok() function seem to be handle any object before, but has problems now?
Thanks.
Edit: as requested, a simple example of something causing the error:
class Foo
{
public int AnInt { get; set; }
}
public IHttpActionResult Get()
{
return Ok(new Wrapper { Success = true, Referrer = "me", Payload = new Foo { AnInt = 7 } });
}
Edit #2: well, I came up with kind of a solution, but it still raises some questions.
I made my Wrapper generic in the type of the payload.
public class Wrapper<T>
{
public bool Success { get; set; }
public int ErrorCode { get; set; }
public String ErrorMessage { get; set; }
public String Referrer { get; set; }
public T Payload { get; set; }
}
So now, this works:
public IHttpActionResult Get()
{
List<Foo> foos = new List<Foo>();
foos.Add(new Foo { AnInt = 7 });
foos.Add(new Foo { AnInt = 8 });
return Ok(new Wrapper<IEnumerable<Foo>> { Success = true, Referrer = "me", Payload = foos });
}
It returns:
{
"Success": true,
"ErrorCode": 0,
"ErrorMessage": null,
"Referrer": "me",
"Payload": [ { "AnInt": 7 }, { "AnInt": 8 } ]
}
And my "real" call:
public IHttpActionResult Get()
{
IEnumerable<ProductSummary> prods = db.getProductSummaries(shopId, culture, queryParams, paging);
return Ok(new Wrapper<IEnumerable<ProductSummary>> { Success = true, Referrer = "me", Payload = prods });
}
returns:
<WrapperOfArrayOfProductSummaryzc2y5_Pnl>
<ErrorCode>0</ErrorCode>
<ErrorMessage i:nil="true"/>
<Payload>
<d2p1:ProductSummary>
<d2p1:Culture i:nil="true"/>
<d2p1:Guid i:nil="true"/>
<d2p1:Id>2</d2p1:Id>
<d2p1:Name>Letto Asia</d2p1:Name>
<d2p1:ambient>
<d2p1:Id>1073741838</d2p1:Id>
<d2p1:Name>notte</d2p1:Name>
</d2p1:ambient>
etc.
So not bad, but this raises two questions:
To test the webapi, I'm calling it by putting an URL in the Firefox address bar and looking an the results in the browser. Why in the world does the first call return Json, and the second XML? As far as I know I'm just using default everything.
Why is the XML now adding that namespace to all the element names? Can I prevent this? When I was just returning the same thing without the wrapper, this didn't happen.
Add this code to global.asax below on Application_Start:
Update from .Ignore to .Serialize. It must work.
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
or you may look at this answer
Related
I have a old MVC program, converting to .net core. but I am facing ReadAsAsync() casting issue. Same code worked on old MVC, but not .NET Core.
I have a User model:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public int Status { get; set; }
public string Address { get; set; }
}
I try test the result by use the following code to get the wep api HttpResponseMessage:
string responseContent = await message.Content.ReadAsStringAsync();
And successful return following User data:
{"id":1, "username":"howard","password":"xxxxx","status":1,"Address":null}
When I cast the response to User model use code below, is working find and return User model.
//worked
var data = await message.Content?.ReadAsAsync<User>();
But when I added a custom ApiResult class, it's become not working, and return null for "result" property in ApiResult class.
//not work
var data = await message.Content?.ReadAsAsync<ApiResult<User>>()
But it's worked on old MVC:
My ApiResult class:
public class ApiResult
{
public bool CustomizedisSuccess { get; set; }
public string CustomizedErrorMsg { get; set; }
public static ApiResult<T> Success<T>(T result = default(T))
{
return new ApiResult<T>
{
Result = result,
CustomizedisSuccess = true,
CustomizedErrorMsg = "Error"
};
}
}
public class ApiResult<T> : ApiResult
{
public T Result { get; set; }
}
The result always return null, by right suppose cast to User model. Same code worked in previous MVC but not .NET Core.
Update:
I am tried use ReadFromJsonAsync, but it's not working, also return null.
// not work also
var result = await message.Content.ReadFromJsonAsync<ApiResult<User>>();
same case with me:
HttpContent.ReadFromJsonAsync Won't Deserialize Generic Object (Blazor WebAssembly)
https://github.com/dotnet/aspnetcore/issues/21030
Deserialize it as a User (because that is what the JSON is) and embed it in an ApiResult afterward. Looks like you already have a helper method for it.
var user = await content?.ReadAsAsync<User>();
var data = ApiResult.Success(user);
I have the following dotnet core code and I'm trying to return a TestResponse JSON object that has a few nodes under it. However, using the return Enumerable.Range(1, 4).Select(index => new Entities.TestResponse call in the post return for some reason all the attributes of response are not found in the enclosure when clearly Entities.TestResponse has the response definition. I'm probably not configuring the Enumerable enclosure correctly. Does anyone know how to resolve this, so I can set the response.result & response.exception and return response JSON from my REST POST method?
namespace TestApi.Entities
{
public class TestResponse
{
public TestResponseNodes response { get; set; }
}
public class TestResponseNodes
{
public string result { get; set; }
public string exception { get; set; }
}
}
[HttpPost]
public Task<IEnumerable<Entities.TestResponse>> Post([FromBody] String input)
{
return Enumerable.Range(1, 4).Select(index => new Entities.TestResponse
{
response.result = "No Error",
response.exception = "None"
}).ToArray();
}
Your syntax is wrong, you need to also new up the inner object, for example:
new Entities.TestResponse
{
response = new Entities.TestResponseNodes
{
result = "No Error",
exception = "None"
}
}
As an aside, you should follow common C# conventions and capitalise your property names, for example:
public class TestResponse
{
public TestResponseNodes Response;
}
public class TestResponseNodes
{
public string Result { get; set; }
public string Exception { get; set; }
}
We've got an API, which simply posts incoming JSON documents to a message bus, having assigned a GUID to each. We're upgrading from .Net Core 2.2 to 3.1 and were aiming to replace NewtonSoft with the new System.Text.Json library.
We deserialise the incoming document, assign the GUID to one of the fields and then reserialise before sending to the message bus. Unfortunately, the reserialisation is failing with the exception Operation is not valid due to the current state of the object.
Here's a controller that shows the problem:-
using System;
using System.Net;
using Project.Models;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Text;
using System.Text.Json;
namespace Project.Controllers
{
[Route("api/test")]
public class TestController : Controller
{
private const string JSONAPIMIMETYPE = "application/vnd.api+json";
public TestController()
{
}
[HttpPost("{eventType}")]
public async System.Threading.Tasks.Task<IActionResult> ProcessEventAsync([FromRoute] string eventType)
{
try
{
JsonApiMessage payload;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) {
string payloadString = await reader.ReadToEndAsync();
try {
payload = JsonSerializer.Deserialize<JsonApiMessage>(payloadString);
}
catch (Exception ex) {
return StatusCode((int)HttpStatusCode.BadRequest);
}
}
if ( ! Request.ContentType.Contains(JSONAPIMIMETYPE) )
{
return StatusCode((int)HttpStatusCode.UnsupportedMediaType);
}
Guid messageID = Guid.NewGuid();
payload.Data.Id = messageID.ToString();
// we would send the message here but for this test, just reserialise it
string reserialisedPayload = JsonSerializer.Serialize(payload);
Request.HttpContext.Response.ContentType = JSONAPIMIMETYPE;
return Accepted(payload);
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
}
}
}
The JsonApiMessage object is defined like this:-
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Project.Models
{
public class JsonApiMessage
{
[JsonPropertyName("data")]
public JsonApiData Data { get; set; }
[JsonPropertyName("included")]
public JsonApiData[] Included { get; set; }
}
public class JsonApiData
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("attributes")]
public JsonElement Attributes { get; set; }
[JsonPropertyName("meta")]
public JsonElement Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement Relationships { get; set; }
}
}
An example call looks like this:-
POST http://localhost:5000/api/test/event
Content-Type: application/vnd.api+json; charset=UTF-8
{
"data": {
"type": "test",
"attributes": {
"source": "postman",
"instance": "jg",
"level": "INFO",
"message": "If this comes back with an ID, the API is probably working"
}
}
}
When I examine the contents of payload at a breakpoint in Visual Studio, it looks OK at the top level but the JsonElement bits look opaque, so I don't know if they've been parsed properly. Their structure can vary, so we only care that they are valid JSON. In the old NewtonSoft version, they were JObjects.
After the GUID has been added, it appears in the payload object when examined at a breakpoint but I'm suspicious that the problem is related to other elements in the object being read-only or something similar.
Your problem can be reproduced with the following more minimal example. Define the following model:
public class JsonApiMessage
{
public JsonElement data { get; set; }
}
Then attempt to deserialize and re-serialize an empty JSON object like so:
var payload = JsonSerializer.Deserialize<JsonApiMessage>("{}");
var newJson = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
And you will get an exception (demo fiddle #1 here):
System.InvalidOperationException: Operation is not valid due to the current state of the object.
at System.Text.Json.JsonElement.WriteTo(Utf8JsonWriter writer)
at System.Text.Json.Serialization.Converters.JsonConverterJsonElement.Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
The problem seems to be that JsonElement is a struct, and the default value for this struct can't be serialized. In fact, simply doing JsonSerializer.Serialize(new JsonElement()); throws the same exception (demo fiddle #2 here). (This contrasts with JObject which is a reference type whose default value is, of course, null.)
So, what are your options? You could make all your JsonElement properties be nullable, and set IgnoreNullValues = true while re-serializing:
public class JsonApiData
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("attributes")]
public JsonElement? Attributes { get; set; }
[JsonPropertyName("meta")]
public JsonElement? Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement? Relationships { get; set; }
}
And then:
var reserialisedPayload = JsonSerializer.Serialize(payload, new JsonSerializerOptions { IgnoreNullValues = true });
Demo fiddle #3 here.
Or, in .NET 5 or later, you could mark all of your JsonElement properties with [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]:
public class JsonApiData
{
// Remainder unchanged
[JsonPropertyName("attributes")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Attributes { get; set; }
[JsonPropertyName("meta")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Meta { get; set; }
[JsonPropertyName("relationships")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Relationships { get; set; }
}
Doing so will cause uninitialized elements to be skipped during serialization without needing to modify serialization options.
Demo fiddle #4 here.
Or, you could simplify your data model by binding all the JSON properties other than Id to a JsonExtensionData property like so:
public class JsonApiData
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData { get; set; }
}
This approach avoids the need to manually set IgnoreNullValues when re-serializing, and thus ASP.NET Core will re-serialize the model correctly automatically.
Demo fiddle #5 here.
The exception is right - the state of the object is invalid. The Meta and Relasionships elements are non-nullable but the JSON string doesn't contain them. The deserialized object ends up with Undefined values in those properties that can't be serialized.
[JsonPropertyName("meta")]
public JsonElement? Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement? Relationships { get; set; }
The quick fix would be to change those properties to JsonElement?. This will allow correct deserialization and serialization. By default, the missing elements will be emitted as nulls:
"meta": null,
"relationships": null
To ignore them, add the IgnoreNullValues =true option :
var newJson = JsonSerializer.Serialize(payload, new JsonSerializerOptions
{ WriteIndented = true,IgnoreNullValues =true });
The real solution though would be to get rid of all that code. It hampers the use of System.Text.Json. Left by itself, ASP.NET Core uses Pipelines to read the input stream without allocating, deserializes the payload and calls the method with the deserialized object as a parameter, using minimal allocations. Any return values are serialized in the same way.
The question's code though allocates a lot - it caches the input in the StreamReader, then the entire payload is cached in the payloadString and then again, as the payload object. The reverse process also uses temporary strings. This code takes at least twice as much RAM as ASP.NET Core would use.
The action code should be just :
[HttpPost("{eventType}")]
public async Task<IActionResult> ProcessEventAsync([FromRoute] string eventType,
MyApiData payload)
{
Guid messageID = Guid.NewGuid();
payload.Data.Id = messageID.ToString();
return Accepted(payload);
}
Where MyApiData is a strongly-typed object. The shape of the Json example corresponds to :
public class Attributes
{
public string source { get; set; }
public string instance { get; set; }
public string level { get; set; }
public string message { get; set; }
}
public class Data
{
public string type { get; set; }
public Attributes attributes { get; set; }
}
public class MyApiData
{
public Data data { get; set; }
public Data[] included {get;set;}
}
All other checks are performed by ASP.NET Core itself - ASP.NET Core will reject any POST that doesn't have the correct MIME type. It will return a 400 if the request is badly formatted. It will return a 500 if the code throws
In one of my API actions (PostOrder) I may be consuming another action in the API (CancelOrder). Both return a JSON formatted ResultOrderDTO type, set as a ResponseTypeAttribute for both actions, which looks like this:
public class ResultOrderDTO
{
public int Id { get; set; }
public OrderStatus StatusCode { get; set; }
public string Status { get; set; }
public string Description { get; set; }
public string PaymentCode { get; set; }
public List<string> Issues { get; set; }
}
What I need is reading/parsing the ResultOrderDTO response from CancelOrder, so that I can use it as response for PostOrder. This is what my PostOrder code looks like:
// Here I call CancelOrder, another action in the same controller
var cancelResponse = CancelOrder(id, new CancelOrderDTO { Reason = CancelReason.Unpaid });
if (cancelResponse is OkNegotiatedContentResult<ResultOrderDTO>)
{
// Here I need to read the contents of the ResultOrderDTO
}
else if (cancelResponse is InternalServerErrorResult)
{
return ResponseMessage(Request.CreateResponse(HttpStatusCode.InternalServerError, new ResultError(ErrorCode.InternalServer)));
}
When I use the debugger, I can see that the ResultOrderDTO it is there somewhere in the response (looks like the Content) as shown in the pic below:
but cancelResponse.Content does not exist (or at least I don't have access to it before I cast my response to something else) and I have no idea about how to read/parse this Content. Any idea?
Simply cast the response object to OkNegotiatedContentResult<T>. The Content property is object of type T. which in your case is object of ResultOrderDTO.
if (cancelResponse is OkNegotiatedContentResult<ResultOrderDTO>)
{
// Here's how you can do it.
var result = cancelResponse as OkNegotiatedContentResult<ResultOrderDTO>;
var content = result.Content;
}
I am doing a WebApi Method in Visual Studio 2013 and I want to Deserialize a Class Type. My Class is like this
[JsonObject]
class JOTA
{
[JsonProperty("ProductId")]
public int ProductId { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
}
My call is like this.
public void ReturnListProd(JOTA PP)
{
JOTA product = Newtonsoft.Json.JsonConvert.DeserializeObject<JOTA>(PP);
}
I have a compile error
'Network.Json.Json.Converter[] has some invalid argument'
But, if a define an ArrayList
public void ReturnListProd(ArrayList PP)
{
JOTA product = Newtonsoft.Json.JsonConvert.DeserializeObject<JOTA>(PP[0].ToString());
}
I have no error. But in this case, it does not help on what I need.
What I am missing?
Thanks
If you want a JOTA object to become a string representation of itself (serialize it) then you should be using
string serialized = Newtonsoft.Json.JsonConvert.SerializeObject(PP);
If you want the string to become a JOTA object then you are using
JOTA product = Newtonsoft.Json.JsonConvert.DeserializeObject<JOTA>(serialized);
the problem is that you are trying to deserialize an object that is not serialized (already deserialized).
You don't need the attributes if the property names are not different.
public class JOTA
{
public int ProductId { get; set; }
public string Name { get; set; }
}
public void ReturnListProd(JOTA PP)
{
var product = PP; //not really needed you can just use PP directly
}
You only need to deserialize if you are receiving a json string. Because you are using WebAPI I would suggest changing your API endpoint to a proper REST endpoint. Example:
[HttpPost, Route("api/products/add")]
public IHttpActionResult ReturnListProd([FromBody]JOTA PP)
{
try
{
//do something with passed in data
var name = PP.Name;
//always at minimum return a status code
return Ok();
}
catch
{
//returns 500
return InternalServerError();
}
}
Then change your ajax url from:
url: "yourController/ReturnListProd"
to:
url: "/api/products/add"