Introducing RequireJS for ASP.NET MVC

This article shows you how to integrate RequireJS in a ASP.NET MVC 4 project and structure the JavaScript code in such a manner that any C# programmer can understand and use it without advanced js programming skills. RequireJS is an implementation of Asynchronous Module Definition (AMD) that provides all the tools you need to write modular JavaScript. If you are working on a large project with lots of JavaScript code, many external components and frameworks, RequireJS will help you manage the complexity of dependencies.

Writing modular JavaScript is the first step in bringing the front-end development closer to the server-side OOP, for a C# programmer, the Module Pattern applied to JavaScript means that you can emulate the .NET class behavior by having public/private members and methods. With RequireJS you can go even further, by declaring a module you can specify dependencies to other modules, the same way as you do in a .NET project when you add references to other .NET components. Before a module is loaded by the browser, RequireJS will look for the dependencies required by that module to function, it will fetch asynchronously from the server all the other modules needed and then will let the browser execute your module code.

Advantages of using RequireJS with Asp.Net MVC

  • better JavaScript code reusability
  • reliable object and dependency management
  • suitable for large and complex applications
  • loads JavaScript files asynchronously
  • using the r.js tool it makes it easy to optimize page load, because it combines all dependencies into one file removing the need for you to search for dependencies manually

You can read all about RequireJS and AMD on requirejs.org

The .NET implementation has the following features:

  • JavaScript file structure integrated with the MVC structure
  • dependencies declaration and module path configuration using xml
  • JavaScript code-behind for each Razor view
  • communication from the server-side controller (c#) to js modules using .NET
  • application wide JavaScript controller
  • Ajax requests management

RequireJS for .NET file structure

The JavaScript code-behind structure is similar to the Views structure, for each action that returns a view there is a JavaScript file that contains the page specific code. On the server-side controller, in the action code, you can set and send options to the corresponding JavaScript module.

 

Configure module paths and dependencies

RequireJS.config
<configuration>
  <paths>
    <path key="jquery" value="lib/vendor/jquery/jquery" />
    <path key="jquery-ui" value="lib/vendor/jquery/ui/jquery-ui" />
    <path key="jquery-ui-i18n" value="lib/vendor/jquery/ui/jquery-ui-i18n" />
    <path key="jquery-mobile" value="lib/vendor/jquery/mobile/jquery.mobile" />
    <path key="jquery-validate" value="lib/vendor/jquery/plugins/validate/jquery.validate" />
    <path key="jquery-validate-unobtrusive" value="lib/vendor/jquery/plugins/validate/jquery.validate.unobtrusive" />
    <path key="amplify" value="lib/vendor/amplify/amplify" />
    <path key="depend" value="lib/vendor/requirejs/depend" />
    <path key="context" value="lib/vendor/requirejs/context" />
  </paths>
  <shim>
    <dependencies for="jquery-ui" exports="">
        <add dependency="jquery"/>
    </dependencies>
    <dependencies for="jquery-ui-i18n" exports="">
      <add dependency="jquery"/>
      <add dependency="jquery-ui"/>
    </dependencies>
    <dependencies for="jquery-mobile" exports="">
      <add dependency="jquery"/>
    </dependencies>
    <dependencies for="jquery-validate" exports="">
      <add dependency="jquery"/>
    </dependencies>
    <dependencies for="jquery-validate-unobtrusive" exports="">
      <add dependency="jquery"/>
      <add dependency="jquery-validate"/>
    </dependencies>
    <dependencies for="amplify" exports="">
      <add dependency="jquery"/>
    </dependencies>
  </shim>
</configuration>

The paths section is used for mapping the file location of a component to a short name, so that you don’t have to write the whole path each time you refer it in code:

//without paths config
require([
        'lib/vendor/jquery/jquery',
        'lib/vendor/amplify/amplify'
],
    function () {  }
);

//with paths config
require([
        'jquery',
        'amplify'
],
    function () {  }
);

The shim section lets you declare dependencies for components that are not programmed as RequireJS modules. When you are referring such a component, the RequireJS engine will load all the dependencies specified in the shim before executing your own code. In the shim section you can use the module short name set in the paths config.

If you create a JS module that uses Microsoft’s unobtrusive validator plugin, all the nested dependencies will be preloaded:

require([
        'jquery-validate-unobtrusive'
],
    function () {
        var myPage = function (opt) {
            
            //jquery.js, jquery.validate.js and jquery.validate.unobtrusive.js
            //are already loaded at this point
            $.validator.unobtrusive.parse($(#myForm));
        }
    }
);

Although the above code works well and RequireJS is smart enough to load all the nested dependencies, I prefer to write in the require [ … ] area all the components that get loaded with a page. So it will be easier  to find out, in what pages a js component is being used, by just searching for the ‘com_name’ in all js files from the folder ~/Scripts/Controllers.

The xml configuration file is transformed into JavaScript code by the RequireJsHtmlHelpers extension methods, the resulting code resides in the  _RequireSetup.cshtml partial view, that is part of _Layout.cshtml:

_RequireSetup.cshtml
@using RequireJS
@{   
    var entryPointPath = Html.RequireJsEntryPoint();
    if(entryPointPath != null)
    {
    <script type="text/javascript">

        var requireConfig = {
            pageOptions: @ViewBag.PageOptions,
            websiteOptions: @ViewBag.WebsiteOptions
        };

        var require = {
            locale: '@Html.CurrentCulture()',
            baseUrl: '@Url.Content("~/Scripts")',
            paths: @Html.GetRequireJsPaths(),
            shim: @Html.GetRequireJsShim()
        };

    </script>

    <script data-main="@entryPointPath" src="@Url.Content("~/Scripts/require.js")"></script>
    }
}

Using the framework

Server-side setup

  • Create an abstract controller derived from RequireJSController, usually you’ll make an abstract controller for each area in your app
  • Implement in your abstract controller the RegisterGlobalOptions method in order to send options to app-global class
  • Create a controller derived from the abstract one

In my case I have created a controller named HomeController derived from PublicController that inherits RequireJsController, because I have multiple areas in my project where I want to set different global options based on the area, in the PublicController I will set options used in all public views and on the AdminController I will set options used only in the Admin area and so on. Here is the code:

RequireJsController.cs
[RequireJsConfig]
public abstract class RequireJsController : Controller
{
    public RequireJsOptions RequireJsOptions;
    protected RequireJsController()
    {
        RequireJsOptions = new RequireJsOptions(this);
    }

    public abstract void RegisterGlobalOptions();
}

The RequireJsOptions object is used by the RequireJsConfigAttribute to save in ViewBag all the options needed in app-global.js and in home-index.js (code-behind for Home/Index.cshtml).

PublicController.cs
public abstract class PublicController : RequireJsController
{
    public override void RegisterGlobalOptions()
    {
        RequireJsOptions.Add(
            "homeUrl",
            Url.Action("Index", "Home", new { area = "" }),
            RequireJsOptionsScope.Website
            );
        RequireJsOptions.Add(
            "adminUrl",
            Url.Action("Index", "Dashboard", new { area = "Admin" }),
            RequireJsOptionsScope.Website
            );
    }
}

Using the RegisterGlobalOptions we can send options to app-global.js, in my case homeUrl and adminUrl. Because all the controllers in the root area are derived from PublicController these options will always be available to app-global.

HomeController.cs
public class HomeController : PublicController
{
    public ActionResult Index()
    {
        RequireJsOptions.Add("ids", new List<int> { 1, 2, 3 });
        return View();
    }
}

In the HomeController you can use RequireJsOptions.Add inside the action code to send options to the home-index.js module. An option can be a string or any other .NET type as long as it gets serialized by the Newtonsoft Json convert.

Front end setup

Now you have to create the view Index.cshtml, do not refer or write JavaScript in the html code.

After the view is done, create a file named home-index.js under ~/Scripts/Controllers/Root/Home folder. Below is an example of how to declare a module and read the options send from the server.

home-index.js
require([
        'jquery',
        'app-global'
], function () {
    //constructor
    var HomeIndex = function (opt) {

        this.options = $.extend(true, {}, opt);

        //init
        this.create();
    }

    //methods
    HomeIndex.prototype = $.extend(true, HomeIndex.prototype, {

        create: function () {          
            //access the id list send by Home/Index
            var ids = this.options.ids;
        }
    });

    //create object
    $(document).ready(function () {
        //load options sent by the server-side controller
        var page = new HomeIndex(requireConfig.pageOptions);
    });

});

Here is a schematic view of the above code

 

Working with JavaScript Modules

The RequireJS for .NET project uses two kinds of modules:

  1. a reusable self-executing module like app-global using the syntax define([ ], function() { } );
  2. an anonymous module like home-index using the syntax require([ ], function() { } );

The difference between the two is that app-global returns a function and can be used (required) by other modules, the home-index class does not return an object, it’s purpose is to manage the behavior of a single web page. The module app-global will self-execute every time a web page is loaded in the browser.

app-global.js
define([
    'jquery'
], function () {

    //constructor
    var Global = function (opt) {
        this.options = $.extend(true, {}, opt);
        this.create();
    }

    Global.prototype = $.extend(true, Global.prototype, {
        create: function () {
            //app wide UI behavior in here
        }
    });

    //self-executing module
    $(document).ready(function () {
        var myModule = new Global(requireConfig.websiteOptions);
    });
    
    //reusable module
    return Global;
});

When a browser first calls your website, require.js along with all the modules required by the home page are downloaded, when the browser navigates to another page, all the scripts downloaded for the home page will be available from cache, this way app-global.js will get download once and loaded in all pages.

home-index.js
require([
        'jquery',
        'app-global'
], function () {

    //constructor
    var HomeIndex = function (opt) {
        this.options = $.extend(true, {}, opt);
        this.create();
    }

    HomeIndex.prototype = $.extend(true, HomeIndex.prototype, {
        create: function () {
            //page UI behavior in here
        }
    });

    //create object on DOM ready
    $(document).ready(function () {
        var myPage = new HomeIndex(requireConfig.pageOptions);
    });
});

On DOM ready the app-global create() function will be called before home-index create().

Using modules inside a code-behind JavaScript class

If you want a collection of JavaScript functions to be  available in your code-behind classes, you can make a module, let’s call it app-utils.js, that is similar to app-global but does not self-execute on DOM ready. This JavaScript module is the equivalent to “public static class Utils { }” in C# that contains only public static methods.

app-utils.js
define([
    'jquery'
], function () {
    return {

        removeCaps: function (str) {
            str += '';
            if (str == str.toUpperCase()) {
                return str.charAt(0) + str.substr(1).toLowerCase();
            }
            return str;
        },

        isWP: function () {
            return /Windows Phone/i.test(navigator.userAgent);
        }
    }
});

All you have to do now is add the app-utils dependency to your page and pass it as a parameter to the anonymous function, like this:

home-index.js
require([
        'app-utils',
        'app-global'

], function (utils) {
    //utils param points to the first dependency

    var Page = function (opt) {
        this.options = $.extend(true, {}, opt);
        this.create();
    }

    Page.prototype = $.extend(true, Page.prototype, {
        create: function () {
            //call functions defined in app-utils.js
            console.log(utils.removeCaps('TEST'));
        }
    });

    //create object on DOM ready
    $(document).ready(function () {
        var myPage = new Page(requireConfig.pageOptions);
    });
});

Using this. inside a JavaScript Module like in a C# class

JavaScript’s functional scoping makes the language very flexible, so depending on the scope, <this> refers different objects, in order to make <this> consistent in a Module, you need to use jQuery’s $.proxy every time the scope changes from Module scope to some anonymous function.

Inside a Module I want to access the options object from any function declared in my module. If, for example, I want to know the homeUrl (options set by the server controller) value inside an on click handler I cant, because calling this.options.homeUrl will return undefined, inside the handler, <this> refers to the anchor DOM object and not my module object. The solution to this problem is to wrap the handler with $.proxy and set the context back to your module, like this:

require([
        'jquery',
        'app-global'
], function () {

    //constructor
    var Page = function (opt) {

        //memebers
        this.options = $.extend(true, {}, opt);
    }

    //methods
    Page.prototype = $.extend(true, Page.prototype, {

        registerEventsWithoutProxy: function () {

            $('a').on('click', function (e) {
                e.preventDefault();

                //<this> refers the anchor DOM element
                $link = $(this);
                console.log($link.text());

                //<this.options> is undefined
                //the Page object is not accesible
                console.log(this.options.homeUrl);

            });
        },

        registerEventsWithProxy: function () {

            //use $.proxy in order to acces
            //the module context <this> inside the function
            $('a').on('click', $.proxy(function (e) {
                e.preventDefault();

                //use $(e.currentTarget) instead of $(this)
                $link = $(e.currentTarget);
                console.log($link.text());

                //<this> refers the Page object
                console.log(this.options.homeUrl);

            }, this));
        }

    });

    //init object
    $(function () {
        //load options sent by the server-side controller
        var myPage = new Page(requireConfig.pageOptions);
    });
});

You can use the same technique in writing iteration or any other procedure that involves scope changing, just use $.proxy(function() { }, this), it makes <this> consistent in your code and avoids confusion.

iterateItems: function () {

    //use $.proxy in order to acces
    //the module context <this> inside the function
    $('p').each($.proxy(function(index, item) {
                
        //access the current <p> DOM object
        $item = $(item);

        //<this> refers the Page object
        console.log(this.options);

    }, this));
}

What’s next

In future articles I will write about:

  • using RequireJS and r.js tool to combine and minify modules based on build profiles
  • managing Ajax calls, making good use of amplify.request and app-global module
  • loose coupling the modules using the pub/sub pattern and amplify.publish

Project RequireJS for .NET
Version 1.0.0.1
Release date 10 June 2013
First release 01 September 2013


You’ll need Visual Studio 2012 and ASP.NET MVC 4 to open the project, all the JavaScript frameworks included in the project are up to date: RequireJS, Amplify, jQuery, jQuery UI, jQuery Mobile , jQuery Validation

Further reading