How to send message to specific user by only userid in Azure signalr? - asp.net-core

Hi I am working in azure SignalR. I have made setup to send message to specif user by using combination of connectionid and userid and this works perfect for me.
public class UserConnectionManager : IUserConnectionManager
private static Dictionary<string, List<string>> userConnectionMap = new Dictionary<string, List<string>>();
private static string userConnectionMapLocker = string.Empty;
public void KeepUserConnection(string userId, string connectionId)
{
lock (userConnectionMapLocker)
{
if (!userConnectionMap.ContainsKey(userId))
{
userConnectionMap[userId] = new List<string>();
}
userConnectionMap[userId].Add(connectionId);
}
}
For me this implementation works perfect. Now I have deployed my app in kubernetes with two pods so whats happening is list I used to store connectionids is static and kind of session behavior. So its not working with two pods.
[Route("Push")]
[HttpPost]
public async Task<ActionResult> Push(Failure Failure)
{
var connections = _userConnectionManager.GetUserConnections(Failure.UserId);
if (connections != null && connections.Count > 0)
{
foreach (var connectionId in connections)
{
await _notificationUserHubContext.Clients.Client(connectionId).SendAsync("MyMessage", Failure);
}
}
return ok();
}
So I do not want to make combination of connectioid and userid. I want to just push message to user by only userid. However I tried below approach
await
_notificationUserHubContext.Clients.User(Failure.UserId).SendAsync("MyMessage",
mapDataResponseSuccess);
This piece of code is not pushing messages to specified userid. Can someone help me to send message to specific user by only UserId? Any help would be appreciated. Thank you

Related

Why can't my xamarin app recieve data from my API call?

Im new to connecting an API to my xamarin app.
When I try to call the API visual studio & the app do not give a response.
Visual studio keeps running but nothing happens.
I've changed the firewall settings, and set my IP adres in all the desired places. Still not luck.
If I go to my API using swager or postman and I just the same Uri as I want to pass trough with my app I get the correct response.
What could be the reason for this?
my code:
Material service:
private readonly string _baseUri;
public APIMaterialService()
{
_baseUri = "https://192.168.1.9:5001/api";
}
public async Task<Material> GetById(Guid id)
{
return await WebApiClient
.GetApiResult<Material>($"{_baseUri}/Materials/{id}");
}
WebApiClient:
public class WebApiClient
{
private static HttpClientHandler ClientHandler()
{
var httpClientHandler = new HttpClientHandler();
#if DEBUG
//allow connecting to untrusted certificates when running a DEBUG assembly
httpClientHandler.ServerCertificateCustomValidationCallback =
(message, cert, chain, errors) => { return true; };
#endif
return httpClientHandler;
}
private static JsonMediaTypeFormatter GetJsonFormatter()
{
var formatter = new JsonMediaTypeFormatter();
formatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
return formatter;
}
public async static Task<T> GetApiResult<T>(string uri)
{
using (HttpClient httpClient = new HttpClient(ClientHandler()))
{
//Gets stuck finding the response
string response = await httpClient.GetStringAsync(uri);
return JsonConvert.DeserializeObject<T>(response, GetJsonFormatter().SerializerSettings);
}
}
I'll also add some images of the postman and swager response:
This is the code fo my controller.
return OK (material) shows me the data retrieved from the API
public async Task<IActionResult> GetMaterialByPartOfMaterialNumberOP(string partOfMaterialNumber)
{
var material = await _materialService.GetMaterialListbyPartOfMaterialNumber(partOfMaterialNumber);
return Ok(material);
}
The symptom you have (stuck on result from calling a method of HttpClient class) suggests a deadlock.
I believe the deadlock happens if you create multiple instances of HttpClient.
Doc HttpClient Class says:
// HttpClient is intended to be instantiated once per application, rather than per-use. See Remarks.
And shows this code:
static readonly HttpClient client = new HttpClient();
HOWEVER a deadlock would only happen the SECOND time your code does new HttpClient. And using ... new HttpClient should protect you, at least in simple situations.
Here are ways there might be TWO HttpClients active:
Is it possible that GetApiResult gets called A SECOND TIME, before the first one finishes?
Does your app do new HttpClient ELSEWHERE?
Here is what the technique might look like in your app:
public class WebApiClient
{
static HttpClient _client = new HttpClient(ClientHandler());
public async static Task<T> GetApiResult<T>(string uri)
{
string response = await _client.GetStringAsync(uri);
return JsonConvert.DeserializeObject<T>(response, GetJsonFormatter().SerializerSettings);
}
}

.NET Core Identity Framework and Telegram Login Widget with no database

I am making a Blazor Server app, which is tied to my Telegram bot. I want to add the ability for the user to login using Telegram Login Widget. I have no plans to add login/password authentication and I therefore don't see any reason to use the database to store anything login-related other than the Telegram User ID.
All of the samples imply using the login-password model along with the database, somewhat like this:
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<AppDbContext>();
Inevitable, this line appears in all of the samples: services.AddEntityFrameworkStores<AppDbContext>();
Here's my question: how do I just put the user's data (after checking the info from Telegram) into app's context, without storing anything in the database? Or if I'm forced to, where do I change the database scheme? Maybe I don't even need to use the Identity framework for this? All I want is for all the pages to have the info about the user, and the authentication happens on Telegram's side, I just get all the info in response and check the hash with my private key. All I want to do after that is put that model into app's context, I'm not even sure I plan on storing the cookie for the user.
To be clear: I already know how to get info from Telegram and check the hash, let's assume after executing some code on a page I already have some User model with some filled out fields
In the end, this is how I did it. While not ideal, this works for me. However, I'd love to get some clarifications from someone, specifically on IUserStore stuff.
I've added Blazored SessionStorage as a dependency to the project
I've registered my own implementations of AuthenticationStateProvider, IUserStore and IRoleStore in Startup.cs like this:
services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
services.AddTransient<IUserStore<User>, CustomUserStore>();
services.AddTransient<IRoleStore<Role>, CustomRoleStore>();
The first line is the most important one. Implementations of IUserStore and IRoleStore don't really matter, but it seems like I have to register them for Identity framework to work, even though I won't use them. All of the methods in my "implementation" are literally just throw new NotImplementedException(); and it still works, it just needs them to exist for the UserManager somewhere deep down, I guess? I'm still a little unclear on that.
My CustomAuthenticationStateProvider looks like this:
public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
private readonly ISessionStorageService _sessionStorage;
private readonly ILogger _logger;
private readonly AuthenticationState _anonymous = new(new ClaimsPrincipal(new ClaimsIdentity()));
public CustomAuthenticationStateProvider(
ILoggerFactory loggerFactory,
ISessionStorageService sessionStorage,
IConfiguration configuration) : base(loggerFactory)
{
_logger = loggerFactory.CreateLogger<CustomAuthenticationStateProvider>();
_sessionStorage = sessionStorage;
// setting up HMACSHA256 for checking user data from Telegram widget
...
}
private bool IsAuthDataValid(User user)
{
// validating user data with bot token as the secret key
...
}
public AuthenticationState AuthenticateUser(User user)
{
if (!IsAuthDataValid(user))
{
return _anonymous;
}
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Sid, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.FirstName),
new Claim("Username", user.Username),
new Claim("Avatar", user.PhotoUrl),
new Claim("AuthDate", user.AuthDate.ToString()),
}, "Telegram");
var principal = new ClaimsPrincipal(identity);
var authState = new AuthenticationState(principal);
base.SetAuthenticationState(Task.FromResult(authState));
_sessionStorage.SetItemAsync("user", user);
return authState;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var state = await base.GetAuthenticationStateAsync();
if (state.User.Identity.IsAuthenticated)
{
return state;
}
try
{
var user = await _sessionStorage.GetItemAsync<User>("user");
return AuthenticateUser(user);
}
// this happens on pre-render
catch (InvalidOperationException)
{
return _anonymous;
}
}
public void Logout()
{
_sessionStorage.RemoveItemAsync("user");
base.SetAuthenticationState(Task.FromResult(_anonymous));
}
protected override async Task<bool> ValidateAuthenticationStateAsync(AuthenticationState authenticationState,
CancellationToken cancellationToken)
{
try
{
var user = await _sessionStorage.GetItemAsync<User>("user");
return user != null && IsAuthDataValid(user);
}
// this shouldn't happen, but just in case
catch (InvalidOperationException)
{
return false;
}
}
protected override TimeSpan RevalidationInterval { get; } = TimeSpan.FromHours(1);
}
In my Login Blazor page I inject the CustomAuthenticationStateProvider like this:
#inject AuthenticationStateProvider _authenticationStateProvider
And finally, after getting data from the Telegram widget, I call the AuthenticateUser method:
((CustomAuthenticationStateProvider)_authenticationStateProvider).AuthenticateUser(user);
Note, that I have to cast AuthenticationStateProvider to CustomAuthenticationStateProvider to get exactly the same instance as AuthorizedView would.
Another important point is that AuthenticateUser method contains call to SessionStorage, which is available later in the lifecycle of the page, when OnAfterRender has completed, so it will throw an exception, if called earlier.

ASP.NET CORE MVC - User in Program.cs

I'm trying to load a different JSON file in the Program class depending on the user role. I use windows authentication on the site and a basic table that stores the roles. I load the roles implementing IClaimsTransformation later on without any issue, but I'm not able to determine the requestor user at this earlier point...
I only get DOMAIN\MACHINE$ when I call System.Security.Principal.WindowsIdentity.GetCurrent().Name;
Any idea about how to get the windows user there? This is my Program.cs file:
public static void Main(string[] args)
{
using (var context = new ManualBookingContext("xxx"))
{
var user = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
User dbUser = context.Users.Include(x => x.UserRoles).ThenInclude(x => x.Role).FirstOrDefault(x => x.Username.ToLower() == user.ToLower());
if(dbUser != null)
args = dbUser.UserRoles.Select(u => u.Role.Name).ToArray();
}
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
var sidebar = "sidebar.json";
foreach (var arg in args)
{
if (arg == "Admin")
sidebar = "sidebar-admin.json";
}
return Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile(sidebar,
optional: true,
reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
WindowsIdentity.GetCurrent get the windows user for the current process. I.e. it seems like the asp.net server is running under the system user account DOMAIN\MACHINE$.
Also see this post:
As a few others have mentioned, it is the computer account itself not the user account.
ASP authentication errors like this are typically due to the computer account itself being passed as the authentication token instead of your user account.
https://serverfault.com/a/66700
To confirm this, you could try to run the application under the desired user and see if that gives the right username.
As for a fix, i've had a similar issue in the past and I had used WMI (windows management instrumentation) to get the currently logged on user. I've found this article which looks great, and contains some ready to use code:
public class Machine
{
private static Object _classLocker = new Object();
private static Machine _machine;
private Machine()
{
} // end private Machine()
public static Machine getInstance()
{
if (_machine == null)
{
lock (_classLocker)
{
if (_machine == null)
{
_machine = new Machine();
}
}
}
return _machine;
} // end public static Machine getInstance()
public String getUsername()
{
string username = null;
try
{
// Define WMI scope to look for the Win32_ComputerSystem object
ManagementScope ms = new ManagementScope("\\\\.\\root\\cimv2");
ms.Connect();
ObjectQuery query = new ObjectQuery
("SELECT * FROM Win32_ComputerSystem");
ManagementObjectSearcher searcher =
new ManagementObjectSearcher(ms, query);
// This loop will only run at most once.
foreach (ManagementObject mo in searcher.Get())
{
// Extract the username
username = mo["UserName"].ToString();
}
// Remove the domain part from the username
string[] usernameParts = username.Split('\\');
// The username is contained in the last string portion.
username = usernameParts[usernameParts.Length - 1];
}
catch (Exception)
{
// The system currently has no users who are logged on
// Set the username to "SYSTEM" to denote that
username = "SYSTEM";
}
return username;
} // end String getUsername()
} // end class Machine
https://www.techcoil.com/blog/how-to-retrieve-the-username-of-the-user-who-logged-onto-windows-from-windows-service/
Note that since you're in asp.net core you may need to install additional packages to use wmi, e.g.
https://www.nuget.org/packages/System.Management/

How to send a SignalR message to a user?

How does the client authorize to send a message to the user?
Sending from the controller
hubContext.Clients.User(User.Identity.Name).SendAsync();
At the moment the message is not sent. Do I need to add something in OnConnection ()? Or does SignalR have a ready-made mapping mechanism for ConnectionId and User.Identity.Name?
That's how I implemented it at the moment, but it seems to me not quite right. The question is how to make the same standard tools?
public static class HubConnections
{
public static Dictionary<string, List<string>> Users = new Dictionary<string, List<string>>();
public static List<string> GetUserId(string name)
{
return Users[name];
}
}
public class GameHub : Hub
{
public override Task OnConnectedAsync()
{
if (Context.User.Identity.IsAuthenticated
&& HubConnections.Users.ContainsKey(Context.User.Identity.Name)
&& !HubConnections.Users[Context.User.Identity.Name].Contains(Context.ConnectionId))
HubConnections.Users[Context.User.Identity.Name].Add(Context.ConnectionId);
else
HubConnections.Users.Add(Context.User.Identity.Name, new List<string> { Context.ConnectionId });
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
if (Context.User.Identity.IsAuthenticated) HubConnections.Users.Remove(Context.User.Identity.Name);
return base.OnDisconnectedAsync(exception);
}
}
As I said above, I tried just like this, and it does not work
hubContext.Clients.User(User.Identity.Name).SendAsync();
Was chasing the same issue and got the solution from https://github.com/aspnet/SignalR/issues/2498
One needs to set the NameIdentifier claim. That is the one checked by SignalR instead of the Name claim which I assumed. I set the NameIdentifier claim and I got my non-hub class to send a notification to a specific user.
The claim that signalR is using to identify the user can be changed. It is important to ensure that this claim has unique values.
Documentation says to setup a custom UserIdProvider like this:
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.Identity?.Name;
}
}
Add then add it to services:
public void ConfigureServices(IServiceCollection services)
{
// ... other services ...
services.AddSignalR();
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
}
The snippets are taken from official documentation:
https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.1#use-claims-to-customize-identity-handling
There's a client-side component. You must reference the SignalR JS file, create a connection and then subscribe to a particular message from the server. Only then will sending that message actually do something.
<script src="~/lib/signalr/signalr.js"></script>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("/gameHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on("Foo", (data) => {
// do something
});
connection.start().catch(err => console.error(err.toString()));
</script>
The above will then cause the client to run the function defined for "Foo" above whenever the server sends a "Foo" message like:
hubContext.Clients.User(User.Identity.Name).SendAsync("Foo", data);
You are using the Users as a store for the connection id. So, for each username, you can send the message to each of the client connections you have stored for that user. Something like this:
public void SendMessage(string username, object data)
{
var connections = HubConnections.Users[Context.User.Identity.Name];
foreach(var id in connections)
{
Clients.client(id).SendAsync("Foo", data);
}
}

use async call to webservice when you are not interested in the response

I use a call to a webservice in order to wake up a process which processes some images.
The process takes 10minutes,so I don't want the asp.net client waiting for that.
What I did was a simple call async to the service to start the processing.
But it looks like ConvertToCubeService service doesn't bother to start.
ConvertToCubeService is a workflow service. I call it from within the activity of another workflow service.
I am sure that the code reaces the async call to ConvertToCubeService .
public sealed class CallProcessingAsync : CodeActivity
{
// Define an activity input argument of type string
public InArgument<string> EquirectangularImagePath { get; set; }
public InArgument<string> SaveImageDirectoryPath { get; set; }
public InArgument<string> ImageName { get; set; }
// If your activity returns a value, derive from CodeActivity<TResult>
// and return the value from the Execute method.
protected override void Execute(CodeActivityContext context)
{
// Obtain the runtime value of the Text input argument
ConvertToCubeService.ServiceClient client = new ConvertToCubeService.ServiceClient();
ConvertToCubeService.ConvertToCubeFaces param = new ConvertToCubeService.ConvertToCubeFaces();
var apPath = System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath;
param.EquirectangularFilePath=apPath+context.GetValue(this.EquirectangularImagePath);
param.imageName = context.GetValue(this.ImageName);
param.SaveCubicFacesDirectory = apPath + context.GetValue(this.SaveImageDirectoryPath);
client.ConvertToCubeFacesCompleted += new EventHandler<ConvertToCubeService.ConvertToCubeFacesCompletedEventArgs>(client_ConvertToCubeFacesCompleted);
client.ConvertToCubeFacesAsync(param);//call async the server to do the work
using (var db = new panonestEntities())
{
var p = db.Panoramas.Where(x => x.ImageName.Equals(param.imageName)).First();
p.Status = "Called processing. wait";
db.SaveChanges();
}
}
void client_ConvertToCubeFacesCompleted(object sender, ConvertToCubeService.ConvertToCubeFacesCompletedEventArgs e)
{
using (var db = new panonestEntities())
{
}
}
}
You shouldn't run something that takes 10 minutes on your web server. You should write a windows service for that. You could have a simple webservice that puts a record into a table to trigger the windows service to do its work
.
I think better way would be not to do the async stuff client side but server side because when you ignore the response, you won't know if the call even has reached the server.
I would wirte the server operation in that way, that it starts the long running task asynchronous and then returns quickly.
The client can wait on the call and can be sure, that the call arrived at the server successfully and the operation was started.