2009-05-20 5 views
45

Come un'estensione a questa domanda qui Linking JavaScript Libraries in User Controls Ero dopo alcuni esempi di come le persone concatenano e minimizzano JavaScript al volo O al momento della compilazione. Mi piacerebbe anche vedere come funziona poi nelle tue pagine master.Concatena e ridimensiona JavaScript al volo O al momento della compilazione - ASP.NET MVC

Non mi dispiace che i file specifici della pagina vengano minimizzati e collegati in modo univoco come sono attualmente (vedi sotto) ma tutti i file JavaScript sulla pagina principale principale (ho circa 5 o 6) vorrei concatenati e miniati.

Punti bonus per chi incorpora anche concatenazione CSS e minification! :-)

corrente pagina master con i file JavaScript comuni che vorrei concatenati e minified:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %> 
<head runat="server"> 
    ... BLAH ... 
    <asp:ContentPlaceHolder ID="AdditionalHead" runat="server" /> 
    ... BLAH ... 
    <%= Html.CSSBlock("/styles/site.css") %> 
    <%= Html.CSSBlock("/styles/jquery-ui-1.7.1.css") %> 
    <%= Html.CSSBlock("/styles/jquery.lightbox-0.5.css") %> 
    <%= Html.CSSBlock("/styles/ie6.css", 6) %> 
    <%= Html.CSSBlock("/styles/ie7.css", 7) %> 
    <asp:ContentPlaceHolder ID="AdditionalCSS" runat="server" /> 
</head> 
<body> 
    ... BLAH ... 
    <%= Html.JSBlock("/scripts/jquery-1.3.2.js", "/scripts/jquery-1.3.2.min.js") %> 
    <%= Html.JSBlock("/scripts/jquery-ui-1.7.1.js", "/scripts/jquery-ui-1.7.1.min.js") %> 
    <%= Html.JSBlock("/scripts/jquery.validate.js", "/scripts/jquery.validate.min.js") %> 
    <%= Html.JSBlock("/scripts/jquery.lightbox-0.5.js", "/scripts/jquery.lightbox-0.5.min.js") %> 
    <%= Html.JSBlock("/scripts/global.js", "/scripts/global.min.js") %> 
    <asp:ContentPlaceHolder ID="AdditionalJS" runat="server" /> 
</body> 

Utilizzato in una pagina come questa (che sono felice con):

<asp:Content ID="signUpContent" ContentPlaceHolderID="AdditionalJS" runat="server"> 
    <%= Html.JSBlock("/scripts/pages/account.signup.js", "/scripts/pages/account.signup.min.js") %> 
</asp:Content> 


UPDATE: Raccomandazioni per la società (fine 2013):

Guarderei Microsoft ASP.NET incorporato nel Bundling and Minification.

+0

Molto interessato a vedere cosa fanno le persone qui. La porta della compressione YUI sembra il posto migliore da cui iniziare. –

+1

Qualcuno ha delle soluzioni usando YUI? – Charlino

+0

Qualche cosa è il "papero d'anatra" significa che è buono o cattivo? – Mark

risposta

7

Nell'appendice di Professional ASP.NET 3.5 Scott Hanselman parla di Packer for .NET. Questo si integrerà con MSBuild e comprimerà i file javascript per le distribuzioni di produzione, ecc.

+0

Sembra fantastico, dovrò dargli un vortice. Ho sentito cose negative su 'Packer' ma vedo che supporta anche 'JSMin'. – Charlino

+0

Mentre sembra bello, un vantaggio che YUI Compress sembra avere è che fa anche la compressione e la concatenazione CSS. –

+0

Packer per .NET fa anche concatenazione e minifrazione CSS - controlla il collegamento :-) Ma sì, sento che YUI Compress fa un lavoro migliore di qualsiasi altra cosa a minimizzare JS e CSS. – Charlino

14

Perché non utilizzare ScriptManager? Ecco uno MVCScriptManager che combinerà AND squish.

+2

Sembra una grande opzione per la concatenazione e la minimizzazione al volo. Ma sto decisamente oscillando verso una soluzione di tempo di costruzione. Molto più pulito senza l'overhead, in più posso fare CSS lì mentre sono a questo :-) – Charlino

6

Utilizzare compressore YUI o compressore Dojo. Entrambi utilizzano il motore di parsing Rhino JS che concede il token al codice e funzionerà quindi solo se il codice è un codice valido. Se c'è un errore, ti faranno sapere (che è un bel bonus IMO!) Packer d'altra parte, comprenderà il tuo codice anche se contiene errori.

Io uso YUI in tutti i miei progetti tramite script di compilazione. Mai farlo al volo, ci vuole troppo tempo per fare la compressione. Sia YUI che Dojo sono basati su Java (ala Rhino) e se lo fai al volo, genererai processi in background per generare l'output, non buono per le prestazioni. Fallo sempre al momento della costruzione.

2

Ecco quello che ho usato per concatenare, file JS la compressione e il caching CSS e: http://gist.github.com/130913

Richiede solo Yahoo.Yui.Compressor.dll nella directory bin. Non si comprime al momento della compilazione, ma i file vengono memorizzati nella cache con una dipendenza dal file, quindi vengono caricati solo una volta, finché non vengono modificati.

Poi ho solo aggiungere questo codice nella <testa>:

<link rel="stylesheet" type="text/css" href="/YuiCompressor.ashx?css=reset,style,etc" /> 

e questo appena prima del </corpo >:

<script type="text/javascript" src="/YuiCompressor.ashx?js=main,other,etc"></script> 

E 'progettato per lavorare con più file nella stesso percorso ma potrebbe essere facilmente aggiornato per supportare percorsi diversi.

40

Prova questo:

Ho recentemente completato un bel po 'di ricerca e di conseguente sviluppo sul posto di lavoro che va abbastanza lontano per migliorare le prestazioni di front-end della nostra applicazione web. Ho pensato di condividere la soluzione di base qui.

La prima cosa ovvia da fare è eseguire il benchmark del tuo sito utilizzando Yahoo YSlow e Google PageSpeed. Questi evidenzieranno i miglioramenti delle prestazioni "a basso impatto" da apportare. A meno che non lo abbiate già fatto, i suggerimenti risultanti includeranno quasi certamente la combinazione, la minimizzazione e il gzip del contenuto statico.

I passi che andremo a svolgere sono:

Scrivi una consuetudine HTTPHandler per combinare e Minimizza CSS. Scrivere un HTTPHandler personalizzato per combinare e minificare JS. Includere un meccanismo per garantire che quanto sopra faccia la loro magia solo quando l'applicazione non è in modalità di debug. Scrivere un controllo Web lato server personalizzato per mantenere facilmente l'inclusione di file css/js. Abilita GZIP di determinati tipi di contenuto su IIS 6. Destra, cominciamo con CSSHandler.asax che implementa l'interfaccia .NET IHttpHandler:

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Text; 
using System.Web; 

namespace WebApplication1 
{ 
    public class CssHandler : IHttpHandler 
    { 
     public bool IsReusable { get { return true; } } 

     public void ProcessRequest(HttpContext context) 
     { 
      string[] cssFiles = context.Request.QueryString["cssfiles"].Split(','); 

      List<string> files = new List<string>(); 
      StringBuilder response = new StringBuilder(); 
      foreach (string cssFile in cssFiles) 
      { 
       if (!cssFile.EndsWith(".css", StringComparison.OrdinalIgnoreCase)) 
       { 
        //log custom exception 
        context.Response.StatusCode = 403; 
        return; 
       } 

       try 
       { 
        string filePath = context.Server.MapPath(cssFile); 
        string css = File.ReadAllText(filePath); 
        string compressedCss = Yahoo.Yui.Compressor.CssCompressor.Compress(css); 
        response.Append(compressedCss); 
       } 
       catch (Exception ex) 
       { 
        //log exception 
        context.Response.StatusCode = 500; 
        return; 
       } 
      } 

      context.Response.Write(response.ToString()); 

      string version = "1.0"; //your dynamic version number 

      context.Response.ContentType = "text/css"; 
      context.Response.AddFileDependencies(files.ToArray()); 
      HttpCachePolicy cache = context.Response.Cache; 
      cache.SetCacheability(HttpCacheability.Public); 
      cache.VaryByParams["cssfiles"] = true; 
      cache.SetETag(version); 
      cache.SetLastModifiedFromFileDependencies(); 
      cache.SetMaxAge(TimeSpan.FromDays(14)); 
      cache.SetRevalidation(HttpCacheRevalidation.AllCaches); 
     } 
    } 
} 

Ok, ora qualche spiegazione:

proprietà IsReusable:

Non abbiamo a che fare con istanze specifiche, il che significa che possiamo riutilizzare in sicurezza la stessa istanza del gestore per gestire più richieste, poiché ProcessRequest è thread-safe. Ulteriori informazioni.

metodo ProcessRequest:

Niente di troppo frenetico succedendo qui. Stiamo eseguendo il looping dei file CSS che ci sono stati dati (vedi il CSSControl in basso per come stanno arrivando) e comprimendoli ognuno, usando una porta .NET di Yahoo YUICompressor, prima di aggiungere il contenuto al flusso di risposta in uscita.

Il resto del metodo riguarda l'impostazione di alcune proprietà di memorizzazione nella cache HTTP per ottimizzare ulteriormente il modo in cui il client browser scarica (o, a seconda dei casi, il contenuto).

Impostiamo gli ETAG nel codice in modo che possano essere uguali su tutte le macchine nella nostra server farm. Impostiamo le dipendenze Response e Cache sui nostri file effettivi quindi, se dovessero essere sostituiti, la cache verrà invalidata. Impostiamo Cacheability in modo che i proxy possano memorizzare nella cache. We VaryByParams utilizza il nostro attributo cssfiles, in modo che possiamo memorizzare nella cache per gruppo di file CSS inviato tramite il gestore. Ed ecco il CSSControl, un controllo personalizzato sul lato server che eredita .NET LiteralControl.

anteriore:

<customcontrols:csscontrol id="cssControl" runat="server"> 
    <CustomControls:Stylesheet File="main.css" /> 
    <CustomControls:Stylesheet File="layout.css" /> 
    <CustomControls:Stylesheet File="formatting.css" /> 
</customcontrols:csscontrol> 

Indietro:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Web; 
using System.Web.UI; 
using System.Linq; 
using TTC.iTropics.Utilities; 

namespace WebApplication1 
{ 
    [DefaultProperty("Stylesheets")] 
    [ParseChildren(true, "Stylesheets")] 
    public class CssControl : LiteralControl 
    { 
     [PersistenceMode(PersistenceMode.InnerDefaultProperty)] 
     public List<Stylesheet> Stylesheets { get; set; } 

     public CssControl() 
     { 
      Stylesheets = new List<Stylesheet>(); 
     } 

     protected override void Render(HtmlTextWriter output) 
     { 
      if (HttpContext.Current.IsDebuggingEnabled) 
      { 
       const string format = "<link rel=\"Stylesheet\" href=\"stylesheets/{0}\"></link>"; 

       foreach (Stylesheet sheet in Stylesheets) 
        output.Write(format, sheet.File); 
      } 
      else 
      { 
       const string format = "<link type=\"text/css\" rel=\"Stylesheet\" href=\"stylesheets/CssHandler.ashx?cssfiles={0}&version={1}\"/>"; 
       IEnumerable<string> stylesheetsArray = Stylesheets.Select(s => s.File); 
       string stylesheets = String.Join(",", stylesheetsArray.ToArray()); 
       string version = "1.00" //your version number 

       output.Write(format, stylesheets, version); 
      } 

     } 
    } 

    public class Stylesheet 
    { 
     public string File { get; set; } 
    } 
} 

HttpContext.Current.IsDebuggingEnabled è collegato al seguente impostazione nel vostro Web.config:

<system.web> 
    <compilation debug="false"> 
</system.web> 

Quindi, in sostanza, se il sito è in modalità debug si ottiene HTML markup come questo:

<link rel="Stylesheet" href="stylesheets/formatting.css"></link> 
<link rel="Stylesheet" href="stylesheets/layout.css"></link 
<link rel="Stylesheet" href="stylesheets/main.css"></link> 

Ma se siete in modalità di produzione (debug = false), è' ll ottenere markup come questo:

<link type="text/css" rel="Stylesheet" href="CssHandler.ashx?cssfiles=main.css,layout.css,formatting.css&version=1.0"/> 

quest'ultimo sarà poi ovviamente invocare il CSSHandler, che si occuperà di combinare, minifying e cache-preparando sul suo sito web CSS statico.

Tutto quanto sopra possono poi essere duplicato anche per il vostro contenuto statico JavaScript:

`JSHandler.ashx:

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Text; 
using System.Web; 

namespace WebApplication1 
{ 
    public class JSHandler : IHttpHandler 
    { 
     public bool IsReusable { get { return true; } } 

     public void ProcessRequest(HttpContext context) 
     { 
      string[] jsFiles = context.Request.QueryString["jsfiles"].Split(','); 

      List<string> files = new List<string>(); 
      StringBuilder response = new StringBuilder(); 

      foreach (string jsFile in jsFiles) 
      { 
       if (!jsFile.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) 
       { 
        //log custom exception 
        context.Response.StatusCode = 403; 
        return; 
       } 

       try 
       { 
        string filePath = context.Server.MapPath(jsFile); 
        files.Add(filePath); 
        string js = File.ReadAllText(filePath); 
        string compressedJS = Yahoo.Yui.Compressor.JavaScriptCompressor.Compress(js); 
        response.Append(compressedJS); 
       } 
       catch (Exception ex) 
       { 
        //log exception 
        context.Response.StatusCode = 500; 
        return; 
       } 
      } 

      context.Response.Write(response.ToString()); 

      string version = "1.0"; //your dynamic version number here 

      context.Response.ContentType = "application/javascript"; 
      context.Response.AddFileDependencies(files.ToArray()); 
      HttpCachePolicy cache = context.Response.Cache; 
      cache.SetCacheability(HttpCacheability.Public); 
      cache.VaryByParams["jsfiles"] = true; 
      cache.VaryByParams["version"] = true; 
      cache.SetETag(version); 
      cache.SetLastModifiedFromFileDependencies(); 
      cache.SetMaxAge(TimeSpan.FromDays(14)); 
      cache.SetRevalidation(HttpCacheRevalidation.AllCaches); 
     } 
    } 
} 

E il suo accompagnamento JSControl:

anteriore:

<customcontrols:JSControl ID="jsControl" runat="server"> 
    <customcontrols:Script File="jquery/jquery-1.3.2.js" /> 
    <customcontrols:Script File="main.js" /> 
    <customcontrols:Script File="creditcardpayments.js" /> 
</customcontrols:JSControl> 

Indietro:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Web; 
using System.Web.UI; 
using System.Linq; 

namespace WebApplication1 
{ 
    [DefaultProperty("Scripts")] 
    [ParseChildren(true, "Scripts")] 
    public class JSControl : LiteralControl 
    { 
     [PersistenceMode(PersistenceMode.InnerDefaultProperty)] 
     public List<Script> Scripts { get; set; } 

     public JSControl() 
     { 
      Scripts = new List<Script>(); 
     } 

     protected override void Render(HtmlTextWriter writer) 
     { 
      if (HttpContext.Current.IsDebuggingEnabled) 
      { 
       const string format = "<script src=\"scripts\\{0}\"></script>"; 

       foreach (Script script in Scripts) 
        writer.Write(format, script.File); 
      } 
      else 
      { 
       IEnumerable<string> scriptsArray = Scripts.Select(s => s.File); 
       string scripts = String.Join(",", scriptsArray.ToArray()); 
       string version = "1.0" //your dynamic version number 
       const string format = "<script src=\"scripts/JsHandler.ashx?jsfiles={0}&version={1}\"></script>"; 

       writer.Write(format, scripts, version); 
      } 
     } 
    } 

    public class Script 
    { 
     public string File { get; set; } 
    } 
} 

Abilitazione GZIP:

Come Jeff Atwood dice, consentendo Gzip sul server del sito web è un gioco da ragazzi. Dopo un po 'tracciato, ho deciso di consentire Gzip sui seguenti tipi di file:

.css .js axd (i file di Microsoft Javascript) aspx (solito web ASP.NET Forms contenuti) .ashx (I nostri gestori) per abilitare la compressione HTTP sul server IIS 6.0 web:

Aprire IIS, fare clic sulla scheda Siti web, servizi, attivare Compress applicazione File e Compress Statico Files Arrestare IIS Aprire IIS metabase in Blocco note (C: \ WINDOWS \ system32 \ inetsrv \ MetaBase.xml) - e fare un backup se si è nervosi su queste cose Individuare e ove rwrite i due IIsCompressionScheme e uno gli elementi IIsCompressionSchemes con il seguente:

E questo è tutto! Questo ci ha permesso di risparmiare un sacco di larghezza di banda e ha portato a un'applicazione web più reattiva.

Divertiti!

+3

Wow - questa è una risposta incredibilmente dettagliata, sicuramente degna di un post sul blog da qualche parte! Sicuramente una buona soluzione se si adatta al tuo sito web. Con il mio sito web sono combinati tutti i js e i css che devono essere combinati, quindi non ho davvero bisogno di una soluzione così complessa. E sì, ho abilitato gzip. In più ho messo molte future intestazioni di scadenza e il controllo automatico delle versioni dei miei file js & css su un dominio gratuito di cookie - ma questa è un'altra domanda tutta insieme! – Charlino

+0

+1 per la risposta più lunga che ho visto questo mese! – UpTheCreek

+1

Alcuni anni dopo e il mondo è andato avanti, anche se avevo bisogno di ri-risolvere questo problema presso il mio nuovo datore di lavoro. Giù le mani ora consiglierei di usare Cassette: http://getcassette.net/ –

4

Rejuicer è una grande nuova minifier per ASP.NET che sta ottenendo un sacco di buzz: http://rejuice.me

Si configura come un modulo HTTP & effettua minification a run-time (una sola volta) e memorizza nella cache l'output.

E:

  • ha un'interfaccia fluida per la configurazione
  • Consente di specificare i file a minify con le regole jolly
  • Gira su Windows Azure
  • si spegne Un po 'magicamente fuori in ambienti di sviluppo, in modo da poter eseguire il debug del codice javascript originale (non modificato).

La configurazione (fatto su ApplicationStart in global.asax.cs) è semplice come:

OnRequest.ForJs("~/Combined.js") 
      .Compact 
      .FilesIn("~/Scripts/") 
       .Matching("*.js") 
      .Cache 
      .Configure(); 
+1

Il link a rejuice non funziona ... – theJerm

2

Io uso una soluzione personalizzata in base MSBuild e Microsoft Ajax Minifier. Gran parte dei post esistenti sul blog non gestiscono correttamente alcuni casi come l'integrazione con la build TFS.

Per ogni progetto Web, creiamo un file "wpp.targets" per estendere la pipeline di pubblicazione sul Web. Ad esempio, se il progetto è "Website.csproj", crea un file denominato "Website.wpp.targets" nel progetto.

Inserire il seguente codice nel file obiettivi:

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 
    <Import Project="$(MSBuildExtensionsPath32)\PATH TO YOUR MSBUILD MINIFY TARGETS" /> 

    <!-- Hook up minification task to WPP build process --> 
    <PropertyGroup> 
    <OnAfterPipelineTransformPhase> 
     $(OnAfterPipelineTransformPhase); 
     MinifyResourceFiles; 
    </OnAfterPipelineTransformPhase> 
    </PropertyGroup> 

    <!-- Define temporary location to store minified resources --> 
    <PropertyGroup> 
    <MinifyResourceIntermediateOutput Condition="'$(MinifyResourceIntermediateOutput)'==''">MinifyResourceFiles</MinifyResourceIntermediateOutput> 
    <MinifyResourceIntermediateLocation Condition="'$(MinifyResourceIntermediateLocation)'==''">$(_WPPDefaultIntermediateOutputPath)$(MinifyResourceIntermediateOutput)</MinifyResourceIntermediateLocation> 
    </PropertyGroup> 

    <Target Name="MinifyResourceFiles" DependsOnTargets="PipelineCollectFilesPhase" Condition="'$(Configuration)' == 'Release'"> 
    <!-- Create lists of the resources to minify --> 
    <!-- These extract all Javascript and CSS files from the publishing pipeline "FilesForPackagingFromProject" and create two new lists. 
    The "MinifiedFile" metadata on each item contains the temporary location where the minified file will be stored --> 
    <ItemGroup> 
     <JavaScriptToMinify Include="@(FilesForPackagingFromProject)" 
          Condition="'%(FilesForPackagingFromProject.Extension)' == '.js'"> 
     <MinifiedFile>$(MinifyResourceIntermediateLocation)\minified\%(DestinationRelativePath)</MinifiedFile> 
     </JavaScriptToMinify> 
     <StylesheetToMinify Include="@(FilesForPackagingFromProject)" 
          Condition="'%(FilesForPackagingFromProject.Extension)' == '.css'"> 
     <MinifiedFile>$(MinifyResourceIntermediateLocation)\minified\%(DestinationRelativePath)</MinifiedFile> 
     </StylesheetToMinify>  
    </ItemGroup> 

    <!-- Minify resources --> 
    <!-- These commands should be replaced with the MSBuild Tasks used to perform your minification 
     I use my own custom tasks based on the Microsoft Ajax Minifier DLL 
     The input of the minifier takes a source file directly from the project and outputs to a temporary location --> 
    <MinifyJavaScript SourceFiles="@(JavaScriptToMinify)" DestinationFiles="@(JavaScriptToMinify->'%(MinifiedFile)')" 
         Comments="None" /> 
    <MinifyStylesheet SourceFiles="@(StylesheetToMinify)" DestinationFiles="@(StylesheetToMinify->'%(MinifiedFile)')" 
         Comments="None" /> 

    <!-- Remove the original source files from the packaging system and include the new minfied resources from the temporary location --> 
    <ItemGroup> 
     <!--Remove unminified resources from the pipeline --> 
     <FilesForPackagingFromProject Remove="@(JavaScriptToMinify)" Condition="'@(JavaScriptToMinify)' != ''" /> 
     <FilesForPackagingFromProject Remove="@(StylesheetToMinify)" Condition="'@(StylesheetToMinify)' != ''" /> 
     <!--Add the minified resources at the new loction to the pipeline --> 
     <FilesForPackagingFromProject Include="@(JavaScriptToMinify->'%(MinifiedFile)')" Condition="'@(JavaScriptToMinify)' != ''"/> 
     <FilesForPackagingFromProject Include="@(StylesheetToMinify->'%(MinifiedFile)')" Condition="'@(StylesheetToMinify)' != ''"/> 
    </ItemGroup> 
    </Target> 
</Project> 

Il " '$ (Configurazione') == 'uscita'" condizione sul bersaglio minification può essere modificato a seconda delle esigenze. Minimizza (e convalida) automaticamente tutti i file CSS e JS nel progetto durante la pubblicazione, la pacchettizzazione e la creazione sul server.

Potrebbe essere necessario abilitare l'obiettivo WPP "CopyWebApplication" per le build del server. Per fare ciò, imposta la proprietà MSBuild UseWP_CopyWebApplication su True e PipelineDependsOnBuild su False. Le impostiamo nel file di progetto, prima che il file di destinazione dell'applicazione Web sia incluso.

2

io consiglierei http://www.RequestReduce.com che riduce al minimo e combina css e javascript, così come le immagini di sfondo sprite CSS e ottimizza la loro compressione PNG. Fa tutto questo in fase di esecuzione e memorizza l'output nella cache. Non richiede codice o configurazione oltre all'aggiunta di HttpModule. Serve tutto il contenuto memorizzato nella cache con intestazioni ottimizzate future lontane e ETags per garantire che i browser memorizzino il css/javascript/sprites il più a lungo possibile. Anche se non richiede alcuna configurazione, è altamente configurabile e può essere configurato per essere eseguito con un CDN e sincronizzare i file memorizzati nella cache in una web farm.

Tutti javascript, immagini css vengono recuperate tramite HTTP in modo che possa includere CSS e JS da terzi e la sua anche un ottimo modo per minify/coniugare axd risorse come WebResource.axd e ScriptResource.axd. Determina la presenza di js e css tramite content-type in modo che la risorsa di destinazione possa avere qualsiasi (o nessuna) estensione. Funziona con qualsiasi tecnologia basata su IIS, incluse tutte le versioni e i motori di visualizzazione di MVC, moduli Web e "pagine Web".

È possibile scaricare da http://www.RequestReduce.com, Nuget o forcella da https://github.com/mwrock/RequestReduce.

Problemi correlati