Best way to communicate between ASP.NET sessions? - asp.net

I'm trying to find what is the right design where a client will open 2 sessions where one is used to upload a large file and another is used to provide SignalR messaging. The goal is that when the server processes the large file, it needs to provide status messages to the SignalR channel so that the client keeps get notified about the processes going on the server during and beyond the upload itself. The assumption is that at the upload, the client will provide the SignalR identifier so that the worker working on the file upload can pass it on to the Hub but what I'm not actually sure about is how the upload worker should connect with Hub. Do I maintain a static singleton class that holds a persistent reference to the hub? Or do I just make a new HTTP session and send via loopback? Or is there a better approach I didn't even think of?

You can use dependency injection to get a reference to your hub;
public class UploadController
{
IHubContext<YourHub> _hub;
public UploadController(IHubContext<YourHub> hub)
{
_hub = hub;
}
public ActionResult Upload(SomeFile model)
{
// start upload processing
// send progress updates
var conectionId = DetermineConnectionId(); // store it in a dictionary maybe?
_hub.Clients.Client(connectionId).SendProgressUpdate();
}
}

Related

Dynamic scoped dependency resolution in a console app for multiple tenants using dependency injection

In case of a Web API, each request is a distinct scope and dependencies registered as scoped will get resolved per request. So resolving dependencies per request per tenant is easy as the tenant information (like TenantId) can be passed in the HTTP Request headers like below:
services.TryAddScoped<ITenantContext>(x =>
{
var context = x.GetService<IHttpContextAccessor>().HttpContext;
var tenantId = context.Request.Headers["TenantId"].ToString();
var tenantContext = GetTenantContext(tenantId);
return tenantContext;
}
Other registrations first resolve TenantContext and use it to resolve other dependencies. For example, IDatabase will be registered as below. During resolution it will resolve and connect to specific tenant database.
services.TryAddScoped<IDatabase>(x =>
{
var tenantContext = x.GetService<ITenantContext>();
return new Database(tenantContext.DatabaseConnectionString);
}
This is all good in a Web API service because each request is a scope. I am facing challenges using dependency injection in a multi-tenant Console App. Suppose the app processes items from a
multi-tenant queue and each message can belong to a different tenant. While processing each message, it commits data to tenant specific database. So in this case the scope is each message in a queue and message contains the tenantId.
So when the app reads a message from queue, it needs to get TenantContext. Then resolve other dependencies based on this TenantContext.
One straightforward option I see how this dynamic resolution can be achieved is to create the dependent objects manually using the TenantContext but then I wouldn't be able to leverage dependency injection. All objects would get created manually and disposed after going out of scope after the message is processed.
var messgage = GetMessageFromQueue(queueName);
var tenantContext = GetTenantContext(message.TenantId);
var database = GetDatabaseObject(tenantContext);
// Do other processing now we got the database object connected to specific tenant DB
Is there an option in DI where I can pass in the TenantId dynamically so that TenantContext gets set for this scope and then all further resolution within this scope leverage this TenantContext?
Because the role of the tenancy goes beyond the implementation ("this uses X database") and is actually contextual to the action being performed ("this uses X database and must use this connection string based on the context being handled in the action"), there's some risk of assuming that ambient context is present in alternate implementations due to it not expressly being described in your interface in some way, which is where the DI issue is coming up here.
You might be able to:
Update your interfaces so that the tenancy information is an expected parameter of your methods. This ensures that regardless of future implementation, the presence of the tenant ID is explicit in their signature:
public interface ITenantDatabase {
public TResponse Get(string TenantId, int Id);
//... other methods ...
}
Add a factory wrapper around your existing interfaces to handle assigning the context at object creation and have that factory return the IDatabase instance. This is basically what you are proposing manually but with an abstraction around it that you could register and inject to keep the code that leverages it from being responsible for the logic:
public interface ITenantDatabaseFactory {
public IDatabase GetDatabaseForTenant(int TenantId);
}
// Add an implementation that manually generates and returns the scoped objects

does onDisconnect event on SignalR2 releases all of its resources upon disconnect?

I have a concurrent dictionary List on my controller that stores list of online users. For example when Client A and Client B connects there are 2 online clients present on the list, but when i disconnect B and then re- connect again it must still show 2 online clients but in my case, only Client B detected as online user(disconnected then reconnected). I am using IIS server 7.5.. Please help me with this, do i need to use a database rather than dictionary? I think it resets the dictionary to zero if one user disconnects and reconnects again.... :( Below is my hub class code
public class Chat : Hub
{
//add online client
private static ConcurrentDictionary<string, string> personLists
= new ConcurrentDictionary<string, string>();
public void Connect(string Username, int ID)
{
string id = Context.ConnectionId;
if (!personLists.ContainsKey(Username))
{
personLists.TryAdd(Username, id);
Clients.Caller.viewOnlinePersons(personLists.Where(p => p.Key != Username));
Clients.Others.enters(Username);
}
else
{
string notif = "user: "+Username+" is already used";
Clients.Caller.onUse(notif);
}
The concurrent dictionary should work just fine. It'd be good if you could post some code, but if you go this route with concurrent dictionary, make sure it's static (the Hub classes get created and destroyed per signal) and I think it'd be better suited placed on the hub itself and private (and of course static, again). You can also use Dependency Injection with SignalR which will be a lot cleaner.
You'll only need database as a backplane if you plan on running the application on multiple servers where of course a memory of a single server is not accessible by the other servers and a duplicate dictionary would be created for each server, so in that case you need to take the storage and move it up a bit in the architecture to be accessible by all the servers.

how to know what kind of clients are connected to signalr hub?

I'm using signalr on my site. I have just one class that inherits from hub, and several aspx forms with client code that have client functions called by the hub.
when a client connects to the hub I add it to a collection with a clientID, when a client disconnect I remove it from that collection. Just to know if any client is connected. As long as at least one client is connected, a timer call some methods that fill a data repository on the server that is propagated to clients.
then I usually do before calling customers things like this:
if (users.Count() > 0)
{
this.Clients.All.UpdateData(JsonConvert.SerializeObject(someData));
...
}
all this works fine. But what I need and can not find how to do it in the hub, is to know what clients are connected to know if I should create the data repository for those clients and avoid making unnecessary database queries.
eg.
myHub.cs
Timer_tick()
{
...
//Collect data to clients Type 1
...
Clients.All.UpdateDataType1(jsonData);
...
//Collect data to clients Type 2
...
Clients.All.UpdateDataType2(jsonData);
...
//Collect data to clients Type 3
...
Clients.All.UpdateDataType3(jsonData);
ClientType1.aspx.js (2 clients connected)
hub.client.UpdateDataType1= function (jsonData) {...);
ClientType2.aspx.js (0 clients connected)
hub.client.UpdateDataType2= function (jsonData) {...);
ClientType2.aspx.js (0 clients connected)
hub.client.UpdateDataType3= function (jsonData) {...);
I know when I call clients 2 and 3 function I not need worry about whether clients are connected. but I need to avoid to obtain data that I will not use. The goal is just obtain data I will use to the connected clients.
All I can do is see if there are clients, but not if there are clients of an specic js
Is there any way to know this?
You can use groups for that, and you can write JavaScript code reusable across pages. Before starting the connection, your JavaScript could do this:
$.connection.hub.qs = { referer: document.location.pathname };
This way you store the calling page in a query string key. Of course you can store whatever other information you think it's useful for your goal. Then you can have this in your hub:
public override Task OnConnected()
{
var referer = Context.Request.QueryString["referer"];
Groups.Add(Context.ConnectionId, referer);
}
This way each client will notify which library is using, and with the groups you created against the client information you can easily target them by client type. No need to store anything in memory, which has always a lot of drawbacks.
You can check that link to map users to conection. If you take a look at the in-memory solution, you will be able to retrieve a list of connected users.

Webmatrix.Data.Database Connection String Cleared After Form Submit

I'm developing an ASP.NET (Razor v2) Web Site, and using the WebMatrix.Data library to connect to a remote DB. I have the Database wrapped in a singleton, because it seemed like a better idea than constantly opening and closing DB connections, implemented like so:
public class DB
{
private static DB sInstance = null;
private Database mDatabase = null;
public static DB Instance
{
get
{
if (sInstance == null)
{
sInstance = new DB();
}
return sInstance;
}
}
private DB()
{
mDatabase = Database.Open("<Connection String name from web.config>");
return;
}
<Query Functions Go Here>
}
("Database" here refers to the WebMatrix.Data.Database class)
The first time I load my page with the form on it and submit, a watch of mDatabase's Database.Connection property shows the following: (Sorry, not enough rep to post images yet.)
http://i.stack.imgur.com/jJ1RK.png
The form submits, the page reloads, the submitted data shows up, everything is a-ok. Then I enter new data and submit the form again, and here's the watch:
http://i.stack.imgur.com/Zorv0.png
The Connection has been closed and its Connection String blanked, despite not calling Database.Close() anywhere in my code. I have absolutely no idea what is causing this, has anyone seen it before?
I'm currently working around the problem by calling Database.Open() before and Database.Close() immediately after every query, which seems inefficient.
The Web Pages framework will ensure that connections opened via the Database helper class are closed and disposed when the current page has finished executing. This is by design. It is also why you rarely see connections explicitly closed in any Web Pages tutorial where the Database helper is used.
It is very rarely a good idea to have permanently opened connections in ASP.NET applications. It can cause memory leaks. When Close is called, the connection is not actually terminated by default. It is returned to a pool of connections that are kept alive by ADO.NET connection pooling. That way, the effort required to instantiate new connections is minimised but managed properly. So all you need to do is call Database.Open in each page. It's the recommended approach.

Is there a way to set any property on hub that persists throughout the lifetime of a connection?

To set the right context, let me explain the problem. Till RC1, we used to implement GenerateConnectionIdPrefix() to prefix user Id to the connection Id. Then we could retrieve user id from the connection string anytime we need.
With RC2, we now cannot inherit IConnectionIdPrefixGenerator & implement GenerateConnectionIdPrefix anymore. So I was wondering what are other avenues available to set any property on the hub with our data, that persists throughout the lifetime of the connection.
Going through documentation, I realized setting query strings is one way, but that would mean we need to set it for every call. Setting a round trip state might be another option, but it looks like even that is persistent for a single round-trip and not entire lifetime.
So my end goal is set to property once at start on SignalR connection that can be used throughout the connection lifetime.
If there is nothing available now, are there any plans to add support to achieve something similar in next version?
[Update]
As suggested below, I tried to set a state Clients.Caller.Userid in the OnConnected method, then tried to access it in the subsequent call, I found that its null. Both calls are from same connection Id.
Look at the "Round-tripping state between client and server" section on https://github.com/SignalR/SignalR/wiki/Hubs.
Basically you can read and write from dynamic properties on Clients.Caller in Hub methods such as OnConnected or anything invoked by a client. Ex:
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
namespace StateDemo
{
public class MyHub : Hub
{
public override Task OnConnected()
{
Clients.Caller.UserId = Context.User.Identity.Name;
Clients.Caller.initialized();
return base.OnConnected();
}
public void Send(string data)
{
// Access the id property set from the client.
string id = Clients.Caller.UserId;
// ...
}
}
}
State that is stored this way will be persisted for the lifetime of the connection.
If you want to learn how to access this state using the SignalR JS client look at the "Round-tripping state" section of https://github.com/SignalR/SignalR/wiki/SignalR-JS-Client-Hubs.
There are other ways to keep track of users without IConnectionIdPrefixGenerator discussed in the following SO answer: SignalR 1.0 beta connection factory

Resources