11

ASP MVC プロジェクトに単純な RESTful API を提供しようとしています。この API のクライアントを制御することはできません。クライアントは、サーバー側でいくつかのアクションを実行するために必要な情報を含む POST メソッドを介して XML を渡し、アクションの結果を XML で返します。XML の返送に問題はありません。問題は、POST 経由で XML を受信することです。JSON の例をいくつか見てきましたが、クライアントを制御しないので (私の観点からは telnet でさえある可能性があります)、JSON が機能するとは思いません。私は正しいですか?

クライアントが要求の本文の一部として正しいフォーム形式を単純に構築し、ASP がメッセージを解析し、データが FormCollection (?param1=value1¶m2=value2& など) として利用できる例を見てきました。ただし、純粋な XML をメッセージ本文の一部として渡したいと考えています。

ご協力いただきありがとうございます、

4

7 に答える 7

10

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

}
于 2011-07-05T09:07:42.250 に答える
8

これは、ActionFilterAttribute を使用して実現できます。アクション フィルタは、基本的にアクション結果の前または後にリクエストと交差します。そこで、POST Action Result のカスタム アクション フィルター属性を作成しました。これが私がしたことです:

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
    }

これが他の誰かに役立つことを願っています。もっとエレガントな方法があると思われる場合は、お知らせください。

于 2009-07-13T20:38:19.713 に答える
3

これを実現するための IMO の最善の方法は、カスタム値プロバイダーを作成することです。これは、フォーム ディクショナリへのリクエストのマッピングを処理するファクトリです。ValueProviderFactory から継承し、タイプが「text/xml」または「application/xml」の場合はリクエストを処理するだけです。</p>

より詳しい情報:

フィル・ハーク

私のブログ

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;
    }
}
于 2012-01-23T19:09:00.607 に答える
3

カスタム値プロバイダー ファクトリを作成できることは知っています。これにより、モデルを保存する前に、投稿時にモデルを検証することもできます。Phil Haackは、これと同じ概念の JSON バージョンに関するブログ投稿を行っています。唯一の問題は、これと同じ種類のものを XML に実装する方法がわからないことです。

于 2010-07-29T20:29:57.673 に答える
2

@Freddy からの回答と @Bowerm からの改善が気に入っています。これは簡潔で、フォームベースのアクションの形式を保持しています。

ただし、IsPostNotification チェックは製品コードでは機能しません。エラー メッセージが示すように HTTP 動詞はチェックされず、コンパイル デバッグ フラグが false に設定されている場合は HTTP コンテキストから取り除かれます。これはここで説明されています: HttpContext.IsPostNotification is false when Compilation debug is 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)
    {
...
于 2016-01-19T16:47:41.753 に答える
1

良い!、

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

これは正しい実装ですか?

ありがとう。

于 2009-08-12T21:04:37.613 に答える