Комплексное решение для объединения, минимизации и архивирования ваших стилей и скриптов на Asp.Net MVC3 с использованием Бритвы

#asp.net-mvc-3 #caching #gzip #minify

#asp.net-mvc-3 #кэширование #gzip #минимизировать

Вопрос:

Извините за мой плохой английский, но я думаю, это не будет проблемой. Я просто не хочу делиться хорошим вспомогательным классом, который я создал для объединения, минимизации и архивирования наших скриптов и стилей с помощью минификатора Microsoft Ajax. Перед запуском загрузите ICSharpCode.Острый щелчок. Это библиотека с открытым исходным кодом для использования gzip.

Давайте начнем с web.config (я сосредоточусь на IIS7). Здесь мы говорим нашему приложению, что любой запрос, направленный на расширения cssh или jsh, будет перенаправлен в класс MinifierHelper . Я решил использовать эти расширения (cssh и jsh), чтобы, если мы хотим по какой-либо причине не уменьшать конкретный скрипт или стиль, использовать его так, как вы используете обычно.

 <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>
 

Я использую скрипты и стили папок для хранения файлов. Я не использую содержимое папки, как это было предложено Visual Studio.

Следующим шагом является настройка 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>
 

В моем примере я сделал так, чтобы логика всех моих скриптов и стилей находилась внутри папок внутри скриптов / стилей. Пример: Site -> Scripts -> Home -> index.css. Я использую ту же структуру, что и для представлений скриптов и стилей. Пример: Site -> Views -> Home -> index.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" amp;amp; 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) amp;amp; (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");
        ...
    }
 

Чтобы минимизировать конкретный сценарий / стиль (специфичный для страницы), добавьте текущую строку в свой вид:

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

Класс MinifierHelper попытается найти метод с именем «add» FolderName FileName «Стили». В нашем случае он будет искать 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" />
 

Комментарии:

1. Очень приятно! Из-за характера формата вопрос / ответ, который мы ожидаем увидеть на этом сайте, было бы лучше перенести ваше решение в answer, а затем принять ваш ответ.

2. Спасибо. Поскольку я новый участник, я не могу ответить на свой собственный вопрос 🙂

3. Это было бы проблемой! Я рекомендую вам продолжать пользоваться сайтом, и когда вы получите достаточно репутации (вам не нужно больше 25 или около того), тогда вы сможете создать ответ 🙂

4. Спасибо за совет. Я так и сделаю. Мне нужны положительные отзывы, чтобы получить несколько очков, хе-хе.

5. 1 Мне нравится, когда люди думают о подобных вещах в рамках проектов!

Ответ №1:

Веб-верстак Mindscape — отличный инструмент для выполнения задач, которые вы ищете.

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

Вот хороший пост в блоге об этом:

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