【问题标题】:How can I get the right type from a FromBody model which is inherits?如何从继承的 FromBody 模型中获得正确的类型?
【发布时间】:2019-09-23 13:21:10
【问题描述】:

body 的帖子有几种不同的 XML 传入。所有的 XMLS 几乎都是一样的,所以我先添加一个基类,其他 XMLS 继承自基类。

这是模型:

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

远程服务器将向我的服务器发布一个包含 XML 的请求。现在传入变量XmlData 只获取基类的值和类型。

我需要得到真正的值和类型,然后通过传入的类型和值返回不同的XML。

我该如何解决这个问题?谢谢。


这是传入的 XML 之一,匹配上面的 TextMsg 模型:

<xml> 
<ToUserName>123</ToUserName>
<FromUserName>456</FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType>text</MsgType>
<Content>this is a test</Content>
<MsgId>1234567890123456</MsgId>
</xml>

【问题讨论】:

  • 传入的 xml 是否包含一些告诉我们类型的属性?或者我们可以仅通过 xml 中的属性计数/名称来了解类型是什么?能否请添加一些可能的传入 xml 示例?
  • @Alexander 例如:1234561348831860text这是一个测试1234567890123456
  • @Alexander 这是另一个例子:1234561348831860image这是一个网址media_id1234567890123456

标签: c# asp.net-core .net-core


【解决方案1】:

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

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

【讨论】:

  • this solution 唯一的缺点是自定义输入格式化程序将应用于每个可能与 bo BaseClass 根本不相关的 xml 请求。
  • 这不仅是模型绑定的最佳演示,而且是我见过的最简单的方法!我非常喜欢它!
  • 我更喜欢这种方式,因为它只适用于一个类而不是每个 xml 请求。众所周知,未来项目会有很多意想不到的 XML 传入。这种方式更灵活。
【解决方案2】:

你可以根据source code写出你自己的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);
            }
        }
    }

}

Startup.cs:

services.AddMvc(options=> options.InputFormatters.Add(new XmlCreationConverter()))
                .AddXmlSerializerFormatters()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

行动:

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

更新: 对于 asp.net core 3.0,将代码更改为以下:

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

    }
}

Startup.cs

services.AddMvc(options => options.InputFormatters.Add(new XmlCreationConverter(options)))
                .AddXmlSerializerFormatters();

【讨论】:

  • 我按照您的代码创建了一个名为 XmlCreationConverter 的新 .cs 文件并添加了整个代码。同时,Visual Studio 报告了两个错误。一个是关于XmlCreationConverter类的“没有给出与'XmlSerializerInputFormatter.XmlSerializerInputFormatter(MVCOptions)的所需形式参数'options'相对应的参数”。
  • 另一个是“'HttpRequest'不包含'EnableRewind'的定义,并且找不到接受'HttpRequest'类型的第一个参数的可访问扩展方法'EnableRewind'”关于“context.HttpContext .Request.EnableRewind();"。我在google上搜索,有人说我应该安装一个Microsoft.AspNetCore.Http NuGet包来解决它。我试过了,但没有用。
  • 很多人让我创建一个自定义模型绑定器。我已经阅读了很多关于它的文章,但仍然很难了解它。看来您的方式比自定义模型绑定器方式更容易且不同。请你告诉我这两种方式有什么区别。谢谢。
  • 另外,我使用的.net core版本是.net core 3.0
  • @102425074 你需要有一个引用using Microsoft.AspNetCore.Http.Internal。我所做的也是input formatter的自定义绑定。哦,我在asp.net core 2.2中测试它
【解决方案3】:

您必须创建一个自定义模型绑定器,这超出了 Stack Overflow 的范围。使用默认的模型绑定器,参数的类型将被实例化,并且正文中不适合该类型的任何内容都将被丢弃。因此,您最终会得到一个 BaseClass 的实例,无法将其转换为任何更具体的派生类型。

唯一的开箱即用选项是逐字绑定到您想要的类类型(即派生类型,而不是基类型),并为每个派生类型提供备用端点。或者,您可以创建一个包含所有各种派生类型的所有属性以便绑定的超类,然后根据填充的属性,您可以手动映射到适当的派生类。

【讨论】:

  • 有人告诉我使用多态模型绑定。如您所见,我的模型是如此简单,我认为我不应该使用这些如此复杂的代码。
  • 这是最简单的方法。创建自己的模型绑定器来处理这个问题会很复杂。
  • 创建包含所有属性的超类的最后一种方法很简单。但是,我怀疑它是否会降低 API 的性能。
猜你喜欢
  • 1970-01-01
  • 2019-05-25
  • 1970-01-01
  • 1970-01-01
  • 2011-12-27
  • 2017-05-26
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多