ASP.NET 核心 WebAPI XML 方法参数反序列化

ASP.NET Core WebAPI XML Method argument deserialization

我正在尝试在 .NET Core 3.1 上制作 WebAPI 控制器 支持 JSON 和 XML 作为 request/response 内容-类型。

控制器在接收 JSON 和 "application/json" 时完美运行,但是当它接收 XML 和 "application/xml" 时,方法参数是使用默认值创建的,而不是使用默认值创建的值已发布在请求正文中。

示例项目 - https://github.com/rincew1nd/ASPNetCore_XMLMethods

启动中的附加 XML 序列化程序:

services.AddControllers().AddXmlSerializerFormatters();

带有方法和测试模型的控制器:

    [ApiController]
    [Route("[controller]")]
    public class TestController : ControllerBase
    {
        [HttpPost, Route("v1")]
        [Consumes("application/json", "application/xml")]
        [Produces("application/json", "application/xml")]
        public TestRequest Test([FromBody] TestRequest data)
        {
            return data;
        }
    }

    [DataContract]
    public class TestRequest
    {
        [DataMember]
        public Guid TestGuid { get; set; }
        [DataMember]
        public string TestString { get; set; }
    }

P.S。项目包含用于 API 测试目的的 Swagger。

不要将 FromBody 属性用于 application/xml

When a parameter has [FromBody], Web API uses the Content-Type header to select a formatter. In this example, the content type is "application/json" and the request body is a raw JSON string (not a JSON object).

Using [FromBody]

您的 xml post 请求正文使用驼峰式大小写,导致模型绑定为空。

starup.cs中添加using Swashbuckle.AspNetCore.SwaggerGen;并尝试配置如下代码:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers().AddXmlSerializerFormatters();

        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "Neocase <-> 1C Integration", Version = "v1" });
            c.SchemaFilter<XmlSchemaFilter>();
        });

    }
public class XmlSchemaFilter : Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter
    {
        public void Apply(OpenApiSchema model, SchemaFilterContext context)
        {
            if (model.Properties == null) return;

            foreach (var entry in model.Properties)
            {
                var name = entry.Key;
                entry.Value.Xml = new OpenApiXml
                {
                    Name = name.Substring(0, 1).ToUpper() + name.Substring(1)
                };
            }
        }
    }

经过更多研究,我发现 swagger 生成了错误的 xml 示例,甚至没有注意到 类 或属性的自定义命名。

我编写了用于命名 xml 属性的自定义架构,因为它们是由 XML 属性命名的。 我遇到的唯一问题是 SchemaFilterContext 不提供枚举类型属性的描述。因此,为了命名枚举,我使用自定义属性作为 swagger 名称,并在 属性 上使用具有相同名称的 XMLElementAttribute(是的,它很垃圾但有效)。

public class XmlSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        //Try to find XmlRootAttribute on class
        var xmlroot = context.Type.GetAttributeValue((XmlRootAttribute xra) => xra);
        if (xmlroot != null)
        {
            schema.Xml = new OpenApiXml
            {
                Name = xmlroot.ElementName
            };
        }

        //Try to find XmlElementAttribute on property
        if (context.MemberInfo != null)
        {
            var xmlelement = context.MemberInfo.GetAttributeValue((XmlElementAttribute xea) => xea);
            if (xmlelement != null)
            {
                schema.Xml = new OpenApiXml
                {
                    Name = xmlelement.ElementName
                };
            }
        }

        //Try to find XmlEnumNameAttribute on enums
        if (context.Type.IsEnum)
        {
            var enumname = context.Type.GetAttributeValue((XmlEnumNameAttribute xea) => xea);
            if (enumname != null)
            {
                schema.Xml = new OpenApiXml
                {
                    Name = enumname.ElementName
                };
            }
        }
    }
}
public static class AttributeHelper
{
    public static TValue GetAttributeValue<TAttribute, TValue>(
        this Type type,
        Func<TAttribute, TValue> valueSelector)
        where TAttribute : Attribute
    {
        var att = type.GetCustomAttributes(
            typeof(TAttribute), true
        ).FirstOrDefault() as TAttribute;
        if (att != null)
        {
            return valueSelector(att);
        }
        return default(TValue);
    }
    public static TValue GetAttributeValue<TAttribute, TValue>(
        this MemberInfo mi,
        Func<TAttribute, TValue> valueSelector)
        where TAttribute : Attribute
    {
        var att = mi.GetCustomAttributes(
            typeof(TAttribute), true
        ).FirstOrDefault() as TAttribute;
        if (att != null)
        {
            return valueSelector(att);
        }
        return default(TValue);
    }
}