5

I'm trying to post XML to asp.net core 2:

$.ajax({
    type: "POST",
    url: 'api/Test',
    data: "<test>hello<test>",
    contentType: "application/xml",
    success: function (response) { alert(response); },
});

How should I write the action, so it accepts the xml as parameter?

  • IActionResult Post([FromBody]string xml) -> xml is null
  • IActionResult Post([FromBody]XElement xml) -> xml is null
  • IActionResult Post(XElement xml) -> InvalidOperationException: Could not create an instance of type 'System.Xml.Linq.XElement'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.
  • IActionResult Post(string xml) -> xml is null

in the Startup.ConfigureServices:

services.AddMvc()
    .AddXmlSerializerFormatters();

How to write the controller so that it accepts XML as a parameter? I know I can read it from HttpContext.Request, but I want it to be parameter

3 Answers 3

6

I've ended up creating custom InputFormatter, which was pretty straitforward, but if there is better alternative, you are very welcome to write an answer!

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 the XDocumentInputFormatter in startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options => options.InputFormatters.Insert(0, new XDocumentInputFormatter()));
}
Sign up to request clarification or add additional context in comments.

1 Comment

services.AddMvc(options => { // allow xml format for input options.InputFormatters.Add(new XmlSerializerInputFormatter(options)); }) ...
2

Just a change for the answer given by Liero, you should use a StreamReader, so you can support multiple encodings. Tested my solution with UTF-8, UTF-16 and ASCI declaration header.

Change the Method from XDocumentInputFormatter:

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);
}

To below

public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context) {
        // Use StreamReader to convert any encoding to UTF-16 (default C# and sql Server).
        using (var streamReader = new StreamReader(context.HttpContext.Request.Body)) {
            var xmlDoc = await XDocument.LoadAsync(streamReader, LoadOptions.None, CancellationToken.None);
            return InputFormatterResult.Success(xmlDoc);
        }
    }

Comments

0

These solutions works, but have one disadvantage in .NET Core 3 - they causes exception (inside calling XDocument.LoadAsync):

System.InvalidOperationException: Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.

Here is my modified solution with FileBufferingReadStream (inspired by code from Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerInputFormatter)

public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
    Check.NotNull(context, nameof(context));

    var xmlDoc = await LoadXmlFromRequestAsync(context.HttpContext);
    return InputFormatterResult.Success(xmlDoc);
}

private static async Task<XDocument> LoadXmlFromRequestAsync(HttpContext httpContext)
{
    Check.NotNull(httpContext, nameof(httpContext));

    //Code from Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerInputFormatter to use FileBufferingReadStream to avoid synchronous read issue:
    //https://github.com/dotnet/aspnetcore/issues/18723 - Synchronous call inside XDocument.LoadAsync causes --> System.InvalidOperationException: Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
    int memoryThreshold = 30720;
    long contentLength = httpContext.Request.ContentLength.GetValueOrDefault();
    if (contentLength > 0 && contentLength < memoryThreshold)
    {
        memoryThreshold = (int)contentLength;
    }

    var readStream = new FileBufferingReadStream(httpContext.Request.Body, memoryThreshold);
    httpContext.Response.RegisterForDispose(readStream);
    await readStream.DrainAsync(CancellationToken.None);
    readStream.Seek(0, SeekOrigin.Begin);

    try
    {
        using (var streamReader = new StreamReader(readStream))
        {
            var xmlDoc = await XDocument.LoadAsync(streamReader, LoadOptions.None, CancellationToken.None);
            return xmlDoc;
        }
    }
    finally
    {
        await readStream.DisposeAsync();
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.