Hopp direkte til innhold

Hopp direkte til ansatte

Hopp direkte til Søk

Clean up your MVC app with SignalR

In this post we'll see what SignalR can do to make our MVC app cleaner. Instead of the more common Server->Client posts on SignalR, this post focuses more on the Client->Server communication of things.

<p>Yngve Bakken Nilsen</p>
 

Yngve Bakken Nilsen

11 juli 2012

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. I plan on writing a follow-up for this post soon, tackling some of the comments - Stay tuned!

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 :)

is a senior developer at Novanet. He specializes in .NET development, and loves everything that involves C# and JavaScript. Feel free to check out my profile on or on Twitter

kommentarer:

  1. One issue with this approach is that a SignalR hub connection is persistent and you can only have so many open connections between one browser and one web server. Having a Hub-per-type (People, Company, Account, ...) each with an open connection isn't going to work.
  2. @Ian - It's mostly by example that I have a Hub-per-type approach. Methods could probably be grouped more logically instead of the pragmatic by-type grouping. On the other hand, I was pretty sure that one browser will only initialize one connection, and all hub-communications will be sent over that single connection. Otherwise multiple hubs would always be an issue, no? I will investigate this further and see what I can figure out :)
  3. As a matter of fact, if you inspect the javascript at /signalr/hubs, you can see that the hubs all return the same connection-instance for the connection-property: peopleHub: { _: { hubName: 'PeopleHub', ignoreMembers: ['findByCompany', 'findPerson', 'getAll', 'namespace', 'ignoreMembers', 'callbacks'], connection: function () { return signalR.hub; } },
  4. But that's a SignalR 'connection' instance not an HTTP request. It may or may not limit the number of concurrent HTTP requests when sending. Look at 'ajaxSend' in jQuery.signalR-0.5.2.js. I don't think the code there limits requests and therefore it hits the browser limit (harmless) or the server limit (fails) for simultaneous connections. I gave up using multiple hubs after hitting this issue in an earlier release, but maybe it's fixed???
  5. Sounds curious... I asked Damian Edwards (on twitter) to read your comment, so maybe he can shed some light on the issue you had. Haven't had that issue myself...
  6. Granted your controller looks better, but you could just as well use WebApi instead of signalr to group those "resources" (people in your example).
  7. @bennyM - yup, you're completely right, as I mention in my intro. Although, WebAPI would Require MVC4, which is still in RC, and not all production environments would allow an upgrade even if it was final. As I state, there are multiple ways of achieving the same goal - this is just one suggestion :)
  8. Nice post. I like the idea that you could use SignalR for something like this. I think WebAPI might however be a more compelling solution, although you still need to code the jquery code to hit the webapi (although granted that's pretty easy). Still, neat idea.
  9. I think SignalR is a little bit overkill. You can achieve the same with an ApiController (WebApi).
  10. Thanks for a nice article. SignalR looks very interesting to me. I would much rather work with named methods instead of Ajax calls. It is also nice to know that SignalR will use the browser and server capabilities. In that regard it is future-ready. That's one less burden for me as a developer. I will have to try it :-)
  11. I agree with Marang, you don't need SignalR, you should use WebAPI.
  12. Yes, you could just as well use WebAPI, but not every project would allow the upgrade to MVC4 yet. Even so, this is nothing but a suggested alternative. I'll write a followup post with a more in-depth comparison between the two.
  13. My understanding too is that there is no built in authentication with SignalR... which means that you either have to have a roll-your-own approach, or you have to have an unsecured API. Has this changed with recent releases of SignalR?
  14. @nick: You're sort of right, but you have access to the HubCallerContext through this.Context in your hub. This contains a .User property of type IPrinciple, in which you'll find authentication-info for the current context. What you would do is throw an HttpException if the .User.Identity.IsAuthenticated is false (or return a different message of course). I'll write more about this in my follow-up post :)
  15. You can use WebAPI, but what I like about this approach is that you don't have to write (and maintain) all the hard-coded WebAPI url's :) signalR.hubs.peopleHub.getAll() is so much cleaner.
  16. signalR.availble in the code - needs another a in "availble"?
  17. Great article, as it is very straight forward. Sorry about the other 90% of the comments focusing on better architectures. It's amazing how so many people love to hear themselves talk. Thanks again!
  18. Hey Nilsen, Any news for the response to first comment. I like your approach :)
  19. Hi Mandeep! Yes - according to Damien Edwards, one connection is made no matter how many hubs you access. I've tried this successfully myself with 10+ hubs, and calling them all from the same page. Tested in IE7+ and Chrome, so I guess that's good enough :)
  20. That's Great :) Another Question, I might be bit lazy not to use my brain here.. but.. suppose i have 100 users online and on different machines, Will SignalR affect my Server's performance, as there might be 100 open connections? Though i think my server will listen only on one port (:80 for example) so that should not be an issue?
  21. The only way it will affect your server's performance is if whatever you do in your hubs is resource-hungry. The connection itself does not create a huge load. If you check out the last 20 minutes of this video: http://vimeo.com/43659069 you can see a nice demonstration live of multiple concurrent connections (on a laptop as the webserver). And, you're right that it will only listen on one port. :)
  22. Thanks for the Video Link Nilsen, I watched few seminars a while back, but have just started to add it to our production code. You arite your Blog posts in English, but your blog is in Norweign language i guess. Would love to follow you on twitter or facebook, will be a good way to learn new things.
  23. Sure thing, Twitter: @yngvenilsen
  24. Hey Neilsen, While watching the video, i noticed the presenter said that IIS used 40kb of heap memory for every connection, so for IIS to handle 100,000 connections you would need 40GB of virtual memory on the server. I thought of sharing it with you as we were discussing it other day :)
  25. @Mandeep: Correct me if I am wrong, but I don't think you can realistically expect to be able to serve 100'000 users from a single server, regardless of the technology.
  26. Hey neilsen, At my workplace i am not responsible for iis deplpyments and taking care of server loads. 100,000 is a figure he gave, but he also mentioned that his machine could handle 30k connections.. I think it should not be a big issue at all, as you said for that many users we would have multiple servers I was just mentioning it to know if that would be an issue for servers. So When we do windows hosting, we might have to consider what kind of virtual memory we could get for a normal .net account. They have a very good disconnect mechanism so overloads should be bery less though
  27. @ManDeep: I'm not sure how you did your math, but 100000 connections with 40kb is 4GB ;) I don't think that's a problem for even a modern laptop... :)
  28. Nilsen yeah you are right, I just mentioned what Damien said in the video. At around 56:00 of his presentation, he mentioned that. He said his laptop used 40Gig of virtual memory for 100,000 connections. anyways that's cool, i am still quite far from getting that many connections for my apps :)
  29. sdfsdfsdfsd
  30. rwerwerwerwre
  31. Do you mind posting your working code? Thx greatly appreciated
  32. Hew: Added link to Github repository on top of the post :)
  33. Great article, great comment feed too! I perspnally learned quite a lot from this article and the comment feed. Also thank you to the commenter that added the video link, well worth the watch!
Legg til kommentar
 
   
 

   

Om meg

is a senior developer at Novanet. He specializes in .NET development, and loves everything that involves C# and JavaScript. Feel free to check out my profile on or on Twitter

Arkiv