If you want to support multiple de-serializers for the same controller endpoint, then you should not try to accept a generic content like JObject
or xElement
as the argument for your controller. The other Faux Pas is that your method can only have a single argument decorated with the FromBodyAttribute
.
Configuring services.AddControllers().AddXmlSerializerFormatters()
is all you need to support automatic deserialization from XML and Serialization to XML via a mechanims called content negotiation. For this to work as an input type, you must first use a typed argument in your method, then the FromBodyAttribute
will use the configured SerializerFormatters to deserialize the body to the requested type based on the Content-Type of the request.
One caveat that I have found is that you should use the application/xml
as the Content-Type
for posting and XML document.
We use the Consumes
attribute to declare the supported input content types, the Produces
attribute describes the response types that are supported.
[HttpPost]
[Route("Patient/$gpc.registerstudent")]
[SwaggerOperation(Summary = "Register student")]
[Consumes("application/xml", "application/json")]
[Produces("application/xml", "application/json")]
public async Task<IActionResult> Registerstudent([FromBody] RegisterStudentRequestDTO request)
{
await Task.CompletedTask;
return Ok(request); // just echo out for now to show the output serialization
}
In this case my register DTO is very simple:
public class RegisterStudentRequestDTO
{
public string Name { get; set; }
public DateTime Date { get; set; }
public string Comments { get; set;}
}
The way that a caller can define the expected response type if by providing an Accept
header. For instance, we can now provide a XML input to this method and tell the controller to serialize the response into XML:
In the request, Content-Type
header describes the input that we are providing, the Accept
header describes the content-type of the response.
curl -X 'POST' \
'https://localhost:7208/WeatherForecast/Patient/$gpc.registerstudent' \
-H 'accept: application/xml' \
-H 'Content-Type: application/xml' \
-d '<?xml version="1.0" encoding="UTF-8"?>
<RegisterStudentRequestDTO>
<Name>string</Name>
<Date>2023-07-04T12:34:43.128Z</Date>
<Comments>string</Comments>
</RegisterStudentRequestDTO>'
You will notice that the name of the elements MUST match the casing of the name of the class and properties, by default the XML serializer is case-sensitive.
This will return:
<RegisterStudentRequestDTO xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>string</Name>
<Date>2023-07-04T12:34:43.128Z</Date>
<Comments>string</Comments>
</RegisterStudentRequestDTO>
But if you change the Accept
header to application/json
then without altering any code, the output changes to this:
{
"name": "string",
"date": "2023-07-04T12:29:14.595Z",
"comments": "string"
}
To complete the demo, to provide JSON input, the request would look like this:
curl -X 'POST' \
'https://localhost:7208/WeatherForecast/Patient/$gpc.registerstudent' \
-H 'accept: application/xml' \
-H 'Content-Type: application/json' \
-d '{ "name":"string", "date":"2023-07-04T12:29:14.595Z" , "comments": "string" }'
The response will still be in application/xml
due to the Accept
header.
Further Reading:
You can also read about a workaround here: Issue receiveing POST request with Content-Type text/xml
If you receive a response similar to this:
<problem xmlns="urn:ietf:rfc:7807">
<status>400</status>
<title>One or more validation errors occurred.</title>
<type>https://tools.ietf.org/html/rfc7231#section-6.5.1</type>
<traceId>00-94549daf9ab55604a516f0b2b012d563-23030c613b93e726-00</traceId>
<MVC-Errors>
<MVC-Empty>An error occurred while deserializing input data.</MVC-Empty>
<request>The request field is required.</request>
</MVC-Errors>
</problem>
Then please check the casing of your class or property names, the swagger document will use (lower) camel case by default in the Try it Now section, this is unfortunate:

Providing the correctly typed input will work, you can also implement DataContractAttribute
and DataMemberAttribute
to explicitly rename the properties to their lower case variant. If you do this, you will need to also add the .AddXmlDataContractSerializerFormatters()
to your fluent AddControllers()
implementation in startup.cs
/program.cs