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
<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:
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:
'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:
@{
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:
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).
{
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.
{
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.
'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:
- a reusable self-executing module like app-global using the syntax define([ ], function() { } );
- 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.
'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.
'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.
'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:
'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:
'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.
//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
- The Module Pattern explained by Addy Osmani
- JavaScript Module Pattern In-Depth by Ben Cherry
- Dependency management with RequireJS by Aaron Hardy
Good article have you looked into http://nuget.org/packages/microsoft.web.optimization
I am using Web.Optimization.StyleBundle from MVC 4 to merge CSS files, for JavaScript I think RequireJS is more suitable.
Like Vinny, I have been struggling with web.optimization and require.js because that is what many online tutorials teach.
John Papa just published a series where js files are bundled and minified using web.optimization and then reference those js with RequireJS. Have you taken a look already? (I heard that course is now free for public view.)
I am looking forward to your next post on bundling and minification using RequireJS. It would be great if you can comment on why we should choose not to bundle js files from MVC.
Great article. Thank you.
Great stuff, needs a nuget package :)
What would be the advantage of RequireJS over Cassette??
Just what I have been looking for… Thanks!
Thanks! I’m trying to implement this using TypeScript. I’m also looking forward to your future articles!
Hi Stefan,
This is a great article, thanks for taking you time to put it together.
I’m looking to implement RequireJS into my future ASP.NET MVC site and wondered if there were any further developments or follow up articles relating to the additions you wanted to make.
I’d just like to use this implementation in my work so wondered if there was any more added.
Thanks again
Sorry for the late answer, RequireJS for .NET has a new home on github http://github.com/stefanprodan/RequireJSDotNet updated today.
Awesome article, thanks for putting it together.
Do you have recommendations for how I might plug basic Knockout.js functionality into this architecture?
Thanks,
Jason