Как передать XML как POST в ActionResult в ASP MVC .NET

Я пытаюсь предоставить простой RESTful API для моего проекта ASP MVC. Я не буду контролировать клиентов этого API, они будут передавать XML через метод POST, который будет содержать информацию, необходимую для выполнения некоторых действий на стороне сервера, и возвращать XML с результатом действия. У меня нет проблем с отправкой обратно XML-файлов, проблема заключается в получении XML через POST. Я видел несколько примеров JSON, но, поскольку я не буду контролировать своих клиентов (с моей точки зрения, это может быть даже telnet), я не думаю, что JSON будет работать. Я прав?

Я видел примеры, когда клиенты просто создают правильный формат формы как часть тела запроса, а затем ASP анализирует сообщение, и данные доступны как FormCollection (?param1=value1¶m2=value2&,etc). Однако я хочу передать чистый XML как часть тела сообщения.

Спасибо за вашу помощь,


person Freddy    schedule 10.07.2009    source источник


Ответы (7)


@Freddy - понравился ваш подход, и вы улучшили его с помощью следующего кода для упрощения чтения потока:

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpContextBase httpContext = filterContext.HttpContext;
        if (!httpContext.IsPostNotification)
        {
            throw new InvalidOperationException("Only POST messages allowed on this resource");
        }

        Stream httpBodyStream = httpContext.Request.InputStream;
        if (httpBodyStream.Length > int.MaxValue)
        {
            throw new ArgumentException("HTTP InputStream too large.");
        }

        StreamReader reader = new StreamReader(httpBodyStream, Encoding.UTF8);
        string xmlBody = reader.ReadToEnd();
        reader.Close();

        filterContext.ActionParameters["message"] = xmlBody;

        // Sends XML Data To Model so it could be available on the ActionResult
        base.OnActionExecuting(filterContext);
    }

Затем в контроллере вы можете получить доступ к xml в виде строки:

[RestAPIAttribute]    
public ActionResult MyActionResult(string message)    
{         

}
person bowerm    schedule 05.07.2011
comment
Это параметр сообщения действия, показанного выше. - person bowerm; 24.04.2015

Этого можно добиться с помощью атрибута ActionFilterAttribute. Фильтры действий в основном пересекают запрос до или после результата действия. Поэтому я только что создал собственный атрибут фильтра действий для результата действия POST. Вот что я сделал:

public class RestAPIAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpContextBase httpContext = filterContext.HttpContext;
        if (!httpContext.IsPostNotification)
        {
            throw new InvalidOperationException("Only POST messages allowed on this resource");
        }
        Stream httpBodyStream = httpContext.Request.InputStream;

        if (httpBodyStream.Length > int.MaxValue)
        {
            throw new ArgumentException("HTTP InputStream too large.");
        }

        int streamLength = Convert.ToInt32(httpBodyStream.Length);
        byte[] byteArray = new byte[streamLength];
        const int startAt = 0;

        /*
         * Copies the stream into a byte array
         */
        httpBodyStream.Read(byteArray, startAt, streamLength);

        /*
         * Convert the byte array into a string
         */
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < streamLength; i++)
        {
            sb.Append(Convert.ToChar(byteArray[i]));
        }

        string xmlBody = sb.ToString();

        //Sends XML Data To Model so it could be available on the ActionResult

        base.OnActionExecuting(filterContext);
    }
}

Затем в методе результата действия на вашем контроллере вы должны сделать что-то вроде этого:

    [RestAPIAttribute]
    public ActionResult MyActionResult()
    {
        //Gets XML Data From Model and do whatever you want to do with it
    }

Надеюсь, это поможет кому-то еще, если вы думаете, что есть более элегантные способы сделать это, дайте мне знать.

person Freddy    schedule 13.07.2009
comment
Я не понимаю, где модель определена в этом примере. Может кто-нибудь объяснить? - person Matt Dell; 20.02.2013
comment
Преобразование массива байтов в строку действительно неэффективно... System.Text.Encoding.UTF8.GetString(byteArray) может быть лучше. - person BJury; 07.01.2014
comment
@Freddy Как получить доступ к XML из запроса? - person Demodave; 23.04.2015
comment
@Demodave, ответ Bowerm лучше иллюстрирует, как получить XML с помощью ActionParameters["message"] = xmlBody, который, похоже, отсутствует в ответе Фредди. - person OutstandingBill; 20.04.2017

Почему они не могут передать xml в виде строки в сообщении формы?

Пример:

public ActionResult SendMeXml(string xml)
{
  //Parse into a XDocument or something else if you want, and return whatever you want.
  XDocument xmlDocument = XDocument.Parse(xml);

  return View();
}

Вы можете создать сообщение формы и отправить его в одном поле формы.

person Dan Atkinson    schedule 11.07.2009
comment
В этом случае клиент(ы) должны знать, как сервер будет обрабатывать запрос. Это будет не XML, это будет форма с входным текстом, который содержит XML. Это не будет RESTful решением. - person Freddy; 13.07.2009
comment
Что ж, ваш клиент должен знать, на какой URL-адрес он собирается отправить запрос, поэтому не так уж сложно указать ему, какое поле формы использовать. - person Dan Atkinson; 16.07.2009
comment
Не должно быть никаких форм, только обычный XML. Не о том, какое поле формы использовать. - person Freddy; 16.07.2009
comment
Тогда, возможно, что-то вроде этого: aleembawany.com/2009/03/27/ Хотя похоже, что у вас уже есть что-то подобное. :) - person Dan Atkinson; 16.07.2009

Я знаю, что вы можете создать собственную фабрику поставщиков значений. Это позволит вам также проверять свои модели при их публикации, прежде чем пытаться их сохранить. У Фила Хаака есть запись в блоге о JSON-версии той же концепции. Единственная проблема в том, что я не знаю, как реализовать нечто подобное для XML.

person Justin    schedule 29.07.2010

IMO лучший способ добиться этого - написать поставщика настраиваемых значений, это фабрика, которая обрабатывает сопоставление запроса со словарем форм. Вы просто наследуете от ValueProviderFactory и обрабатываете запрос, если он имеет тип «text/xml» или «application/xml».

Больше информации:

Фил Хаак

Мой блог

MSDN

protected override void OnApplicationStarted()
{
    AreaRegistration.RegisterAllAreas();

    RegisterRoutes(RouteTable.Routes);

    ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
    ValueProviderFactories.Factories.Add(new XmlValueProviderFactory());
}

Фабрика XmlValueProviderFactory

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Web.Mvc;
using System.Xml;
using System.Xml.Linq;

public class XmlValueProviderFactory : ValueProviderFactory
{

    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        var deserializedXml = GetDeserializedXml(controllerContext);

        if (deserializedXml == null) return null;

        var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

        AddToBackingStore(backingStore, string.Empty, deserializedXml.Root);

        return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);

    }

    private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, XElement xmlDoc)
    {
        // Check the keys to see if this is an array or an object
        var uniqueElements = new List<String>();
        var totalElments = 0;
        foreach (XElement element in xmlDoc.Elements())
        {
            if (!uniqueElements.Contains(element.Name.LocalName))
                uniqueElements.Add(element.Name.LocalName);
            totalElments++;
        }

        var isArray = (uniqueElements.Count == 1 && totalElments > 1);


        // Add the elements to the backing store
        var elementCount = 0;
        foreach (XElement element in xmlDoc.Elements())
        {
            if (element.HasElements)
            {
                if (isArray)
                    AddToBackingStore(backingStore, MakeArrayKey(prefix, elementCount), element);
                else
                    AddToBackingStore(backingStore, MakePropertyKey(prefix, element.Name.LocalName), element);
            }
            else
            {
                backingStore.Add(MakePropertyKey(prefix, element.Name.LocalName), element.Value);
            }
            elementCount++;
        }
    }


    private static string MakeArrayKey(string prefix, int index)
    {
        return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
    }

    private static string MakePropertyKey(string prefix, string propertyName)
    {
        if (!string.IsNullOrEmpty(prefix))
            return prefix + "." + propertyName;
        return propertyName;
    }

    private XDocument GetDeserializedXml(ControllerContext controllerContext)
    {
        var contentType = controllerContext.HttpContext.Request.ContentType;
        if (!contentType.StartsWith("text/xml", StringComparison.OrdinalIgnoreCase) &&
            !contentType.StartsWith("application/xml", StringComparison.OrdinalIgnoreCase))
            return null;

        XDocument xml;
        try
        {
            var xmlReader = new XmlTextReader(controllerContext.HttpContext.Request.InputStream);
            xml = XDocument.Load(xmlReader);
        }
        catch (Exception)
        {
            return null;
        }

        if (xml.FirstNode == null)//no xml.
            return null;

        return xml;
    }
}
person Aaron    schedule 23.01.2012

Мне нравится ответ от @Freddy и улучшение от @Bowerm. Он лаконичен и сохраняет формат действий на основе форм.

Но проверка IsPostNotification не будет работать в рабочем коде. Он не проверяет HTTP-глагол, как, по-видимому, подразумевает сообщение об ошибке, и удаляется из контекста HTTP, когда для флага отладки компиляции установлено значение false. Это объясняется здесь: HttpContext.IsPostNotification имеет значение false, когда отладка компиляции ложь

Я надеюсь, что это сэкономит кому-то 1/2 дня отладки маршрутов из-за этой проблемы. Вот решение без этой проверки:

public class XmlApiAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpContextBase httpContext = filterContext.HttpContext;
        // Note: for release code IsPostNotification stripped away, so don't check it!
        // https://stackoverflow.com/questions/28877619/httpcontext-ispostnotification-is-false-when-compilation-debug-is-false            

        Stream httpBodyStream = httpContext.Request.InputStream;
        if (httpBodyStream.Length > int.MaxValue)
        {
            throw new ArgumentException("HTTP InputStream too large.");
        }

        StreamReader reader = new StreamReader(httpBodyStream, Encoding.UTF8);
        string xmlBody = reader.ReadToEnd();
        reader.Close();

        filterContext.ActionParameters["xmlDoc"] = xmlBody;

        // Sends XML Data To Model so it could be available on the ActionResult
        base.OnActionExecuting(filterContext);
    }
}
...
public class MyXmlController 
{ ...
    [XmlApiAttribute]
    public JsonResult PostXml(string xmlDoc)
    {
...
person Aaron Newman    schedule 19.01.2016

Хороший!,

Какой объект я получил в своем методе контроллера для управления Xml?

Я использую этот способ:

В actionFilter я заполняю модель:

        .
        .

        string xmlBody = sb.ToString();

        filterContext.Controller.ViewData.Model = xmlBody;

И в моем методе контроллера я получаю модель как:

        string xmlUserResult = ViewData.Model as string;

        XmlSerializer ser = new XmlSerializer(typeof(UserDTO));
        StringReader stringReader = new StringReader(xmlUserResult);
        XmlTextReader xmlReader = new XmlTextReader(stringReader);
        UserDTO userToUpdate = ser.Deserialize(xmlReader) as UserDTO;
        xmlReader.Close();
        stringReader.Close();

Это правильная реализация?

Спасибо.

person Community    schedule 12.08.2009