Using dependency injection to replace a default behavior

SignalR's dependency injection system has been exploited so far to hook into the factory pipeline implemented to create instances of any Hub-derived type, but what's even more interesting about the whole usage of this system is the fact that most of SignalR's internals are exposed through the same mechanism. This means that you can actually replace entire portions of the SignalR machinery. Maybe you know a better way to perform their task, or maybe you just need to add some more features to a specific part, whatever the reason you can do it as we'll see here.

SignalR is organized in services, and all of them have a default implementation available. Let's list a few of them to just give you an idea:

  • IMessageBus: A service implementing this contract processes every single message, sent or received, with the goal of making them available to every subscriber; we already mentioned this interface when explaining how backplanes are implemented.
  • IJavaScriptProxyGenerator: It defines how a service generating JavaScript proxies for any Hub has to look like.
  • IAssemblyLocator: It is a service contract defining how to locate all the assemblies potentially containing hubs.

There are many more, and you can check the source code on GitHub to discover which they are. In this recipe, we'll concentrate on a specific one called IUserIdProvider, and we'll see how to replace it with our custom implementation. The sample application will allow us to send messages to a specific user, and it will achieve this goal by using a specific SignalR API that makes use of the IUserIdProvider service we'll be replacing.

Getting ready

For this recipe, we'll use a new empty web application, which we'll call Recipe44.

How to do it…

After creating our empty web application, we will proceed as usual using the following steps:

  1. We start with a new Hub called EchoHub; you can see its code as follows:
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace Recipe44
    {
        [HubName("echo")]
        public class EchoHub : Hub
        {
            public void SayHello(string recipient)
            {
                Clients.User(recipient).greetings("Hello");
            }
        }
    }

    The SayHello() method has a single parameter for the name of the user we want to target with a message, and it uses the Clients.User member to get the connection(s) owned by the specified user. On the dynamic object returned, it finally calls the greetings() dynamic method. This mechanism works as for every other member of the Clients object we described in the first part of the book.

    The Clients.User resolution process is based on IUserIdProvider, which is used by SignalR to extract useful information related to the identity of the connected user from every single request it performs. Let's see how it works.

  2. We need to add our custom implementation of IUserIdProvider to the project. For that, we'll introduce a new class called EchoHubUserProvider with the following code:
    using Microsoft.AspNet.SignalR;
    
    namespace Recipe44
    {
        public class EchoHubUserProvider : IUserIdProvider
        {
            public string GetUserId(IRequest request)
            {
                return request.QueryString["user"];
            }
        }
    }

    The class implements IUserIdProvider, which implies providing a method called GetUserId(), receiving an IRequest instance and returning a string. For every incoming request, SignalR will ask the dependency resolver to provide an instance of a service implementing this contract, in order to extract any useful information to determine an identifier for the connected user. The default implementation embedded in SignalR returns the name extracted from the identity principal attached to the supplied IRequest instance. Our sample implementation will get that information from a query string parameter called user. This means that we'll need to put this value in place, and we'll take care of this detail on the test page.

    It's a basic example, with nothing in place to take care of concerns like authentication or security, but it should be enough to let you imagine more sophisticated user-mapping strategies and handshaking workflows. It also shows how you could design an authentication mechanism based on some sort of client-side-generated token.

  3. We then create the Startup class by performing the usual MapSignalR() call, but not before having registered our custom IUserIdProvider as follows:
    using Microsoft.AspNet.SignalR;
    using Microsoft.Owin;
    using Owin;
    using Recipe44;
    
    [assembly: OwinStartup(typeof(Startup))]
    
    namespace Recipe44
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                GlobalHost.DependencyResolver.Register(     typeof(IUserIdProvider),() => new EchoHubUserProvider());
    
                app.MapSignalR();
            }
        }
    }

    The call is very straightforward, and it's using the Register() method from DependencyResolver as we did when performing the registration and implementation of the ITranslator contract in the Introducing dependencies injection (simple approach) recipe. This is good news: the same API we leveraged to register a custom service to be injected in a Hub can be used to replace any SignalR default implementation of an internal service!

  4. We're almost done. We just need a client test page, which as usual we'll call index.html, from where anybody can type in the name of a target user who will receive a "Hello" message. First, we'll need some HTML controls on the page for the login process and to send messages, as shown in the following code:
            <div id="login-form">
                Your name: <input type="text" id="user" />
                <button id="login">Login</button>
            </div>
            <div id="message-form" style="display: none">
                Say hello to: <input type="text" id="recipient"/>
                <button id="send">Send</button>
                <ul id="messages"></ul>
            </div>
  5. We then need some JavaScript code to handle the login phase first, and later on the input of the recipient name and the posting of the message:
        <script src="Scripts/jquery-2.1.0.js"></script>
        <script src="Scripts/jquery.signalR-2.0.2.js"></script>
        <script src="/signalr/hubs"></script>
        <script>
    
            $(function () {
                $('#login').click(function() {
                    $('#login-form').toggle();
                    $('#message-form').toggle();
    
                    var hub  = $.connection.hub,
                        echo = $.connection.echo;
    
                    hub.qs = { 'user': $('#user').val() };
    
                    echo.client.greetings = function(message) {
                        $('<li/>').html(message).appendTo('#messages'),
                    };
    
                    hub.start().done(function () {
                        $('#send').click(function () {
                            echo.server.sayHello($('#recipient').val());
                        });
                    });
                });
            });
    
        </script>

    There is an interesting and new SignalR feature we need to illustrate in this code. We do not start SignalR immediately after loading the page; instead, we do that just after receiving the username. This is because we want to use the query string to supply that information to SignalR at every call. The hub object exposes a property called qs, which can be assigned a JavaScript object representing a set of parameters we want to send to the server using the query string. This property has to be set before starting the connection, and cannot be changed afterwards. That's why we need to know what to put in there before connecting. We use this feature to take the username supplied and add it as a query string parameter called user, which matches the one expected by the implementation of IUserIdProvider we saw earlier. SignalR will guarantee that such a value will be provided for every request that comes to the server.

We can test our page by opening it in a couple of browser windows. In each of them, we'll type a different username and log in, and finally we'll be able to send direct messages to specific users identified by the names they used to log in and discriminated on the server by our EchoHubUserProvider service. We could even use the same username in multiple tabs, all of them would receive the same message when their user is the target, thanks to the way Clients.User, and IUserIdProvider work together.

How it works…

If we put a breakpoint on the line of code inside the EchoHubUserProvider, we'll see that line hit on every request going to the server. This means that SignalR resolves the user ID at every request and then keeps it around so that an API like Clients.User is able to find the connection mapped to that user. The value returned by GetUserId() becomes a key through which we can find a set of connections just by knowing an identifier associated to the user owning them.

The details about how IUserIdProvider works are interesting, but those wouldn't have been possible without the DependencyResolver API.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.34.0