I use SignalR in my asp.net web application. Session state doesn't exist in SignalR hub, so I decided to save username <-> connectionId pair in static dictionary.
public static class UsernameConnectionsMaps
{
private static Dictionary<string, string> data = new Dictionary<string, string>();
public static void Add(string username, string connectionId)
{
data[username] = connectionId;
}
public static string Get(string username)
{
return data[username];
}
public static void Remove(string username)
{
return data.Remove(username);
}
}
On a particular controller request, I want to send data to the client with signalR. Knowing the username of current user, I can easily get connectionId for the client and send data.
I use Get method in my controller.
I use Add and Remove methods in hub's OnConnected() and OnDisconnected() methods respectively.
I am interested in, if this solution will have any problem regarding thread safety (or other) in my web application? What will be better approach?
If you use a static dictionary you will lose the ability to scale out your API.
Each instance will keep a different static Dictionary, so notifications will not always reach their destination, consider azure signalr or some kind of persistent storage like redis.
The best you can do and it is what is said in the Microsoft documentation, map users to groups, even if it will produce groups with single users or a groups with many users that are actually the same user. Even if you will use Redis backplane or Azure SignalR, you will have the groups in all context and can communicate with them, add, remove and etc...
A single user can have multiple connections to a SignalR app. For example, a user could be connected on their desktop as well as their phone. Each device has a separate SignalR connection, but they're all associated with the same user. If a message is sent to the user, all of the connections associated with that user receive the message.
Related
I am using SignalR v2.41, which is old, but I have to use it since I am also limited to using an old version of MVC. That aside, I am also using FluentScheduler to send targeted messages to clients at intervals.
Problem is, I am keeping a dictionary of user connections in my Hub:
public class MyHub: Hub
{
public Dictionary<string, User> Connections { get; set; }
public MyHub()
{
Connections = new Dictionary<string, User>();
}
public override Task OnConnected()
{
// add connection
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
// remove connection
return base.OnDisconnected(stopCalled);
}
}
Now in the FluentScheduler code I need to get hold of the hub for the connections list so I know which connection to send what to:
public class MyJob : IJob
{
public void Execute()
{
var hub = new DefaultHubManager(GlobalHost.DependencyResolver).ResolveHub("MyHub") as MyHub;
foreach (var conn in hub.Connections)
{
foreach (var msg in msgs)
{
hub.Clients.Client(conn.Key).send(msg);
}
}
}
}
Problem is, the hub instance I get using var hub = new DefaultHubManager(GlobalHost.DependencyResolver).ResolveHub("MyHub") as MyHub; is different from the one to which clients connect, as this one never has any connections.
How can I get the right hub instance?
The new is always a new instance so you will never get the hub where your clients are connected because you creating a new hub.
You should resolve the hub like this:
static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
You also can check this question.
Edit: Since you need to send messages to specific users, I would recommend to implement a class to add and remove the connections, or even better, map users to groups.
It's recommend that you always inject IHubContext than the Hub. Quote from a SignalR developer on github:
You generally shouldn't resolve the Hub out of DI. If you need to share code between your Hub and some other component, I'd suggest using either IHubContext or putting the shared code in a separate DI service instead.
Also you should not add the Hub as a singleton:
SignalR expects the Hub to be created separately for each message. You need to add it as a Transient service if you want your Hub to be in DI.
and
Because instances of the Hub class are transient, you can't use them to maintain state from one method call to the next. Each time the server receives a method call from a client, a new instance of your Hub class processes the message. To maintain state through multiple connections and method calls, use some other method such as a database, or a static variable on the Hub class, or a different class that does not derive from Hub.
More documentation about Hub object lifetime.
Background
I've created a working bot in C# but I'm failing to expand it to be a multi-tenant bot. I have created multiple bots in the Microsoft portal using this technique to identify themselves from the messaging endpoint:
https://example.com/api/messages/bot1
https://example.com/api/messages/bot2
https://example.com/api/messages/bot3
I can grab the LastSegment from the URL while in the MessagesController and store it in PrivateConversationData so I know which bot is talking in the current conversation. I intended use this stored 'bot id' in order to retrieve the Microsoft AppId & Password from the web.config (the bot's credentials are stored as a series of custom entries and not the standard appSettings as that only works for a single bot).
Credentials Problem
The authentication works well (nearly) as described here except when using async code with .ConfigureAwait(false) I can't get the HttpContext.Current as it becomes null when running on a different thread. This means I can't get the authenticated user's credentials either by looking them up in the web.config or by calling GetCredentialsFromClaims() since I've lost the authenticated user. If I use .ConfigureAwait(true) I just get deadlocks all over the place.
I have the credentials in the web.config but they are stored per bot and I need the 'bot id' from the URL above in order to get the credentials.
Question
The crux of the problem is: I need the URL to get the 'bot id' and I need the 'bot id' to get the credentials from the web.config but I can never reliably get access to the URL once I've passed a .ConfigureAwait(false) in the code. On the flip side, I can't get the 'bot id' from the PrivateConversationData since I need the bot's credentials in order to load it. A bit chicken and egg :-(
If anyone has any ideas of what I may be doing wrong or has an alternative approach to know which 'bot id' is currently executing I'd very much appreciate it.
Thanks
Please find below given the sample code.
public class StartUp {
public void Configuration(IAppBuilder app) {
var builder = new ContainerBuilder();
//Note: Initialize / register the Metadata Service that can bring the tenant details from the corresponding store
builder.RegisterType<TenantMetadataService>().As<ITenantMetadataService>();
//Note: This helps you in accessing the TenantMetadata from any constructor going forward after the below registry
builder.Register(ti => TenantMetadata.GetTenantMetadataFromRequest()).InstancePerRequest();
//TODO: Register the various services / controllers etc which may require the tenant details here
}
}
public class TenantMetadata {
public Guid TenantId { get;set; }
public Uri TenantUrl { get;set; }
public string TenantName { get;set; }
public static TenantMetadata GetTenantMetadataFromRequest() {
var context = HttpContext.Current;
//TODO: If you have any header like TenantId coming from the request, you can read and use it
var tenantIdFromRequestHeader = "";
//TODO: There will be a lazy cache that keeps building the data as new tenant's login or use the application
if(TenantCache.Contains(...))return TenantCache[Key];
//TODO: Do a look-up from the above step and then construct the metadata
var tenantMetadata = metadataSvc.GetTenantMetadata(...);
//TODO: If the data match does not happen from the Step2, build the cache and then return the value.
TenantCache.Add(key,tenantMetadata);
return tenantMetadata;
}
}
Note
The above code snippet uses the various service placeholders, cache and the other methods which will require to be used based on the designed application services. If you wish not to cache the tenant metadata, if it may contain some sensitive data, you can remove the caching implementation parts.
This implementation can be spread across all your web facing portals like your Web UI, Web Api and WebJobs etc so that it is same across all apps and it is easy to test and consume.
HTH.
Due to lack of tutorials and information i am unable to find how i can save the information in bots. Lets say i ask user to make a selection like this:
public enum City
{
Cleveland, Columbus, Kentucky, Mason, Akron
};
[Serializable]
public class SandwichOrder
{
[Prompt("Please select what {&} you are in? {||}")]
public City? City;
public static IForm<SandwichOrder> BuildForm()
{
return new FormBuilder<SandwichOrder>()
.Message("Welcome to the my bot!")
.Build();
}
};
I just want to ask for city once how can i do that? How can i preserve the value of user selection and only call this method if it is first user interaction.
Controller class:
internal static IDialog<SandwichOrder> MakeRootDialog()
{
return Chain.From(() => FormDialog.FromForm(SandwichOrder.BuildForm));
}
[ResponseType(typeof(void))]
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity != null)
{
// one of these will have an interface and process it
switch (activity.GetActivityType())
{
case ActivityTypes.Message:
await Conversation.SendAsync(activity, MakeRootDialog);
break;
}
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
The SDK includes several ways of persisting data relative to a user or conversation:
userData stores information globally for the user across all conversations.
conversationData stores information globally for a single conversation. This data is visible to everyone within the conversation so care should be used to what’s stored there. It’s disabled by default and needs to be enabled using the bots persistConversationData setting.
privateConversationData stores information globally for a single conversation but its private data for the current user. This data spans all dialogs so it’s useful for storing temporary state that you want cleaned up when the conversation ends.
dialogData persists information for a single dialog instance. This is essential for storing temporary information in between the steps of a waterfall.
Bots built using Bot Builder are designed to be stateless so that they can easily be scaled to run across multiple compute nodes. Because of that you should generally avoid the temptation to save state using a global variable or function closure. Doing so will create issues when you want to scale out your bot. Instead leverage the data bags above to persist temporary and permanent state.
More info here:
https://docs.botframework.com/en-us/node/builder/guides/core-concepts/#adding-dialogs-and-memory
I'm using offline sync in a Xamarin app. I have the following scenario:
I have a couple of tables which sync fine and one table called LocalOnlyTable which I don't want to sync. I just want to read/write it locally.
The problem appears when I pull one of my tables like so:
await exerciseTable.PullAsync(string.Format("{0}ItemByFK", typeof(Exercise).Name), exerciseTable.CreateQuery());
I get a MobileServicePushFailedException saying 404 LocalOnlyTable does not exist.
I'm wondering why Mobile Services tries to push/pull the LocalOnlyTable and
How can I prevent Mobile Services from trying to sync LocalOnlyTable?
Just came across your issue here and thought of sharing my solution.
1) Create a custom TableSyncHandler to block off local-only tables:
public class TableSyncHandler : IMobileServiceSyncHandler
{
private readonly IMobileServiceClient _client;
private readonly HashSet<string> _excludedTables = new HashSet<string>();
public TableSyncHandler(IMobileServiceClient client)
{
_client = client;
}
public void Exclude<T>()
{
_excludedTables.Add(_client.SerializerSettings.ContractResolver.ResolveTableName(typeof(T)));
}
public Task OnPushCompleteAsync(MobileServicePushCompletionResult result)
{
return Task.FromResult(0);
}
public Task<JObject> ExecuteTableOperationAsync(IMobileServiceTableOperation operation)
{
if (_excludedTables.Contains(operation.Table.TableName))
{
return Task.FromResult((JObject) null);
}
return operation.ExecuteAsync();
}
}
2) When you are initializing MobileServiceClient's SyncContext, register the tables you want to exclude to this syncHandler, and then initialize SyncContext using the syncHandler:
_store = new MobileServiceSQLiteStore("YourStore");
_store.DefineTable<User>();
_store.DefineTable<LocalOnlyTable>();
_syncHandler = new TableSyncHandler(client);
// LocalOnlyTable is excluded from sync operations
_syncHandler.Exclude<LocalOnlyTable>();
await client.SyncContext.InitializeAsync(_store, _syncHandler);
Disclaimer:
This has not gone to production yet, so I don't know if there will be performance impact, but seems to be working fine so far in testing.
This solution is based on Azure Mobile Services client v1.3.2 source code. It's not doing anything (pull/push) when the synchandler returns null result. This behaviour can possibly change in the future.
All actions take using the MSSyncTable APIs are tracked to be sent to the server. If you have a table you do not want to track you shouldn't use the MSSyncTable APIs to insert/update records.
You should be able to use either the SQLiteStore methods (like upsert) or execute SQL on your SQLite Db directly for your untracked tables.
I am creating a login system and I want a way to sort of cache information without retrieving the same information from the database.
for example I would have a static class called tokenData. token data would be a private class to store login token, username, expireDate, etc. So every time I visit another page it would check the static class for the data. The token is then stored in session / cookie to produce the lookup. If the data is not in the token static class (e.g. application pool restart) then it would check the database for the record when the user logs in and creates another based on the data in the token table.
Can someone offer me any advice is this is acceptable practice or offer me anything to improve and issues that can arise?
an exmaple is
public class userToken
{
private string name;
private string tokenId;
private static List<userToken> userData = new List<userToken>();
public void add(userToken);
public userToken Find(string tokenId);
}
Never ever ever use static for user or session specific data. static is shared across ALL sessions! You might end up with user sessions sharing confidential data.
Use HttpContext.Session or HttpContext.Cache.
Your solution can introduce errors when run on more than a single server with a single user. The cache you are building is not thread safe. It will also introduce errors when your app is run across 2+ servers in a cluster (load balanced).
I would look into using a proper caching toolset (memcached, etc.)