Output XML validation error from Web API Middleware (400 Bad request)

Viewed 10

I'd like to validate XML posted to a controller against a given XSD before modelbinding is done. For this I wrote the following middleware:

using Microsoft.Extensions.Options;
using System.Net;
using System.Xml.Linq;
using System.Xml.Schema;

namespace MyProject.Middleware;

public class XmlValidatingMiddlewareOptions
{
    public bool Enabled { get; init; } = false;
    public IDictionary<string, string> XmlSchemas { get; init; } = new Dictionary<string, string>();
}

public class XmlValidatingMiddleware
{
    private readonly XmlValidatingMiddlewareOptions _options;
    private readonly RequestDelegate _next;
    private readonly XmlSchemaSet _xmlschemaset = new();

    public XmlValidatingMiddleware(RequestDelegate next, IOptions<XmlValidatingMiddlewareOptions> options)
    {
        _next = next ?? throw new ArgumentNullException(nameof(next));
        _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
        foreach (var xskv in _options.XmlSchemas)
            _xmlschemaset.Add(xskv.Key, xskv.Value);
    }

    public async Task Invoke(HttpContext context)
    {
        if (_options.Enabled && IsXMLContentType(context.Request.ContentType) && IsPost(context.Request.Method))
        {
            context.Request.EnableBuffering();

            // Validate XML against XSD schema
            var validationerrors = await ValidateAsync(_xmlschemaset, context.Request.Body).ConfigureAwait(false);
            if (validationerrors.Any())
            {
                // Validation failed, provide feedback
                context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
                await context.Response.WriteAsync(
                    $"One or more validation errors occurred:\n\n{string.Join("\n", validationerrors.Select(r => $"{r.Severity}: {r.Message}"))}"
                ).ConfigureAwait(false);
                return;
            }
            context.Request.Body.Position = 0;
        }

        await _next.Invoke(context);
    }

    private async Task<IEnumerable<ValidationEventArgs>> ValidateAsync(XmlSchemaSet xmlSchemaSet, Stream stream, CancellationToken cancellationToken = default)
    {
        var exceptions = new List<ValidationEventArgs>();
        var doc = await XDocument.LoadAsync(stream, LoadOptions.None, cancellationToken).ConfigureAwait(false);
        doc.Validate(xmlSchemaSet, (s, e) => exceptions.Add(e), true);
        return exceptions;
    }

    private bool IsPost(string? method)
        => "POST".Equals(method, StringComparison.OrdinalIgnoreCase);

    private bool IsXMLContentType(string? contentType)
        => contentType is not null
            && (
            contentType.Equals("application/xml", StringComparison.OrdinalIgnoreCase)
            ||
            contentType.Equals("text/xml", StringComparison.OrdinalIgnoreCase)
        );
}

As you can see I output the HTTP statuscode 400 (Bad Request) on this line:

context.Response.StatusCode = (int)HttpStatusCode.BadRequest;

Right after that I also write some information about the validation errors. However, the body is not output. It is when I change the status to, say, 200 OK. I am not aware of an HTTP status 400 not having a body?

So my question: Why is there no body output when I send a 400 Bad Request, or, if a 400 Bad Request isn't supposed to have a body, what would be the correct Http status when invalid XML is passed and I want to provide feedback on the warnings/errors found?

0 Answers
Related