Web API - Converting JSON to Complex Object Type (DTO) with inheritance - json

I have the following scenario:
public class WidgetBaseDTO
{
public int WidgetID
{
get;
set;
}
}
public class WidgetTypeA : WidgetBaseDTO
{
public string SomeProperty1
{
get;
set;
}
}
public class WidgetTypeB : WidgetBaseDTO
{
public int SomeProperty2
{
get;
set;
}
}
and my web service returns the following dashboard object whereas the Widgets collection could be of either type A or B:
public class DashboardDTO
{
public List<WidgetBaseDTO> Widgets
{
get;
set;
}
}
my problem is that although the client receives correct JSON content, which is dependent on the Widget type, when reading the response content, they are all being translated to WidgetBaseDTO. what is the correct way to convert these objects to the relevant types?
this is how the response is being read:
string relativeRequestUri = string.Format("api/dashboards/GetDashboard?dashboardID={0}", dashboardID);
using (var client = new HttpClient())
{
// set client options
client.BaseAddress = this.BaseUri;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// make request
HttpResponseMessage response = client.GetAsync(relativeRequestUri).Result;
if (response.IsSuccessStatusCode)
{
DashboardDTO dashboard = response.Content.ReadAsAsync<DashboardDTO>().Result;
}

I believe after receiving the response you are probably trying to cast WidgetBaseDTO to either WidgetTypeA or WidgetTypeB and you are seeing null? if yes, then you can try after making the following setting to the Json formatter on the server...make sure to make this setting on the client side's json formatter too.
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects;
The above setting will cause the type information of WidgetTypeA or WidgetTypeB to be put over the wire which gives a hint to the client as to the actual type of the object being deserialized...you can try looking at the wire format of the response to get an idea...
Client side:
JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
jsonFormatter.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects;
WidgetBaseDTO baseDTO = resp.Content.ReadAsAsync<WidgetBaseDTO>(new MediaTypeFormatter[] { jsonFormatter }).Result;

Related

Dealing with external namespaces for a POCO data model implemented through NJsonSchema auto-generated classes

In a dotnet microservice architecture, to avoid exposing my data model and its logic, I am willing to add a POCO data model layer following a mechanism: fullDataModel>json>pocoDataModel.
Here is my reduced fullDataModel
public class MyDto
{
public string Name { get; set; }
public AnExternalNamespace.MyExternalType ExternalObject { get; set; }
}
To implement the above mechanism, I am using NJSonSchema CSharpGenerator as follow:
// generate Json schema from ConsoleApp1
JsonSchema schema = await JsonSchema.FromFileAsync("MyDto.txt");
CSharpGeneratorSettings settings = new CSharpGeneratorSettings
{
ClassStyle = CSharpClassStyle.Poco,
Namespace = "MS1Namespace",
GenerateDataAnnotations = true
};
var generator = new CSharpGenerator(schema, settings);
var modelFile = generator.GenerateFile();
// generate the C# file in a another ConsoleApp2 for testing purpose
using (StreamWriter writer = new StreamWriter("..\\ConsoleApp2\\MyDtoFromJson.cs"))
{
writer.WriteLine(modelFile);
}
Problem, the MyDto class refers to a type which comes from an external library (AnExternalNamespace.MyExternalType). But the mechanism I implemented does not preserve this type of reference, rather it generates a type bounded to each namespace associated with each microservice I use that implement this type. Have a look at the C# generated file which is bounded to MS1Namespace
namespace MS1Namespace
{
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.8.0.0 (Newtonsoft.Json v13.0.0.0)")]
public partial class MyExternalType
{
[Newtonsoft.Json.JsonProperty("Name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Name { get; set; }
[Newtonsoft.Json.JsonProperty("Description", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Description { get; set; }
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.8.0.0 (Newtonsoft.Json v13.0.0.0)")]
public partial class MyDto
{
[Newtonsoft.Json.JsonProperty("Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Name { get; set; }
[Newtonsoft.Json.JsonProperty("ExternalId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public MyExternalType ExternalObject { get; set; }
}
}
In other words, with this mechanism, MyExternalType is kind of redefined relative to each MSnNamespace for every microservice (1 to n) where it is embedded. On the contrary, I would like to be able to receive objects that refer to the type defined in the AnExternalNamespace each time I send a request to these microservices. That would allow me treat the data of same type coming from these different microservices in a unified way with the unique AnExternalNamespace.MyExternalType
I have tried to play with Json annotations in MyDto class. For example by doing this:
[JsonProperty(TypeNameHandling = TypeNameHandling.Auto)]
public MyPublicLibrary.MyExternalType ExternalId { get; set; }
This does not allow to get out of the MS1Namespace scope to which all the auto-generated classes are bounded.
So, the only solution I see to overcome this for now is to implement an explicit&implicit cast in each microservice embedding this type from MSnNamespace.MyExternalType to AnExternalNamespace.MyExternalType.
But I was thinking NJSonSchema settings could offer me a more elegant way to deal with this situation. Am I missing something here? Do you see any other options or better practice while continuing keeping passing through the Json serialization/deserialization process?
Thanks in advance for your help!

How do I consume a Web API that returns Json?

Any suggestions would be most appreciated at this point.
I have a simple method in an MVC controller.
public IHttpActionResult GetCrudeAssessmentPrices(Int32? sellerid, DateTime? pdate, DateTime? pdateend)
{
var data = (dynamic)null;
data = db.CrudeAssessmentPrices.Select(i => new { sellerid, i.Price, i.Assessment, i.PriceDate }).Where(r => r.PriceDate >= pdate).Where(r => r.PriceDate <= pdateend).OrderBy(a => a.Assessment).ThenBy(b => b.PriceDate).ToList();
data = Json(data);
return data;
}
This method returns the following when views from a browser:
[{"sellerid":95,"Price":47.14000,"Assessment":"Argus: ANS","PriceDate":"2015-10-01T00:00:00"},{"sellerid":95,"Price":49.02500,"Assessment":"Argus: ANS","PriceDate":"2015-10-01T00:00:00"}]
In a console application I have this:
using System;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
namespace ConsumeJson
{
class AssessmentPrice
{
public int sellerid { get; set; }
public double Price { get; set; }
public string Assessment { get; set; }
public DateTime PriceDate { get; set; }
}
class Program
{
static void Main()
{
RunAsync().Wait();
}
static async Task RunAsync()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:49467/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP GET
try
{
HttpResponseMessage response = await client.GetAsync("api/CrudeAssessmentPrices?sellerid=95&pdate=10/1/2015&pdateend=10/1/2015");
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
AssessmentPrice assessmentprice = await response.Content.ReadAsAsync<AssessmentPrice>();
Console.WriteLine("{0}\t${1}\t{2}\t{3}", assessmentprice.sellerid, assessmentprice.Price, assessmentprice.Assessment, assessmentprice.PriceDate);
}
}
catch (HttpRequestException e)
{
// Handle exception.
}
}
}
}
}
Running the console application the response object is returned from the api but the content property shows the header and it has a length but there is no content. The following error is produced:
***Message=Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'ConsumeJson.AssessmentPrice' 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.
Source=Newtonsoft.Json***
Your service is returning an array of JSON objects but your console application is expecting a JSON object. You could return a JSON object that contains the array like this: {"arrayOfObjects" : [{"one"}, {"two"}]} or tell it to expect an array.

HttpClient.PostAsJsonAsync content empty

I'm trying to send a complex data type from one process to another using ASP.net MVC. For some reason the receiving end always receives blank (zero/default) data.
My sending side:
static void SendResult(ReportResultModel result)
{
//result contains valid data at this point
string portalRootPath = ConfigurationManager.AppSettings["webHost"];
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(portalRootPath);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage resp = client.PostAsJsonAsync("Reports/MetricEngineReport/MetricResultUpdate", result).Result;
if (!resp.IsSuccessStatusCode) {
//I've confirmed this isn't happening by putting a breakpoint in here.
}
}
My receiving side, in a different class, running in a different process on my local machine:
public class MetricEngineReportController : Controller
{
...
[HttpPost]
public void MetricResultUpdate(ReportResultModel result)
{
//this does get called, but
//all the guids in result are zero here :(
}
...
}
My model is a bit complicated:
[Serializable]
public class ReportResultModel
{
public ReportID reportID {get;set;}
public List<MetricResultModel> Results { get; set; }
}
[Serializable]
public class MetricResultModel
{
public Guid MetricGuid { get; set; }
public int Value { get; set; }
public MetricResultModel(MetricResultModel other)
{
MetricGuid = other.MetricGuid;
Value = other.Value;
}
public MetricResultModel(Guid MetricGuid, int Value)
{
this.MetricGuid = MetricGuid;
this.Value = Value;
}
}
[Serializable]
public struct ReportID
{
public Guid _topologyGuid;
public Guid _matchGuid;
}
Any idea why the data's not arriving?
Any help would be much appreciated...
P.S. For some reason I can't seem to catch the http POST message on fiddler, not sure why that is.
Try using "[FromBody]" parameter in Controller's Action. As you post data is passed to body not in url.
[HttpPost]
public void MetricResultUpdate([FromBody] ReportResultModel result)
{
//this does get called, but
//all the guids in result are zero here :(
}
The problem was twofold:
I needed to specify the type in my JSON post like this:
HttpResponseMessage resp = client.PostAsJsonAsync<MetricResultModel>("Reports/MetricEngineReport/MetricResultUpdate", result.Results[0]).Result;
The components of my model did not have default constructors, which is necessary for the JSON deserialization on the receiving end.
I just had the same problem. It seems that the content-length header is set to 0 when using the default PostAsJsonAsync extension method, which causes the server to ignore the request body.
My solution was to install the System.Net.Http.Json nuget package that uses the new System.Text.Json serializer.
When you add using System.Net.Http.Json;, you should be able to use the new extension method PostAsJsonAsync that works (sets the content-length header) properly.
namespace System.Net.Http.Json
{
public static class HttpClientJsonExtensions
{
public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, CancellationToken cancellationToken)
{
return client.PostAsJsonAsync(requestUri, value, null, cancellationToken);
}
}
}

Using Json.NET for JSON Model Binding

I have a method being posted to via AJAX with the following header:
public JsonResult GetDocuments(string searchTerm, SortRequest sort)
The SortRequest object is defined as follows:
[DataContract]
public class SortRequest
{
[DataMember(Name = "field")]
public string Field { get; set; }
[DataMember(Name = "dir")]
public string Direction { get; set; }
}
Because of legacy code, the JSON object has the property name "dir" which doesn't directly match the C# property name. We want to use Json.NET as the model binder for JSON requests because it is able to handle this, but the problem is that the JSON coming into the model binder looks like a single object with two top level properties, "searchTerm" and "sort". The deserialization process then tries to map that entire JSON string into each method parameter which obviously fails.
I have tried looking through the now open source .NET MVC code and have not yet been able to determine how the DefaultModelBinder class handles this gracefully. The only option I can see so far is to convert every JSON action to take in a single request parameter but this doesn't seem like a good solution as the DefaultModelBinder doesn't require this.
Edit for clarification:
The JSON request string looks something like this:
{
"searchTerm": "test",
"sort": {
"field": "name",
"dir": "asc"
}
}
We are overriding the DefaultModelBinder and only using Json.NET when the request is of type application/json. Here is the relevant code:
var request = controllerContext.HttpContext.Request;
request.InputStream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(request.InputStream))
{
var jsonString = reader.ReadToEnd();
result = JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType);
}
The bindingContext.ModelType is going to be set to String and SortRequest for each parameter in the method, but since the above is a single JSON object, it doesn't map to either of those types and thus inside the method itself, everything is set to default values.
I think the JsonProperty attribute can be used for this as follows:
[DataContract]
public class SortRequest
{
[DataMember(Name = "field")]
[JsonProperty("field")]
public string Field { get; set; }
[DataMember(Name = "dir")]
[JsonProperty("dir")]
public string Direction { get; set; }
}
Update
Based upon the json add a binding prefix:
public JsonResult GetDocuments(string searchTerm, [Bind(Prefix="sort"] SortRequest sort)
I ended up going with a solution using the JToken.Parse method in the Json.NET library. Essentially what is happening is that we check the top level properties of the JSON object and see if there exists the current action parameter we are trying to bind to. Where this falls down is if there is overlap between the parameter name of the action and a property name of a single request being passed in. I think this is enough of an edge case to let slide as it would require only a single object be passed into an action that is expecting multiple.
Here is the modified BindModel method:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result;
if (IsJSONRequest(controllerContext))
{
var request = controllerContext.HttpContext.Request;
request.InputStream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(request.InputStream))
{
var jsonString = reader.ReadToEnd();
// Only parse non-empty requests.
if (!String.IsNullOrWhiteSpace(jsonString))
{
// Parse the JSON into a generic key/value pair object.
var obj = JToken.Parse(jsonString);
// If the string parsed and there is a top level property of the same
// name as the parameter name we are looking for, use that property
// as the JSON object to de-serialize.
if (obj != null && obj.HasValues && obj[bindingContext.ModelName] != null)
{
jsonString = obj[bindingContext.ModelName].ToString();
}
}
result = JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType);
}
}
else
{
result = base.BindModel(controllerContext, bindingContext);
}
return result;
}

Change the json DateTime serialization in WCF 4.0 REST Service

I need to replace the DateTime serialization for JSON in WCF REST Self Hosted service. Right now, I'm using something like the following code to do it, but it's definitely not the way to go since it requires manipulating each class.
[DataContract]
public class Test
{
[IgnoreDataMember]
public DateTime StartDate;
[DataMember(Name = "StartDate")]
public string StartDateStr
{
get { return DateUtil.DateToStr(StartDate); }
set { StartDate = DateTime.Parse(value); }
}
}
where my utility function DateUtil.DateToStr does all the formatting work.
Is there any easy way to do it without having to touch the attributes on my classes which have the DataContract attribute? Ideally, there would be no attributes, but a couple of lines of code in my configuration to replace the serializer with one where I've overridden DateTime serialization.
Everything that I've found looks like I have to replace huge pieces of the pipeline.
This article doesn't appear to apply because in I'm using WebServiceHost not HttpServiceHost, which not part of the 4.5.1 Framework.
JSON.NET Serializer for WCF REST Services
By default WCF uses DataContractJsonSerializer to serialize data into JSON. Unfortunatelly date from this serializer is in very difficult format to parse by human brain.
"DateTime": "\/Date(1535481994306+0200)\/"
To override this behavior we need to write custom IDispatchMessageFormatter. This class will receive all data which should be returned to requester and change it according to our needs.
To make it happen to the operations in the endpoint add custom formatter - ClientJsonDateFormatter:
ServiceHost host=new ServiceHost(typeof(CustomService));
host.AddServiceEndpoint(typeof(ICustomContract), new WebHttpBinding(), Consts.WebHttpAddress);
foreach (var endpoint in host.Description.Endpoints)
{
if (endpoint.Address.Uri.Scheme.StartsWith("http"))
{
foreach (var operation in endpoint.Contract.Operations)
{
operation.OperationBehaviors.Add(new ClientJsonDateFormatter());
}
endpoint.Behaviors.Add(new WebHttpBehavior());
}
}
ClientJsonDateFormatter is simple class which just applies formatter ClientJsonDateFormatter
public class ClientJsonDateFormatter : IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { }
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Formatter = new ResponseJsonFormatter(operationDescription);
}
public void Validate(OperationDescription operationDescription) { }
}
In the formatter we took imput and serialize it with the changed Serializer:
public class ResponseJsonFormatter : IDispatchMessageFormatter
{
OperationDescription Operation;
public ResponseJsonFormatter(OperationDescription operation)
{
this.Operation = operation;
}
public void DeserializeRequest(Message message, object[] parameters)
{
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
string json=Newtonsoft.Json.JsonConvert.SerializeObject(result);
byte[] bytes = Encoding.UTF8.GetBytes(json);
Message replyMessage = Message.CreateMessage(messageVersion, Operation.Messages[1].Action, new RawDataWriter(bytes));
replyMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
return replyMessage;
}
}
And to send information to client we need data writer - RawDataWriter. Its implementation is simple:
class RawDataWriter : BodyWriter
{
byte[] data;
public RawDataWriter(byte[] data)
: base(true)
{
this.data = data;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("Binary");
writer.WriteBase64(data, 0, data.Length);
writer.WriteEndElement();
}
}
Applying all code will result in returning date in more friendly format:
"DateTime":"2018-08-28T20:56:48.6411976+02:00"
To show it in practice I created example in the github branch DateTimeFormatter.
Please check also this answer as very likely you also will need it.
There is a limitation in JSON to convert DateTime, specially according to your case.
Please see http://msdn.microsoft.com/en-us/library/bb412170(v=vs.110).aspx
and read the section Dates/Times and JSON
To resolve this problem, I simply changed the type of serialization from JSON to XML for all the calls including DateTime.
After long time discussion ,I have find out the solution for it.
Please Use the following Code to Solve serialized date..
[IgnoreDataMember]
public DateTime? PerformanceDate { get; set; }
[DataMember(EmitDefaultValue = false, Name = "PerformanceDate")]
public string UpdateStartDateStr
{
get
{
if (this.PerformanceDate.HasValue)
return this.PerformanceDate.Value.ToUniversalTime().ToString("s", CultureInfo.InvariantCulture);
else
return null;
}
set
{
// should implement this...
}
}