C# 如何从继承的FromBody模型中获取正确的类型?

C# 如何从继承的FromBody模型中获取正确的类型?,c#,asp.net-core,.net-core,C#,Asp.net Core,.net Core,正文中有几个不同的XML收入。所有的XML几乎都是相同的,所以我首先添加了一个基类,其他的XML继承自基类 模型如下: [XmlInclude(typeof(TextMsg))] [XmlRoot("xml")] public class BaseClass { public string ToUserName { get; set; } public string FromUserName { get; set; } public string Crea

正文中有几个不同的XML收入。所有的XML几乎都是相同的,所以我首先添加了一个基类,其他的XML继承自基类

模型如下:

[XmlInclude(typeof(TextMsg))]
[XmlRoot("xml")]        
public class BaseClass
{
    public string ToUserName { get; set; }
    public string FromUserName { get; set; }
    public string CreateTime { get; set; }
    public string MsgType { get; set; }            
}        
[XmlRoot("xml")]        
public class TextMsg : BaseClass
{
    public TextMsg()
    {
        MsgType = "text";
    }
    public string Content { get; set; }
    public string MsgId { get; set; }
}
还有几个类继承自基类模型,现在我只在这里展示一个

方法如下:

[HttpPost]
[Produces("application/xml")]
public async Task<IActionResult> mp([FromBody]BaseClass XmlData)
{
    BaseClass ReturnXmlData = null;
    var a = XmlData.GetType();            
    return Ok(ReturnXmlData);
}  

您必须创建自定义modelbinder,这超出了堆栈溢出的范围。使用默认的modelbinder,参数的类型将被实例化,主体中任何不适合该类型的内容都将被丢弃。因此,您最终将得到一个
基类的实例,无法将其转换为任何更具体的派生类型


唯一的现成选项是直接绑定到所需的类类型(即派生类型,而不是基类型),并为每个派生类型指定备用端点。或者,您可以创建一个超类,该超类包含所有派生类型的所有属性,以便绑定到,然后根据填充的属性,您可以手动映射到相应的派生类。

您可以根据其属性编写自己的
XmlSerializerInputFormatter
,例如:

[Obsolete]
public class XmlCreationConverter: XmlSerializerInputFormatter
{    
    public override async Task<InputFormatterResult> ReadRequestBodyAsync(
       InputFormatterContext context,
       Encoding encoding)
    {
        XElement xml = null;
        Type type = typeof(BaseClass);
        var request = context.HttpContext.Request;

        context.HttpContext.Request.EnableRewind();
        using (StreamReader reader = new StreamReader(request.Body))
        {
            reader.BaseStream.Seek(0, SeekOrigin.Begin);
            string text = reader.ReadToEnd();
            request.Body.Position = 0; // rewind
            xml = XElement.Parse(text);

            //your logic to return the right type
            if (xml.Element("MsgType").Value == "text")
            {
                type = typeof(TextMsg);
            }
            else if(...)
            {
                type = ...
            }
            else
            {
            }

            using (XmlReader xmlReader = CreateXmlReader(request.Body, encoding))
            {
                var serializer = GetCachedSerializer(type);
                var deserializedObject = serializer.Deserialize(xmlReader);
                return InputFormatterResult.Success(deserializedObject);
            }
        }
    }

}
行动:

[HttpPost]
[Produces("application/xml")]
public async Task<IActionResult> mp([FromBody]BaseClass XmlData)

可以为此创建自定义模型绑定器

public class XmlBinder : IModelBinder
{
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        try
        {
            var memoryStream = new MemoryStream();
            await bindingContext.HttpContext.Request.Body.CopyToAsync(memoryStream);
            memoryStream.Seek(0, SeekOrigin.Begin);
            XElement root = XElement.Load(memoryStream);
            memoryStream.Seek(0, SeekOrigin.Begin);

            string messageType = root.Element("MsgType").Value;
            Type xmlType = GetTypeFromMessage(messageType);
            var serializer = new XmlSerializer(xmlType);
            var model = serializer.Deserialize(memoryStream);

            bindingContext.Result = ModelBindingResult.Success(model);
        }
        catch
        {
            bindingContext.Result = ModelBindingResult.Failed();
        }
    }

    private Type GetTypeFromMessage(string msgType)
    {
        switch (msgType)
        {
            case "text":
                return typeof(TextMsg);
            case "image":
                return typeof(ImageMsg);
            //... other cases
            default:
                return typeof(BaseClass);
        }
    }
}
将此型号活页夹应用于
BaseClass

[ModelBinder(typeof(XmlBinder))]
public class BaseXml

传入的xml是否包含一些告诉我们类型的属性?或者我们只能通过xml中的属性计数/名称来理解类型是什么?请添加一些可能传入xml的示例好吗?@Alexander例如:123 456 1348831860文本这是一个测试1234567890123456@Alexander这里是另一个示例:123 456 1348831860图像这是一个url媒体id 1234567890123456有人告诉我使用多态模型绑定。正如你所看到的,我的模型非常简单,我认为我还不应该使用这些如此复杂的代码。这是一个简单的方法。创建自己的modelbinder来处理这一问题是很复杂的。最后一种方法是创建一个包含所有属性的超类。但是,我怀疑它是否会降低API的性能。我按照您的代码创建了一个名为XmlCreationConverter的新.cs文件,并添加了完整的代码。同时,VisualStudio报告了两个错误。一个是关于XmlCreationConverter类的“没有与所需的形式参数'options'相对应的参数'options'“'HttpRequest'不包含'EnableRewind'的定义,并且在“about”context.HttpContext.Request.EnableRewind()中找不到接受'HttpRequest'类型的第一个参数的可访问扩展方法'EnableRewind';“。我在谷歌上搜索过,有人说我应该安装一个Microsoft.AspNetCore.Http NuGet包来解决这个问题。我尝试过,但没有效果。很多人让我创建一个自定义modelbinder。我读过很多关于它的文章,但仍然很难了解它。看起来你的方式比自定义modelbinder的方式简单得多,也不同。你愿意吗?”请告诉我这两种方式的区别。谢谢。此外,我使用的.net core版本是.net core 3。0@102425074你需要有一个使用Microsoft.AspNetCore.Http.Internal的参考
。我做的也是自定义绑定。哦,我在asp.net core 2.2中测试了它。唯一的缺点是matter将应用于所有可能与基类无关的xml请求。这不仅是模型绑定的最佳演示,也是我见过的最简单的方式!我非常喜欢它!我更喜欢这种方式,因为它只应用于一个类,而不是所有xml请求。正如我们所知,XM有很多意想不到的类型L未来项目的收入。这种方式更灵活。
public class XmlCreationConverter : XmlSerializerInputFormatter
{
    private readonly MvcOptions _options;
    public XmlCreationConverter(MvcOptions options) : base(options)

    {
        _options = options;
    }
    public override async Task<InputFormatterResult> ReadRequestBodyAsync(
       InputFormatterContext context,
       Encoding encoding)
    {
        XElement xml = null;
        Type type = typeof(BaseClass);
        var request = context.HttpContext.Request;

        context.HttpContext.Request.EnableBuffering();

       //for model type is Baseclass
       if (context.ModelType == typeof(BaseClass))
        {
            using (StreamReader reader = new StreamReader(request.Body))
            {
                var text = await reader.ReadToEndAsync();
                request.Body.Position = 0;
                xml = XElement.Parse(text);
                if (xml.Element("MsgType").Value == "text")
                {
                    type = typeof(TextMsg);
                }

                using (XmlReader xmlReader = CreateXmlReader(request.Body, encoding))
                {
                    var serializer = GetCachedSerializer(type);
                    var deserializedObject = serializer.Deserialize(xmlReader);
                    return InputFormatterResult.Success(deserializedObject);
                }
            }
        }
        else if(context.ModelType == ...)
        else
        {
            using (StreamReader reader = new StreamReader(request.Body))
            {
                var text = await reader.ReadToEndAsync();
                request.Body.Position = 0;
                using (var xmlReader = CreateXmlReader(request.Body, encoding))
                {
                    var modelType = GetSerializableType(context.ModelType);

                    var serializer = GetCachedSerializer(modelType);

                    var deserializedObject = serializer.Deserialize(xmlReader);

                    // Unwrap only if the original type was wrapped.
                    if (type != context.ModelType)
                    {
                        if (deserializedObject is IUnwrappable unwrappable)
                        {
                            deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType);
                        }
                    }

                    return InputFormatterResult.Success(deserializedObject);
                }
            }
        }

    }
}
services.AddMvc(options => options.InputFormatters.Add(new XmlCreationConverter(options)))
                .AddXmlSerializerFormatters();
public class XmlBinder : IModelBinder
{
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        try
        {
            var memoryStream = new MemoryStream();
            await bindingContext.HttpContext.Request.Body.CopyToAsync(memoryStream);
            memoryStream.Seek(0, SeekOrigin.Begin);
            XElement root = XElement.Load(memoryStream);
            memoryStream.Seek(0, SeekOrigin.Begin);

            string messageType = root.Element("MsgType").Value;
            Type xmlType = GetTypeFromMessage(messageType);
            var serializer = new XmlSerializer(xmlType);
            var model = serializer.Deserialize(memoryStream);

            bindingContext.Result = ModelBindingResult.Success(model);
        }
        catch
        {
            bindingContext.Result = ModelBindingResult.Failed();
        }
    }

    private Type GetTypeFromMessage(string msgType)
    {
        switch (msgType)
        {
            case "text":
                return typeof(TextMsg);
            case "image":
                return typeof(ImageMsg);
            //... other cases
            default:
                return typeof(BaseClass);
        }
    }
}
[ModelBinder(typeof(XmlBinder))]
public class BaseXml