Pergunta

Eu estou tentando fornecer uma API RESTful simples para o meu projeto ASP MVC. Eu não terá o controle dos clientes deste API, eles estarão passando um XML através de um método POST que irá conter as informações necessárias para executar algumas ações no lado do servidor e fornecer de volta um XML com o resultado da ação. Eu não tenho problemas para enviar XMLs de volta, o problema está recebendo XML através de um POST. Tenho visto alguns exemplos JSON, mas desde que eu não vou controlar meus clientes (que poderia ser mesmo um telnet do meu ponto de vista) Eu não acho JSON vai funcionar. Estou correcto?

Eu vi exemplos onde os clientes simplesmente construir o formato de forma correta como parte do corpo da solicitação e, em seguida, o ASP analisar a mensagem e os dados está disponível como FormCollection (? Param1 = valor1 & param2 = valor2 &, etc). No entanto, eu quero passar XML puro como parte do corpo da mensagem.

obrigado pela sua ajuda,

Foi útil?

Solução 2

Isto pode ser feito usando o ActionFilterAttribute. Ação Filtros basicamente intercepta o pedido antes ou depois do resultado de ação. Então, eu só construiu um atributo de filtro de ação personalizada para Ação Resultado POST. Aqui está o que eu fiz:

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

Em seguida, no método de resultado de ação em seu controlador você deve fazer algo como isto:

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

Espero que isso ajude alguém, se você acha que são mais elegantes maneiras de fazer isso, deixe-me saber.

Outras dicas

@Freddy - gostava de sua abordagem e melhorada nele com o seguinte código para a leitura fluxo de simplificar:

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

Em seguida, no controlador, é possível acessar o XML como uma string:

[RestAPIAttribute]    
public ActionResult MyActionResult(string message)    
{         

}

Eu sei que você pode criar uma fábrica de costume provedor de valor. Isso permitirá que você também validar seus modelos quando eles são colocados antes de tentar salvá-los. Phil Haack tem um post sobre uma versão JSON deste mesmo conceito. O único problema é que eu não sei como implementar um este mesmo tipo de coisa para XML.

IMO a melhor maneira de alcançar este objetivo é escrever um provedor de valor personalizado, esta é uma fábrica que processa o mapeamento do pedido às formas dicionário. Você acabou de herdar de ValueProviderFactory e lidar com o pedido se ele é do tipo “text / xml” ou “application / xml.”

Mais informações:

Phil Haack

Meu blog

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

Por que eles não passar o XML como uma string no pós forma?

Exemplo:

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

Você pode criar um post de formulário e enviá-lo em um único campo de formulário.

Eu gosto da resposta do @Freddy e melhoria da @Bowerm. É conciso e preserva o formato das ações com base em formulários.

Mas o cheque IsPostNotification não vai funcionar no código de produção. Ele não verifica o HTTP verbo como a mensagem de erro parece implicar, e é retirado de HTTP contexto quando o sinalizador de compilação de depuração está definido como falso. Isto é explicado aqui: HttpContext.IsPostNotification é falsa quando compilação de depuração é falsa

Espero que esta salva alguém um 1/2 dia de depuração rotas devido a este problema. Aqui está a solução sem que o check:

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)
    {
...

Nice!,

O objeto que eu tenho no meu método de controle para manipular o XML?

Estou usando este caminho:

Em ActionFilter, eu preencher o modelo com:

        .
        .

        string xmlBody = sb.ToString();

        filterContext.Controller.ViewData.Model = xmlBody;

E no meu método de controlador, recebo o modelo como:

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

Isso é uma implementação correcta?

Graças.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top