SignalR, ActionFilters and ASP.NET Web API - StrathWeb

Strath

February 18th, 2013

SignalR, ActionFilters and ASP.NET Web API

Because if something is reusable, move it out of controller

There have been quite a few examples circulating on the web on how one would use SignalR together with Web API. It all started after Brad Wilson had a great example on calling SignalR from API controllers at NDC Oslo 2012.

Even on this blog we looked at calling SignalR from Web API ITraceWriter to provide realtime tracing capabilities.

How about, to avoid any controller-level noise, messaging the connected SignalR subscribers from ActionFilters? While this approach might not be applicable in all scenarios, when it is, I think it could provide a nice layer of separation.

More after the jump.

SignalR and attributes

Traditionally, we need to start with something, so let’s create a Web API web project & SignalR.

We’d have the following simple, default hub:

And a simple controller:

Suppose we have a following scenario. We’d like to notify a connected hub client (i.e. admin) with all incoming API requests & all outgoing objects sent out from our API.

It would be quite convenient to be able to decorate the Action with an ActionFilter attribute, indicating how SignalR should behave for this action:

So effecitvely, to tell each action that whenever it’s called, notify the client using IncomingMethod on a specific hub, and that its output should also be passed over to same hub and invoked on the client using another method (OutgoingMethod). We have to address hub by string name, because SignalR currently does not expose a way to address hub by Type (only via a strongly typed generic, which is not allowed in Attributes).

The ActionFilter attribute looks like this:

The construct is a bit unusual, but that’s due to the nature of Attributes. Named parameters are not allowed there, and since I want to avoid a constructor with two strings (which is not very intuitive), we use public properties to fake the named paramaters (a common approach when working with attributes).

On the incoming side, we read the request body stream (which needs resetting) into a string an log it (with some more effort we could extract specific type explicitly), to the SignalR clients together with timestamp and HTTP method.

On the outgoing side, we extract the response object (based on action’s return type) from the HTTP response, and send that to the client (notice, there is no specific reason why I’d do that – this is just to illustrate that we don’t *have to* deal with pure HTTP, we could access our CLR types as well and send those down to JS, which I think is pretty cool). We extract the response object by inspecting the action’s return Type, to which we have access. Of course we are not limited to doing that – we might add more info. But, for the purpose of this demo – we send the exact same response content to SignalR clients as the API responds with.

In both cases, we are using a not widely known Invoke method on SignalR’s ClientProxy object. This allows us to invoke a function by string name, rather than using the conventional dynamic object.

Trying this out

We just need to add some HTML and JavaScript to handle the incoming SignalR messages. Notice we expose two methods, corresponding to the methods we specified on our attributes.

Note: I’m using here SignalR alpha2 since there were some OWIN issues preventing the Nuget installation of the RC2 at the time of writing this – but everything should be the same in RC2, except the JS reference would need to be updated.

We also need to make sure that SignalR hub routes are registered:

Finally, we can now run this solution.

If I navigate to:

  • /api/values

  • /api/values/{id}

We’ll see the log filling with the responses. Here’s how it might look after a few requests:

Of course, this can be used for other scenarios than just a dummy log like this – but I hope this illustrates well the idea. You might consider notifying specific connection ids, injecting some identity context into the ActionFilter and so on.

All in all, while it’s not always suitable (if you watch Brad’s talk from the beginning, he notifies SignalR client from the controller’s action when resource gets created – obviously such scenarios must be handled inside of an action since that’s dealing with local variables inside an action), IMHO, this is still quite an interesting approach to SignalR presence in your API application, since it would help you clean up the controllers and isolate the logic responsible for communicating with the hubs into a reusable attribute.

Be Sociable, Share!

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1298

  • Braian Bressan

    Hi! this is great, could you provide an download link with the complete example files?
    Thanks in advance.

  • Pingback: Dew Drop – February 19, 2013 (#1,501) | Alvin Ashcraft's Morning Dew

  • Greg Lemons

    Another great post! I’m having a little trouble getting the HubAttribute class straightened out.

    System.Net.Http.HttpContent does not contain a definition for ‘ReadAsAsync’…. I see ReadAs[String,Stream,ByteArray]Async,

    Could I be missing a using?

  • http://twitter.com/mvsaradhi vijaya malla

    FYI, For everyone who is trying this, if you get an error 404 about signalr/hubs, put the the RouteTable.Routes.MapHubs(); line before the RouteConfig.RegisterRoutes(RouteTable.Routes) line in global.asax.cs

  • http://twitter.com/mvsaradhi vijaya malla

    Hi Filip, I created a default MVC4 project and added the js stuff in the layout.cshtml, creaed the attribute class, updated the controller but it is not working, when i debug i see that it is going to the attribute class for logging but when i go back to the home page i dont see the logs like you showed. can you please help me with this, I can email you the zip of my code. please advice, looking forward to hear from you.

  • Pingback: Scott Banwart's Blog › Distributed Weekly 195

  • http://twitter.com/m_tahir_naseem Muhammad Tahir

    Realy awesome. . Great Post.

  • Sandesh Deshmukh

    This is an awesome example!