Durandal with ASP.NET MVC Conventions

Background

Durandal is a SPA framework that is built on top of already popular Javascript libraries including jQuery, Knockout and RequireJS.

It provides Javascript/HTML modularity, SPA lifecycle management, navigation and screen state management plus various other features that simplify SPA development.

However out of the box Durandal seems to throw away common ASP.NET MVC conventions in favour of its own.

If you install Durandal into your project via the NuGet package you will see that it creates an “App” folder that is intended to host not only all of the Durandal modules, but also all of your views and viewmodels.

This convention is fine for small scale applications and makes optimisation using Durandal’s optimizer.exe straightforward.

However there are some issues with this folder structure that I’m not initially fond of:

  • Views are no longer in the default locations searched for by the RazorViewEngine. I still want to leverage the power of Razor views to control markup generation via the use of various HtmlHelpers.
  • Views are now potentially in two separate locations (/Views and /App/views).

  • Scripts are also now in two separate locations (/Scripts and /App/viewmodels).

I’m currently working on rewriting a large scale enterprise application using ASP.NET MVC. This application is broken down into a number of modules, with each module being its own SPA within its own assembly. With this in mind, it’s easy to see why the “out of the box” conventions do not work for me.

I would personally prefer to have Durandal work around MVC, as opposed to it being the defining aspect of the project. The rest of this post will discuss how to configure Durandal to work within existing MVC conventions, and to also use Razor to provide views for your SPA.

Initial Structure

I’m going to create a new ASP.NET MVC 4 Web Application using the “Basic” template to keep things simple.

First create a basic HomeController with two actions…

public class HomeController : Controller
{
    //
    // GET: /Home/
    // (this is for the SPA landing page)
    public ActionResult Index()
    {
        return View();
    }

    //
    // GET: /Home/Shell
    // (this will serve the initial shell for the SPA)
    public ActionResult Shell()
    {
        return View();
    }
}

Also go ahead and create their associated views…

Index.cshtml

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<div id="applicationHost">

<h2>Landing Page...</h2>

</div>

Shell.cshtml

@{
    Layout = null;
}

<div>This is the shell for the app!</div>

Next we’re going to pull Durandal in via NuGet.

As previously described Durandal creates an “App” folder that is meant to host all of the views and viewmodels for the SPA. I’m going to move the Durandal modules into the main Scripts folder in order to keep all of my vendor scripts where I would prefer them to be.

Here is what my structure looks like afterwards:

shot1

I have chosen to separate out the 3rd party vendor libraries (including Durandal) into a “lib” folder, leaving my application specific modules in an “app” folder.

Note: I’ve also updated these paths in BundleConfig

I’ve also created a “viewmodels” folder within app to hold all of the viewmodel modules.

As part of the changes to BundleConfig I have created a “vendor” bundle that contains jQuery and Knockout and also updated _Layout.cshtml to use this bundle.

BundleConfig.cs

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

_Layout.cshtml

@Scripts.Render("~/bundles/vendor")

Also notice that I have created a main.js file in the “app” folder and a shell.js.

The main.js file will be the entry point to the SPA and will be responsible for configuring and bootstrapping the app.

shell.js will be the initial module for the SPA. It is important to organise the app specific modules under the same folder structure as shared by the views. We will be configuring Durandal to use this convention next…

Configuring Durandal

The first thing we need to do is add a reference to RequireJS in our landing page.

@section Scripts {
    <script src="~/Scripts/lib/durandal/amd/require.js" 
            type="text/javascript" 
            data-main="@Url.Content("></script>
}

This is the full contents of the entry point file, I will explain each part individually below…

main.js

require.config({
    paths: {
        'lib': '/Scripts/lib',
        'app': '/Scripts/app',
        'durandal': '/Scripts/lib/durandal'
    }
});
define(['durandal/app', 'durandal/system', 'durandal/viewLocator', 'durandal/viewEngine'],
    function (app, system, viewLocator, viewEngine) {
        system.debug(true);
        
        viewLocator.useConvention('viewmodels', '../../..');
        viewEngine.viewExtension = '/';
        viewEngine.viewPlugin = 'durandal/amd/text';
        
        app.start().then(function () {
            app.setRoot('viewmodels/home/shell');
        });
    }
);

First we need to configure Durandal/RequireJS in main.js in order to make them aware of our new conventions.
RequireJS loads modules according to a baseUrl. As we used the data-main attribute on the require.js include, RequireJS uses the location of main.js as this baseUrl.

I’ve configured RequireJS with some default paths to my app/lib directories, as well as to the Durandal directory.

require.config({
    paths: {
        'lib': '/Scripts/lib',
        'app': '/Scripts/app',
        'durandal': '/Scripts/lib/durandal'
    }
});

The following line instructs Durandals viewLocator of the convention we want to use when mapping module folders to view folders. The first parameter is a string in the path that will be replaced by the second parameter.

viewLocator.useConvention('viewmodels', '../../..');

So for example if retrieving the shell module, this will reference /home/shell as the view directory is mapped relative to the viewmodels directory.

The next line instructs the view engine to use “/” as the view extension instead of the default “.html”. We want this as we will be accessing our views through our controllers and the ASP.NET routing mechanism.

viewEngine.viewExtension = '/';

Finally, because we moved Durandal from its default location we need to update where it looks for the text module.

viewEngine.viewPlugin = 'durandal/amd/text';

With the config/bootstrap code in place, all that is left to do is provide the code for the shell module…

shell.js

define(function () {
    var shell = {
        activate: activate
    };
    function activate() {
        alert('Shell started!');
    }
    return shell;
});

And there you have it, Durandal working nicely within ASP.NET MVC conventions. Although you can easily take the concepts outlined here and apply them to whatever conventions you prefer for your projects.

A sample project can be found here.

Leave a Reply

28 Comments

  1. A couple of points:

    1. Durandal is a JS framework built independent of .NET web stacks. So, it can't really adopt an MVC organization because that wouldn't make sense to a Ruby or Node dev. So, we opted to organize in our own way, based on what JS build tools expect. You will probably find a similar mentality in other SPA frameworks.

    2. Our organization was intentional in order to make optimization of the final SPA simple. It made it possible to relieve the developer of needing to do custom r.js config, which I believe is not possible with the above configuration.

    3. Our organization was also designed to make it easy to build an app for PhoneGap and AppJS because a large percentage of SPA development is happening as native mobile apps. The organization above prevents that as well.

    Just wanted to explain a few reasons why we did what we did. You can certainly do it differently as you have.

  2. I am glad that Durandal is flexible enough to enable the above scenario. Just know that that is not the main use case and that you give up some things by doing it this way (optimization and native app deploy). Still, I know you probably have your reasons….just have to note here that this is a bit outside the norm.

    • I am completely new to einvythreg webdesign and jsquery, and I still managed to make it work, so thanks a lot, this is great!.However, I still have a question: I would like to hide’ the initial link as soon as it is clicked.So in other words, the initially visible text (show/hide in your example) should be hidden when it is clicked. How should I go about this?Thanks in advance,Thomas (Belgium)

  3. Rob, I understand that your default conventions will have had plenty of reasoning and thought behind them (as you've outlined).

    My intentions for this post was not to question those, but to provide a solution for those that are coming from an ASP.NET MVC angle and want to keep to what they're used to.

    My exposure to Durandal was through John Papa's SPA course on Pluralsight and his HotTowel SPA template. So initially it felt quite odd, and based on several questions on StackOverflow it seems I'm not alone.

    As you say it's great that the framework is flexible enough to allow this sort of configuration!

  4. Hi Brett,

    And thanks for a very great post! it was exactly what I was looking for. I have been going through many posts in stackoverflow and this one: https://groups.google.com/forum/?fromgroups=#!topic/durandaljs/hpfZB-WFEXk

    As a small disclaimer, I must say that I am a noob in this area for now, but I am learning quickly.

    I also went through the course of John Potato, oops sorry Papa! hehehe(papa = potato in chilean spanish…)

    I can see that you have solve the problem that I have regarding using razor pages with Durandal. However, I have a some questions, that I hope that you can help me with, I really need some help right now, since the clock is ticking…

    Now that the server, serves the pages to the client. Then that means that I can just create viewmodels, name them like the view and they will be binded to the View at the client side?

    what about static html, can I have that too? and Breeze and knockout, can I use that with cshtml?
    would that just work as expected?
    well, that's a lot of questions.. but is really critical for me to know, in order to start creating the new project.

    thanks alot for this blog and for you time.

    Oscar

  5. Hi Oscar,

    Glad the post was of some use. I'm also new to SPA development and we seem to have started out from the same point (John Papas SPA course).

    In answer to your questions, yes using the conventions I setup in this post you should just be able to create viewmodels with matching names and folder structure to your Razor views and they will be "composed" correctly by Durandal.

    Having a mix of static HTML and razor views I'm not so sure about. In my post I configure Durandal to look for the "/" extension as I was mapping to a controller action. Whether you can change that dynamically at runtime based on some condition you would need to investigate.

    Breeze and Knockout should work fine. You are only using Razor views to serve up dynamic markup, no different from the static html John Papa fetches in his course, except that it's going through the MVC pipeline.

    When I wrote this post I was still evaluating various SPA libraries and frameworks. I had one important question that came up out of this. See here:

    http://stackoverflow.com/questions/15788659/html-markup-abstraction-and-consistency-in-spas

    I would recommend looking into AngularJS for your SPA project as it's now what I have switched to for mine.

  6. Hi Madhu,

    I appreciate Durandal can be configured to work with different folder conventions. That was the main point of this post, to show it configured to work with the conventions ASP.NET MVC comes with "out of the box".

    Your example would not work if your views were Razor views. You would need to extend the RazorViewEngine to look for the views in the App folder in this scenario.

    Kind regards,
    Brett

    • Dear Simon,I’ve tried putting your line $(‘a#showhidetrigger’).hide(); on a clupoe of places, but it doesn’t seem to work. The target is now no longer hidden when the page is loaded. Did I missplace your code?This is how I’ve tried it, maybe this can help you to help me: $(document).ready(function () { $( #showhidetarget’).hide(); $( a#showhidetrigger’).click(function () { $( #showhidetarget’).show(200); $(‘a#showhidetrigger’).hide(); }); });Thanks in advance,Thomas

  7. This comment has been removed by the author.

  8. Hi Brett,

    Whats the catch if we mix asp.net mvc and durandal framework?Was there a problem if the page was rendered from the server side (RazorViewEngine)?Final output would still be HTML, right?Any problem with Sammy or on the Navigation history?Correct me if im wrong that on some point durandal will cache the content of the page, what would be the effect of using chtml?

    How about on using another view like url http://localhost/Report/Index , will this work?

    Thanks,
    Ace

  9. Hi Brett,

    Thanks a lot for the reply, I check out the link, Angular looks promising…
    However, I am struggling to make the demo work, I am trying to port John's Papa app into this folder structure, I am getting loading errors at the moment.

    Is it possible for you to post the source code of this tutorial?
    it would be of great help…

    However, did you opt out of using Durandal for your project?? if so, was it because you are using Angular now, and that's the alternative of knockout?

    /O

    /O

  10. Hi Ace,

    The driving factor behind us wanting to have the server provide the markup was that we already had a bunch of MVC HtmlHelpers that developers used to provide a component suite for our UI elements.

    The thinking was that instead of serving up static html files, we could instead map to controller actions and allow the Razor views to produce that same "static" html using our existing helpers. The end result is that html is returned so I don't think there is much difference between doing that and serving up static html files (I could be wrong).

    In terms of additional problems further down the line (e.g. Sammy/Navigation/Caching etc.) I'm not really in a position to comment. At the original time of writing we were evaluating a number of frameworks and we have since decided to go with AngularJS as it provides the same abstractions we had on the server (HtmlHelpers) but in the client (AngularJS Directives).

    I've since come to realise that trying to mix and match markup generation between server and client isn't a great solution. If you are developing a SPA-like application I would stick to doing it all client side. A framework like AngularJS really helps with this.

    Brett

    • Simon,I’ve put in front of (the section cnatoining) the tag, and a at the end. I assume that’s all it takes to make it into a div? I haven’t typed anything else.Then I tried to put $(‘#hideafterclick’).hide(); can you confirm that the syntax is correct?So then the code reads: $(document).ready(function () { $( #showhidetarget’).hide(); $( a#showhidetrigger’).click(function () { $( #showhidetarget’).show(200); $(‘#hideafterclick’).hide(); }); });It still doesn’t work, i.e. the target is still showing.So IF you can confirm me that my code is as good as it gets and doesn’t contain any syntax errors or missplacements, THEN we can decide that this simply doesn’t work. Would be a real pity, I was so close!Kind regards,Thomas

  11. Hi Brett,

    What do mean by "I've since come to realise that trying to mix and match markup generation between server and client isn't a great solution. If you are developing a SPA-like application I would stick to doing it all client side.". After the rendering KO will be used for its binding so it will be on the client side then?

    Isn't that AngularJS works the same as DurandalJS also?

    If you don't mind why did you stop using DurandalJS if you have this working?

    I'm also using asp.net mvc and i love how htmlhelpers functions and same as you i want to create SPA.

    Thanks,
    Ace

    • , I’m a complete nocive, so I’ll give it a try. At first glance, everything looks like Chinese to me. But when looking closer, I think I understand your reply, and actually your suggestion to modify toggle’ into show’ makes sense, and it’s not a problem at all, I prefer it that way. Now all I have to do is figure out WHERE exactly to put your code I’ll give it a try, and I will let you know in a couple of minutes!Kind regards,Thomas

  12. Ace, Oscar…

    I recommend listening to this video, it summarises many of the main reasons why we switched to Angular:

    http://goo.gl/g5AB3

    Basically you can try and roll your own client side framework by piecing together various libraries like Durandal, Knockout, Backbone etc.

    Alternatively you can go for something like Angular which is an all-in-one framework, without being overly restrictive. We chose it as it has so many great features out of the box, and it was opinionated enough to guide us in our initial architectural decisions.

    Brett

  13. Oscar,

    I have added a link to a sample project at the end of the post.

    Also read my comment below re AngularJS.

    Regards,
    Brett

  14. Great article, but here I am facing few problem using this. My validation are not working that are defined in models.

  15. Hi!

    I'm also looking into converting my current MVC project to a SPA. Allthrough I experience some issues using the default routing. It works for the index and the shell.

    But I don't know how I'm able to route to other controllers/views. Is it by adding a # tag in the url? How is durandal intercepting the route atm? Or is the router not yet configured?

    Kr

  16. Hi Brett or anyone that made progress with this.

    This is exactly what I was hoping to do with Durandal even though it's not meant to work this way.

    I had your sample working fully in my project. I am trying to bring the actual Durandal sample over into the MVC version but I'm running into problems. I have the new convention all in place. I'm not sure if I'm using router.mapNav properly. I can see that app.setRoot is pulling the viewmodel for shell with this app.setRoot('viewModels/home/shell', 'entrance');

    My router mapNav is done like so
    router.mapNav('viewModels/home/welcome');
    router.mapNav('viewModels/home/flickr');

    I'm not sure if that is correct.

    On the shell viewModel activate I am trying to return the welcome page. I have a welcome cshtml view in my Views/Home folder with a controller action set to return it.

    • Simon,I’ve put in front of (the section conntiniag) the tag, and a at the end. I assume that’s all it takes to make it into a div? I haven’t typed anything else.Then I tried to put $(‘#hideafterclick’).hide(); can you confirm that the syntax is correct?So then the code reads: $(document).ready(function () { $( #showhidetarget’).hide(); $( a#showhidetrigger’).click(function () { $( #showhidetarget’).show(200); $(‘#hideafterclick’).hide(); }); });It still doesn’t work, i.e. the target is still showing.So IF you can confirm me that my code is as good as it gets and doesn’t contain any syntax errors or missplacements, THEN we can decide that this simply doesn’t work. Would be a real pity, I was so close!Kind regards,Thomas

  17. I was able to get everything working except widgets. I'm still trying to figure that part out.

  18. The Keshri Software Solutions provides Web Application development,Website Promotions, Search Engine Optimizations services .we have a very dedicated and hard working team of web application developers(asp.net/c#/sql server/MVC) , Search engine optimizers. Fast communication and quality delivery product is our commitment.

    To get more details please visit to – http://www.ksoftware.co.in .

    • Excellent, thanks! I do see hweover that the default MVC3 jQuery site created by VS.2010 Ultimate does *not* source the theme/base CSS on the header.So, if I add a new theme, say redmond like yours, should I then add to the standard template both the base UI as well as the customized redmond script as follows?:or remove the base theme CSS entry and just use the customized redmond css?

  19. Sorry Simon,a part of message seems to have been deelted, it probably contained too many dangerous tags or brackets:-)I wrote that I’ve put __ div id= hideafterclick __ in front of the section containing the tag, between brackets of course, I haven’t defined the div in any other way. And then I put the slash div at the end of that section.I hope it makes enough sense for you to verify if I dind’t make any mistakes!Thomas

Next ArticleTypeScript Support in Visual Studio 2013