问题
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>
回答1:
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
- Understanding Known Types.
- Data Contract Known Types.
- Known Types.
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
ASP.NET Web API 2: See How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API? and also the second part of this answer to Registering a custom JsonConverter globally in Json.Net. In this scenario your code would look something like:
protected void Application_Start() { var config = GlobalConfiguration.Configuration; var settings = config.Formatters.JsonFormatter.SerializerSettings; settings.Converters.Add(new XmlNodeConverter()); }
ASP.NET Core MVC: see JsonSerializerSettings and Asp.Net Core or Setting JsonConvert.DefaultSettings asp net core 2.0 not working as expected. Here again you would access the global
JsonSerializerSettings
and add anXmlNodeConverter
as shown above.
来源:https://stackoverflow.com/questions/52698929/returning-a-dynamic-object-is-throwing-runtime-error