Update
This blogpost was written with the 0.6b release of SignalR, and the examples wont necessarily work with the latest release. I've pushed working code with the latest SignalR version (1.0.1) on GitHub.
Clean up your MVC app with SignalR!
If you're working on any web application with ASP.NET MVC these days, chances are you're doing alot of jQuery and Ajax to get that nice clientside responsiveness. I love what the web is becoming as a user, but as a developer it has a tendency to make alot of code both duplicate and messy. Mostly because there are few 'best practices' when it comes to this. In this post I'll propose one solution that might clean up your project a bit. Keep in mind, it's no silver bullet or the only way to do this. It's merely a suggestion
This is not a post on the 'usual' SignalR usage. No chat-applications will be written :)
ASP.NET MVC without SignalR
Normally when writing ASP.NET MVC applications with some sort of client side Javascript framework, there's a risk that we'll end up with more than just View-presenting actionmethods on our Controllers. This might be actions returning a partial view - or more commonly - actions returning JsonResults. I'm sure you've seen stuff like this before:
public class HomeController : Controller { IPeopleRepository _repo; public HomeController(IPeopleRepository repo) { _repo = repo; }public ActionResult Index() { return View(); } public ActionResult About() { return View(); } [HttpGet] public JsonResult GetPeople() { return Json(_repo.GetPeople(), JsonRequestBehavior.AllowGet); } [HttpGet] public JsonResult FindPerson(string name) { return Json(_repo.Find(name), JsonRequestBehavior.AllowGet); } [HttpGet] public JsonResult ByCompany(string companyName) { return Json(_repo.GetPeople().Where(c => c.Company == companyName), JsonRequestBehavior.AllowGet); } }
And probably some client side code like so
$.getJSON("/home/getPeople", function (res) { // do something with the result });
There's nothing wrong with this code per say, but as your application grows, you'll see alot of these extra actions on your controllers. Also you'll probably find yourself trying to extract them into some other Json-only controller or an API or similar. In my opinion, it's still a borderline responsibility for any controller to contain these actions (except for in a pure RESTish API I guess)
Ever since MVC v1 I've been struggling with the concept of controllers and what their responsibility should be. I often conclude that they should return only what the View requires in order to render, and that means that I should put the asynchronous actions somewhere else. This is where I've always stopped, falling back to either making and API-area or just bloating the controllers with actions like in the example above.
Another drawback about this is that it's not really good for unittesting. We have to retrieve the ActionResult, and parse the Json. That's not really what we need to test. Also, when using some sort of IoC framework the constructor tends to be more and more bloated with all sorts of dependencies because the different actions require access to different data from the businesslayer
SignalR to the rescue!
SignalR is one of the coolest Javascript frameworks out there today, and it's also in constant development by a couple of really smart guys over at Microsoft
I've also used SignalR in real production code with no problems, and at NDC this year Damian Edwards adressed the questions everyone has about scalability and throughput.
But isn't SignalR all about the real time web?
Sure it is, and it's awesome at it. But SignalR provides us with a two-way communication framework that handles all the bootstrapping we normally do ourselves when it comes to communicating with the server.
SignalR hubs
What I'm proposing is to utilize the Hub class in SignalR in order to extract the code we saw in the controller earlier.
If you haven't worked with SignalR before I recommend checking out the basic walkthroughs on their Github wiki-page, as I wont cover the basics here
Let's start by adding a People-hub:
public class PeopleHub: Hub { IPeopleRepository _repo; public PeopleHub() { _repo = DependencyResolver.Current.GetService<IPeopleRepository>(); }public IEnumerable<Person> GetAll() { return _repo.GetPeople(); } public IEnumerable<Person> FindPerson(string name) { return _repo.Find(name); } public IEnumerable<Person> FindByCompany(string companyName) { return _repo.GetPeople(); } }
In the code above, I've taken our [HttpGet] methods from the controller and put them into a PeopleHub (that inherits from the SignalR.Hub class). I've also removed all traces of Json-parsing and results, returning only the class that I'm actually working with. SignalR takes care of the rest.
What I also want to do is pull the SignalR startup-mechanisms on the client out into a global Javascript file for easier handling:
var signalR = { available: false };$.connection.hub.start().done(function () { signalR.availble = true; signalR.hubs = $.connection; });
Basically what I'm doing here is making an object literal that will contain the $.connection object from SignalR for easy access throughout my application. Keep in mind this is a very simple approach and can be massivly improved. I'm also using the .done() callback from SignalR to properly assign the connection-object to my signalR literal
So what does our previous getJSON code look like now?
signalR.hubs.peopleHub.getAll().done(function (res) { // do something with the result });
Neat, eh? Not only did we get rid of the URL in the method, but we have a correct representation of our method on the hub with the getPeople() method name. We can also chain the call further with the .fail() callback:
signalR.hubs.peopleHub.getAll().done(function (res) { // do something with the result }).fail(function(error){ alert("Something went wrong: "+error); });
Tip: If you use Resharper, you can also easily find usages of the getAll()-method instead of doing a textsearch for /Home/GetPeople as you would have done in the earlier example.
As a result of this, our controller can now be slimmed down to this:
public class HomeController : Controller { public ActionResult Index() { return View(); }public ActionResult About() { return View(); } }
Returning nothing but Views. Another problem with using controllers as containers for async/ajax actions is that as soon as you start making up your own SEO-friendly Routenames and you application grows, it gets more and more difficult for other developers (and yourself probably) to figure out where the different urls points, without checking the route-configuration first.
In the SignalR version, we know it's a method called GetPeople()
on the class PeopleHub
.
Clean controllers, readable Javascript. What else?
Now that we have an actual class called PeopleHub, that actually returns strongly typed objects of type Person, we have all of a sudden have a reusable class. So instead of doing the same in other controllers, or other logic high up in the layers, we can easily utilize the methods on the PeopleHub. Even injecting it with your favourite IoC framework is easy.
The fact that we have a simple class with a nicely defined responsibility, it's much easier to do Unit- or Integrationtesting against it. In some cases we can argue that we would benefit from doing presentationlayer-testing and that we don't need to separate this logic from the controller, but I would say this is rarely the case. Presentation layer testing (Selenium, WatiN) is great, and should be done, but in many cases you end up testing the Webserver, the Modelbinder, the ActionResult class or other mechanisms that are already pretty much verified to work - thus not getting to focus on what presentationlayer testing should really cover.
Wrapping it up
So we've seen that we can use SignalR in order to clean up our server side code. I think this is a really practical approach, and SignalR is developed and maintained by commited people, so new versions and bugfixes will be out pretty often. SignalR also takes care of the transport to the server, making use of ServerSentEvents, regular Ajax, WebSockets etc, so we don't need to think about that at all.
In closing - using SignalR is not only about pushing data to the client, but also a really great framework for communicating back to the server.
Hope you enjoyed the post, and thanks for reading! If you have any suggestions or comments, please post them below :)