ASP.NET Bundling/Minification and Embedded Resources
Introduction:
If you want to share your application resources(like css, javascript, images, etc) between different projects then embedded resource is a great choice. Embedded resource is also good for component/control writers because it allows component/control writers to distribute all the application resources with just a single assembly. A lot of vendors are already using this approach. It will great for component/control writers if they can leverage the ASP.NET bundling and minification for improving the performance. So, in this article I will show you how to write a very basic component(helper) which will use the ASP.NET bundling and minification feature on embedded javascript/css files.
Description:First of all create a new Class Library project and install the Microsoft.AspNet.Mvc, WebActivator and Microsoft ASP.NET Web Optimization Framework 1.1.0-alpha1(make sure to include the -Pre parameter in Package Manager Console) nuget packages. Next, add a reference of System.Web assembly. Then, create your control/component/helper. For demonstration purpose, I will use this sample helper,
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);
}
}In this helper, I am just using a textbox with a style and script
bundle. Style bundle include 2 css files and script bundle include 2 js
files. So, just create 2 css files(NewTextBox1.css and NewTextBox2.css)
and 2 javascript files(NewTextBox1.js and NewTextBox2.js) and then mark
these files as embedded resource. Next, add a AppStart.cs file and add
the following lines inside this file,[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()
));
}
}
}The above class using WebActivator's PostApplicationStartMethod,
which allows your assembly to run some code after the Application_Start
method of global.asax. The Start method simply register a custom
virtual path provider and two bundles which are used in our NewText
helper class. But ASP.NET optimization framework will only emit a bundle
url when debug="false" or when BundleTable.EnableOptimizations =
true. Therefore, we also need to handle the normal javascript and css
requests. For this case, the above method has also register a specific
route for handling embedded resource requests using
EmbeddedResourceRouteHandler route handler. Here is the definition of
this handler, 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 returns EmbeddedResourceHttpHandler http
handler which will be used to extract embedded resource file from the
assembly and then write the file to the response body. Now, the only
missing thing is 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 class is self explanatory. It simply maps a
bundling url(used above) and return the embedded request
as stream. Note, this class will be invoked by ASP.NET
optimization framework during bundling and minifying process. Now, just
build your component/control/helper assembly. Then, create a sample
ASP.NET(MVC) application and then use this component/control/helper in
your page. For example, like,@using ImranB.Helpers
@Html.NewTextBox("New")Summary:
In this article, I showed you how to create a component/control/helper that leverage the ASP.NET Optimization framework. A sample application is available at github for download. Hopefully you will enjoy my this article too.
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)





