ASP.NET 打包多CSS或JS文件以加快页面加载速度的Handler

ASP.NET 打包多CSS或JS文件以加快页面加载速度的Handler,

使用<link type="text/css" rel="Stylesheet" href="HttpCombiner.ashx?" />,具体的参数请参考程序中的介绍。附件

using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Services;

namespace SLTech.DST.Web.Application
{
    /// <summary>
    /// Summary description for $codebehindclassname$
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class HttpCombiner : IHttpHandler
    {
        private const bool DO_GZIP = true;
        private readonly static TimeSpan CACHE_DURATION = TimeSpan.FromDays(30);
        private const string DEFAULT_CSS = "decision-support-toolkit.css,controls/tab.css,controls/extension-button.css,";

        public void ProcessRequest(HttpContext context)
        {
            var request = context.Request;

            // Read setName, contentType and version. 
            //All are required. They are used as cache key
            var setName = request["s"] ?? string.Empty;
            var contentType = request["t"] ?? string.Empty;
            var version = request["v"] ?? string.Empty;
            var files = request["f"] ?? string.Empty;
            files = DEFAULT_CSS + files;

            // Decide if browser supports compressed response
            var isCompressed = DO_GZIP && this.CanGZip(context.Request);

            // Response is written as UTF8 encoding. 
            var encoding = new UTF8Encoding(false);

            // If the set has already been cached, write the response directly from
            // cache. Otherwise generate the response and cache it
            if (!this.WriteFromCache(context, setName, version, isCompressed, contentType))
            {
                using (var memoryStream = new MemoryStream(5000))
                {
                    // Decide regular stream or GZipStream based on whether the response
                    // can be cached or not
                    using (var writer = isCompressed ?
                        (Stream)(new GZipStream(memoryStream, CompressionMode.Compress)) :
                        memoryStream)
                    {
                        // Load the files defined and process each file
                        var fileNames = files.Split(new char[] { ',' },
                            StringSplitOptions.RemoveEmptyEntries);

                        foreach (string fileName in fileNames)
                        {
                            var fileBytes = this.GetFileBytes(context,"css/"+fileName.Trim(), encoding);
                            writer.Write(fileBytes, 0, fileBytes.Length);
                        }

                        writer.Close();
                    }

                    var responseBytes = memoryStream.ToArray();
                    context.Cache.Insert(GetCacheKey(setName, version, isCompressed),
                        responseBytes, null, System.Web.Caching.Cache.NoAbsoluteExpiration,
                        CACHE_DURATION);

                    // Generate the response
                    this.WriteBytes(responseBytes, context, isCompressed, contentType);
                }
            }
        }

        private byte[] GetFileBytes(HttpContext context, string virtualPath, Encoding encoding)
        {
            if (virtualPath.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
            {
                using (var client = new WebClient())
                {
                    return client.DownloadData(virtualPath);
                }
            }
            else
            {
                var physicalPath = context.Server.MapPath(virtualPath);
                var bytes = File.ReadAllBytes(physicalPath);
                return bytes;
            }
        }

        private bool WriteFromCache(HttpContext context, string setName, string version,
            bool isCompressed, string contentType)
        {
            var responseBytes = context.Cache[GetCacheKey(setName, version, isCompressed)] as byte[];

            if (null == responseBytes || 0 == responseBytes.Length) return false;

            this.WriteBytes(responseBytes, context, isCompressed, contentType);
            return true;
        }

        private void WriteBytes(byte[] bytes, HttpContext context,
            bool isCompressed, string contentType)
        {
            var response = context.Response;

            response.AppendHeader("Content-Length", bytes.Length.ToString());
            response.ContentType = contentType;
            if (isCompressed)
                response.AppendHeader("Content-Encoding", "gzip");

            context.Response.Cache.SetCacheability(HttpCacheability.Public);
            context.Response.Cache.SetExpires(DateTime.Now.Add(CACHE_DURATION));
            context.Response.Cache.SetMaxAge(CACHE_DURATION);
            context.Response.Cache.AppendCacheExtension("must-revalidate, proxy-revalidate");

            response.OutputStream.Write(bytes, 0, bytes.Length);
            response.Flush();
        }

        private bool CanGZip(HttpRequest request)
        {
            var acceptEncoding = request.Headers["Accept-Encoding"];
            return (!string.IsNullOrEmpty(acceptEncoding) &&
                    (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate")));
        }

        private string GetCacheKey(string setName, string version, bool isCompressed)
        {
            return "HttpCombiner." + setName + "." + version + "." + isCompressed;
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}