Your HTML, CSS, JavaScript and images working together

Co-locate HTML, CSS, JavaScript and image files that make up a page or component. Auto generate your MVC bundles to ensure the right files are loaded in the right order.

Key benefits

Dynamic Bundles is an extension of the Razor view engine and MVC bundles. It greatly improves maintainability and code reuse of ASP.NET MVC sites:

  • Co-locate the HTML, CSS, JavaScript and images that make up a page or component in the same directory, instead of organizing these files in separate directories by type. This clearly exposes dependencies.
  • Auto generate CSS and JavaScript bundles that contain the right files in the right order, based on their file organisation. No need to recompile when files are added or deleted. Caching keeps CPU usage and disk accesses minimal.
  • Provides the same minification and file combining as standard MVC bundles.

File Structure

Classic MVC

  • HTML, CSS, JavaScript and image files organized in separate directories by type.
  • CSS and JavaScript that support different pages and components often put in the same physical files, creating hidden dependencies.
  • Long brittle urls from CSS files to background images.
  • Unclear what CSS, JavaScript and images are required for a given component, making reuse harder.

Dynamic Bundles

  • HTML, CSS, JavaScript and image files that belong together sit in the same directory.
  • The view engine included in Dynamic Bundles lets you put partial views and layout files in their own sub directories.
  • Splitting CSS, JavaScript by component encourages developers to keep these files small and focussed.
  • Short simple image background image urls in your CSS.
  • Co-locating all assets that make up a component makes reuse much easier.

Bundles

Classic MVC

  • You have to create and maintain bundles yourself.
  • You have to make sure to include the correct files, and in the right order.
  • When CSS and JavaScript files are added or deleted, the site needs to be recompiled.

From having to create your own bundles ...

    public static void RegisterBundles(BundleCollection bundles)
    {
        // Need to create bundles yourself, in code. To make any change,
        // you have to recompile. Must always make sure to include the
        // right files in the right order.

        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/Scripts/jquery-{version}.js"));

        bundles.Add(new ScriptBundle("~/bundles/shared/js").Include(
                    "~/Scripts/SharedCode.js",
                    "~/Scripts/VariousCode.js"));

        bundles.Add(new ScriptBundle("~/bundles/pile/js").Include(
                    "~/Scripts/PileOfCode.js"));

        bundles.Add(new StyleBundle("~/Content/shared/css").Include(
                    "~/Content/Reset.css",
                    "~/Content/Site.css"));

        bundles.Add(new StyleBundle("~/Content/account/css").Include(
                    "~/Content/Account.css"));
    }
                            

From having to ensure the right files are loaded in the right order ...

    @Styles.Render("~/Content/shared/css")
    @Styles.Render("~/Content/account/css")
    ...
    @RenderBody()
    ...
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/shared/js")
    @Scripts.Render("~/bundles/pile/js")
                            

Dynamic Bundles

  • Bundles are auto generated. No need to create bundles yourself.
  • Ensures only required CSS and JavaScript files are loaded, and in the right order.
  • Optimizes client side caching, by combining files into bundles by area, controller, shared and layout.
  • When CSS and JavaScript files are added or deleted, new bundles are automatically generated without recompilation.

To no longer having to worry about them ...

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        // No need to register bundles in BundleConfig
    }
}
                        

To Dynamic Bundles doing this for you ...

@*Nominate where to load the bundles.
The bundles themselves are automatically generated.*@

@DynamicBundlesTopRender()
...
@RenderBody()
...
@DynamicBundlesBottomRender()
                        

Installation

  1. Install Dynamic Bundles
  2. Add view engine to global.asax
  3. Add layout container
  4. Update Web.config for views
  5. Co-locate assets
  6. Create explicit dependencies

1. Install Dynamic Bundles

Install the DynamicBundles package from NuGet:

Install-Package DynamicBundles

2. Add view engine to global.asax

Update your global.asax.cs or global.asax.vb, to add the DynamicBundles view engine:

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
    
            // Add DynamicBundles view engine. This functions the same as 
            // the Razor view engine, but can find views sitting in their own 
            // directories, such as ~/Views/Home/Index/Index.cshtml
            // Note: this leaves the other view engines in place, so they can still be used.
            ViewEngines.Engines.Add(new DynamicBundles.DynamicBundlesViewEngine()); 
        }

        ...
    }
                

3. Add layout container

In classic MVC sites, pages sit within a _Layout.cshtml or _Layout.vbhtml file, which contains shared headers, footers, etc.

The problem when introducing Dynamic Bundles is that you want to separate CSS, JavaScript and pictures that are specific to the layout from those that are shared by the entire site.

To make this separation happen, create a new file _LayoutContainer.cshtml (you'll see the content in a moment). This and the _Layout.cshtml go into their own directory. The result looks like this:

Classic MVC

Dynamic Bundles

Contents of _LayoutContainer.cshtml

    <!DOCTYPE html>
    <html>
        @*Nominate where to load the bundles. The bundles themselves are automatically generated.*@
        @DynamicBundlesTopRender()

        @RenderBody()

        @DynamicBundlesBottomRender()
    </html>
                

Changes to _Layout.cshtml

  1. Set Layout to _LayoutContainer.cshtml, which acts as the overall container of the site.
  2. Remove the doctype and html tags.
  3. Remove all style and script rendering, including rendering of script sections.
    @{
        // Add _LayoutContainer as the container for the _Layout.cshtml file itself.
        Layout = "../_LayoutContainer/_LayoutContainer.cshtml";
    }

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width" />
        <title>Dynamic Bundles for ASP.NET MVC</title>

        @Styles.Render("~/Content/shared/css")
        @Styles.Render("~/Content/account/css")
    </head>
    <body>
        @RenderBody()

        @Scripts.Render("~/bundles/jquery")
        @Scripts.Render("~/bundles/shared/js")
        @Scripts.Render("~/bundles/pile/js")

        @RenderSection("scripts", required: false)
    </body>
    </html>
                

4. Update Web.config for views

In addition to the Web.config file in the root directory of your site, each MVC site also has a Web.config file in its Views directory. If your site uses areas, each area has a Views directory as well, with its own Web.config file.

The Web.config files in the Views directories need to be updated to:

  1. Install the Dynamic Bundles page base type. This gets each view to register the assets it needs, so bundles with the right files can be generated.
  2. Allow the web server to serve CSS, JavaScript and image files from the Views directory.
    <configuration>
      <system.web.webPages.razor>
        <pages pageBaseType="System.Web.Mvc.WebViewPageDynamicBundles.WebViewPage">
          ...
        </pages>
      </system.web.webPages.razor>

      <system.webServer>
  
        <!--
        The BlockViewHandler blocks all requests. In classic MVC sites, it is used to block all 
        requests for files from a Views directory. With Dynamic Bundles where CSS, JavaScript 
        and images files are co-located with the view files, we only want to block requests 
        for the view files.
        -->
    
        <handlers>
          <remove name="BlockViewHandler"/> 
          <!-- Replace path="*.cshtml" with path="*.vbhtml" if you use Visual Basic. -->
          <add name="BlockViewHandler" path="*.cshtml" verb="*" preCondition="integratedMode" 
               type="System.Web.HttpNotFoundHandler" />
        </handlers>
      </system.webServer>
    </configuration>
                

5. Co-locate assets

Finally co-locate your asset files (CSS, JavaScript, images) with the views where they are used:

  • Move all assets that are shared throughout the site in the _LayoutContainer directory.
  • Move all assets that are shared by all views for a controller into that controller's directory.
  • If there are assets specific to a single view file, create a sub directory for that view file and put all assets (including the view itself) into that sub directory. Be sure to name the sub directory the same as the view file, without the extension.
  • Dynamic Bundles will add both the assets in the sub directory and those in its parent directory(s), less specific assets first. In the example below, when /Product/List is loaded, first the assets in ~/Views/Product and then those in ~/Views/Product/List are added.

    This works for both controller specific views and shared (partial) views. Dynamic Bundles makes sure that assets are only ever added once to a bundle.

6. Create explicit dependencies

You may have dependencies from one directory on another. For example, a JavaScript file assumes that a file in another directory has been loaded.

You can specify these dependencies with .nuspec files. These have the same structure as their NuGet counterparts (definition).

If Dynamic Bundles finds a nuspec file in a directory, it will find the directories specified in that nuspec file and add the assets in those directories to the bundles. If those directories have nuspec files themselves, Dynamic Bundles processes those nuspec files as well, etc. This is a fully recurrent process.

To create a dependency from a directory X to some other directories, include a .nuspec file in directory X that looks like this:

<?xml version="1.0"?>
<package >
  <metadata>
      <dependencies>
        <dependency id="../AccountDetailsAssets" />
        <dependency id="~/Views/Shared/DetailsAssets" />
      </dependencies>
  </metadata>
</package>

Note that:

  • The name of the .nuspec file doesn't matter, as long as it has the extension .nuspec.
  • You can specify root relative paths (starting with ~/) and paths relative to the .nuspec file, but not absolute paths (such as C:\Dev\Views\Accounts).