1

I am developing a Web API, where the GET method needs to return an object, whose variables will be decided based on an XML file. The returned format must be either XML or JSON as requested by the client. I want to return the data inside XML file into XML format to the client, and something reasonable for JSON when JSON is requested.

The nodes in the XML might increase or decrease and therefore I cannot define a fixed class in the Models. My current solution is to return a dynamic object, but I am getting an exception shown below. What can I do to avoid the exception?

GET Api

[AllowAnonymous]
public class DataController : ApiController
{      
    //GET api/----based on dynamic binding
    public object Get()
    {
        //Read XML
        XDocument xDoc = XDocument.Load(@"D:\data.xml");

        string jsonStr = JsonConvert.SerializeXNode(xDoc);
        dynamic dynamicObject = JsonConvert.DeserializeObject<ExpandoObject>(jsonStr);


        return dynamicObject; //THIS LINE IS THROWING RUNTIME ERROR
    }
}

Sample XML File:

<Data>    
    <Name>abcd</Name>
    <bad>100</bad>
    <status>running</status>    
</Data> 

When I try to access the GET api, the following error appears on the web page:

<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace/>
<InnerException>
<Message>An error has occurred.</Message>
<ExceptionMessage>
Type 'System.Dynamic.ExpandoObject' with data contract name 'ArrayOfKeyValueOfstringanyType:http://schemas.microsoft.com/2003/10/Serialization/Arrays' 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.
</ExceptionMessage>
<ExceptionType>
System.Runtime.Serialization.SerializationException
</ExceptionType>
<StackTrace>
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiTypeAtTopLevel(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle originalDeclaredTypeHandle, Type graphType) at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.DataContractSerializer.WriteObject(XmlWriter writer, Object graph) at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content) at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken) --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()
</StackTrace>
</InnerException>
</Error>
dbc
  • 104,963
  • 20
  • 228
  • 340
skm
  • 5,015
  • 8
  • 43
  • 104
  • https://briancaos.wordpress.com/2017/09/22/c-using-newtonsoft-and-dynamic-expandoobject-to-convert-one-json-to-another/ – NitinSingh Oct 08 '18 at 09:27
  • API will return JSON anyway and you already have JSON string, so return string instead of having an object. – Fabio Oct 08 '18 at 09:30
  • @Fabio: I need to return XML from API. Secondly, if I define a Model then, it returns the data automatically in XML format. – skm Oct 08 '18 at 09:31
  • Did you try converting the json string- jsonStr to Object type while deserializing ? – kaarthick raman Oct 08 '18 at 09:34
  • @kaarthickraman: Sorry, I do not know how to do it – skm Oct 08 '18 at 09:36
  • 1
    @skm, based on your code you have Xml as well `xDoc.ToString()` ;) – Fabio Oct 08 '18 at 09:37
  • Please refer this article, this will help you - https://www.codeproject.com/Articles/1163664/Convert-XML-to-Csharp-Object – kaarthick raman Oct 08 '18 at 10:21
  • @kaarthickraman. In that article, there is this line `Customer customer = ser.Deserialize(xmlInputData);` and I don't know where is `Customer` declared. I get an error regarding `Customer` reference. – skm Oct 08 '18 at 11:25
  • What do you want to do? Return the contents of the file `data.xml` verbatim when returning XML, and return a reasonable JSON translation when returning JSON? – dbc Oct 08 '18 at 13:56
  • @dbc: Yes, basically I want to return the data inside XML file into xml format to the client. – skm Oct 08 '18 at 14:02
  • @skm - Could you check my edits to your question? I tried to add all your stated requirements from the comments into the question itself to clarify the problem. If I did it wrongly, please feel free to roll my edits back or fix them. – dbc Oct 08 '18 at 15:33

1 Answers1

0

The reason you are getting that error is that you have declared your method to return an object of type object -- but have in fact returned a polymorphic subtype, namely ExpandoObject. Since DataContractSerializer (and XmlSerializer) will refuse to serialize unexpected polymorphic types, they throw the exception you are seeing. For details see

That being said, I'd like to suggest a different, simpler approach. First, define your Get() method to explicitly return an XElement like so:

public XElement Get()
{
    //Read XML
    XDocument xDoc = XDocument.Load(@"D:\data.xml");
    return xDoc.Root;
}

DataContractSerializer (and XmlSerializer) are both able to serialize an object of this type (since it implements IXmlSerializable), so your method will now successfully return contents of the file "D:\data.xml" verbatim when XML is requested.

Now, what to do when JSON is requested? As it turns out, Json.NET has a built-in converter XmlNodeConverter that can serialize an XElement to and from JSON. It's used internally by JsonConvert.SerializeXNode() but is public and so can be used directly. Thus if you add the converter to your global Web API list of converters in JsonSerializerSettings.Converters, your method should now return something reasonable for JSON as well.

You don't specify which version of Web API you are using. To add a converter globally, see

dbc
  • 104,963
  • 20
  • 228
  • 340
  • @skm - Note - you may need to check that the MIME type is set correctly, see https://stackoverflow.com/a/7771932. I haven't been able to test this. I've tested the components of this answer but I haven't built a sample wep api project to make sure everything works together. – dbc Oct 08 '18 at 15:46