Search code examples
c#asp.net-core.net-corehttpwebrequesthttpwebresponse

How can I do a POST in .NET Core 2.2 if the content is XML formatted?


I am really surprised by this issue because I remember succeeding in it in an earlier .NET Core version. I am working on a .NET Core 2.2 application which now needs to be called by another application (developed externally) which can only post xml....

This is my ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).AddXmlSerializerFormatters();
}

This is my controller:

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // POST api/values
    [HttpPost]
    public ActionResult<object> Post([FromBody] object value)
    {
        return ("Hi", "Hi2");
    }
}

The following requests do cause a response with status code 200:

POST http://localhost:58774/api/values
Content-Type: application/json
Accept: application/xml
User-Agent: vscode-restclient
Accept-Encoding: gzip

{"a":5}

which gives me xml as a response and

POST http://localhost:58774/api/values
Content-Type: application/json
Accept: application/json
User-Agent: vscode-restclient
Accept-Encoding: gzip

{"a":5}

which gives json as a response.

However, this call results in a response with status code 500 (which is exactly my problem):

POST http://localhost:58774/api/values
Content-Type: application/xml
Accept: application/xml
User-Agent: vscode-restclient
Accept-Encoding: gzip

<A a="5"/>

So now I am in trouble. Xml formatting works which becomes clear if I Accept it as an output type. However, if I post it myself as a Content-Type and test that, I get a 500. I also tried this (old) approach but it does not seem to work in .NET Core 2.2 . What am I doing wrong? How can I post my xml in .net core 2.2?

An update after a useful remark. The exception causing the 500 is this one:

System.InvalidOperationException: There is an error in XML document (1, 11). ---> System.InvalidOperationException: was not expected.

However, if I add an xmlns (based on this), I still have the 500:

POST http://localhost:5000/api/values
Content-Type: application/xml
Accept: application/xml
User-Agent: vscode-restclient
Accept-Encoding: gzip

<f:table xmlns:f="https://www.w3schools.com/furniture">
  <f:name>African Coffee Table</f:name>
  <f:width>80</f:width>
  <f:length>120</f:length>
</f:table>

Then the exception message is:

System.InvalidOperationException: There is an error in XML document (1, 56). ---> System.InvalidOperationException: https://www.w3schools.com/furniture'> was not expected.

Probably, I need to change my xml. How? Even the example from w3cschools does not help me.


Solution

  • For Content-Type: application/json with {"a":5}, you will receive { "a": 5 } at server side. It received plain text.

    For Content-Type: application/xml with <A a="5"/>, if you prefer receive <A a="5" />, you could implement custom XDocumentInputFormatter like

    public class XDocumentInputFormatter : InputFormatter, IInputFormatter, IApiRequestFormatMetadataProvider
    {
        public XDocumentInputFormatter()
        {
            SupportedMediaTypes.Add("application/xml");
        }
    
        protected override bool CanReadType(Type type)
        {
            if (type.IsAssignableFrom(typeof(XDocument))) return true;
            return base.CanReadType(type);
        }
    
        public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
        {
            var xmlDoc = await XDocument.LoadAsync(context.HttpContext.Request.Body, LoadOptions.None, CancellationToken.None);
    
            return InputFormatterResult.Success(xmlDoc);
        }
    }
    

    Register it in Startup.cs

    services.AddMvc(config =>
            {
                config.InputFormatters.Insert(0, new XDocumentInputFormatter());
            })
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
            .AddXmlSerializerFormatters();
    

    By default, For XmlSerializer, we need to provide Type type, an object type would not be able to deserialize.

    If the type of object value is definted like

    public class A
    {
        public int a { get; set; }
    }
    

    You could change your method like

    public ActionResult<object> Post([FromBody] A value)
    {
        return new A { a = 1 };//("Hi", "Hi2");
    }
    

    And with request like

    <A>
        <a>1</a>
    </A>
    

    It will fill value with class A object.