Coder Social home page Coder Social logo

uzbekdev1 / signalr.orleansintegration Goto Github PK

View Code? Open in Web Editor NEW

This project forked from hahn-software/signalr.orleansintegration

0.0 0.0 0.0 789 KB

An integration between AspNetCore.SignalR and Orleans to allow communication between clients distributed across multiple servers and backend services.

License: MIT License

C# 100.00%

signalr.orleansintegration's Introduction

SignalR.OrleansIntegration

An integration between AspNetCore.SignalR and Orleans to allow communication between clients distributed across multiple servers and backend services.

Originally inspired by AspNetCore.SignalR.Orleans and SignalR.Orleans.

The previous implementations lacked some functionality and handling mechanisms required by our applications so we created a new implementation based on the other projects merging in new functionality and taking the existing functionality to a new level suitable for our internal applications.

The integration plugin is pushing the SignalR communication between different SignalR clients and backend server parts onto Orleans streams and enables seamless communication between the servers and clients distributed across multiple servers and clustered services.

SignalR Hubs are enabled to communicate with clients connected to different servers and can synchronize their messages to huge applications. Also any Orleans Grain is able to access SignalR Hub Proxies for sending messages from distributed backend services to all connected clients. It's also possible to send messages to specific groups of clients and also single clients or users connected with multiple clients.

For enabling groups of clients the implementation pushes group memberships onto Orleans Grain states decoupling the grouping mechanism from SignalR. This way it's also possible to add clients to groups from other service parts outside the hubs.

Setting up the integration plugins

Web / Client project (SignalR hub host)

Install the integration plugin using NuGet:

Install-Package AspNetCore.SignalR.OrleansIntegration

or

dotnet add package AspNetCore.SignalR.OrleansIntegration

Next you have to add an Orleans cluster client (IClusterClient) to your service collection. It's up to you how to configure or create your cluster client. You're only required to include a call to .UseSignalR() in your cluster client setup:

var clusterClient = new ClientBuilder()
    .UseSignalR()
    // ...
    .Build();

services.AddSingleton(clusterClient);

It's also possible for you to configure whether to use fire and forget delivery for the underlying stream provider by passing in a configuration action to the .UseSignalR() method:

    // ...
    .UseSignalR(options =>
    {
        options.FireAndForgetDelivery = true;
    })
    // ...

In your SignalR service registration you also need to add the Orleans integration for each Hub you want to integrate:

services.AddSignalR()
    .AddOrleans<ChatHub>()
    .AddOrleans<AnotherHub>();

If necessary you can configure a specific cluster client to be used for a hub but this step is not required if you're good with your previously registered cluster client from MS DI.

.AddOrleans<ChatHub>(options => options.ClusterClient = clusterClient)

Now that you've set up all the integration it's time to add identifiers to your hubs in order to be able to get proxies for them in the backend code. You can put the identifiers in shared common libraries or hardcode them. You can add an identifier using the HubName-Attribute:

// Share this class between projects
public static class HubNames {
    public const string ChatHub = "MyChatHub";
}



[HubName(HubNames.ChatHub)]
public class ChatHub : Hub
{
    // ...
}

Notice: If you don't provide a name for your hub class it's using the class name by default. But it's recommended to add a dedicated name to each hub in order to prevent issues with renaming classes or problems introduced by multiple hubs with the same class name.

Orleans Server project (Orleans silo host)

Install the integration plugin using NuGet:

Install-Package Orleans.SignalRIntegration

or

dotnet add package Orleans.SignalRIntegration

On your silo host builder add a call to .UseSignalR() like for the orleans client in the SignalR Host App.

var siloHost = new SiloHostBuilder()
    .UseSignalR()
    // ...
    .Build();

That's all. You're ready to start using your new cluster-able multi server SignalR services.

Note that by default the Orleans plugin is registering Memory Storage as default for the PubSubStore. You can disable this behaviour by passing in a configuration action to the UseSignalR call on the silo host builder. Then you are able to register your own storage providers for the the mentioned grain storage provider. There's also an option for you to register a custom SignalR Grains storage but normally that's not required because it's ok in most cases when currently connected clients are dropped on silo restart. Clients wouldn't anyway be able to communicate. Also if required you can enable or disable fire and forget delivery for the underlying stream provider using the configure action.

const string invariant = "System.Data.SqlClient";
var connectionString = _configuration.GetConnectionString("Orleans");

var siloHost = new SiloHostBuilder()
    .UseSignalR(options =>
    {
        options.RegisterDefaultPubSubStorage = false;
        options.RegisterDefaultSignalRGrainStorage = true;
    })
    .AddAdoNetGrainStorage("PubSubStore", options =>
    {
        options.Invariant = invariant;
        options.ConnectionString = connectionString;
        options.UseJsonFormat = true;
    })
    // ...
    .Build();

Using the hub proxy

Orleans Grains

You can retrieve SignalR hub proxies using your well known GrainFactory of every Grain. Use the OnActivateAsync method to retrieve your proxies. After retrieving them you can call any method for sending messages to clients you already know from SignalR.

public class ChatChannelGrainState
{
    public ISet<string> ChannelMembers { get; set; } = new HashSet<string>();
}

public class ChatChannelGrain : Grain<ChatChannelGrainState>, IChatChannelGrain
{
    private IHubProxy _hubProxy;
    private IHubProxy _anotherHubProxy;

    public override Task OnActivateAsync()
    {
        // For now there's no easy way for us to access the GetStreamProvider method from our integration
        // so you have to retrieve the stream provider yourself.
        var streamProvider = GetStreamProvider(SignalRConstants.STREAM_PROVIDER);

        // Retrieve some hub proxies
        _hubProxy = GrainFactory.GetHubProxy(streamProvider, Guid.Parse(HubNames.ChatHub));
        _anotherHubProxy = GrainFactory.GetHubProxy(streamProvider, Guid.Parse(HubNames.AnotherHub));

        return base.OnActivateAsync();
    }

    public async Task AddToGroupWithConditionAsync(string connectionId, string groupName)
    {
        // Maybe limit the number of members in a channel to 5. Why not.
        if (State.ChannelMembers.Count <= 5)
        {
            // Add a client (identified by the connection id) to a group
            await _hubProxy.AddToGroupAsync(connectionId, groupName);

            // Also save the member list to our ChatChannel state
            State.ChannelMembers.Add(connectionId);
        }
    }

    public Task NotifyAllExceptCurrentMembersAsync()
    {
        // Send a message to all clients except our own channel members.
        // There are plenty more methods for sending messages. See the documentation further below.
        return _hubProxy.SendAllExceptAsync("ReceiveMessage", State.ChannelMembers.ToList(), "Server", "Hello, Client!");
    }

    public Task NotifyUserFromAnotherHubAsync(string userId)
    {
        // Send a message to a specific user on another hub.
        return _anotherHubProxy.SendUserAsync(userId, "ReceiveMessage", "Hello, User!");
    }
}

SignalR Hubs

The following example shows how to send messages to the same or other hubs utilizing the generic IHubProxy.

[HubName(HubNames.ChatHub)]
public class ChatHub : Hub
{
    private readonly IHubProxy<SampleHub> _hubProxy;
    private readonly IHubProxy<AnotherSampleHub> _anotherHubProxy;

    // Just inject some hub proxies like every other service in ASP.NET Core
    public SampleHub(IHubProxy<ChatHub> hubProxy, IHubProxy<AnotherHub> anotherHubProxy)
    {
        _hubProxy = hubProxy;
        _anotherHubProxy = anotherHubProxy;
    }

    public Task NotifyUserFromAnotherHubAsync(string userId)
    {
        // Use the same methods like on the hub proxy in Orleans Grains. They're the same abstraction!
        return _anotherHubProxy.SendUserAsync(userId, "ReceiveMessage", "Server", "Hello, user!");
    }
}

Any other code of your application

As long as you're inside of your SignalR host application use it like in the example for SignalR Hubs a few lines above. You can inject it and you're good to go.

For your other distributed backend services you may use a cluster client and grains for proxying in the messages. But you're also able to add the hub proxy to your dependency injection by using the HubProxy class as implementation for IHubProxy. Create a factory method and construct a hub proxy using the constructor of your choice. For example:

public HubProxy(IGrainFactory grainFactory, IStreamProvider streamProvider, string hubName)

You can pass in the IClusterClient as IGrainFactory without a problem and you can use the IHubProxy in every part of your application.

IHubProxy

Any hub proxy you're using with the integration plugins implement the same IHubProxy interface and therefore share the same abstractions of SignalR hubs. For convenience it's recommended to use the patterns described in the samples above to retrieve your IHubProxy.

public interface IHubProxy
{
    // ... Some method definitions which are mainly used for internal processes are omitted for clarity.

    Task AddToGroupAsync(string connectionId, string groupName);
    
    Task RemoveFromGroupAsync(string connectionId, string groupName);
    
    Task SendAllAsync(string method, params object[] args);
    
    Task SendAllExceptAsync(string method, IEnumerable<string> excludedConnectionIds, params object[] args);
    
    Task SendClientAsync(string connectionId, string method, params object[] args);
    
    Task SendClientsAsync(IEnumerable<string> connectionIds, string method, params object[] args);
    
    Task SendClientsExceptAsync(IEnumerable<string> connectionIds, string method, IEnumerable<string> excludedConnectionIds, params object[] args);
    
    Task SendGroupAsync(string groupName, string method, params object[] args);
    
    Task SendGroupExceptAsync(string groupName, string method, IEnumerable<string> excludedConnectionIds, params object[] args);
    
    Task SendGroupsAsync(IEnumerable<string> groupNames, string method, params object[] args);
    
    Task SendUserAsync(string userId, string method, params object[] args);
    
    Task SendUsersAsync(IEnumerable<string> userIds, string method, params object[] args);
    
    Task SendUsersExceptAsync(IEnumerable<string> userIds, string method, IEnumerable<string> excludedConnectionIds, params object[] args);
}

Planned Features

  • Events / Streams for connecting and disconnecting clients in order to use those for processing in backend
  • Dynamic API like SignalR's .Clients.All.SomeMethod(arg1, arg2, ...)
  • API for strongly typed Clients
  • Automated Stream Provider resolution in Grains
  • Advanced logging and metric mechanisms
  • Infrastructure Stream for providing information about active SignalR hosts
  • More error handling possibilities
  • More documentation
  • More automated tests
  • More samples

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.