2012-05-04 33 views
17

Attualmente sto lavorando alla migrazione di alcuni dei miei controller MVC3 su controller MVC4 Api. Ho implementato il meccanismo di compressione per il controller MVC3 Ottieni risposte al metodo ereditando ActionFilterAttribute e sovrascrivendo il metodo OnActionExecutiong. Dopo alcune ricerche ho scoperto che ho bisogno di usare ActionFilterMethod da System.Web.HttpFilters. Sarebbe bello se qualcuno potesse condividere parte del codice di esempio per iniziare a comprimere la risposta HTTP usando GZipComprimi HTTP Risposta GET

+0

Sto avendo lo stesso problema, anche se nel mio caso ho già attivato la compressione di IIS. Nel tuo caso, era la compressione di IIS o hai creato il gestore personalizzato? – Carvellis

+0

Sì, ho usato questo gestore personalizzato proprio come il modo in cui Darin ha menzionato qui. –

risposta

39

Il più semplice è quello di enable compression direttamente a livello di IIS.

Se volete farlo a livello di applicazione si potrebbe scrivere un custom delegando gestore di messaggi, come mostrato nella 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(); 
      } 
     }); 
    } 
} 

Tutto ciò che resta ora è quello di registrare il gestore in Application_Start:

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

Penso che ci sia un bug in questo codice (così come in esempi simili trovati sul web): l'intestazione Content-Length è impostata in modo errato perché l'intestazione Content-Length viene copiata dal contenuto gzip. Questo può essere facilmente riprodotto passando un String Content attraverso il gestore di compressione. Per risolvere questo problema, la riga con 'originalContent.Headers' deve essere corretta in questo modo:' originalContent.Headers.Where (x => x.Key! = "Content-Length") ' –

+0

Il codice non funzionerà se non si accetta Accept-Encoding è fornito. 'if (response.RequestMessage.Headers.AcceptEncoding! = null)' dovrebbe essere 'if (response.RequestMessage.Headers.AcceptEncoding.Any())' –

+0

Ti consiglio di aggiungere quanto segue in SendAsync tra l'assegnazione di encodingType e l'assegnazione di response.Content per consentire risposte di errore per restituire senza compressione 'if (response.StatusCode! = HttpStatusCode.OK || response.Content == null || string.IsNullOrWhiteSpace (encodingType)) risposta di risposta;' – Paul

6

Se si utilizza IIS 7+, direi di lasciare la compressione a IIS poiché supporta la compressione GZIP. Solo turn it on.

D'altra parte, la compressione è troppo vicina al metallo per il controller. Idealmente, il controller dovrebbe funzionare a un livello molto più alto di byte e flussi.

+0

In generale sono d'accordo, tuttavia la compressione a livello IIS richiederebbe la configurazione di qualsiasi server che la utilizza. – samosaris

3

Utilizzare una classe e scrivere il seguente codice

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

Ora creare un'altra classe e scrivere il seguente codice.

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

Ora usare il seguente attributo nel Controller o in qualsiasi metodo di azione api come questo

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

Ho OWIN Middleware configurato sulla mia API Web e questa è l'unica soluzione che ha funzionato per me. Inoltre, puoi davvero mirare a ciò che vuoi comprimere. Buona soluzione! – Elferone

Problemi correlati