15

私はかなり標準的な .net MVC 4 Web API アプリケーションを持っています。

 public class LogsController : ApiController
{

    public HttpResponseMessage PostLog(List<LogDto> logs)
    {
        if (logs != null && logs.Any())
        {
            var goodLogs = new List<Log>();
            var badLogs = new List<LogBad>();

            foreach (var logDto in logs)
            {
                if (logDto.IsValid())
                {
                    goodLogs.Add(logDto.ToLog());
                }
                else
                {
                    badLogs.Add(logDto.ToLogBad());
                }
            }

            if (goodLogs.Any())
            {
                _logsRepo.Save(goodLogs);
            }

            if(badLogs.Any())
            {
                _logsBadRepo.Save(badLogs);
            }


        }
        return new HttpResponseMessage(HttpStatusCode.OK);
    }
}

これはすべて正常に機能します。ログを送信できるデバイスがあり、正常に機能します。しかし、転送されるデータのサイズに懸念が生じ始めているため、GZIP を使用して圧縮された投稿の受け入れを検討したいと考えていますか?

どうすればこれを行うことができますか?IIS での設定ですか、それともアクション フィルターを使用できますか?

編集1

Filip の回答に続いて、私の考えでは、リクエストがコントローラーに到達する前に、リクエストの処理を傍受する必要があるということです。Web API フレームワークがリクエストの本文をビジネス オブジェクトに解析しようとする前にリクエストをキャッチできた場合、リクエストの本文がまだ圧縮されているために失敗します。次に、リクエストの本文を解凍し、リクエストを処理チェーンに戻すことができます。うまくいけば、Web Api フレームワークが (解凍された) 本文をビジネス オブジェクトに解析できるようになります。

DelagatingHandler を使用する方法のようです。処理中にリクエストにアクセスできますが、コントローラーの前です。だから私は次のことを試しましたか?

 public class gZipHandler : DelegatingHandler
{

    protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        string encodingType = request.Headers.AcceptEncoding.First().Value;

        request.Content = new DeCompressedContent(request.Content, encodingType);

        return base.SendAsync(request, cancellationToken);
    }
}

public class DeCompressedContent : HttpContent
{
    private HttpContent originalContent;
    private string encodingType;

    public DeCompressedContent(HttpContent content, string encodType)
    {
        originalContent = content;
        encodingType = encodType;
    }

    protected override bool TryComputeLength(out long length)
    {
        length = -1;

        return false;
    }


    protected override Task<Stream> CreateContentReadStreamAsync()
    {
        return base.CreateContentReadStreamAsync();
    }

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        Stream compressedStream = null;

        if (encodingType == "gzip")
        {
            compressedStream = new GZipStream(stream, CompressionMode.Decompress, leaveOpen: true);
        }

        return originalContent.CopyToAsync(compressedStream).ContinueWith(tsk =>
        {
            if (compressedStream != null)
            {
                compressedStream.Dispose();
            }
        });
    }



}

}

これは問題なく動作しているようです。コントローラーの前に SendAsync メソッドが呼び出され、DecompressedContent のコンストラクターが呼び出されます。ただし、SerializeToStreamAsync が呼び出されることはないため、CreateContentReadStreamAsync を追加して、解凍が発生する場所であるかどうかを確認しましたが、それも呼び出されていません。

私は解決策に近づいているように感じましたが、一線を越えるには少し余分に必要です.

4

4 に答える 4

23

gzip 圧縮されたデータを .NET Web API コントローラーに POST するという同じ要件がありました。私はこの解決策を思いつきました:

public class GZipToJsonHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                                                           CancellationToken cancellationToken)
    {
        // Handle only if content type is 'application/gzip'
        if (request.Content.Headers.ContentType == null ||
            request.Content.Headers.ContentType.MediaType != "application/gzip")
        {
            return base.SendAsync(request, cancellationToken);
        }

        // Read in the input stream, then decompress in to the outputstream.
        // Doing this asynronously, but not really required at this point
        // since we end up waiting on it right after this.
        Stream outputStream = new MemoryStream();
        Task task = request.Content.ReadAsStreamAsync().ContinueWith(t =>
            {
                Stream inputStream = t.Result;
                var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress);

                gzipStream.CopyTo(outputStream);
                gzipStream.Dispose();

                outputStream.Seek(0, SeekOrigin.Begin);
            });

        // Wait for inputstream and decompression to complete. Would be nice
        // to not block here and work async when ready instead, but I couldn't 
        // figure out how to do it in context of a DelegatingHandler.
        task.Wait();

        // This next section is the key...

        // Save the original content
        HttpContent origContent = request.Content;

        // Replace request content with the newly decompressed stream
        request.Content = new StreamContent(outputStream);

        // Copy all headers from original content in to new one
        foreach (var header in origContent.Headers)
        {
            request.Content.Headers.Add(header.Key, header.Value);
        }

        // Replace the original content-type with content type
        // of decompressed data. In our case, we can assume application/json. A
        // more generic and reuseable handler would need some other 
        // way to differentiate the decompressed content type.
        request.Content.Headers.Remove("Content-Type");
        request.Content.Headers.Add("Content-Type", "application/json");

        return base.SendAsync(request, cancellationToken);
    }
}

このアプローチを使用すると、通常は JSON コンテンツと自動モデル バインディングで動作する既存のコントローラーが、変更なしで引き続き動作します。

なぜ他の答えが受け入れられたのかわかりません。応答 (一般的) を処理するためのソリューションを提供しますが、要求 (これは一般的ではありません) を処理するためのソリューションは提供しません。Accept-Encoding ヘッダーは、受け入れ可能な応答エンコーディングを指定するために使用され、要求エンコーディングとは関係ありません。

于 2013-02-20T21:59:33.453 に答える
6

Web API はそのままではヘッダーをサポートしていませんAccept-Encodingが、Kiran はその方法について素晴らしいブログ投稿をしています - http://blogs.msdn.com/b/kiranchalla/archive/2012/09/04/handling- compression-accept-encoding-sample.aspx - カスタム MessageHandler の使用

彼のソリューションを実装すると、Accept-Encoding: gzipまたはAccept-Encoding: deflateヘッダーを使用してリクエストを発行するだけで、Web API レスポンスがメッセージ ハンドラーで圧縮されます。

于 2012-09-10T21:38:26.723 に答える