Question

I am using the new System.Web.Optimization and have created a bundle like this:

bundles.Add(New ScriptBundle("~/bundles/BaseJS").Include(
                "~/Resources/Core/Javascripts/jquery-1.7.1.js",
                "~/Resources/Core/Javascripts/jquery-ui-1.8.16.js",
                "~/Resources/Core/Javascripts/jquery.validate.js",
                "~/Resources/Core/Javascripts/jquery.validate.unobtrusive.js",
                "~/Resources/Core/Javascripts/jquery.unobtrusive-ajax.js"))

and in my view I have added this

@System.Web.Optimization.Scripts.Render("~/bundles/BaseJS")

In fiddler the URL comes across with an expires header of 1 year in the future and a content type of text/javascript

In the web.config I have some code for gzip that is working on static JS files but it doesn't seem to on the minified bundles.

<staticContent>
  <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00"/>
  <remove fileExtension=".js"/>
  <mimeMap fileExtension=".js" mimeType="text/javascript"/>
</staticContent>
<urlCompression doDynamicCompression="true" doStaticCompression="true" dynamicCompressionBeforeCache="true"/>
<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files">
  <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll"/>
  <dynamicTypes>
    <add mimeType="text/*" enabled="true"/>
    <add mimeType="text/javascript" enabled="true"/>
  </dynamicTypes>
  <staticTypes>
    <add mimeType="text/*" enabled="true"/>
    <add mimeType="text/javascript" enabled="true"/>
  </staticTypes>
</httpCompression>

Is there a way to make the render bundle gzip the content?

Was it helpful?

Solution

As you noted, creating a custom bundle transform by creating a class that implements IBundleTransform is the right way to go. For example, the following is an example bundle transform that uses the SharpZipLib (via NuGet) to do GZip compression:

public class GZipTransform : IBundleTransform 
{
    string _contentType;

    public GZipTransform(string contentType)
    {
        _contentType = contentType;
    }

    public void Process(BundleContext context, BundleResponse response)
    {
        var contentBytes = new UTF8Encoding().GetBytes(response.Content);

        var outputStream = new MemoryStream();
        var gzipOutputStream = new GZipOutputStream(outputStream);
        gzipOutputStream.Write(contentBytes, 0, contentBytes.Length);

        var outputBytes = outputStream.GetBuffer();
        response.Content = Convert.ToBase64String(outputBytes);


        // NOTE: this part is broken
        context.HttpContext.Response.Headers["Content-Encoding"] = "gzip";
        response.ContentType = _contentType ;
    }
}

Now, here's the unfortunate part - in testing this sample, I uncovered a bug that will keep it from working. The original design expected that folks would do pretty simple things - and as such, BundleResponse exposes properties that allow you to set the content (more specifically, string content) and the content type. The BundleContext exposes a property of HttpContext, which would lead a reasonable person to believe that additional properties of a response could be set there (as shown above). However, this is misleading for 2 reasons:

  1. Bundle transforms are run as a part of creating the bundle - and creating the bundle happens the first time it is referenced (not dereferenced, as in, the browser follows the src attribute in a script tag - but referenced, as in, the view calls the Scripts.Render helper method). In my example above, this means that a content-encoding header with a value of gzip will be set on the first page with a view that uses bundling's helper methods to generate a link - and if the actual HTTP content is not gzipped, you'll get an error since the browser can't decode the HTTP content.

  2. Even if #1 weren't an issue, the bundle is put immediately put into the ASP.NET cache after it is created - so this code path will only be executed once.

We're taking a hard look at the design in the next version of framework to enable you to specify all (ideally) aspects of the HTTP response message that is free of the HTTP context (meaning it's easily cachable).

One additional note. To supply custom bundle transforms, you'll need to fall back to creating an instance of Bundle rather than ScriptBundle/StyleBundle. Those classes are really just shorthand types for bundles with preconfigured bundle transforms. To create a bundle based on Bundle, you would do something like the following:

var jqueryBundle = new Bundle("~/bundles/jqueryall", new GZipTransform("text/javascript"));
jqueryBundle.Include("~/Scripts/jquery-1.*",
    "~/Scripts/jquery-ui*",
    "~/Scripts/jquery.unobtrusive*",
    "~/Scripts/jquery.validate*");
bundles.Add(jqueryBundle);

OTHER TIPS

With the latest ASP.NET Optimization (v1.1.2), the GZipTransform class does not work well.

I found a new way with a custom Bundle class that will always compress bundle content (that was transformed and cached) before response:

public class GZipBundle : Bundle
{
    public GZipBundle(string virtualPath, params IBundleTransform[] transforms)
        : base(virtualPath, null, transforms) { }

    public override BundleResponse CacheLookup(BundleContext context)
    {
        if (null != context) GZipEncodePage(context.HttpContext);
        return base.CacheLookup(context);
    }

    // Sets up the current page or handler to use GZip through a Response.Filter.
    public static void GZipEncodePage(HttpContextBase httpContext)
    {
        if (null != httpContext && null != httpContext.Request && null != httpContext.Response
            && (null == httpContext.Response.Filter
            || !(httpContext.Response.Filter is GZipStream || httpContext.Response.Filter is DeflateStream)))
        {
            // Is GZip supported?
            string acceptEncoding = httpContext.Request.Headers["Accept-Encoding"];
            if (null != acceptEncoding
                && acceptEncoding.IndexOf(DecompressionMethods.GZip.ToString(), StringComparison.OrdinalIgnoreCase) >= 0)
            {
                httpContext.Response.Filter = new GZipStream(httpContext.Response.Filter, CompressionMode.Compress);
                httpContext.Response.AddHeader("Content-Encoding", DecompressionMethods.GZip.ToString().ToLowerInvariant());
            }
            else if (null != acceptEncoding
                && acceptEncoding.IndexOf(DecompressionMethods.Deflate.ToString(), StringComparison.OrdinalIgnoreCase) >= 0)
            {
                httpContext.Response.Filter = new DeflateStream(httpContext.Response.Filter, CompressionMode.Compress);
                httpContext.Response.AddHeader("Content-Encoding", DecompressionMethods.Deflate.ToString().ToLowerInvariant());
            }

            // Allow proxy servers to cache encoded and unencoded versions separately
            httpContext.Response.AppendHeader("Vary", "Content-Encoding");
        }
    }
}

// Represents a bundle that does CSS minification and GZip compression.
public sealed class GZipStyleBundle : GZipBundle
{
    public GZipStyleBundle(string virtualPath, params IBundleTransform[] transforms) : base(virtualPath, transforms) { }
}

// Represents a bundle that does JS minification and GZip compression.
public sealed class GZipScriptBundle : GZipBundle
{
    public GZipScriptBundle(string virtualPath, params IBundleTransform[] transforms)
        : base(virtualPath, transforms)
    {
        base.ConcatenationToken = ";" + Environment.NewLine;
    }
}

Then you can use GZipStyleBundle and GZipScriptBundle to replace the original Bundle classes: StyleBundle, ScriptBundle. Ex:

public static class BundleConfig
{
    // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new GZipScriptBundle("~/bundles/jquery.js").Include(...));
        bundles.Add(new GZipScriptBundle("~/bundles/jquery-ui.js", new JsMinify()).Include(...));

        bundles.Add(new GZipStyleBundle("~/bundles/all.css", new CssMinify()).Include(...));
    }
}

Regards

It can be achieved using HttpModule

public class GzipModule : IHttpModule
{
    #region IHttpModule Members

    public void Init(HttpApplication application)
    {
        application.BeginRequest += Application_BeginRequest;
    }

    public void Dispose()
    {
    }

    #endregion

    private void Application_BeginRequest(Object source, EventArgs e)
    {
        HttpContext context = HttpContext.Current;
        HttpRequest request = context.Request;
        HttpResponse response = context.Response;
        string acceptEncoding = request.Headers["Accept-Encoding"];

        if (String.IsNullOrEmpty(acceptEncoding))
            return;

        acceptEncoding = acceptEncoding.ToUpperInvariant();

        if (acceptEncoding.Contains("GZIP"))
        {
            response.AppendHeader("Content-Encoding", "gzip");
            response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
        }
        else if (acceptEncoding.Contains("DEFLATE"))
        {
            response.AppendHeader("Content-Encoding", "deflate");
            response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
        }
    }
}

and register it in config

  <system.webServer>
    <modules>
        <add name="Gzip" type="Gecko.Web.GzipModule" />
    </modules>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top