I have an existing Web API endpoint that we have, up until now, only supported when accessed via JSON.
That is, we only supported its use when content-type was "application/json". We are now trying to make it accept "application/xml".
And we've worked through a number of issues, with not having [DataContract] on the model, then with not specifying namespace="" on [DataContract] for the model, etc. But we've worked through that, and we now have the XML marshalling correctly. ModelState is valid.
But we're not getting all of the data.
Our controller action:
[HttpPost, Route("")]
public HttpResponseMessage PostNoteForTickets([FromBody] IEnumerable<NoteDto> dto)
{
...
}
Our NoteDto:
[DataContract(Namespace = "")]
public class NoteDto
{
[DataMember]
public string TicketNumber { get; set; }
[DataMember]
public string CreatedBy { get; set; }
[DataMember]
public DateTime? CreatedDateTime { get; set; }
[DataMember]
public string Contents { get; set; }
}
The XML we're sending:
<?xml version="1.0"?>
<ArrayOfNoteDto>
<NoteDto>
<TicketNumber>TestJob1</TicketNumber>
<CreatedBy>PostMan</CreatedBy>
<CreatedDateTime>2020-11-02T13:14:15.678</CreatedDateTime>
<Contents>This is a note</Contents>
</NoteDto>
</ArrayOfNoteDto>
As I said, this is marshalling without errors
Our IEnumerable dto is populated with an array of NoteDto containing one NoteDto object.
And that one NoteDto object has TicketNumber set to "TestJob1", but CreatedBy, CreatedDateTime, and Contents are null - when they most definitely weren't null in the sent XML.
I've checked the raw content:
var rawContent = Request.Content.asString();
And all of the fields are present in the XML that is being sent into the controller. Why is one field being populated and the others not?
Continuing to explore:
If I specify the field order in the datacontract:
[DataContract(Namespace = "")]
public class NoteDto
{
[DataMember(Order = 1)]
public string TicketNumber { get; set; }
[DataMember(Order = 2)]
public string CreatedBy { get; set; }
[DataMember(Order = 3)]
public DateTime? CreatedDateTime { get; set; }
[DataMember(Order = 4)]
public string Contents { get; set; }
}
And then submit XML with its elements in that same order, all of the fields populate correctly. If I submit the XML in a different order, some of the fields end up null.
This strikes me as a quite unreasonable restriction on our API. I really don't want to have to tell our customers that their clients aren't working because they're submitting their XML with the elements in the wrong order.
Especially when XML does not require element ordering. It's something that can be required, in an XML schema, but very rarely is.
But Web API seems to be forcing me to require element ordering in the XML that is submitted to the endpoints in my API, whether I want to or not.
And that seems like such an absurd idea that I am very hesitant to think that I actually understand what is going on.
Anyone have any ideas?
Another bit of exploration.
If I do not specify an order, and submit XML with the elements in alphabetic order, they all populate as desired. You only need to specify order in the DataContract if you want other than alphabetic.
So, further exploration - as David pointed out in comments, this is specifically a behavior of Microsoft's DataContractSerializer.
So that raises a question - are there alternatives to the DataContractSerializer that can be configured to handle Web API marshalling that do not impose ordering on the XML elements?