I am writing a chat application. I use Angular 6 and .Net Core 3.1.
I have read a lot of documents but still do not understand where there is an error.
My Hub Class
public class ChatHub : Hub
{
private static List<ConnectedUser> _connectedUsers = new List<ConnectedUser>();
public override Task OnConnectedAsync()
{
var username = Context.GetHttpContext().Request.Query["username"];
var status = _connectedUsers.FirstOrDefault(x => x.Username == username);
if (status == null)
{
_connectedUsers.Add(new ConnectedUser
{
ConnId = Context.ConnectionId,
Username = username
});
}
return base.OnConnectedAsync();
}
...
I have a list that keeps the connection id information of the users connected in my Hub Class. When users connect, I add to this list.
I operate in my Private Chat Function below.
public void PrivateChat(string toUser,string fromUser,string message)
{
_connectedUsers.ForEach(val =>
{
if(val.Username == toUser)
Clients.User(val.ConnId).SendAsync("receiveMessage", message,fromUser);
});
}
However, I cannot return to the user I want.
You use it in Angular this way
this._hubConnection.on('receiveMessage', (message,fromUser) => {
console.log(message , "+" , fromUser);
}
);
You should use
Clients.Clients(val.ConnId).SendAsync("receiveMessage", message,fromUser);
Related
I am trying out the new MassTransit IJobConsumer implementation, and although I've tried to follow the documentation, the JobConsumer I have written is never being run/hit.
I have:
created the JobConsumer which has a run method that runs the code I need it to
public class CalculationStartRunJobConsumer : IJobConsumer<ICalculationStartRun>
{
private readonly ICalculationRunQueue runQueue;
public CalculationStartRunJobConsumer(ICalculationRunQueue runQueue)
{
this.runQueue = runQueue;
}
public Task Run(JobContext<ICalculationStartRun> context)
{
return Task.Run(
() =>
{
var longRunningJob = new LongRunningJob<ICalculationStartRun>
{
Job = context.Job,
CancellationToken = context.CancellationToken,
JobId = context.JobId,
};
runQueue.StartSpecial(longRunningJob);
},
context.CancellationToken);
}
}
I have registered that consumer trying both ConnectReceiveEndpoint and AddConsumer
Configured the ServiceInstance as shown in the documentation
services.AddMassTransit(busRegistrationConfigurator =>
{
// TODO: Get rid of this ugly if statement.
if (consumerTypes != null)
{
foreach (var consumerType in consumerTypes)
{
busRegistrationConfigurator.AddConsumer(consumerType);
}
}
if(requestClientType != null)
{
busRegistrationConfigurator.AddRequestClient(requestClientType);
}
busRegistrationConfigurator.UsingRabbitMq((context, cfg) =>
{
cfg.UseNewtonsoftJsonSerializer();
cfg.UseNewtonsoftJsonDeserializer();
cfg.ConfigureNewtonsoftJsonSerializer(settings =>
{
// The serializer by default omits fields that are set to their default value, but this causes unintended effects
settings.NullValueHandling = NullValueHandling.Include;
settings.DefaultValueHandling = DefaultValueHandling.Include;
return settings;
});
cfg.Host(
messagingHostInfo.HostAddress,
hostConfigurator =>
{
hostConfigurator.Username(messagingHostInfo.UserName);
hostConfigurator.Password(messagingHostInfo.Password);
});
cfg.ServiceInstance(instance =>
{
instance.ConfigureJobServiceEndpoints(serviceCfg =>
{
serviceCfg.FinalizeCompleted = true;
});
instance.ConfigureEndpoints(context);
});
});
});
Seen that the queue for the job does appear in the queue for RabbitMQ
When I call .Send to send a message to that queue, it does not activate the Run method on the JobConsumer.
public async Task Send<T>(string queueName, T message) where T : class
{
var endpointUri = GetEndpointUri(messagingHostInfo.HostAddress, queueName);
var sendEndpoint = await bus.GetSendEndpoint(endpointUri);
await sendEndpoint.Send(message);
}
Can anyone help?
Software
MassTransit 8.0.2
MassTransit.RabbitMq 8.0.2
MassTransit.NewtonsoftJson 8.0.2
.NET6
Using in-memory for JobConsumer
The setup of any type of repository for long running jobs is missing. We needed to either:
explicitly specify that it was using InMemory (missing from the docs)
Setup saga repositories using e.g. EF Core.
As recommended by MassTransit, we went with the option of setting up saga repositories by implementing databases and interacting with them using EF Core.
I am trying to send a message. I have tried to connection id
public Task SendMessaageToConnectionID(string ConnectionID,string Message)
{
return Clients.Clients(ConnectionID).SendAsync("RecieveMessage", Message);
}
it is successfully done
Now I am trying this
public Task SendMessageToUser(string userId,string Message)
{
return Clients.Clients(userId).SendAsync(Message);
}
I am sending the user id of user Saved in AspNetUser Table
How Can I send this to a User ID or is there any other way except connection id to send the message to user?
SignalR won't store the UserId-ConnectionId mappings for us. We need to do that by our own. For example, when some user sets up a connection to the Hub, it should trigger a ReJoinGroup() method.
In addition, in order to make sure the Groups property works fine, you need also :
invoke RemoveFromGroupAsync to remove the old <connectionId, groupName> mapping
invoke AddToGroupAsync to add a new <connectionId, groupName> mapping.
Typically, you might want to store these information in Redis or RDBMS. For testing purpose, I create a demo that stores these mappings in memory for your reference:
public class MyHub:Hub
{
/// a in-memory store that stores the <userId, connectionId> mappings
private Dictionary<string, string> _userConn = new Dictionary<string,string>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public override async Task OnConnectedAsync()
{
// get the real group Name by userId,
// for testing purpose, I use the userId as the groupName,
// in your scenario, you could use the ChatRoom Id
var groupName = Context.UserIdentifier;
await this.ReJoinGroup(groupName);
}
// whenever a connection is setup, invoke this Hub method to update the store
public async Task<KeyValuePair<string,string>> ReJoinGroup(string groupName)
{
var newConnectionId = Context.ConnectionId;
var userId = Context.UserIdentifier;
await this._semaphore.WaitAsync();
try{
if(_userConn.TryGetValue(userId, out var oldConnectionId))
{
_userConn[userId]= newConnectionId;
// remove the old connectionId from the Group
if(!string.IsNullOrEmpty(groupName)){
await Groups.RemoveFromGroupAsync(oldConnectionId, groupName);
await Groups.AddToGroupAsync(newConnectionId, groupName);
}
} else {
_userConn[userId]= newConnectionId;
if(!string.IsNullOrEmpty(groupName)){
await Groups.AddToGroupAsync(newConnectionId, groupName);
}
}
} finally{
this._semaphore.Release();
}
return new KeyValuePair<string,string>(userId, newConnectionId);
}
/// your SendMessageToUser() method
public async Task SendMessageToUser(string userId,string Message)
{
// get the connectionId of target user
var userConn = await this.GetUserConnection(userId);
if( userConn.Equals(default(KeyValuePair<string,string>))) {
throw new Exception($"unknown user connection with userId={userId}");
}
await Clients.Clients(userConn.Value).SendAsync(Message);
}
/// a private helper that returns a pair of <UserId,ConnectionId>
private async Task<KeyValuePair<string,string>> GetUserConnection(string userId)
{
KeyValuePair<string,string> kvp = default;
string newConnectionId = default;
await this._semaphore.WaitAsync();
try{
if(this._userConn.TryGetValue(userId, out newConnectionId)){
kvp= new KeyValuePair<string, string>(userId, newConnectionId);
}
} finally{
this._semaphore.Release();
}
return kvp;
}
}
I've built an MVC 5 intranet web app in the company I work for. It's built using windows authentication and impersonates the user of the app.
On the backend, there is a centralized security database which has an employee table, a role table and a bridge table to join both.
There is an MVC view which is basically a datatable with employees and permissions, pivoted. Users connect to the webapp, the webapps reads the username and checks SQL Server to see what permissions they have. Therefore it's very easy for administrators to authorize use of the site with a nice front end.
Here's the issue - when I assign 'SRPE' permission to myself (which successfully adds the record to the bridge table), I expect to see that change reflected. Effectively this permission, if true, makes MVC return an alternative view for one of the views I've built. Anyway, it doesn't work.
The actual result is that nothing happens, i.e. the same view is being returned. By the way this is all running from VS for now. So by accident I managed to cause an error in the app during some other operation and funnily enough when I fixed the bug and restarted the site, the permission was reflected.
What I don't understand is why the permission isn't reflected during runtime. This design pattern I've used all over. The security class looks like this:
using System;
using System.Linq;
using MDMWebApp.Models;
namespace MDMWebApp.Security
{
public class MenuSecurity
{
// DMPA DMP Admin
// EIPA EIP Admin
// EIPU EIP User
// SRPA Standard Reporting Admin
// SRPE Standard Reporting Entity User
// SRPM Standard Reporting Master Data Contributer
// SRPU Standard Reporting User
private static SecurityDbContext _context = new SecurityDbContext();
private vEmployeeRole employeeRole = _context.vEmployeeRoles.SingleOrDefault(er => er.Username == Environment.UserName);
private bool isOnSystem => employeeRole != null ? true : false;
private bool isDMPAdmin => employeeRole.DMPA;
private bool isEIPAdmin => employeeRole.EIPA;
private bool isEIPUser => employeeRole.EIPU;
private bool isStandardReportingAdmin => employeeRole.SRPA;
private bool isStandardReportingEntityUser => employeeRole.SRPE;
private bool isStandardReportingMasterDataContributer => employeeRole.SRPM;
private bool isStandardReportingUser => employeeRole.SRPU;
public bool CanUseSystem
{
get { return isOnSystem; }
}
public bool DMPAdmin
{
get
{
return isDMPAdmin;
}
}
public bool EIPAdmin
{
get
{
return isDMPAdmin || isEIPAdmin;
}
}
public bool CanSeeEIP
{
get
{
return isDMPAdmin || isEIPAdmin || isEIPUser;
}
}
public bool StandardReportingEntityUser
{
get
{
return isStandardReportingEntityUser;
}
}
public bool StandardReportingMasterDataContributer
{
get
{
return isDMPAdmin || isStandardReportingAdmin || isStandardReportingMasterDataContributer;
}
}
}
}
and controller/action in question:
public ActionResult TaskTracker()
{
var lastWeekEnding = DateTime.Now.AddDays(-7);
var taskTracker = _context.TaskTracker.Where(d => d.WeekEnding >= lastWeekEnding);
MenuSecurity ms = new MenuSecurity();
if (ms.StandardReportingEntityUser)
{
return View("TaskTrackerEntity",taskTracker);
}
else return View("TaskTracker", taskTracker);
}
Does MVC not execute the action on hitting the appropriate route and then do a fresh retrieval of permissions at action execution time?
Finally resolved the issue. By implementing the using statement in the MenuSecurity class, the connection was disposed of after each use, thus retrieving fresh records each time. To do this I used an initialization method and called it each time I created a new instance of Menu Security.
Here is the updated class:
using System;
using System.Linq;
using MDMWebApp.Models;
namespace MDMWebApp.Security
{
public class MenuSecurity
{
// DMPA DMP Admin
// EIPA EIP Admin
// EIPU EIP User
// SRPA Standard Reporting Admin
// SRPE Standard Reporting Entity User
// SRPM Standard Reporting Master Data Contributer
// SRPU Standard Reporting User
private bool isOnSystem;
private bool isDMPAdmin;
private bool isEIPAdmin;
private bool isEIPUser;
private bool isStandardReportingAdmin;
private bool isStandardReportingEntityUser;
private bool isStandardReportingMasterDataContributer;
private bool isStandardReportingUser;
public void Initialize()
{
using (var _context = new SecurityDbContext())
{
var employeeRole = _context.vEmployeeRoles.SingleOrDefault(er => er.Username == Environment.UserName);
isOnSystem = employeeRole != null ? true : false;
isDMPAdmin = employeeRole.DMPA;
isEIPAdmin = employeeRole.EIPA;
isEIPUser = employeeRole.EIPU;
isStandardReportingAdmin = employeeRole.SRPA;
isStandardReportingEntityUser = employeeRole.SRPE;
isStandardReportingMasterDataContributer = employeeRole.SRPM;
isStandardReportingUser = employeeRole.SRPU;
}
}
public bool CanUseSystem
{
get { return isOnSystem; }
}
public bool DMPAdmin
{
get
{
return isDMPAdmin;
}
}
public bool EIPAdmin
{
get
{
return isDMPAdmin || isEIPAdmin;
}
}
public bool CanSeeEIP
{
get
{
return isDMPAdmin || isEIPAdmin || isEIPUser;
}
}
public bool StandardReportingEntityUser
{
get
{
return isStandardReportingEntityUser;
}
}
public bool StandardReportingMasterDataContributer
{
get
{
return isDMPAdmin || isStandardReportingAdmin || isStandardReportingMasterDataContributer;
}
}
}
}
I implemented my custom AuthorizationHandler.
On that i check i the user can resolved and is active.
If the user isn't active then i would like to return an 403 status.
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidUserRequirement requirement)
{
var userId = context.User.FindFirstValue( ClaimTypes.NameIdentifier );
if (userId != null)
{
var user = await _userManager.GetUserAsync(userId);
if (user != null)
{
_httpContextAccessor.HttpContext.AddCurrentUser(user);
if (user.Active)
{
context.Succeed(requirement);
return;
}
else
{
_log.LogWarning(string.Format("User ´{1}´ with id: ´{0} isn't active", userId, user.UserName), null);
}
}
else
{
_log.LogWarning(string.Format("Can't find user with id: ´{0}´", userId), null);
}
} else
{
_log.LogWarning(string.Format("Can't get user id from token"), null);
}
context.Fail();
var response = _httpContextAccessor.HttpContext.Response;
response.StatusCode = 403;
}
But i receive a 401. Can you please help me?
Could you check that on the end of your function? I'm using that in my custom middleware to rewrite status code to 401 in some cases but in your scenario should also work
var filterContext = context.Resource as AuthorizationFilterContext;
var response = filterContext?.HttpContext.Response;
response?.OnStarting(async () =>
{
filterContext.HttpContext.Response.StatusCode = 403;
//await response.Body.WriteAsync(message, 0, message.Length); only when you want to pass a message
});
According to the Single Responsibility Principle , we should not use the HandleRequirementAsync() method to redirect reponse , we should use middleware or Controller to do that instead . If you put the redirect logic in HandleRequirementAsync() , how about if you want to use it in View page ?
You can remove the redirection-related code to somewhere else (outside) , and now you inject an IAuthorizationService to authorize anything as you like , even a resource-based authorization :
public class YourController : Controller{
private readonly IAuthorizationService _authorizationService;
public YourController(IAuthorizationService authorizationService)
{
this._authorizationService = authorizationService;
}
[Authorize("YYY")]
public async Task<IActionResult> Index()
{
var resource /* = ... */ ;
var x = await this._authorizationService.AuthorizeAsync(User,resource , "UserNameActiveCheck");
if (x.Succeeded)
{
return View();
}
else {
return new StatusCodeResult(403);
}
}
}
in .NET core 6.0 you can use the Fail method
AuthorizationHandlerContext.Fail Method
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AppAuthorizationRequirement requirement)
{
context.Fail(); //Use this
}
I have a hierarchy of actors in Akka.Net and am wondering whether I've chosen the right way to do something, or if there are better/simpler ways to achieve what I want.
My specific example is that I'm constructing a User actor in response to a user logging into the system, and when constructing this actor there are two pieces of data I need in order to complete the construction of the actor.
If this were regular .NET code I might have something like the following...
public Task<User> LoadUserAsync (string username)
{
IProfileService profileService = ...;
IMessageService messageService = ...;
var loadProfileTask = profileService.GetUserProfileAsync(username);
var loadMessagesTask = messageService.GetMessagesAsync(username);
Task.WaitAll(loadProfileTask, loadMessagesTask);
// Now construct the user from the result of both tasks
var user = new User
{
Profile = loadProfileTask.Result,
Messages = loadMessagesTask.Result
}
return Task.FromResult(user);
}
Here I use WaitAll to wait for the subordinate tasks to complete, and let them run concurrently.
My question is - if I wanted to do the same in Akka.Net, would the following be the most regular way to do this? Pictorially I've created the following...
When I create my User actor, I then construct a (temporary) User Loader Actor, whose job it is to get the full user details by calling to the Profile actor and the Messages actor. The leaf actors that get the data are as follows...
public class UserProfileLoader : ReceiveActor
{
public UserProfileLoader()
{
Receive<LoadUserRequest>(msg =>
{
// Load the user profile from somewhere
var profile = new UserProfile();
// And respond to the Sender
Sender.Tell(profile);
Self.Tell(PoisonPill.Instance);
});
}
}
public class UserMessagesLoader : ReceiveActor
{
public UserMessagesLoader()
{
Receive<LoadUserRequest>(msg =>
{
// Load the messages from somewhere
var messages = new List<Message>();
// And respond to the Sender
Sender.Tell(messages);
Self.Tell(PoisonPill.Instance);
});
}
}
It doesn't really matter where they get the data from for this discussion, but both simply respond to a request by returning some data.
Then I have the actor that coordinates the two data gathering actors...
public class UserLoaderActor : ReceiveActor
{
public UserLoaderActor()
{
Receive<LoadUserRequest>(msg => LoadProfileAndMessages(msg));
Receive<UserProfile>(msg =>
{
_profile = msg;
FinishIfPossible();
});
Receive<List<Message>>(msg =>
{
_messages = msg;
FinishIfPossible();
});
}
private void LoadProfileAndMessages(LoadUserRequest msg)
{
_originalSender = Sender;
Context.ActorOf<UserProfileLoader>().Tell(msg);
Context.ActorOf<UserMessagesLoader>().Tell(msg);
}
private void FinishIfPossible()
{
if ((null != _messages) && (null != _profile))
{
_originalSender.Tell(new LoadUserResponse(_profile, _messages));
Self.Tell(PoisonPill.Instance);
}
}
private IActorRef _originalSender;
private UserProfile _profile;
private List<Message> _messages;
}
This just creates the two subordinate actors, sends them a message to get cracking, and then waits for both to respond before sending back all the data that's been gathered to the original requestor.
So, does this seem like a reasonable way to coordinate two disparate responses, in order to combine them? Is there an easier way to do this than craft it up myself?
Thanks in advance for your responses!
Thanks folks, so I've now simplified the actor significantly into the following, based on both Roger and Jeff's suggestions...
public class TaskBasedUserLoader : ReceiveActor
{
public TaskBasedUserLoader()
{
Receive<LoadUserRequest>(msg => LoadProfileAndMessages(msg));
}
private void LoadProfileAndMessages(LoadUserRequest msg)
{
var originalSender = Sender;
var loadPreferences = this.LoadProfile(msg.UserId);
var loadMessages = this.LoadMessages(msg.UserId);
Task.WhenAll(loadPreferences, loadMessages)
.ContinueWith(t => new UserLoadedResponse(loadPreferences.Result, loadMessages.Result),
TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.ExecuteSynchronously)
.PipeTo(originalSender);
}
private Task<UserProfile> LoadProfile(string userId)
{
return Task.FromResult(new UserProfile { UserId = userId });
}
private Task<List<Message>> LoadMessages(string userId)
{
return Task.FromResult(new List<Message>());
}
}
The LoadProfile and LoadMessages methods will ultimately call a repository to get the data, but for now I have a succinct way to do what I wanted.
Thanks again!
IMHO that's a valid process, as you fork action and then join it.
BTW you could use this.Self.GracefulStop(new TimeSpan(1)); instead of sending poison pill.
You could use a combination of Ask, WhenAll and PipeTo:
var task1 = actor1.Ask<Result1>(request1);
var task2 = actor2.Ask<Result2>(request2);
Task.WhenAll(task1, task2)
.ContinueWith(_ => new Result3(task1.Result, task2.Result))
.PipeTo(Self);
...
Receive<Result3>(msg => { ... });