I'm writing a C# test automation to validate web services that return JSON strings. I created a DataContract that mapped to what was being returned. Assume this is what was being returned:
{"DataModule" : {"required":"false", "location":"toolbar"}}
My test automation was working fine, but then I started getting this error:
"The data contract type 'DataModule' cannot be deserialized because
the required data members 'required, location' were not found."
I checked the JSON and the data module was now
{"DataModule" : {"width":"400", "height":"320"}}
I discovered that the dev implementation is that if the first type of data module is encountered, the client parses that and creates a button on a toolbar. If the second type of data module is returned, the button appears on the toolbar AND a panel appears in another location with those measurements.
Is there a way to either create optional members in a data contract OR implement conditional deserialization to account for JSON objects that may have multiple implementations?
If you declare the model with all of the likely properties, only the ones found in the JSON string will be populated:
public class DataModule
{
public bool Required { get; set; }
public string Location { get; set; }
public string Width { get; set; }
public string Height { get; set; }
}
#dave-s
I had already tried adding all of the properties, but since I knew I was on the right track, your suggestion keyed me into something else. The properties were all decorated with
[System.Runtime.Serialization.DataMemberAttribute(IsRequired = false)]
But the class itself, was decorated with only [Serializable]. When I changed [Serializable] to
[System.Runtime.Serialization.DataContractAttribute()]
it started working.
Related
Situation
I'm writing an application that can display different types of charts, with a vanilla JS front-end and an ASP.NET 6 API back-end. There are different types of charts in the application, like LineChart and PieChart. These different chart types all implement the interface IChart. The IChart interface has properties for information like the ID and name of the chart, whereas the LineChart, for example, has properties for the name of the X- and Y-axis.
Here are the model classes as minimum example:
IChart.cs:
public interface IChart
{
public int Id { set; get; }
public string Title { set; get; }
public string Description { set; get; }
}
LineChart.cs:
public class LineChart : IChart
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string xAxisName { get; set; }
public string yAxisName { get; set; }
}
My Problem
When I'm returning a single chart using an ASP.NET API-controller, it works just fine; if the object I return is a LineChart, I get the property xAxisName in the JSON response even if the API method return type is IChart. But when I return multiple Charts in a List<IChart>, I only get the properties provided by IChart in my JSON, so no xAxisName. How can I make a controller return all the items in the List<IChart while keeping the properties of the derived classes in the JSON result? Is there some JSON middleware setting I need to set?
As an illustration, here are a few sample API calls.
The following code returns a LineChart with the X axis name set in the JSON reponse body:
// POST: api/<ChartController>
[HttpPost("{xAxisName}")]
public IChart MakeOneChart(string xAxisName)
{
IChart chart = new LineChart() {xAxisName = xAxisName};
return chart;
}
But in the following code, the response body is not filled with values for the X axis
// GET: api/<ChartController>
[HttpPost]
public IEnumerable<IChart> MakeManyCharts()
{
IEnumerable<IChart> charts = new List<IChart>()
{
new LineChart() {xAxisName = "Duration (in minutes)"},
new LineChart() {xAxisName = "Duration (in years)"}
};
return charts;
}
People here had a similar problem. The returned object has an interface as a property and the derived classes didn't serialize properly but I bet any property defined in that interface would show.
Now I don't know much about the inner workings of Json conversion but from what I've observed, here's what I'm guessing is happening:
The standard Json serializer provided with .Net doesn't recognize the object's runtime type so it doesn't know anything about the object beneath the surface. Only exception is the outermost object you're returning. For example, when your return type is IChart, it can recognize when you return LineChart or PieChart since it's just checking the outermost object. However, when you return IEnumerable<IChart> it can't recognize the derived type of IChart objects since they're wrapped by IEnumerable. I bet if you returned List<IChart> it could recognize properties related to List<T>
There are some possible solutions:
An answer in the linked question suggests using Json.NET package from Newtonsoft
A different answer from the question suggests writing custom serializers
Another option I would say would be to try writing a custom formatter. Check the type of object and cast it like this:
if (context.Object is IEnumerable<LineChart> lineCharts)
{
// serialize the lineCharts
}
else if (context.Object is IEnumerable<PieChart> pieCharts)
{
// serialize the pieCharts
}
// return the json string
though this type of solution probably would not work if you have base interfaces as properties like in the question and you wanted the derived objects of those to be recognized in the serialization (or rather you would have to write nested if statements and check every property of every chart object in the IEnumerable and it would go on and on if you have a very complex object), but should be enough for your situation
I've added an existing method to a web service (that I did not write).
I can bring the method into Soap UI by performing and update on the service.
I can run the method in SOAP UI and debug it and clearly see my method is pulling the data I want, processing it correctly but I am getting nothing back in Soap UI and my debugger terminates at that point.
Its worth nothing I build this method from an existing working method that returns data just fine. I imagine I am just missing some minor configuration some ware.
Things I have done,
1) Added the new method to the operation contract
2) Added all the necessary logic for processing the data.
I can furnish any code/configs but I just don't know what exactly is needed to troubleshoot this as I am attempting to add this off existing code. I'm generally not a vague with the details but I'm in the process of educating myself at the same time as well as trudging through some existing documentation I have found online.
The service is intended to return a class. We define a DTO and paste the results from a SQL query into our DTO. We then do some integrity checking on the DTO and test for failure or success. We then add the DTO to a container object named CustomResult based on success or failure and return the CustomResult. The CustomResult class should not need any modifying (famous last words). I did however create a new DTO class which I can add to my original post
DTO Class
namespace Custom.Company.Services
{
[DataContract]
public class TimeUnitDto
{
[DataMember]
public string Calendar { get; set; }
[DataMember]
public long AverageHour { get; set; }
[DataMember]
public long AverageDay { get; set; }
[DataMember]
public long AverageWeek { get; set; }
[DataMember]
public long AverageMonth { get; set; }
[DataMember]
public long AverageYear { get; set; }
[DataMember]
public long LookupRefreshInd { get; set; }
}
}
Thanks,
All, I figured it out. Unfortunately the answer is propriety to what I am doing but basically we the DTO to our CustomResult Class.
I copy/pasted the code from another service we use since the functionality was similar. I forgot to replace of the 'Status' assignments to the new status I created for this dto.
It was trying to place it in a Status object that the data is not going to recognize. Thanks to everyone who looked into this for me.
I'm facing little stranger issue with Web API controller. I have a collection which is being passed in an action of api controller. Object being used is collection is having 4 properties.
My action is able to accept collection parameter when it's properties are in specific order. See below :-
[HttpPost]
public ForexRates UpdateRates([FromBody] Rates rates)
{
// TODO: Obviously code :)
return rates;
}
This code is being place in API controller & calling from Postman. See below:-
<rates>
<rate>
<id>fefef</id>
<rate>35353.333</rate>
<series>dfefge</series>
<series-order>sfefefef</series-order>
</rate></rates>
If I change the order of the properties I started getting null value in my action. Can some one please explain this :)
Models
public class Rate
{
public string Id { get; set; }
public string Date { get; set; }
public double Rate { get; set; }
}
public class Rates : Collection<ForexRate>
{
}
You will need to control the order with which your XML is serialized. Use XmlElementAttribute and specify the Order.
There is a similar question here
FYI, I suppose there is no way for you to change the order of the properties, while you supply from PostMan to your WebApi service. You will need to follow the exact order.
If you don't wanna do that, then pass this Xml as a string parameter and then parse it inside a method.
The default binder can have issues when the same name is used in different places during binding.
In your case you've got Rate.Rate - both class name and property name. Try changing your class to (and corresponding xml for the post) :
public class Rate
{
public string Id { get; set; }
public string Date { get; set; }
public double Value { get; set; }
}
and then try changing the order.
While I don't have a definitive reason why it works in one order and not another, it's likely that when it gets to the Rate(double) value it tries to create a new Rate(object) but doesn't have the correct properties (as its just a double).
A more complicated solution would be to write a specific model binder for the Rate object.
The issue has to do with the DataContractSerializer which expects the elements to occur in a specific order (alphabetical with some consideration given to inheritance). That's the default serializer used when creating a Web API project.
You can override this and specify a different serializer during API Configuration like this:
GlobalConfiguration.Configuration.Formatters.XmlFormatter
.SetSerializer<SomeType>(new XmlSerializer(typeof(SomeType)));
I am working on an endpoint in asp.net that serializes and returns some data, using the default serializer.
The consuming applications are transitioning between changing names for properties (in other words, some existing applications are using names like ...Vat; while newer ones are using ...Tax. I therefore need to keep both names in the response for the moment, until these changes are complete.
The return type is IList.
public class Product
{
...
public decimal PriceIncVat { get; set; }
public decimal PriceIncTax { get { return PriceIncVat; } }
public int TaxCode { get; set; }
...
}
However, when I examine the response in fiddler, only the PriceIncVat property exists in the json list of products.
I can't think of any reason why the above wouldn't work. I added TaxCode at the same time as PriceIncTax, and it is returned, so I know the code of the endpoint is up to date.
And on the client side of a newer client project we have:
public class ProductDto
{
...
public decimal PriceIncTax { get; set; }
public string TaxCode { get; set; }
...
}
Very confused here.
The serializer assumes you will need to deserialize the data some time. Hence by default only properties with a getter and a setter are considered.
When using the DataContractJsonSerializer, it's possible to turn on serialization of read-only properties using the SerializeReadOnlyTypes property (despite its rather misleading name).
Side note: Check-out the Json.NET serializer, which gives more options and better control over the (de)serialization process.
I have an ASMX webservice with a number of methods which will return XML.
The service returns various different objects and I have created a wrapper object which contains information about the request e.g:
[Serializable]
[XmlRoot("response")]
public class DtoWrapper<T>
{
[XmlElement("error")]
public bool Error { get; set; }
[XmlElement("error_message")]
public string ErrorMessage { get; set; }
[XmlElement("success")]
public bool Success { get; set; }
[XmlElement("friendly_message")]
public string FriendlyMessage { get; set; }
[XmlArray("result")]
[XmlArrayItem("item")]
public List<T> Payload { get; set; }
}
Now this works fine until I defined my second method with a different type. Then I get this error when I try and load the ASMX test page
The top XML element 'response' from namespace 'http://tempuri.org/'
references distinct types
MyProject.Web.webservices.DtoWrapper1[MyProject.BusinessLogic.ClassA]
and
MyProject.Web.webservices.DtoWrapper1[MyProject.BusinessLogic.ClassB].
Use XML attributes to specify another
XML name or namespace for the element
or types.
I have tried marking my objects up with [XmlType(Namespace="com.temp.A")] and [XmlType(Namespace="com.temp.B")] but it doesn't seem to help.
Any ideas? Will I have to create a wrapper object for each type I want to use?
EDIT: I've realised it's not actually the type arguments that are the problem. It's the fact that the [XmlRoot] tag is specified on the class. The serializer is treating them as 2 types but they have the same root element in the same namespace.
You cannot do this. XML has no concept of generics, neither do XML Schema or SOAP. As far as XML Schema is concerned, if it has the same element name and same namespace, then it's the same thing.
You cannot have a generic web service, as the concepts do not exist.