使用 Razor 在 Asp.Net MVC3 上组合、缩小和 GZIP 样式和脚本的完整解决方案

发布于 2024-12-11 21:49:12 字数 10906 浏览 0 评论 0 原文

抱歉我的英语不好,但我想这不会有问题。我只是不想分享一个很好的助手类,我使用 组合、缩小gzip我们的脚本和样式="http://ajaxmin.codeplex.com/" rel="noreferrer">Microsoft Ajax Minifier。开始之前,请下载 ICSharpCode.SharpZipLib。这是一个使用gzip的开源库。

让我们从 web.config 开始(我将重点关注 IIS7)。在这里,我们对应用程序说,对 csshjsh 扩展发出的任何请求都将转发到 MinifierHelper 类。我选择使用这些扩展(csshjsh),以便如果我们出于任何原因想要不缩小特定的脚本或样式,就按照您使用的方式使用它通常情况下。

<system.webServer>
  <handlers>
    <remove name="ScriptHandler" />
    <remove name="StyleHandler" />
    <add name="ScriptHandler" verb="*" path="*.jsh" type="Site.Helpers.MinifierHelper" resourceType="Unspecified" />
    <add name="StyleHandler" verb="*" path="*.cssh" type="Site.Helpers.MinifierHelper" resourceType="Unspecified" />
  </handlers>
</system.webServer>

我使用文件夹 Scripts 和 Styles 来存储文件。我没有按照 Visual Studio 的建议使用 Content 文件夹。

下一步是配置 global.asax。我们必须告诉我们的应用程序不要路由这些文件夹。将这些行添加到您的 RegisterRoutes 方法中。

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("Scripts/{*path}");
    routes.IgnoreRoute("Styles/{*path}");
    ...
}

好的。现在我将展示如何使用我们的类。在视图中:

<link href="/Styles/Folder/File.cssh" type="text/css" rel="stylesheet" />
<script src="/Scripts/Folder/File.jsh" type="text/javascript"></script>

在我的示例中,我将所有脚本和样式的逻辑都放在脚本/样式内的文件夹内。 例如:站点 ->脚本->首页->索引.css。我使用与脚本和样式视图相同的结构。 例如:站点 ->浏览次数->首页->索引.cshtml。如果需要,您可以更改此模式。

现在是发挥魔力的代码:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Caching;
using System.Web.SessionState;
using ICSharpCode.SharpZipLib.GZip;
using Microsoft.Ajax.Utilities;

namespace Site.Helpers
{
    public abstract class MinifierBase : IHttpHandler, IRequiresSessionState
    {
        #region Fields

        private HttpContext context;
        private byte[] responseBytes;
        private bool isScript;

        protected string fileName;
        protected string folderName;
        protected List<string> files;

        #endregion

        #region Properties

        public bool IsReusable
        {
            get { return false; }
        }

        #endregion

        #region Methods

        public static string setUrl(string url)
        {
            var publishDate = ConfigurationManager.AppSettings["PublishDate"];
            return url + "h" + ((publishDate != null) ? "?id=" + publishDate : "");
        }

        public void ProcessRequest(HttpContext context)
        {
            this.context = context;
            this.isScript = context.Request.Url.PathAndQuery.Contains("/Scripts/");
            this.process();
        }

        private void process()
        {
            if (this.context.Request.QueryString.HasKeys())
            {
                string url = this.context.Request.Url.PathAndQuery;

                if (this.context.Cache[url] != null)
                {
                    this.responseBytes = this.context.Cache[url] as byte[];
                }
                else
                {
                    this.writeResponseBytes();
                    this.context.Cache.Add
                    (
                        url,
                        this.responseBytes,
                        null,
                        DateTime.Now.AddMonths(1),
                        Cache.NoSlidingExpiration,
                        CacheItemPriority.Low,
                        null
                    );
                }
            }
            else
            {
                this.writeResponseBytes();
            }

            this.writeBytes();
        }

        private void writeResponseBytes()
        {
            using (MemoryStream ms = new MemoryStream(8092))
            {
                using (Stream writer = this.canGZip() ? (Stream)(new GZipOutputStream(ms)) : ms)
                {
                    var sb = new StringBuilder();
                    var regex = new Regex(@"^/.+/(?<folder>.+)/(?<name>.+)\..+");
                    var url = regex.Match(this.context.Request.Path);
                    var folderName = url.Groups["folder"].Value;
                    var fileName = url.Groups["name"].Value;

                    this.getFileNames(fileName, folderName).ForEach(delegate(string file)
                    {
                        sb.Append(File.ReadAllText(this.context.Server.MapPath(file)));
                    });

                    var minifier = new Minifier();
                    var minified = string.Empty;

                    if (this.isScript)
                    {
                        var settings = new CodeSettings();

                        settings.LocalRenaming = LocalRenaming.CrunchAll;
                        settings.OutputMode = OutputMode.SingleLine;
                        settings.PreserveImportantComments = false;
                        settings.TermSemicolons = true;

                        minified = minifier.MinifyJavaScript(sb.ToString(), settings);
                    }
                    else
                    {
                        var settings = new CssSettings();

                        settings.CommentMode = CssComment.Important;
                        settings.OutputMode = OutputMode.SingleLine;

                        minified = minifier.MinifyStyleSheet(sb.ToString(), settings);
                    }

                    var bts = Encoding.UTF8.GetBytes(minified);

                    writer.Write(bts, 0, bts.Length);
                }

                this.responseBytes = ms.ToArray();
            }
        }

    private List<String> getFileNames(string fileName, string folderName = "")
    {
        this.files = new List<String>();
        this.fileName = fileName;
        this.folderName = folderName;

        if (folderName == "Global" && fileName == "global-min")
        {
            if (this.isScript) this.addGlobalScripts();
            else this.addDefaultStyles();
        }
        else
        {
            var flags = BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance;
            var mi = this.GetType().GetMethod
            (
                "add" +
                this.folderName +
                CultureInfo.CurrentCulture.TextInfo.ToTitleCase(fileName).Replace("-", "") +
                (this.isScript ? "Scripts" : "Styles"),
                flags
            );

            if (mi != null)
            {
                mi.Invoke(this, null);
            }
            else
            {
                if (this.isScript) this.addDefaultScripts();
                else this.addDefaultStyles();
            }
        }

        return files;
    }

    private void writeBytes()
    {
        var response = this.context.Response;

        response.AppendHeader("Content-Length", this.responseBytes.Length.ToString());
        response.ContentType = this.isScript ? "text/javascript" : "text/css";

        if (this.canGZip())
        {
            response.AppendHeader("Content-Encoding", "gzip");
        }
        else
        {
            response.AppendHeader("Content-Encoding", "utf-8");
        }

        response.ContentEncoding = Encoding.Unicode;
        response.OutputStream.Write(this.responseBytes, 0, this.responseBytes.Length);
        response.Flush();
    }

    private bool canGZip()
    {
        string acceptEncoding = this.context.Request.Headers["Accept-Encoding"];
        return (!string.IsNullOrEmpty(acceptEncoding) && (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate")));
    }

    protected abstract void addGlobalScripts();
    protected abstract void addGlobalStyles();
    protected abstract void addDefaultScripts();
    protected abstract void addDefaultStyles();

    #endregion
}

这就是基类。现在我们将创建继承基类的 Helper 类。这是我们选择应添加哪些脚本的类。

public class MinifierHelper : MinifierBase
{
    #region Methods

要组合和缩小全局脚本/样式,请将当前行添加到视图中:

<link href="@MinifierHelper.setUrl("/Styles/Global/global-min.css")" type="text/css" rel="stylesheet" />
<script src="@MinifierHelper.setUrl("/Scripts/Global/global-min.js")" type="text/javascript"></script>

它将调用 MinifierHelper 类中的 addGlobalScripts/addGlobalStyles 方法。

    protected override void addGlobalScripts()
    {
        this.files.Add("~/Scripts/Lib/jquery-1.6.2.js");
        this.files.Add("~/Scripts/Lib/jquery-ui-1.8.16.js");
        this.files.Add("~/Scripts/Lib/jquery.unobtrusive-ajax.js");
        this.files.Add("~/Scripts/Lib/jquery.validate.js");
        ...
    }

    protected override void addGlobalStyles()
    {
        this.files.Add("~/Styles/Global/reset.css");
        this.files.Add("~/Styles/Global/main.css");
        this.files.Add("~/Styles/Global/form.css");
        ...
    }

要缩小特定脚本/样式(特定于页面),请将当前行添加到您的视图中:

<link href="@MinifierHelper.setUrl("/Styles/Curriculum/index.css")" type="text/css" rel="stylesheet" />

MinifierHelper 类将尝试查找名为 "add" + FolderName + FileName + "Styles" 的方法。在我们的例子中,它将查找addCurriculumIndexStyles。在我的示例中它存在,因此该方法将被触发。

    public void addCurriculumIndexStyles()
    {
        this.files.Add("~/Styles/Global/curriculum.css");
        this.files.Add("~/Styles/Curriculum/personal-info.css");
        this.files.Add("~/Styles/Curriculum/academic-info.css");
        this.files.Add("~/Styles/Curriculum/professional-info.css");
    }

如果该类找不到该特定方法,它将触发默认方法。默认方法使用指定的相同文件夹/名称来缩小脚本/样式。

protected override void addDefaultScripts()
{
    this.files.Add("~/Scripts/" + this.folderName + "/" + this.fileName + ".js");
}

protected override void addDefaultStyles()
{
    this.files.Add("~/Styles/" + this.folderName + "/" + this.fileName + ".css");
}

不要忘记关闭区域和类。

    #endregion
}

就是这样。我希望你们已经理解了。

我忘了告诉最后一件事。在 web.config 中,在 AppSettings 中添加一个名为 PublishDate 的键。我在值中输入了一个包含完整日期和时间的字符串(例如:261020111245)。目标是独一无二。该密钥将用于缓存我们的缩小脚本。如果您不创建此密钥,您的应用程序将不会使用缓存。我建议使用这个。因此,每次更新脚本/样式时,也要更新您的发布日期。

<add key="PublishDate" value="261020111245" />

Sorry for my bad English, but i guess it won't be a problem. I just wan't to share a nice Helper Class that i made to combine, minify and gzip our scripts and styles using the Microsoft Ajax Minifier. Before start, download the ICSharpCode.SharpZipLib. This is an open source lib to use gzip.

Let's start by the web.config (i'll focus at IIS7). Here we are saying to our application that any request made to cssh or jsh extensions, will be forwarded to the class MinifierHelper. I chosen to use these extensions (cssh and jsh) to if we want, for any reason, don't minify a specific script or style, use it the way you use normally.

<system.webServer>
  <handlers>
    <remove name="ScriptHandler" />
    <remove name="StyleHandler" />
    <add name="ScriptHandler" verb="*" path="*.jsh" type="Site.Helpers.MinifierHelper" resourceType="Unspecified" />
    <add name="StyleHandler" verb="*" path="*.cssh" type="Site.Helpers.MinifierHelper" resourceType="Unspecified" />
  </handlers>
</system.webServer>

I use the folders Scripts and Styles to store the files. I don't use the folder Content as suggested by Visual Studio.

The next step is to configure global.asax. We have to tell our application to not route these folders. Add these lines to your RegisterRoutes Method.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("Scripts/{*path}");
    routes.IgnoreRoute("Styles/{*path}");
    ...
}

OK. Now i'll show how to use our class. In the View:

<link href="/Styles/Folder/File.cssh" type="text/css" rel="stylesheet" />
<script src="/Scripts/Folder/File.jsh" type="text/javascript"></script>

In my example, i made the logic to all my scripts and styles to be inside folders inside the Scripts/Styles. Ex: Site -> Scripts -> Home -> index.css. I use the same structure used to the Views to the Scripts and Styles. Ex: Site -> Views -> Home -> index.cshtml. You can change this pattern if you want.

Now the code to make the magic:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Caching;
using System.Web.SessionState;
using ICSharpCode.SharpZipLib.GZip;
using Microsoft.Ajax.Utilities;

namespace Site.Helpers
{
    public abstract class MinifierBase : IHttpHandler, IRequiresSessionState
    {
        #region Fields

        private HttpContext context;
        private byte[] responseBytes;
        private bool isScript;

        protected string fileName;
        protected string folderName;
        protected List<string> files;

        #endregion

        #region Properties

        public bool IsReusable
        {
            get { return false; }
        }

        #endregion

        #region Methods

        public static string setUrl(string url)
        {
            var publishDate = ConfigurationManager.AppSettings["PublishDate"];
            return url + "h" + ((publishDate != null) ? "?id=" + publishDate : "");
        }

        public void ProcessRequest(HttpContext context)
        {
            this.context = context;
            this.isScript = context.Request.Url.PathAndQuery.Contains("/Scripts/");
            this.process();
        }

        private void process()
        {
            if (this.context.Request.QueryString.HasKeys())
            {
                string url = this.context.Request.Url.PathAndQuery;

                if (this.context.Cache[url] != null)
                {
                    this.responseBytes = this.context.Cache[url] as byte[];
                }
                else
                {
                    this.writeResponseBytes();
                    this.context.Cache.Add
                    (
                        url,
                        this.responseBytes,
                        null,
                        DateTime.Now.AddMonths(1),
                        Cache.NoSlidingExpiration,
                        CacheItemPriority.Low,
                        null
                    );
                }
            }
            else
            {
                this.writeResponseBytes();
            }

            this.writeBytes();
        }

        private void writeResponseBytes()
        {
            using (MemoryStream ms = new MemoryStream(8092))
            {
                using (Stream writer = this.canGZip() ? (Stream)(new GZipOutputStream(ms)) : ms)
                {
                    var sb = new StringBuilder();
                    var regex = new Regex(@"^/.+/(?<folder>.+)/(?<name>.+)\..+");
                    var url = regex.Match(this.context.Request.Path);
                    var folderName = url.Groups["folder"].Value;
                    var fileName = url.Groups["name"].Value;

                    this.getFileNames(fileName, folderName).ForEach(delegate(string file)
                    {
                        sb.Append(File.ReadAllText(this.context.Server.MapPath(file)));
                    });

                    var minifier = new Minifier();
                    var minified = string.Empty;

                    if (this.isScript)
                    {
                        var settings = new CodeSettings();

                        settings.LocalRenaming = LocalRenaming.CrunchAll;
                        settings.OutputMode = OutputMode.SingleLine;
                        settings.PreserveImportantComments = false;
                        settings.TermSemicolons = true;

                        minified = minifier.MinifyJavaScript(sb.ToString(), settings);
                    }
                    else
                    {
                        var settings = new CssSettings();

                        settings.CommentMode = CssComment.Important;
                        settings.OutputMode = OutputMode.SingleLine;

                        minified = minifier.MinifyStyleSheet(sb.ToString(), settings);
                    }

                    var bts = Encoding.UTF8.GetBytes(minified);

                    writer.Write(bts, 0, bts.Length);
                }

                this.responseBytes = ms.ToArray();
            }
        }

    private List<String> getFileNames(string fileName, string folderName = "")
    {
        this.files = new List<String>();
        this.fileName = fileName;
        this.folderName = folderName;

        if (folderName == "Global" && fileName == "global-min")
        {
            if (this.isScript) this.addGlobalScripts();
            else this.addDefaultStyles();
        }
        else
        {
            var flags = BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance;
            var mi = this.GetType().GetMethod
            (
                "add" +
                this.folderName +
                CultureInfo.CurrentCulture.TextInfo.ToTitleCase(fileName).Replace("-", "") +
                (this.isScript ? "Scripts" : "Styles"),
                flags
            );

            if (mi != null)
            {
                mi.Invoke(this, null);
            }
            else
            {
                if (this.isScript) this.addDefaultScripts();
                else this.addDefaultStyles();
            }
        }

        return files;
    }

    private void writeBytes()
    {
        var response = this.context.Response;

        response.AppendHeader("Content-Length", this.responseBytes.Length.ToString());
        response.ContentType = this.isScript ? "text/javascript" : "text/css";

        if (this.canGZip())
        {
            response.AppendHeader("Content-Encoding", "gzip");
        }
        else
        {
            response.AppendHeader("Content-Encoding", "utf-8");
        }

        response.ContentEncoding = Encoding.Unicode;
        response.OutputStream.Write(this.responseBytes, 0, this.responseBytes.Length);
        response.Flush();
    }

    private bool canGZip()
    {
        string acceptEncoding = this.context.Request.Headers["Accept-Encoding"];
        return (!string.IsNullOrEmpty(acceptEncoding) && (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate")));
    }

    protected abstract void addGlobalScripts();
    protected abstract void addGlobalStyles();
    protected abstract void addDefaultScripts();
    protected abstract void addDefaultStyles();

    #endregion
}

That's the base class. Now we will create the Helper class that inherits from the base class. That's the class where we choose which scripts should be added.

public class MinifierHelper : MinifierBase
{
    #region Methods

To combine and minify global scripts/styles add the current line to your View:

<link href="@MinifierHelper.setUrl("/Styles/Global/global-min.css")" type="text/css" rel="stylesheet" />
<script src="@MinifierHelper.setUrl("/Scripts/Global/global-min.js")" type="text/javascript"></script>

It'll invoke the methods addGlobalScripts/addGlobalStyles in our MinifierHelper class.

    protected override void addGlobalScripts()
    {
        this.files.Add("~/Scripts/Lib/jquery-1.6.2.js");
        this.files.Add("~/Scripts/Lib/jquery-ui-1.8.16.js");
        this.files.Add("~/Scripts/Lib/jquery.unobtrusive-ajax.js");
        this.files.Add("~/Scripts/Lib/jquery.validate.js");
        ...
    }

    protected override void addGlobalStyles()
    {
        this.files.Add("~/Styles/Global/reset.css");
        this.files.Add("~/Styles/Global/main.css");
        this.files.Add("~/Styles/Global/form.css");
        ...
    }

To minify specific script/style (specific to the page) add the current line to your View:

<link href="@MinifierHelper.setUrl("/Styles/Curriculum/index.css")" type="text/css" rel="stylesheet" />

The MinifierHelper class will try to find a method with the name "add" + FolderName + FileName + "Styles". In our case it will look for addCurriculumIndexStyles. In my example it exists, so the method will be triggered.

    public void addCurriculumIndexStyles()
    {
        this.files.Add("~/Styles/Global/curriculum.css");
        this.files.Add("~/Styles/Curriculum/personal-info.css");
        this.files.Add("~/Styles/Curriculum/academic-info.css");
        this.files.Add("~/Styles/Curriculum/professional-info.css");
    }

If the class don't find that specific method, it'll trigger the default method. The default method minify a script/style using the same folder/name specified.

protected override void addDefaultScripts()
{
    this.files.Add("~/Scripts/" + this.folderName + "/" + this.fileName + ".js");
}

protected override void addDefaultStyles()
{
    this.files.Add("~/Styles/" + this.folderName + "/" + this.fileName + ".css");
}

Don't forget to close the region and class.

    #endregion
}

Thats it. I hope you guys have understood.

I forgot to tell one last thing. In the web.config add a key in AppSettings with the name PublishDate. I put in the value a string with the full date and time (ex: 261020111245). The goal is to be unique. This key will be used to cache our minified scripts. If you don't create this key, your application won't use cache. I recommend to use this. So everytime you update your scripts/styles, update your PublishDate also.

<add key="PublishDate" value="261020111245" />

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

老子叫无熙 2024-12-18 21:49:12

Mindscape Web Workbench 是完成您正在寻找的项目的绝佳工具。

http://visualstudiogallery.msdn.microsoft.com/2b96d16a-c986-4501-8f97-8008f9db141a

这是一篇关于它的很好的博客文章:

http://visualstudiogallery.msdn.microsoft.com/2b96d16a-c986-4501-8f97 -8008f9db141a

The Mindscape Web Workbench is a great tool for doing the items you are looking for.

http://visualstudiogallery.msdn.microsoft.com/2b96d16a-c986-4501-8f97-8008f9db141a

Here is a good blog post about it:

http://visualstudiogallery.msdn.microsoft.com/2b96d16a-c986-4501-8f97-8008f9db141a

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文