2012-05-04 3 views
17

В настоящее время я работаю над переносом нескольких моих MVC3-контроллеров в контроллеры MVC4 Api. Я реализовал механизм сжатия для контроллера MVC3. Получить ответы метода по умолчанию ActionFilterAttribute и переопределить метод OnActionExecutiong. После некоторых исследований я обнаружил, что мне нужно использовать ActionFilterMethod от System.Web.HttpFilters. Было бы здорово, если бы кто-нибудь мог поделиться куском кода примера, чтобы начать работу с этим сжатием HTTP-ответа, используя GZipСжатие HTTP GET Response

+0

У меня та же проблема, хотя в моем случае я уже включен сжатие IIS. В вашем случае это было сжатие IIS, или вы создали пользовательский обработчик? – Carvellis

+0

Да, я использовал пользовательский обработчик для этого точно так же, как здесь упоминал Дарин. –

ответ

39

Самый простой способ - enable compression непосредственно на уровне IIS.

Если вы хотите сделать это на уровне приложения можно написать пользовательское делегирование обработчика сообщений, как показано на following post:

public class CompressHandler : DelegatingHandler 
{ 
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
    { 
     return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) => 
     { 
      HttpResponseMessage response = responseToCompleteTask.Result; 

      if (response.RequestMessage.Headers.AcceptEncoding != null) 
      { 
       string encodingType = response.RequestMessage.Headers.AcceptEncoding.First().Value; 

       response.Content = new CompressedContent(response.Content, encodingType); 
      } 

      return response; 
     }, 
     TaskContinuationOptions.OnlyOnRanToCompletion); 
    } 
} 

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

    public CompressedContent(HttpContent content, string encodingType) 
    { 
     if (content == null) 
     { 
      throw new ArgumentNullException("content"); 
     } 

     if (encodingType == null) 
     { 
      throw new ArgumentNullException("encodingType"); 
     } 

     originalContent = content; 
     this.encodingType = encodingType.ToLowerInvariant(); 

     if (this.encodingType != "gzip" && this.encodingType != "deflate") 
     { 
      throw new InvalidOperationException(string.Format("Encoding '{0}' is not supported. Only supports gzip or deflate encoding.", this.encodingType)); 
     } 

     // copy the headers from the original content 
     foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers) 
     { 
      this.Headers.AddWithoutValidation(header.Key, header.Value); 
     } 

     this.Headers.ContentEncoding.Add(encodingType); 
    } 

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

     return false; 
    } 

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

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

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

Все, что осталось теперь зарегистрировать обработчик в Application_Start:

GlobalConfiguration.Configuration.MessageHandlers.Add(new CompressHandler()); 
+0

Я думаю, что в этом коде есть ошибка (как и в аналогичных примерах, найденных в Интернете): Заголовок Content-Length задан неправильно, потому что заголовок Content-Length Header копируется из содержимого gzipped. Это можно легко воспроизвести, передав StringContent через обработчик сжатия. Чтобы исправить это, строка с 'originalContent.Headers' должна быть исправлена ​​следующим образом:' originalContent.Headers.Where (x => x.Key! = "Content-Length") ' –

+0

Код не будет работать, если нет Accept-Encoding предоставлен. 'if (response.RequestMessage.Headers.AcceptEncoding! = null)' должно быть 'if (response.RequestMessage.Headers.AcceptEncoding.Any())' –

+0

Я бы рекомендовал добавить следующее в SendAsync между назначением encodingType и присваиванием ответа.Content, чтобы позволить ответы об ошибках возвращаться без сжатия 'if (response.StatusCode! = HttpStatusCode.OK || response.Content == null || string.IsNullOrWhiteSpace (encodingType)) return response;' – Paul

6

Если вы используете IIS 7+, я бы сказал, что вы должны отключить сжатие до IIS, поскольку оно поддерживает сжатие GZIP. Просто turn it on.

С другой стороны, сжатие слишком близко к металлу для контроллера. В идеале контроллер должен работать на гораздо более высоком уровне, чем байты и потоки.

+0

В общем, я согласен, однако для сжатия уровня IIS потребуется настройка любых серверов, использующих его. – samosaris

3

Используйте класс и написать следующий код

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 
public class CompressFilter : ActionFilterAttribute 
{ 
    public override void OnActionExecuted(HttpActionExecutedContext context) 
    { 
     var acceptedEncoding = context.Response.RequestMessage.Headers.AcceptEncoding.First().Value; 
     if (!acceptedEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase) 
     && !acceptedEncoding.Equals("deflate", StringComparison.InvariantCultureIgnoreCase)) 
     { 
      return; 
     } 
     context.Response.Content = new CompressedContent(context.Response.Content, acceptedEncoding); 
    } 
} 

Теперь создайте еще один класс и напишите следующий код.

public class CompressedContent : HttpContent 
{ 
    private readonly string _encodingType; 
    private readonly HttpContent _originalContent; 
    public CompressedContent(HttpContent content, string encodingType = "gzip") 
    { 
     if (content == null) 
     { 
      throw new ArgumentNullException("content"); 
     } 
     _originalContent = content; 
     _encodingType = encodingType.ToLowerInvariant(); 
     foreach (var header in _originalContent.Headers) 
     { 
      Headers.TryAddWithoutValidation(header.Key, header.Value); 
     } 
     Headers.ContentEncoding.Add(encodingType); 
    } 
    protected override bool TryComputeLength(out long length) 
    { 
     length = -1; 
     return false; 
    } 
    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) 
    { 
     Stream compressedStream = null; 
     switch (_encodingType) 
     { 
      case "gzip": 
       compressedStream = new GZipStream(stream, CompressionMode.Compress, true); 
       break; 
      case "deflate": 
       compressedStream = new DeflateStream(stream, CompressionMode.Compress, true); 
       break; 
      default: 
       compressedStream = stream; 
       break; 
     } 
     return _originalContent.CopyToAsync(compressedStream).ContinueWith(tsk => 
     { 
      if (compressedStream != null) 
      { 
       compressedStream.Dispose(); 
      } 
     }); 
    } 
} 

Теперь используйте следующий атрибут в контроллере или в любом методе апите действия как этот

[Route("GetData")] 
[CompressFilter]   
public HttpResponseMessage GetData() 
{ 
} 
+0

У меня есть промежуточное ПО OWIN, настроенное в моем веб-API, и это единственное решение, которое сработало для меня. Кроме того, вы можете настроить таргетинг на то, что вы хотите сжать. Хорошее решение! – Elferone