Статьи

ASP.NET Bundling / Minification и встроенные ресурсы

Вступление:

Если вы хотите разделить ресурсы своего приложения (например, CSS, JavaScript, изображения и т. Д.) Между различными проектами, тогда встроенный ресурс — отличный выбор. Встроенный ресурс также удобен для разработчиков компонентов / элементов управления, поскольку он позволяет разработчикам компонентов / элементов управления распределять все ресурсы приложения всего за одну сборку. Многие поставщики уже используют этот подход. Разработчикам компонентов / элементов управления будет хорошо, если они смогут использовать пакетирование и минимизацию ASP.NET для повышения производительности. Итак, в этой статье я покажу вам, как написать очень простой компонент (помощник), который будет использовать функцию связывания и минимизации ASP.NET для встроенных файлов javascript / css.

Описание:

Прежде всего создайте новый проект библиотеки классов и установите пакеты nuget для Microsoft.AspNet.Mvc , WebActivator  и Microsoft ASP.NET Web Optimization Framework 1.1.0-alpha1 (не забудьте включить параметр -Pre в Консоль диспетчера пакетов). Далее добавьте ссылку на сборку System.Web. Затем создайте свой элемент управления / компонент / помощник. Для демонстрации я буду использовать этот пример помощника,

public static class HtmlHelpers
        {
            public static MvcHtmlString NewTextBox(this HtmlHelper html, string name)
            {
                var js = Scripts.Render("~/ImranB/Embedded/Js").ToString();
                var css = Scripts.Render("~/ImranB/Embedded/Css").ToString();
                var textbox = html.TextBox(name).ToString();
                return MvcHtmlString.Create(textbox + js + css);
            }
}

В этом помощнике я просто использую текстовое поле со стилем и набором скриптов. Пакет стилей включает 2 файла CSS, а пакет скриптов включает 2 файла JS. Итак, просто создайте 2 файла css (NewTextBox1.css и NewTextBox2.css) и 2 файла javascript (NewTextBox1.js и NewTextBox2.js), а затем пометьте эти файлы как встроенный ресурс. Затем добавьте файл AppStart.cs и добавьте в него следующие строки:

[assembly: WebActivator.PostApplicationStartMethod(typeof(AppStart), "Start")]
        namespace ImranB
        {
            public static class AppStart
            {
                public static void Start()
                {
                    ConfigureRoutes();
                    ConfigureBundles();
                }
                private static void ConfigureBundles()
                {
                    BundleTable.VirtualPathProvider = new EmbeddedVirtualPathProvider(HostingEnvironment.VirtualPathProvider);
                    BundleTable.Bundles.Add(new ScriptBundle("~/ImranB/Embedded/Js")
                        .Include("~/ImranB/Embedded/NewTextBox1.js")
                        .Include("~/ImranB/Embedded/NewTextBox2.js")
                        );
                    BundleTable.Bundles.Add(new StyleBundle("~/ImranB/Embedded/Css")
                        .Include("~/ImranB/Embedded/NewTextBox1.css")
                        .Include("~/ImranB/Embedded/NewTextBox2.css")
                        );
                }
                private static void ConfigureRoutes()
                {
                    RouteTable.Routes.Insert(0,
                        new Route("ImranB/Embedded/{file}.{extension}",
                            new RouteValueDictionary(new { }),
                            new RouteValueDictionary(new { extension = "css|js" }),
                            new EmbeddedResourceRouteHandler()
                        ));
                }
            }
        }

Вышеупомянутый класс использует PostApplicationStartMethod WebActivator , который позволяет вашей сборке запускать некоторый код после метода Application_Start файла global.asax. Метод Start просто регистрирует пользовательский поставщик виртуального пути и два пакета, которые используются в нашем вспомогательном классе NewText. Но платформа оптимизации ASP.NET будет выдавать URL-адрес пакета только тогда, когда debug = «false» или когда BundleTable.EnableOptimizations = true. Поэтому нам также нужно обрабатывать обычные запросы javascript и css. Для этого случая вышеупомянутый метод также зарегистрировал определенный маршрут для обработки запросов встроенных ресурсов, используя обработчик маршрута EmbeddedResourceRouteHandler. Вот определение этого обработчика,

public class EmbeddedResourceRouteHandler : IRouteHandler
        {
            IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
            {
                return new EmbeddedResourceHttpHandler(requestContext.RouteData);
            }
        }
        public class EmbeddedResourceHttpHandler : IHttpHandler
        {
            private RouteData _routeData;
            public EmbeddedResourceHttpHandler(RouteData routeData)
            {
                _routeData = routeData;
            }
            public bool IsReusable
            {
                get { return false; }
            }
            public void ProcessRequest(HttpContext context)
            {
                var routeDataValues = _routeData.Values;
                var fileName = routeDataValues["file"].ToString();
                var fileExtension = routeDataValues["extension"].ToString();
                string nameSpace = typeof(EmbeddedResourceHttpHandler)
                                    .Assembly
                                    .GetName()
                                    .Name;// Mostly the default namespace and assembly name are same
                string manifestResourceName = string.Format("{0}.{1}.{2}", nameSpace, fileName, fileExtension);
                var stream = typeof(EmbeddedResourceHttpHandler).Assembly.GetManifestResourceStream(manifestResourceName);
                context.Response.Clear();
                context.Response.ContentType = "text/css";// default
                if (fileExtension == "js")
                    context.Response.ContentType = "text/javascript";
                stream.CopyTo(context.Response.OutputStream);
            }
        }

EmbeddedResourceRouteHandler возвращает http-обработчик EmbeddedResourceHttpHandler, который будет использоваться для извлечения файла встроенного ресурса из сборки и последующей записи файла в тело ответа. Теперь единственная недостающая вещь — это EmbeddedVirtualPathProvider, 

public class EmbeddedVirtualPathProvider : VirtualPathProvider
        {
            private VirtualPathProvider _previous;
            public EmbeddedVirtualPathProvider(VirtualPathProvider previous)
            {
                _previous = previous;
            }
            public override bool FileExists(string virtualPath)
            {
                if (IsEmbeddedPath(virtualPath))
                    return true;
                else
                    return _previous.FileExists(virtualPath);
            }
            public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
            {
                if (IsEmbeddedPath(virtualPath))
                {
                    return null;
                }
                else
                {
                    return _previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
                }
            }
            public override VirtualDirectory GetDirectory(string virtualDir)
            {
                return _previous.GetDirectory(virtualDir);
            }
            public override bool DirectoryExists(string virtualDir)
            {
                return _previous.DirectoryExists(virtualDir);
            }
            public override VirtualFile GetFile(string virtualPath)
            {
                if (IsEmbeddedPath(virtualPath))
                {
                    string fileNameWithExtension = virtualPath.Substring(virtualPath.LastIndexOf("/") + 1);
                    string nameSpace = typeof(EmbeddedResourceHttpHandler)
                                    .Assembly
                                    .GetName()
                                    .Name;// Mostly the default namespace and assembly name are same
                    string manifestResourceName = string.Format("{0}.{1}", nameSpace, fileNameWithExtension);
                    var stream = typeof(EmbeddedVirtualPathProvider).Assembly.GetManifestResourceStream(manifestResourceName);
                    return new EmbeddedVirtualFile(virtualPath, stream);
                }
                else
                    return _previous.GetFile(virtualPath);
            }
            private bool IsEmbeddedPath(string path)
            {
                return path.Contains("~/ImranB/Embedded");
            }
        }

    public class EmbeddedVirtualFile : VirtualFile
    {
        private Stream _stream;
        public EmbeddedVirtualFile(string virtualPath, Stream stream)
            : base(virtualPath)
        {
            _stream = stream;
        }
        public override Stream Open()
        {
            return _stream;
        }
    } 

Класс EmbeddedVirtualPathProvider не требует пояснений. Он просто отображает объединенный URL (использованный выше) и возвращает встроенный запрос в виде потока. Обратите внимание, что этот класс будет вызываться платформой оптимизации ASP.NET во время процесса связывания и минимизации. Теперь просто создайте сборку ваш компонент / управление / помощник. Затем создайте пример приложения ASP.NET (MVC) и затем используйте этот компонент / control / helper на своей странице. Например, как,

@using ImranB.Helpers
@Html.NewTextBox("New")

Резюме:

В этой статье я показал вам, как создать компонент / control / helper, который использует среду оптимизации ASP.NET. Пример приложения доступен на github для скачивания. Надеюсь, вам тоже понравится моя статья.