Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SignalR service: Get message depending on user role

I created

  1. MVC app (installed -Install-Package Microsoft.AspNet.SignalR.JS) (ref here)

  2. Web Service (//Install from Nuget Package window //Install-Package Microsoft ASP.NET SignalR .NET Client //Install-Package Microsoft ASP.NET SignalR Core Components)

  3. Signalr Service (Installed -Install-Package Microsoft.AspNet.SignalR.SelfHost and Install-Package Microsoft.Owin.Cors)

What I am doing: I am calling MVC page and after processing one task using web service. In that web service when task is processing I want to notify user what is going on behind the seen like task is in processing or it is done using Signalr service.

I created All project separately.

using web Service I am calling the signalr hubs (see here)

challenges facing: I want to broadcast the message to that user and if no. of user are there then depending on there role i want to send messages.

enter image description here

Edited:Extra Enhancement added in my project:I have no. of MVC app and its corresponding Web services and My single signalR service so how can I identify which MVC app calling it corresponding service and service pushing to all or it application users or particular user. Like pusher will create application Id to application and number of tokens for user. It is possible to do it.

like image 227
Prasad Phule Avatar asked Nov 01 '22 00:11

Prasad Phule


1 Answers

Summary:

I'm not sure that it's possible to have the hubs living on the WCF SignalR service. It would be best to let the MVC project act as a proxy between the client and web service. You can connect to SignalR later with other clients (such as desktop clients) if that's one of your requirements, and also connect to this hub from your web service to send updates to the clients and/or users in a specified group.


Workflow:

To start, the flow would look more like this: SignalR Workflow

Managing Client Connections:

If you are using an in-memory approach to managing your connected users, then you could start by adding the connection id and the user id to whatever collection you are using to handle this. For example:

    public static ConcurrentDictionary<String, String> UsersOnline = new ConcurrentDictionary<String, String>();

    public override System.Threading.Tasks.Task OnConnected()
    {
        UsersOnline.TryAdd(Context.ConnectionId, Context.User.Identity.GetUserId());
        return base.OnConnected();
    }

A word of caution: The Context.User will be null unless you map SignalR after the authentication.

It may be beneficial to store the connection id in variable on the client side as well so you can pass it to your methods later.

        var connectionId;

        var testHub = $.connection.testHub;

         $.connection.hub.start().done(function () {
            connectionId = $.connection.hub.id;
        }

The Hub:

The hub can be used to communicate with the web service. In this example I'll be using it as a soap service, but rest should work just the same.

    public void LongRunningTask(String ConnectionId)
    {
        using (var svc = new Services.MyWebService.SignalRTestServiceClient())
        {
            svc.LongRunningTask(ConnectionId);
        } // end using
    } // end LongRunningTask

Note that we pass the connection id to the service as well. This comes into play when the service starts sending messages back to the MVC project to deliver to the client(s).


Listener or Web API:

Set up a listener controller or a Web API on the MVC site to receive messages from the web service.

    public ActionResult SignalR(String Message, String Type, String ConnectionId)
    {
        if (!String.IsNullOrWhiteSpace(Message) && !String.IsNullOrWhiteSpace(Type) && !String.IsNullOrWhiteSpace(ConnectionId))
        {
            if (Type == "ShowAlert")
            {
                // Determine if the user that started the process is still online
                bool UserIsOnline = Hubs.TestHub.UsersOnline.ContainsKey(ConnectionId);

                // We need this to execute our client methods
                IHubContext TestHub = GlobalHost.ConnectionManager.GetHubContext<Hubs.TestHub>();
                if (UserIsOnline)
                {
                    // Show the alert to only the client that started the process.
                    TestHub.Clients.Client(ConnectionId).showAlert(Message);
                } // end if
                else
                {
                    List<String> UserIdsInRole = new List<String>();
                    using (var connection = new System.Data.SqlClient.SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["DefaultConnection"].ToString()))
                    {
                        // Assuming you're using Identity framework since it is an MVC project, get all the ids for users in a given role.
                        // This is using Dapper
                        UserIdsInRole = connection.Query<String>(@"
SELECT                  ur.UserId
FROM                    AspNetUserRoles ur
JOIN                    AspNetRoles r ON ur.RoleId = r.Id
WHERE                   r.Name = @rolename
", new { rolename = "SpecialRole" }).ToList();
                    } // end using

                    // Find what users from that role are currently connected
                    List<String> ActiveUsersInRoleConnectionIds = Hubs.TestHub.UsersOnline.Where(x => UserIdsInRole.Contains(x.Value)).Select(y => y.Key).ToList();

                    // Send the message to the users in that role who are currently connected
                    TestHub.Clients.Clients(ActiveUsersInRoleConnectionIds).showAlert(Message);
                } // end else (user is not online)
            } // end if type show alert
        } // end if nothing is null or whitespace
        return new HttpStatusCodeResult(200);
    } // end SignalR

Web Service:

The web service method that does the long running work should accept a client id as well, so it can send it back to the listener controller or web API. It can use a method similar to this (using RestSharp) to connect back to the MVC project:

    public void ShowAlert(String Message, String ConnectionId)
    {
        RestClient Client = new RestClient("http://localhost:8888");
        RestRequest Request = new RestRequest("/Listener/SignalR", Method.POST);
        Request.Parameters.Add(new Parameter() { Name = "Message", Type = ParameterType.QueryString, Value = Message });
        Request.Parameters.Add(new Parameter() { Name = "Type", Type = ParameterType.QueryString, Value = "ShowAlert" });
        Request.Parameters.Add(new Parameter() { Name = "ConnectionId", Type = ParameterType.QueryString, Value = ConnectionId });
        IRestResponse Response = Client.Execute(Request);
    } // end Show Alert

Demo:

I did a proof of concept and uploaded it to Github.

like image 134
silencedmessage Avatar answered Nov 12 '22 18:11

silencedmessage