Как я могу получить правильный тип из модели FromBody, которая наследуется? - PullRequest
2 голосов
/ 23 сентября 2019

Есть несколько разных 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>

Ответы [ 3 ]

2 голосов
/ 24 сентября 2019

Вы можете написать свой собственный 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();
2 голосов
/ 24 сентября 2019

Вы можете создать пользовательское связующее для модели

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
0 голосов
/ 23 сентября 2019

Вам необходимо создать пользовательское связующее для моделей, которое выходит за рамки переполнения стека.При использовании связующего устройства по умолчанию тип параметра будет создан, и все элементы тела, не соответствующие этому типу, будут просто отброшены.В результате вы получите буквально экземпляр BaseClass без возможности приведения его к какому-либо более конкретному производному типу.

Единственный готовый вариант - это буквальная привязка кТип класса, который вы хотите (т.е. производный тип, а не базовый тип) и иметь альтернативные конечные точки для каждого производного типизированного.Кроме того, вы можете создать суперкласс, который охватывает все свойства всех различных производных типов для привязки, а затем, в зависимости от того, какие свойства заполнены, вы можете вручную отобразить соответствующий производный класс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...