You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Web API中间件XML验证400错误:响应体无法显示问题排查

问题描述

为了在模型绑定完成前验证提交到控制器的XML是否符合指定XSD,编写了如下中间件:

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

当返回HTTP 400(Bad Request)状态码时,响应体的错误信息无法输出;但改为200 OK时,响应体正常显示。存在两个疑问:

  1. 为何返回400 Bad Request时响应体无法输出?
  2. 若400不应携带响应体,传递无效XML并返回错误反馈时应使用什么正确的HTTP状态码?

问题解答

一、400状态码下响应体无法输出的原因

HTTP 400本身允许携带响应体,问题出在代码实现细节上:

  • 未设置响应的Content-Type头:返回400时,ASP.NET Core可能会默认替换响应内容(比如返回内置错误页面),而200状态下框架不会干预自定义响应内容;
  • 客户端解析问题:WriteAsync默认使用UTF-8编码,但缺少Content-Type声明时,客户端可能无法正确识别响应格式,导致看起来像是没有输出。

只需在写入响应体前添加Content-Type设置即可修复:

context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.Response.ContentType = "text/plain; charset=utf-8"; // 添加该行
await context.Response.WriteAsync(
    $"One or more validation errors occurred:\n\n{string.Join("\n", validationerrors.Select(r => $"{r.Severity}: {r.Message}"))}"
).ConfigureAwait(false);

二、HTTP状态码的选择

  1. 400 Bad Request:完全适配该场景,它的语义就是「请求格式错误」,XML不符合XSD属于请求内容格式不符合预期,完全匹配400的定义;
  2. 422 Unprocessable Entity:如果需要更精准的语义,可以使用这个状态码,它专门用于「请求语法正确,但语义/数据校验失败」的场景,XML结构合法但不符合XSD规则的情况,用422也很合适。

两种状态码都允许携带响应体,可用来返回具体错误信息,帮助客户端排查问题。


内容的提问来源于stack exchange,提问作者RobIII

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 17:20:58