Orleans is a framework that provides a straight-forward approach to building distributed high-scale computing applications, without the need to learn and apply complex concurrency or other scaling patterns.
ASP.NET Core SignalR is a new library for ASP.NET Core developers that makes it incredibly simple to add real-time web functionality to your applications. What is "real-time web" functionality? It's the ability to have your server-side code push content to the connected clients as it happens, in real-time.
SignalR.Orleans is a package that allow us to enhance the real-time capabilities of SignalR by leveraging Orleans distributed cloud platform capabilities.
Installation is performed via NuGet
From Package Manager:
PS> Install-Package SignalR.Orleans
.Net CLI:
# dotnet add package SignalR.Orleans
Paket:
# paket add SignalR.Orleans
We need to configure the Orleans Silo with the below:
- Use
.UseSignalR()
onISiloHostBuilder
. - Make sure to call
RegisterHub<THub>()
whereTHub
is the type of the Hub you want to be added to the backplane.
Example
var silo = new SiloHostBuilder()
.UseSignalR()
.RegisterHub<MyHub>() // You need to call this per `Hub` type.
.AddMemoryGrainStorage("PubSubStore") // You can use any other storage provider as long as you have one registered as "PubSubStore".
.Build();
await silo.StartAsync();
Optional configuration to override the default implementation for both providers which by default are set as Memory
.
Example
.UseSignalR(cfg =>
{
cfg.ConfigureBuilder = (builder, config) =>
{
builder
.AddMemoryGrainStorage(config.PubSubProvider)
.AddMemoryGrainStorage(config.StorageProvider);
};
})
.RegisterHub<MyHub>()
Now your SignalR application needs to connect to the Orleans Cluster by using an Orleans Client:
- Use
.UseSignalR()
onIClientBuilder
.
Example
var client = new ClientBuilder()
.UseSignalR()
.Build();
await client.Connect();
Somewhere in your Startup.cs
:
- Add
IClusterClient
(created in the above example) toIServiceCollection
. - Use
.AddSignalR()
onIServiceCollection
(this is part ofMicrosoft.AspNetCore.SignalR
nuget package). - Use
AddOrleans()
on.AddSignalR()
.
Example
public void ConfigureServices(IServiceCollection services)
{
...
services
.AddSingleton<IClusterClient>(client)
.AddSignalR()
.AddOrleans();
...
}
Great! Now you have SignalR configured and Orleans SignalR backplane built in Orleans!
HubContext
gives you the ability to communicate with the client from orleans grains (outside the hub).
Sample usage: Receiving server push notifications from message brokers, web hooks, etc. Ideally first update your grain state and then push signalr message to the client.
public class UserNotificationGrain : Grain<UserNotificationState>, IUserNotificationGrain
{
private HubContext<IUserNotificationHub> _hubContext;
public override async Task OnActivateAsync()
{
_hubContext = GrainFactory.GetHub<IUserNotificationHub>();
// some code...
await _hubContext.User(this.GetPrimaryKeyString()).Send("Broadcast", State.UserNotification);
}
}
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Orleans;
using Orleans.Hosting;
// Cohosting aspnetcore website and Orleans with signalR
var host = Host.CreateDefaultBuilder(args)
// Add the webhost with SignalR configured.
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureServices((webBuilderContext, services) =>
{
// Add response compression used by the SignalR hubs.
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
// Adds SignalR hubs to the aspnetcore website
services.AddSignalR(options =>
{
})
.AddOrleans(); // Tells SignalR to use Orleans as the backplane.
});
webBuilder.Configure((ctx, app) =>
{
// Adds response compression for use by the SignalR hubs
app.UseResponseCompression();
// Map SignalR hub endpoints
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<MyHubType1>("/hub1"); // use your own hub types
endpoints.MapHub<MyHubType2>("/hub2"); // use your own hub types
// ... etc
});
});
})
// Add Orleans with SignalR configured
.UseOrleans((context, siloBuilder) =>
{
siloBuilder
.UseSignalR(signalRConfig =>
{
// Optional.
signalRConfig.UseFireAndForgetDelivery = true;
signalRConfig.Configure((siloBuilder, signalRConstants) =>
{
// **************************************************************************
// Use memory storage ONLY when your app is not clustered, otherwise you'll
// need to use proper external storage providers
// **************************************************************************
siloBuilder.AddMemoryGrainStorage(signalRConstants.StorageProvider);
// This wouldn't be be necessary if you already added "PubSubStore" elsewhere.
siloBuilder.AddMemoryGrainStorage(signalRConstants.PubSubProvider /*Same as "PubSubStore"*/);
});
})
// Allows Orleans grains to inject IHubContext<HubType>
.RegisterHub<MyHubType1>()
.RegisterHub<MyHubType2>();
// ... etc
})
.UseConsoleLifetime()
.Build();
await host.StartAsync();
await host.WaitForShutdownAsync(default);
PRs and feedback are very welcome!