Best practice front-end architecture using Microsoft ASP.Net MVC and Rivets.js
A few years ago I wrote an article about best-practice architecture for web applications built in Microsoft.Net. This was focused entirely on the back-end and I mentioned at the end that I would do a front-end article one day. So, here we go…
First of all, let’s get some basic requirements down:
- your business logic should be separated from your presentation logic
- your business logic should be unit testable – and this means abstracting as much as possible so you can mock it later
- your application should be as lightweight as possible – but more importantly, the application must not ‘bloat’ with superfluous or rarely-used features as it grows bigger
In addition, since I wrote the last article, the way we build modern web applications has made a massive shift to client-side focus, with much of my work written in javascript these days. The trouble with javascript is that doesn’t naturally enforce rules on the developer. If you are developing by yourself, you may be able to get away with this because you understand your own way of working. But if you’re working in a multi-team environment, this isn’t good enough and you need to enforce your own rules using conventions which other developers must follow. This article shows the conventions which I currently use to keep things organized and understandable.
Simple huh? Let’s get into it. If you’d like to follow along, you can download the sample project here (and don’t forget to run the included .sql file to create your database structure).
Separating your business logic from your presentation logic (a.k.a. Separating your javascript from your HTML)
For me, the reason for doing this primarily comes down to unit testing – you can’t be dealing with HTML manipulation when you are trying to test the SavePerson() method of your javascript file.
The typical way to go about this is via two-way data binding and for years the common ways of doing this have been using third party tools like Knockout.js. Personally, I detest Knockout – they’ve done amazing work (including older browser support), but you have to completely rewrite your javascript models in order to make it work – which means:
- you and other developers must become proficient ‘knockout developers’ in order to maintain the application
- you become massively tied-in to the knockout framework
Because of these reasons, I have never built a proper data-bound front-side framework on any of my applications. At least, until rivets.js came along.
Rivets.js
This was a huge game changer for me. It’s not as big or popular as the older frameworks such as Knockout, but it has one massive advantage – you can develop your javascript files with absolutely no knowledge of (or reference to) the fact that it is data-bound to rivets. In fact, your javascript files have no idea that they are data-bound at all!!. That is perfect – just the way it is supposed to be. To clarify, here is an example of a file that displays a list of people:
function (model) { var Init = function () { console.log("People", model); }, ViewPerson = function(ev, data) { alert('You have clicked ' + data.person.DisplayName); }, AddPerson = function () { var params = { firstName: 'Person ' + (model.People.length + 1) }; // Call our MVC controller method lib.Data.CallJSON('home/createperson', params, function (newPerson) { // Add to our model - the view will update automatically model.People.push(newPerson); }); return false; }; Init(); return { model: model, AddPerson: AddPerson, ViewPerson: ViewPerson }; };
Beautiful huh? Imagine unit-testing that bad-boy – piece of cake!
So, with rivets.js you grab this javascript file and you ‘bind’ it to a block of HTML, and suddenly, as the user interacts with the HTML (like clicking an ‘Add person’ button), your javascript file will handle the events and react according (like creating a new person). For reference, here is my associated HTML view:
<div id="MyPeopleList" class="home"> <h2>People list</h2> <table> <tr data-each-person="model.People" data-on-click="ViewPerson"> <td data-html="person.DisplayName"></td> </tr> </table> <p> <a data-on-click="AddPerson">Create a new person</a> </p> </div> <script> var viewID = 'MyPeopleList'; var view = document.getElementById(viewID); rivets.bind(view, new PeopleList(model));
See the data-* attributes? That’s rivets.js. I’m not going to into how the binding works – check out the rivets.js documentation for that.
Automatically wiring up your views to your controllers
In the HTML sample above, you can see a little script tag at the bottom which pulls in my PersonList javascript and applies to our HTML. I build a very modular type of architecture so I end up with 100’s of these files and quite honestly I get sick of retyping the same thing again and again. So, this is a good chance to introduce the first of my ‘conventions’ which I apply using the MVC framework – let’s jump to our C# code. Specifically, the OnResultExecuted() method which gets called after each of my MVC views are rendered (BTW, if you’re not familiar with Microsoft MVC then you’ll probably need to brush up on another blog before proceeding):
protected override void OnResultExecuted(ResultExecutedContext filterContext) { var viewFolder = this.GetViewFolderName(filterContext.Result); var viewFile = this.GetViewFileName(filterContext.Result); var modelJSON = ""; // Cast depending on result type if (filterContext.Result is ViewResult) { var view = ((ViewResult)filterContext.Result); if (view != null && view.Model is BaseModel) modelJSON = view.Model.ToJSON(); } else if (filterContext.Result is PartialViewResult) { var view = ((PartialViewResult)filterContext.Result); if (view != null && view.Model is BaseModel) modelJSON = view.Model.ToJSON(); } // Render our javascript tag which automatically brings in the file based on the view name if (!string.IsNullOrWhiteSpace(viewFolder) && !string.IsNullOrWhiteSpace(modelJSON)) { var js = @" <script> require(['lib', 'controllers/" + viewFolder + @"/" + viewFile + @"'], function(lib, ctrl) { lib.BindView(" + modelJSON + @", ctrl); }); </script>"; // Write script filterContext.HttpContext.Response.Write(js); } base.OnResultExecuted(filterContext); }
Don’t worry about all the custom function calls – you’ll find them in the example project download – the key points are:
- find the physical path of the view that we are rendering (e.g. /home/welcome.cshtml)
- use this path to determine which javascript file we have associated to it (e.g. /scripts/controllers/home/welcome.js)
- automatically render the <script/> tag at the end of our view – exactly the same as we manually typed it into the HTML example I pasted above
So, this handy method does a few things:
- it saves me typing
- it forces me and other developers in the team to store the javascript files in a consistent and predictable format. So, if I’m working in Visual Studio on the Welcome.cshtml view, I know immediately where I can find its javascript just by looking at the file name.
- it provides a clean way for me to do my server-to-client model serialization, which deserves its own section…
Serializing your C# MVC model into your client side (javascript) model
Note the modelJSON variable you can see above. Because I’m loading my javascript controller from server-side code, I have access to the MVC ViewModel and I am able to serialize it directly into my page. This is something which few online examples of javascript data-binding frameworks show you – they always start with some model which is hard-coded into your javascript, which is completely impractical in real-life.
In practical terms, this has the other advantage that my server-side MVC models have precisely the same structure as the model I have dealing with in javascript. This makes it easy for me to understand how my model is formatted when I’m working in javascript.
BONUS: It would be amazing if I could somehow get some kind of javascript intellisense to work on my models by parsing in the C# structure of my MVC models. I could also use the same mechanism to do compile-time checking of my javascript code. If anybody can think of a way to do this, please let me know.
Managing your CSS files in a large web application
Another problem I find with large projects is managing CSS files. One typically has a common.css file and perhaps an admin.css file to try to split it out as required. But asyour project grows, you add more and more fluff to these files and they end up very very large. Then, to reduce the initial site load, you think you’ll pull out some targeted CSS classes into a separate file and reference it just in the files you need. Except then you start forgetting which files need it – and besides, with MVC applications these days you tend to pull in partial views all the time – they have no idea what page they are on and what CSS files they currently have access too.
So, here’s what I’ve finally come up with, and it’s very similar to how I pull in the javascript files above, only this time it uses MVC’s OnResultExecuting() method:
protected override void OnResultExecuting(ResultExecutingContext filterContext) { if (filterContext.Result is ViewResult || filterContext.Result is PartialViewResult) { // Render a reference to our CSS file var viewPath = this.GetViewFolderName(filterContext.Result); if (!string.IsNullOrWhiteSpace(viewPath)) this.RenderPartialCssClass(viewPath); } base.OnResultExecuting(filterContext); }
Again, ignore the missing method references, they’re in the example project. Here’s what it’s doing:
- find the path of the current view (e.g. /home/welcome.cshtml)
- generate a CSS path name based on this path name (e.g. /content/xhome.min.css)
- Render a <link/> tag pointing to the CSS file
The <link/> tag is written to the HTML stream before the HTML of the view is rendered, and voila – every time you pull down a view (either with a direct request, or a partial render using AJAX), the first thing it will do is tell the browser what CSS file is needs and the browser will pop off and download that too.
Of course, this has its drawbacks:
- the first time a view in each folder is downloaded, the browser has to make another (blocking) request to get the CSS file. Remember though that the CSS file is cached so subsequent requests have no additional overhead.
- it splits the CSS into files which aren’t named or organized according to their use. For example, another way to organize files would be by function (admin, public etc). Personally, this doesn’t bother me so I’m happy.
In addition to these files, I also have the usual ‘base’ CSS file, but now it should include only general styles like tables, headings etc. When you have functionality which is specific to a module (HTML view) then you add it to the relevant file in its own section. In the example project, you can see that xhome.min.css contains the styles which the welcome.cshtml view uses.
BONUS: the above works perfectly for partial views which are injected into an existing DOM. However, if you use it for a regular view, then you’ll notice the <link/> tag is inserted at the very top of the HTML element – ie. above the opening <html/> tag. Not proper although I haven’t found any practical problems in any browsers. Still, if anybody finds an efficient way to render it in the <head/> tag instead, please let me know – I haven’t bothered looking into it.
Dependency injection using require.js
The final excellent technology I use in my front-end architecture is require.js. This serves two purposes:
- it allows my various javascript files to load their dependencies on demand instead of pre-loading all my files on the initial page load. This is absolutely essential for your large application to be well performing.
- it allows us to mock out files for unit testing, simply by replacing the require-main.js file
I’m not going to go in to how require.js works – you can find out all about it on their website. But again, it is a must-have as far as I’m concerned.
Unit testing
Unfortunately, the example project doesn’t have examples of unit testing. I usually use Jasmine and Karma to run my tests, and I mock out the require-main.js file to stub out things like my jQuery dependencies.
Conclusion
So, that’s it – my current take on ‘best practice’ MVC architecture for real-world, large-scape web applications. The next steps I’m looking forward solving over the next year are:
- compile time javascript errors
- introduction of ES6 javascript
- implementing the proposed Object.observe() pattern in ES7 (which could technically allow me to replace rivets.js but why re-invent the wheel?)
- Visual Studio ‘knowing’ how javascript and cshtml files are linked, so you can do something akin to ‘view source’ to jump between them
- a framework to bind my javascript files to non-HTML framework, perhaps Android axml files? Perhaps I’m dreaming….
Seeya