Enabling bundling and minification on 3rd party libraries - asp.net-mvc

I am including a big javascript application into our MVC-based solution. However, the application includes a lot of files due to which I would like to enable bundling and minification on it. In fact, I would like to enable bundling on all 3rd party javascript and CSS files while keeping the files we develop ourselves unminified and unbundled. Until release, of course.
There is way to enable optimizations globally:
public static void RegisterBundles(BundleCollection bundles)
{
Bundle ckScripts = new ScriptBundle("~/scripts/ckeditor")
.IncludeDirectory("~/Areas/CMS/Editor", "*.js", true);
bundles.Add(ckScripts);
BundleTable.EnableOptimizations = true;
}
However, this happens in the top BundleTable level enabling optimizations on all the bundles within the bundle table.
I would need to have something like this:
public static void RegisterBundles(BundleCollection bundles)
{
Bundle ckScripts = new ScriptBundle("~/scripts/ckeditor")
.IncludeDirectory("~/Areas/CMS/Editor", "*.js", true)
.EnableOptimizations();
bundles.Add(ckScripts);
}
Which would effectively enable optimizations only for that particular bundle.
I know, there is currently no Bundle.EnableOptimizations() method and creating such given the fact that the optimization happens in the BundleTable level, which is inherently global by design, creating such method, would prove very difficult.
So, here I in loss of ideas where to look into.
Questions:
Is there an alternative framework somewhere that would support this
Is there a contrib project somewhere that would provide this
Have you encountered such need, possibly have a solution
Provided that there's no existing solution, please post an idea how to start unfolding this challenge.
From what I know, BundleTable is a singleton. Which means there can be only one instance. I had an idea of creating another bundle table but got lost when I started figuring out how to make MVC use it.
Another starting point would be to code a custom renderer. One that mimics the behavior of System.Web.Optimization.Scripts.Render(), but again, I'm getting lost with it trying to figure out in which state the BundleTable comes into picture.
UPDATE
Seems like I can create a new BundleContext and BundleResponse by using a HtmlHelper.
public static IHtmlString RenderBundled<TSource>(this HtmlHelper<TSource> helper, string bundlePath)
{
// Find the bundle in question
var bundle = BundleTable.Bundles.FirstOrDefault(b => b.Path == bundlePath);
// No bundle found, return
if (bundle == null) return MvcHtmlString.Create(String.Empty);
// Add the bundle found into a new collection
BundleCollection coll = new BundleCollection {bundle};
// Create a new BundleContext
BundleContext ctx = new BundleContext(helper.ViewContext.HttpContext, coll, "~/bundles");
// Enable optimizations
ctx.EnableOptimizations = true;
// Create the response (this contains bundled & minified script/styles from bundle)
BundleResponse response = bundle.GenerateBundleResponse(ctx);
// Render the content based on ContentType
if (response.ContentType == "text/css")
return RenderStyle(response.Content);// returns <style>bundled content</style>
if (response.ContentType == "text/javascript")
return RenderScript(response.Content); // returns <script>bundled content</script>
// In any other case return "nothing"
return MvcHtmlString.Create(String.Empty);
}
This probably is not the best approach. Overhead from creating BundleContext on every page request and adding the script/styles payload into the page output without the caching abilities. But it's a start. One thing I noticed was that the bundled content will actually be cached in HttpContext.Cache. So, theoretically I could just put the bundle path into script src or style href and somehow then handle the request from server side.

Related

Can I set a single .NET MVC bundle to always be bundled (even in debug mode)?

I am using .LESS variables in my files. I have a LessTransform in my Bundler, which allows all my .less to see the variables. But when I turn bundling off, obviously it no longer works!
Can I see just a single bundle to always be bundled? (even when compilation debug=true)
Unfortunately it's an all or nothing setup (determined very early on by AssetManager.DeterminePathsToRender which, based on EnableOptimizations, either emits a bundle URL or individual script paths).
You could look into using the WebEssentials extension which handles .less (as well as other) files natively. At least then you'll be able to include the compiled version and let you move onto more important matters. Once you've finalized, you can bring bundling back into the equation.
I do not work on/for WebEssentials, I just find the extension very helpful
In the main application that I work with, we use the DotLess compiler directly to serve our stylesheets.
We store custom .LESS variables in the database and combine them with the .less file on the fly.
using System.Web.Mvc;
using dotless.Core;
using System.Web.Helpers;
public class SkinController : Controller
{
private const int TwentyMinutes = 1200;
[OutputCache(Duration = TwentyMinutes, VaryByParam = "*", VaryByContentEncoding = "gzip;deflate", VaryByCustom = "Scheme")]
public ActionResult Index()
{
string variablesFromDatabase = "these came from the database";
string lessFileContents = "this was read from the disk";
string content = Less.Parse(string.Concat(variablesFromDatabase, lessFileContents));
SetEtag(content);
return Content(content, "text/css");
}
private void SetEtag(string content)
{
string acceptEncoding = Request.Headers["Accept-Encoding"];
string value = string.Concat(content, acceptEncoding);
Response.AppendHeader("etag", string.Format("\"{0}\"", Crypto.Hash(value, "md5")));
}
}

ImageResizer and image files without an extension

I've been using ImageResizer.net just fine in our web app, but now I need it to resize and serve images that don't (and cannot) have a file extension, like this one:
http://localhost:58306/ClientImages/Batch/2012/12/10/f45198b7c452466684a4079de8d5f85f?width=600
In this situation, I know that my files are always TIFF's, but they wont have a file extension.
What are my options?
/resizer.debug.ashx: https://gist.github.com/raw/9c867823c983f0e5be10/4db31cb21af8b9b36f0aa4e765f6f459ba4b309f/gistfile1.txt
Update
I followed Computer Linguist's instructions:
protected void Application_Start()
{
Config.Current.Pipeline.PostAuthorizeRequestStart +=
delegate
{
var path = Config.Current.Pipeline.PreRewritePath;
var clientImgsRelPath = PathUtils.ResolveAppRelative("~/ClientImages/");
var isClientImageRequest = path.StartsWith(clientImgsRelPath, StringComparison.OrdinalIgnoreCase);
if (isClientImageRequest)
Config.Current.Pipeline.SkipFileTypeCheck = true;
};
// other app start code here
}
http://localhost:58306/ClientImages/Batch/2012/12/10/92d67b45584144beb5f791aaaf760252?width=600 just responds with the original image with no resizing.
This was also asked about here: http://imageresizing.net/docs/howto/cache-non-images#comment-571615564
This is happening during development with the Cassini or Visual Studio web server or whatever you want to call it.
First, you MUST be using IIS7 Integrated mode. Classic mode will not work; it does not permit ASP.NET access to extensionless requests
ImageResizer can't know that extension-less URLs are images unless you explicitly tell it.
This doc explains:
http://imageresizing.net/docs/howto/cache-non-images
Essentially, you'll end up performing logic (usually String.StartsWith) on your URLs to find out if ImageResizer should treat the file as an image.
Config.Current.Pipeline.PostAuthorizeRequestStart += delegate(IHttpModule sender, HttpContext context) {
string path = Config.Current.Pipeline.PreRewritePath;
//Skip the file extension check for everything in this folder:
if (path.StartsWith(PathUtils.ResolveAppRelative("~/folder/of/images"),
StringComparison.OrdinalIgnoreCase)){
Config.Current.Pipeline.SkipFileTypeCheck = true;
}
};
You should register this event handler in Application_Start in global.asax.

Absolute URL in ASP bundle

I use a jQuery library for Google Maps, and it depends on the Google scripts to be loaded first. I'd like to be able to include both in the bundle as such:
bundles.Add(new ScriptBundle("myfoobundle").Include(
"http://maps.googleapis.com/maps/api/js?sensor=false&libraries=places",
"~/scripts/jquery.fooplugin-{version}.js"
));
This doesn't seem to work (throws an exception complaining about the first string). And one may say that this shouldn't work because that absolute URL is not meant to be minified/bundled.
But the current approach is a hassle, as I need to ensure that the dependencies are correct, and that happens in different places (half the problem in the bundling code, the other half in the view).
Would be nice to have a 1-step solution as above. Do I have any options in this regard?
UPDATE:
To address the comments regarding using a CDN as a solution: if I specify bundles.UseCdn = true it has no effect, and I still get the exception The URL 'http://maps.googleapis.com/maps/api/js?sensor=false&libraries=places' is not valid. Only application relative URLs (~/url) are allowed. Also I'm unsure what the implication of doing that is in the first place, because I already use CDN support for jQuery, etc., so unsure how that would conflict with my use case.
If you are using a version of System.Web.Optimization >= 1.1.2, there is a new convenient way of overriding the url's for Styles and Scripts. In the example below, I am grabbing a CdnBaseUrl from web.config to use as the base url for all scripts and stylesheets:
public class BundleConfig
{
private static readonly string BaseUrl = ConfigurationManager.AppSettings["CdnBaseUrl"];
public static void RegisterBundles(BundleCollection bundles)
{
// This is the new hotness!!
Styles.DefaultTagFormat = "<link href=\"" + BaseUrl + "{0}\" rel=\"stylesheet\"/>";
Scripts.DefaultTagFormat = "<script src=\"" + BaseUrl + "{0}\"></script>";
bundles.Add(new ScriptBundle("~/bundles/js").Include(
"Your scripts here..."
));
bundles.Add(new StyleBundle("~/bundles/css").Include(
"Your css files here..."
));
}
}
More info on static site (CDN) optimization
Currently you would have to include a local copy of the jquery that you are depending on inside of the bundle, or you would have to manage the script tags as you mention. We are aware of this kind of depedency management issue and it falls under the category of asset management which we are tracking with this work item on codeplex
Based on the MVC tutorials, your syntax is incorrect for creating a bundle from a CDN. And as others have said, ensure that you have the bundles.UseCdn = true; property set. Using the example on the MVC site - your code should reflect the following:
public static void RegisterBundles(BundleCollection bundles)
{
bundles.UseCdn = true; //enable CDN support
//add link to jquery on the CDN
var jqueryCdnPath = "http://maps.googleapis.com/maps/api/js?sensor=false&libraries=places";
bundles.Add(new ScriptBundle("myfoobundle", jqueryCdnPath).Include(
"~/Scripts/jquery-{version}.js"));
}
If it is just a matter of getting absolute url in bundle then you can go for this.
public static class Extensions
{
public static IHtmlString RenderScript(this UrlHelper helper, params string[] paths)
{
string scripts = System.Web.Optimization.Scripts.Render(paths).ToHtmlString();
string hostName = HttpContext.Current.Request.Url.Scheme + Uri.SchemeDelimiter + HttpContext.Current.Request.Url.Authority;
string replaced = Regex.Replace(scripts, "src=\"/", "src=\"" + hostName + "/", RegexOptions.Multiline | RegexOptions.IgnoreCase);
return new HtmlString(replaced);
}
}
This will basically take the bahvior from Scripts.Render and then apply absolute urls to it. Then in the view you have to write
#Url.RenderScript("~/bundles/jquery")
instead of
#Scripts.Render("~/bundles/jquery")
Enjoy coding!!...
I tried this as suggested and it didn't work:
string googleMapsApiCDN = "http://maps.google.com/maps/api/js?sensor=false&language=en";
bundles.Add(new ScriptBundle("~/bundles/gmap3", googleMapsApiCDN).Include(
"~/Scripts/GMap3/gmap3.min.js", // GMap3 library
"~/Scripts/GMap3/mygmap3-about.js" // Pops up and configures
GMap3 on About page
));
The mygmap3-about.js script was rendered but the gmap3.min.js and the CDN script from google where both excluded.
Have you tried enabling CDN support and seeing if that allows the absolute URL to work:
bundles.UseCdn = true;

OSGi/Equinox: How do I convert a resource URI into a bundle name?

After looking up a resource on the classpath, I got this URL:
bundleresource://23.fwk1186515174/com/google/inject/Injector.class
How can I find out in which bundle provided the resource?
[EDIT] I'm trying to debug a problem where I have duplicate classes on the classpath. Here is the code I'm using:
private void debugClassPath() {
String resource = "com/google/inject/Injector.class";
try {
Enumeration<URL> urls = getClass().getClassLoader().getResources( resource );
while( urls.hasMoreElements() ) {
System.out.println(urls.nextElement());
}
System.out.println("---");
urls = XtextRunner.class.getClassLoader().getResources( resource );
while( urls.hasMoreElements() ) {
System.out.println(urls.nextElement());
}
} catch( IOException e ) {
e.printStackTrace();
}
}
That gives me several URLs for com.google.inject.Injector and I want to figure out which bundles add them to the classpath.
OSGi urls guarantee the hierarchy. So just replace com/google/inject/Injector.class with META-INF/MANIFEST.MF and read the resource as a manifest (or text file). The information in there tells you which bundle you're looking at.
Duplicate classes can ONLY happen with split packages which is considered bad (very bad imho) practice. In OSGi, split packages require Require-Bundle with re-export or Bundle-Classpath. Life is a lot easier without these ...
One hacky way is to parse the URL, but this will only work when running on Equinox. According to the source for BundleResourceHandler , that integer ('23') to the right of the URL scheme is the bundle ID. If you have a BundleContext (check your Activator) then you can find which bundle this is with that ID using getBundle(long).
There's likely to be a much better way of doing this however. How do you look up the resource on the classpath?
There is no standard way to do this. However, for Equinox, there is an implementation specific way (subject to change in future releases because it is an implementation specific way) by seeing how the URL handler encodes it in the authority.

File IO within an ASP.NET MVC Action

Is it possible to use some kind of 'critical section' so that it is safe to do something like the following within an action...
public ActionResult GenerateTasks()
{
string someDir = ....
if (!Directory.Exists(someDir))
{
Directory.CreateDirectory(someDir);
}
...
}
You can do this only by using a system-wide mutex. Process or app-domain locking primitives will fail to work under certain conditions (for instance when an application pool is recycled).
However, for the specific case here that's not necessary: Directory.CreateDirectory already does implement an existence check on its own, so that you shouldn't need to do anything in this regard.
I'm assuming by your question that the concurrent safety you're interested in is whether or not the directory is created between the Directory.Exists and the Directory.CreateDirectory on a different thread. (If you're concerned about Directory.CreateDirectory throwing an exception if the directory already exists, it won't.) If so, and this is the point in your code that will have the potential to do that, then you can simply use a lock object to make these set of operations safe across multiple threads:
private static object lockObject = new object();
public ActionResult GenerateTasks()
{
string someDir = ....
lock(lockObject)
{
if (!Directory.Exists(someDir))
{
Directory.CreateDirectory(someDir);
}
}
...
}
This does not however make any garauntees that the directory isn't being interacted with outside of your control, say, in another application process.

Resources