- •Contents at a Glance
- •Introduction
- •Who should read this book
- •Assumptions
- •Who should not read this book
- •Organization of this book
- •Finding your best starting point in this book
- •Conventions and features in this book
- •System requirements
- •Code samples
- •Notes on the version
- •Installing the code samples
- •Using the code samples
- •Acknowledgments
- •Errata & book support
- •We want to hear from you
- •Stay in touch
- •HTTP operations
- •Polling: The answer?
- •Push: The server takes the initiative
- •WebSockets
- •Server-Sent Events (API Event Source)
- •Push today
- •The world needs more than just push
- •What does SignalR offer?
- •Two levels of abstraction
- •Supported platforms
- •OWIN and Katana: The new kids on the block
- •Installing SignalR
- •Implementation on the server side
- •Mapping and configuring persistent connections
- •Events of a persistent connection
- •Sending messages to clients
- •Asynchronous event processing
- •Connection groups
- •The OWIN startup class
- •Implementation on the client side
- •Initiating the connection by using the JavaScript client
- •Support for older browsers
- •Support for cross-domain connections
- •Sending messages
- •Receiving messages
- •Sending additional information to the server
- •Other events available at the client
- •Transport negotiation
- •Adjusting SignalR configuration parameters
- •Complete example: Tracking visitors
- •Project creation and setup
- •Implementation on the client side
- •Implementation on the server side
- •Server implementation
- •Hub registration and configuration
- •Creating hubs
- •Receiving messages
- •Sending messages to clients
- •Sending messages to specific users
- •State maintenance
- •Accessing information about the request context
- •Notification of connections and disconnections
- •Managing groups
- •Maintaining state at the server
- •Client implementation
- •JavaScript clients
- •Generating the proxy
- •Manual generation of JavaScript proxies
- •Establishing the connection
- •Sending messages to the server
- •Sending additional information
- •Receiving messages sent from the server
- •Logging
- •State maintenance
- •Implementing the client without a proxy
- •Complete example: Shared drawing board
- •Project creation and setup
- •Implementation on the client side
- •Implementation on the server side
- •Access from other threads
- •External access using persistent connections
- •Complete example: Monitoring connections at the server
- •Project creation and setup
- •Implementing the website
- •System for tracing requests (server side)
- •System for tracing requests (client side)
- •External access using hubs
- •Complete example: Progress bar
- •Project creation and setup
- •Implementation on the client side
- •Implementation on the server side
- •Multiplatform SignalR servers
- •SignalR hosting in non-web applications
- •SignalR hosting in platforms other than Windows
- •Multiplatform SignalR clients
- •Accessing services from .NET non-web clients
- •Consumption of services from other platforms
- •Growing pains
- •Scalability in SignalR
- •Scaling on backplanes
- •Windows Azure Service Bus
- •SQL Server
- •Redis
- •Custom backplanes
- •Improving performance in SignalR services
- •Server configuration
- •Monitoring performance
- •Authorization in SignalR
- •Access control in persistent connections
- •Access control in hubs
- •Client authentication
- •An extensible framework
- •Dependency Injection
- •Manual dependency injection
- •Releasing dependencies
- •Inversion of Control containers
- •Unit testing with SignalR
- •Unit testing of hubs
- •Unit testing persistent connections
- •Intercepting messages in hubs
- •Integration with other frameworks
- •Knockout
- •AngularJS
- •Index
- •About the author
Sending messages to specific users
We previously saw that we can invoke a method on a specific client by using its connection identifier via a construct similar to Clients.Client(id).MethodToInvoke(params).
We also briefly commented on another option: using the User(userName) selector, thus adding an additional way of selecting the recipient or recipients of the message by their user names.
public Task ObiWanMessage()
{
return Clients.User("luke").Message("Use the force");
}
In the preceding example, the message will be sent to all the SignalR clients that are authenticated in the system as “luke”. Note that, as opposed to Clients.Client(), which locates the recipient uniquely, in this case it would be possible to send the message via several connections, as many as are associated to the authenticated user.
The small distinguishing nuance is the distinction between the terms “client” and “user.” A client is equivalent to a connection and has a unique identifier, whereas a user can have several active connections (for example, by having several tabs open in their browser), each one with a different identifier.
Nevertheless, the best thing about this feature is its flexibility. When SignalR needs to know the name of the user, it employs a class that implements the IUserIdProvider interface, whose single method will be in charge of returning the user name, using information from the current request, if necessary.
The default implementation is found in the PrincipalUserIdProvider class, defined in the Microsoft.AspNet.SignalR.Infrastructure namespace, and it returns the name of the authenticated user:
public class PrincipalUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
if (request == null)
throw new ArgumentNullException("request");
if (request.User != null && request.User.Identity != null) return request.User.Identity.Name;
else
return (string) null;
}
}
68 Chapter 5 Hubs
www.it-ebooks.info
Obviously, we can implement our own logic to obtain the name of the user associated to a connection. For example, the following code shows how we could use the content of a cookie for this:
public class CookiesUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
if (request == null)
throw new ArgumentNullException("request"); Cookie cookie;
if (request.Cookies.TryGetValue("username", out cookie))
{
return cookie.Value;
}
else
{
return null;
}
}
}
SignalR will know that it must use this class instead of the one included out of the box because we are going to specifically indicate so by registering this class in a component called the dependency resolver. In Chapter 9, “Advanced topics,” we will delve into the dependency resolver. For now, it will suffice to understand that the following code, entered in the application startup, tells
SignalR that when it needs an object of the IUserIdProvider type it must use the delegate that we are providing it to obtain the instance. In this case, we will always provide it an instance of the
CookiesUserIdProvider type:
GlobalHost.DependencyResolver.Register(
typeof(IUserIdProvider),
()=> new CookiesUserIdProvider()
);
State maintenance
Clients automatically include state information in the calls they make to the methods of a hub. This information consists of a key-value dictionary that contains data that might be of use to either end. From the point of view of the server, this gives us the possibility of accessing these data from the body of the methods, again thanks to .NET dynamic types.
Hubs Chapter 5 |
69 |
www.it-ebooks.info
Thus, on the client side, we can create and use arbitrary properties that could be directly accessed from the server method invoked. In the case of a JavaScript client, these properties are created inside the state property of the proxy, as you can see in the following code:
Obviously, we can access data relative only to the client that invoked the current method; for this reason, we use the familiar Clients.Caller property to access such data.
It is also possible to modify the values at the server. The new state will be transferred to the client in the response to the invocation. Notice that in this case we are directly applying an autoincrement operator on the MsgId property:
public Task Alert(string msg)
{
var alert = string.Format("#{0} alert from {1}: {2}", Clients.Caller.MsgId++, Clients.Caller.UserName, msg);
return Clients.All.ShowAlert(alert);
}
The new value of the MsgId property will be returned to the client as part of the response to the call to the method. Upon arrival, the local value of the property will be updated.
This capability can be useful for the purpose of simplifying the signature of the methods of the hub, although we should bear in mind that the state information that we include at the client will travel in all requests, to maintain synchronization between both ends, so we must use it carefully.
Later, in “State maintenance” in the “Client implementation” section, we will look at the mechanisms behind this interesting feature.
70 Chapter 5 Hubs
www.it-ebooks.info
Accessing information about the request context
When using hubs, in method signatures we include only the parameters that the methods need to receive from the client to perform the task assigned to them. Consequently, obtaining information from the context of the request or even the identifier of the connection that makes the call is not as direct as when we used persistent connections, where we received these data as parameters of the methods provided by the PersistentConnection base class.
Nevertheless, it is just as easy. To access the context data, the Hub class offers the Context property—of the HubCallerContext type—through which it exposes properties, including
ConnectionId, Headers, QueryString, or User. See Figure 5-4.
FIGURE 5-4 Members of the Context property of the Hub class.
As its name implies, in Context.ConnectionId we will always have the identifier of the connection that the hub is currently instantiating and executing.
public Task NewUser()
{
var message = "New user: " + Context.ConnectionId; return Clients.Others.ShowAlert(message);
}
Other properties of Context, such as Headers, QueryString, RequestCookies, and User, are just shortcuts to members of the Request property, which is the one that really contains the information about the request.
Although one might expect the type of this Request property to be the traditional System
.Web.HttpRequest, this is not so. The property uses the IRequest type, which is a SignalR-specific abstraction and which allows accessing information on the context of the request in a decoupled way, without needing to know exactly what its implementation is. If we keep poring over the code of the framework, we will see that this interface is implemented in a class that, in turn, obtains all the data
it needs using the OWIN standard. This constitutes a complete isolation from the host in which the application is executed.
Hubs Chapter 5 |
71 |
www.it-ebooks.info