5

POSTリクエストで送信されたXMLデータが特定のカスタムXMLスキーマを満たしているかどうかを検証するためのソリューションを見つけようとしています。

ASP.NET Web APIで提供されているものを使用する場合XmlMediaTypeFormatter、私が見る限り、利用可能なスキーマ検証がありません。例:モデルタイプがある場合...

public class Order
{
    public string Code { get; set; }
    public int Quantity { get; set; }
}

ApiController...および...のPOSTアクション

public HttpResponseMessage Post(Order order)
{
    if (ModelState.IsValid)
    {
        // process order...
        // send 200 OK response for example
    }
    else
        // send 400 BadRequest response with ModelState errors in response body
}

...次の「間違った」XMLデータを投稿できますが、それでも200OKの応答が返されます。

User-Agent: Fiddler
Host: localhost:45678
Content-Type: application/xml; charset=utf-8

<Order> <Code>12345</Nonsense> </Order>   // malformed XML

または:

<Order> <CustomerName>12345</CustomerName> </Order>    // invalid property

または:

<Customer> <Code>12345</Code> </Customer>    // invalid root

または:

"Hello World"    // no XML at all

などなど。

リクエストの検証を行う唯一のポイントはモデルバインディングです。リクエストの例1、3、4では、メソッドにorder渡されるのは、例2では、​​プロパティをテストするか、プロパティにマークを付けることで無効にできるプロパティです。属性。この検証結果を、400の「BadRequest」Httpステータスコードと応答本文の検証メッセージとともに応答に送り返すことができます。しかし、何が間違っているのか正確にはわかりません。たとえば、例1、3、4の間違ったXMLを区別することはできません(投稿されていません。私が見ることができるのはそれだけです)。Postnullorder.Codenullorder == nullCode[Required]order

Orderたとえば、を特定のカスタムXMLスキーマで投稿する必要があるxmlns="http://test.org/OrderSchema.xsd"場合、投稿されたXMLがこのスキーマに関して有効かどうかを検証し、有効でない場合は、スキーマ検証エラーを応答で送り返します。これを達成するために、私はカスタムから始めましたMediaTypeFormatter

public class MyXmlMediaTypeFormatter : MediaTypeFormatter
{
    // constructor, CanReadType, CanWriteType, ...

    public override Task<object> ReadFromStreamAsync(Type type, Stream stream,
        HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
    {
        var task = Task.Factory.StartNew(() =>
        {
            using (var streamReader = new StreamReader(stream))
            {
                XDocument document = XDocument.Load(streamReader);
                // TODO: exceptions must the catched here,
                // for example due to malformed XML
                XmlSchemaSet schemaSet = new XmlSchemaSet();
                schemaSet.Add(null, "OrderSchema.xsd");

                var msgs = new List<string>();
                document.Validate(schemaSet, (s, e) => msgs.Add(e.Message));
                // msgs contains now the list of XML schema validation errors
                // I want to send back in the response
                if (msgs.Count == 0)
                {
                    var order = ... // deserialize XML to order
                    return (object)order;
                }
                else
                    // WHAT NOW ?
            }
        });
        return task;
    }
}

これは、すべてが正しい限り機能します。

しかし、私はどうしたらいいのかわかりませんmsgs.Count > 0。この検証結果リストをPostアクションに「転送」するにはどうすればよいですか、またはそれらのXMLスキーマ検証メッセージを含むHttp応答を作成するにはどうすればよいですか?

また、カスタムMediaTypeFormatterがそのようなXMLスキーマ検証の最良の拡張ポイントであるかどうか、そして私のアプローチが間違った方法ではないかどうかもわかりません。おそらくカスタムHttpMessageHandler/DelegatingHandlerこれのためのより良い場所でしょうか?それとも、箱から出してもっと簡単なものがあるのでしょうか?

4

2 に答える 2

6

これを行う場合、フォーマッターは使用しません。フォーマッタの主な目的は、ワイヤ表現をCLRタイプに変換することです。ここに、まったく別のタスクであるスキーマに対して検証するXMLドキュメントがあります。

検証を行うために、新しいMessageHandlerを作成することをお勧めします。DelegatingHandlerから派生し、コンテンツタイプがapplication/xmlXDocumentにコンテンツをロードして検証する場合。失敗した場合は、HttpResponseExceptionをスローします。

MessageHandlerをConfiguration.MessageHandlersコレクションに追加するだけで、設定が完了します。

派生したXmlMediaTypeFormatterを使用する際の問題は、ObjectContentコード内に埋め込まれたある時点で実行していることであり、正常に終了するのは難しい可能性があります。また、XmlMediaTypeFormatterをさらに複雑にすることは、おそらく良い考えではありません。

私はMessageHandlerを作成するのに苦労しました。私は実際にこのコードを実行しようとしなかったので、購入者は注意してください。また、発信者をブロックしないようにすると、タスクはかなり厄介になります。たぶん誰かが私のためにそのコードをクリーンアップするでしょう、とにかくここにあります。

  public class SchemaValidationMessageHandler : DelegatingHandler {

        private XmlSchemaSet _schemaSet;
        public SchemaValidationMessageHandler() {

            _schemaSet = new XmlSchemaSet();
            _schemaSet.Add(null, "OrderSchema.xsd");
        }

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {

            if (request.Content != null && request.Content.Headers.ContentType.MediaType == "application/xml")
            {
                var tcs = new TaskCompletionSource<HttpResponseMessage>();

                var task =  request.Content.LoadIntoBufferAsync()  // I think this is needed so XmlMediaTypeFormatter will still have access to the content
                    .ContinueWith(t => {
                                      request.Content.ReadAsStreamAsync()
                                          .ContinueWith(t2 => {
                                                            var doc = XDocument.Load(t2.Result);
                                                            var msgs = new List<string>();
                                                            doc.Validate(_schemaSet, (s, e) => msgs.Add(e.Message));
                                                            if (msgs.Count > 0) {
                                                                var responseContent = new StringContent(String.Join(Environment.NewLine, msgs.ToArray()));
                                                                 tcs.TrySetException(new HttpResponseException(
                                                                    new HttpResponseMessage(HttpStatusCode.BadRequest) {
                                                                        Content = responseContent
                                                                    }));
                                                            } else {
                                                                tcs.TrySetResult(base.SendAsync(request, cancellationToken).Result);
                                                            }
                                                        });

                                  });
                return tcs.Task;
            } else {
                return base.SendAsync(request, cancellationToken);
            }

        }
于 2012-08-06T01:53:01.960 に答える
0

試行錯誤によって、私は解決策を見つけました(WHAT NOW ?質問のコードのプレースホルダー用):

//...
else
{
    PostOrderErrors errors = new PostOrderErrors
    {
        XmlValidationErrors = msgs
    };
    HttpResponseMessage response = new HttpResponseMessage(
        HttpStatusCode.BadRequest);
    response.Content = new ObjectContent(typeof(PostOrderErrors), errors,
        GlobalConfiguration.Configuration.Formatters.XmlFormatter);
    throw new HttpResponseException(response);
}

...次のような応答クラスを使用します。

public class PostOrderErrors
{
    public List<string> XmlValidationErrors { get; set; }
    //...
}

それはうまくいくようで、応答は次のようになります。

HTTP/1.1 400 Bad Request
Content-Type: application/xml; charset=utf-8
<PostOrderErrors xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <XmlValidationErrors>
        <string>Some error text...</string>
        <string>Another error text...</string>
    </XmlValidationErrors>
</PostOrderErrors>
于 2012-08-06T10:43:57.720 に答える