- •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
server: "localhost", port: 54321, password: "12345",
eventKey: "Broadcaster"
);
app.MapSignalR();
}
}
Again, this is all that we need to run our services on Redis. When we execute them, we will also see that the difference in performance compared to the other two mechanisms provided by SignalR is quite significant.
Custom backplanes
The developer community has also created some backplanes for SignalR that cover other serial technologies without out-of-the-box support, and their number is expected to increase with time. We must take into account the short age of the project and, more specifically, of scale-out mechanisms.
Obviously, in these cases, Microsoft does not provide official support, but these backplanes might be quite useful in scenarios not initially covered, such as those that use components such as NServiceBus or RabbitMQ.
If we have specific needs, nothing stops us from creating our own backplane. Although its creation is not overly complex, right now it is not very well documented and learning relies heavily on observing the code of existing adapters and trying to replicate them, adding the necessary customizations.
Very broadly speaking, the process consists of at least the following steps:
■■Creating a messaging bus inheriting from the ScaleoutMessageBus class provided by SignalR.
■■Implementing the Send() method, which will be invoked by SignalR when sending data to the backplane from the current node. In it, we should contact our bus and enter in it the messages that are received. This bus will be queried by all nodes connected to the server farm.
■■Creating a process to retrieve the messages sent from persistence and entering them into the message flow of the local node. This can be a background process, or it can be code written in the event handler that receives data from the system used for persistence.
■■At application startup, telling SignalR that the messaging bus to be used must be the class that we created earlier.
The following code shows a backplane that uses the file system as persistence. Obviously, it is pointless outside the local computer and its sole purpose is to give an example of the smallest messaging bus that would allow sharing messages between nodes within the same computer. Therefore, it must not be used in real environments.
170 Chapter 8 Deploying and scaling SignalR
www.it-ebooks.info
// File: FilesystemMessageBus.cs
public class FileSystemMessageBus : ScaleoutMessageBus
{
// Uses the folder %temp%/backplane private readonly string BasePath =
Path.Combine(Path.GetTempPath(), "Backplane"); private FileSystemWatcher _watcher;
public FileSystemMessageBus(IDependencyResolver resolver, ScaleoutConfiguration configuration)
: base(resolver, configuration)
{
Open(0); // Use only one stream if (Directory.Exists(BasePath))
{
var files = new DirectoryInfo(BasePath).GetFiles(); foreach (var file in files)
{
file.Delete();
}
}
else Directory.CreateDirectory(BasePath);
_watcher = new FileSystemWatcher(BasePath, "*.txt")
{
IncludeSubdirectories = false, EnableRaisingEvents = true
};
_watcher.Created += FileCreated;
}
// Process messages sent from the backplane to the server private void FileCreated(object sender, FileSystemEventArgs e)
{
byte[] bytes; while (true)
{
try
{
bytes = File.ReadAllBytes(e.FullPath);
break; |
|
} |
|
catch |
// The file is still in use |
{ |
|
Thread.Sleep(10); // Let's wait for a short while |
|
} |
// and try again |
}
var scaleoutMessage = ScaleoutMessage.FromBytes(bytes); ulong id;
string fileName = Path.GetFileNameWithoutExtension(e.Name); ulong.TryParse(fileName, out id);
foreach (var message in scaleoutMessage.Messages)
{
OnReceived(0, id,
new ScaleoutMessage(new[] { message }));
}
Deploying and scaling SignalR Chapter 8 |
171 |
www.it-ebooks.info
}
// Send messages from the server to the backplane protected override Task Send(int streamIndex,
IList<Message> messages)
{
return Task.Factory.StartNew(() =>
{
var bytes = new ScaleoutMessage(messages).ToBytes(); var filePath = BasePath + "\\" +
DateTime.Now.Ticks + ".txt";
File.WriteAllBytes(filePath, bytes);
});
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_watcher.Dispose();
}
base.Dispose(disposing);
}
}
As you can see, sending messages to the backplane is done just with the Send() method, serializing the message to a file in the “%temp%\Backplane” folder.
To detect new messages from the backplane, we are using a FileSystemWatcher object on this same folder. When it detects new files—messages—the FileCreated() method is executed, which gets the new file, deserializes it, and enters it into the flow of messages of the current node.
To inform SignalR that it is the message bus to be used, we would just need to execute the following code during startup:
// File: Startup.cs
var bus = new Lazy<FileSystemMessageBus>(
() => new FileSystemMessageBus( GlobalHost.DependencyResolver, new ScaleoutConfiguration())
);
GlobalHost.DependencyResolver.Register(
typeof(IMessageBus),
() => (object)bus.Value
);
In Chapter 9, “Advanced topics,” we will learn what the Dependency Resolver is, and we will fully understand what we are doing with this code. For now, it will suffice to know that we are setting the object to be used internally when any component of SignalR needs to access the messaging bus.
172 Chapter 8 Deploying and scaling SignalR
www.it-ebooks.info