Domanda

Sto cercando di fornire una semplice API RESTful al mio progetto ASP MVC. Non voglio avere il controllo dei clienti di questa API, saranno passando un XML tramite un metodo POST che conterrà le informazioni necessarie per eseguire alcune azioni sul lato server e fornire di nuovo un XML con il risultato dell'azione. Non ho problemi di restituzione XMLs, il problema sta ricevendo XML tramite un post. Ho visto alcuni esempi JSON, ma dal momento che non voglio controllare i miei clienti (che potrebbe essere anche un telnet dal mio punto di vista) non credo che funzionerà JSON. Ho ragione?

Ho esempi visti in cui i clienti semplicemente costruiscono il formato di forma corretta, come parte del corpo della richiesta e quindi l'ASP analizzare il messaggio, e dei dati è disponibile come FormCollection (? Param1 = value1 & param2 = valore2 &, ecc). Tuttavia, voglio passare puro XML come parte del corpo del messaggio.

Grazie per il vostro aiuto,

È stato utile?

Soluzione 2

Ciò potrebbe essere realizzato tramite ActionFilterAttribute. Azione filtri fondamentalmente interseca la domanda prima o dopo il risultato di azione. Così ho appena costruito un attributo di filtro un'azione personalizzata per POST Azione Risultato. Ecco quello che ho fatto:

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

Poi sul metodo seguito all'azione sul controller si dovrebbe fare qualcosa di simile:

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

Spero che questo aiuti qualcun altro, se pensate che ci sono più eleganti modi per farlo, fatemi sapere.

Altri suggerimenti

@Freddy - è piaciuto il suo approccio e migliorato su di esso con il seguente codice per semplificare la lettura flusso:

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

Poi nel controller è possibile accedere al XML come una stringa:

[RestAPIAttribute]    
public ActionResult MyActionResult(string message)    
{         

}

So che è possibile creare una fabbrica di fornitore di valore personalizzato. Questo vi permetterà di convalidare anche i vostri modelli qualora siano fornite prima di tentare di salvarli. Phil Haack ha un post sul blog su una versione JSON di questo stesso concetto. L'unico problema è che non so come implementare uno stesso genere di cose per XML.

IMO il modo migliore per farlo è quello di scrivere un provider di valore personalizzato, questa è una fabbrica che gestisce la mappatura della richiesta al dizionario forme. Basta eredita dalla ValueProviderFactory e gestire la richiesta, se è di tipo “text / xml” o “application / xml”.

Ulteriori informazioni:

Phil Haack

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

perché non possono passare il xml come stringa nel post sotto forma?

Esempio:

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

Si potrebbe creare un post modulo e inviarlo in un singolo campo modulo.

Mi piace la risposta da @Freddy e miglioramento dal @Bowerm. E 'conciso e conserva il formato di azioni basate su form.

Ma l'assegno IsPostNotification non funziona nel codice di produzione. Esso non controlla il verbo HTTP come il messaggio di errore sembra implicare, e si è spogliato fuori contesto HTTP quando il flag compilation debug è impostata su false. Questo è spiegato qui: HttpContext.IsPostNotification è falso quando compilation debug è falsa

Spero che questo consente di risparmiare qualcuno un 1/2 giornata di percorsi di debug a causa di questo problema. Ecco la soluzione, senza che il 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)
    {
...

Nizza!

Quale oggetto ho ottenuto nel mio metodo di controllo per manipolare la Xml?

sto usando in questo modo:

L'ActionFilter, io popolo il modello con:

        .
        .

        string xmlBody = sb.ToString();

        filterContext.Controller.ViewData.Model = xmlBody;

E sul mio metodo di controllo, ottengo il modello come:

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

Si tratta di una corretta attuazione?

Grazie.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top