EF Core - query is changing depending of the role - c#

I have two roles, Admin and Operator. Admin can see all rows, Operator only those which he inserted.
I can do this with if-else
public async Task<PagedResponse<TodoItemBriefDto>> Handle(GetTodoItemsWithPaginationQuery request, CancellationToken cancellationToken)
{
if(request.User.RoleId=="OPERATOR")
{
return await _context.Set<TodoItemExample>()
.Where(x => x.UserId== request.User.Id));
}
else
{
return await _context.Set<TodoItemExample>();
}
}
I am wondering, is there more elegant way to do this?

What you have is fine but if by elegant, you mean less lines then you can move the condition into the where statement.
private const string AdminRole = "ADMIN";
public async Task<PagedResponse<TodoItemBriefDto>> Handle(GetTodoItemsWithPaginationQuery request, CancellationToken cancellationToken)
{
var userRole = request.User.RoleId;
return await _context.Set<TodoItemExample>()
.Where(x => userRole == AdminRole || x.UserId == request.User.Id));
}
If the user has the admin role then it won't filter the items that have the same user Id as the requesting user. This example assumes there are only two roles.
PS you should be using constant field members for known, unchanging strings as in the example above.
If you actually want to make the code cleaner then you should have separate methods for each role and use a switch case statement e.g.
private const string AdminRole = "ADMIN";
public async Task<PagedResponse<TodoItemBriefDto>> Handle(GetTodoItemsWithPaginationQuery request, CancellationToken cancellationToken)
{
switch (request.User.RoleId)
{
case AdminRole:
return await HandleAdmin();
default:
return await HandleOperator();
}
}
private async Task<PagedResponse<TodoItemBriefDto>> HandleAdmin()
{
return await _context.Set<TodoItemExample>();
}
private async Task<PagedResponse<TodoItemBriefDto>> HandleOperator()
{
return await _context.Set<TodoItemExample>()
.Where(x => x.UserId== request.User.Id));
}

Related

The instance of entity type 'x' cannot be tracked because another instance with the key value '{Id: 6}' is already being tracked

I am working a asp .netcore 6.0 clean architecture project.
When I try to update a site, I got this error,
System.InvalidOperationException: The instance of entity type 'SiteCode' cannot be tracked because another instance with the key value '{Id: 6}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Before We use services, in there this same code worked fine. Now we move to clean architecture(CQRS and Mediatr). I used same update code but I got this error.
.AsNoTracking()
I tried with
var result = await _DbContext.SiteCodes.FindAsync(request.Id).AsNoTracking(); this line,
But got error as,
'ValueTask<SiteCode?>' does not contain a definition for 'AsNoTracking' and no accessible extension method 'AsNoTracking' accepting a first argument of type 'ValueTask<SiteCode?>' could be found (are you missing a using directive or an assembly reference?) [Application]
Here is my codes
UpdateSiteCommandHandler.cs
public async Task<SiteCode> Handle(UpdateSiteCommand request, CancellationToken cancellationToken)
{
var result = _mapper.Map<SiteCode>(request);
_DbContext.SiteCodes.Update(result); // goes to exception after this line
await _DbContext.SaveChangesAsync(cancellationToken);
return result;
}
GetSiteByIdQueryHandler.cs
public async Task<SiteCode> Handle(GetSiteByIdQuery request, CancellationToken cancellationToken)
{
var result = await _DbContext.SiteCodes.FindAsync(request.Id);
if (result == null)
{
throw new NotFoundException(nameof(SiteCode), request.Id);
}
return result;
}
controller
public async Task<IActionResult> Update(int id, [FromBody] UpdateSiteCommand command)
{
command.Id = id;
var siteCode = await _mediator.Send(new GetSiteByIdQuery(Id: id));
var result = await _mediator.Send(command);
return Ok(result);
}
DependencyInjection.cs
public static class DependencyInjection
{
public static IServiceCollection AddApplication(this IServiceCollection services)
{
services.AddAutoMapper(Assembly.GetExecutingAssembly());
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>)); // I tried with AddScoped() But not work
return services;
}
}
in DbContext
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var entries = ChangeTracker
.Entries()
.Where(e => e.Entity is AuditableEntity && (
e.State == EntityState.Added
|| e.State == EntityState.Modified));
foreach (var entityEntry in entries)
{
if (entityEntry.State == EntityState.Added)
{
((AuditableEntity)entityEntry.Entity).CreatedAt = DateTime.UtcNow;
((AuditableEntity)entityEntry.Entity).CreatedBy = _httpContextAccessor?.HttpContext?.User?.Identity?.Name ?? null;
}
else
{
Entry((AuditableEntity)entityEntry.Entity).Property(p => p.CreatedAt).IsModified = false;
Entry((AuditableEntity)entityEntry.Entity).Property(p => p.CreatedBy).IsModified = false;
}
((AuditableEntity)entityEntry.Entity).ModifiedAt = DateTime.UtcNow;
((AuditableEntity)entityEntry.Entity).ModifiedBy = _httpContextAccessor?.HttpContext?.User?.Identity?.Name ?? null;
}
var result = await base.SaveChangesAsync(cancellationToken)
return result;
}
Anyone has idea how can solve this issue?
EF Core uses ChangeTracker to detect changes in loaded entities, and better (not faster) solution is to load entity for update.
public async Task<SiteCode> Handle(UpdateSiteCommand request, CancellationToken cancellationToken)
{
// the following code will load entitiy if it is still not loaded.
var dbRequest = await _DbContext.SiteCodes.FindAsync(request.Id);
if (dbRequest == null)
throw new Exception("Not found");
_mapper.Map<SiteCode>(request, dbRequest);
await _DbContext.SaveChangesAsync(cancellationToken);
return result;
}
Try to add the method AsNoTracking() after SiteCodes to your query in Handler (GetSiteByIdQueryHandler.cs).
public async Task<SiteCode> Handle(GetSiteByIdQuery request, CancellationToken cancellationToken)
{
var result = await _DbContext.SiteCodes.AsNoTracking().FindAsync(request.Id);
if (result == null)
{
throw new NotFoundException(nameof(SiteCode), request.Id);
}
return result;
}

How can I get logged in user's claim on Persistence layer (Onion Architecture)

I am trying to access logged in user id in Persistence layer.
I am using Claim and want to capture centralize created by and created on, logic below is my code kindly suggest me best approach
public class DBContext : DbContext
{
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
var AddedEntities = ChangeTracker.Entries().Where(E => E.State == EntityState.Added).ToList();
AddedEntities.ForEach(E =>
{
var prop = E.Metadata.FindProperty("CreatedOn");
if (prop != null)
{
E.Property("CreatedOn").CurrentValue = DateTimeHelper.DateTimeNow;
// This is a Persistence layer and want here to get logged in user Id
// E.Property("CreatedBy").CurrentValue=loggedInUserIdfromClaim
}
});
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
}

User.IsInRole() returns false and Authorize Roles gives me an Access Denied

I'm trying to set up the authentication in my existing App with ASP.NET Core Identity 2.0. As I use my own database schema and classes, I have my own User and Role classes and I had to create custom UserStore/UserManager/RoleStore/RoleManager.
I declare them in my services :
services.AddIdentity<User, Profile().AddUserManager<CustomUserManager<User>>().AddRoleManager<CustomRoleManager>().AddDefaultTokenProviders();
services.AddTransient<IUserStore<User>, UserStore>();
services.AddTransient<IRoleStore<Profile>, ProfileStore>();
services.AddTransient<UserResolverService>();
I implement the interface UserRoleStore in the UserStore with these methods :
public Task AddToRoleAsync(User user, string roleName, CancellationToken cancellationToken)
{
user.Profiles.Add(db.Profiles.ToList().Find(x => x.Name.Equals(roleName, StringComparison.OrdinalIgnoreCase)));
db.SaveChanges();
return Task.FromResult((object)null);
}
public Task RemoveFromRoleAsync(User user, string roleName, CancellationToken cancellationToken)
{
user.Profiles.Remove(db.Profiles.ToList().Find(x => x.Name.Equals(roleName, StringComparison.OrdinalIgnoreCase)));
db.SaveChanges();
return Task.FromResult((object)null);
}
public Task<IList<string>> GetRolesAsync(User user, CancellationToken cancellationToken)
{
IList<string> ret = new List<string>();
user.Profiles.ToList().ForEach(x => ret.Add(x.Name));
return Task.FromResult(ret);
}
public Task<bool> IsInRoleAsync(Useruser, string roleName, CancellationToken cancellationToken)
{
Profile searchRole = db.Profiles.Include(profile => profile.ProfileUsers).ToList().Find(x => x.Active && x.Name.Equals(roleName, StringComparison.OrdinalIgnoreCase));
return Task.FromResult(searchRole.ProfileUsers.ToList().Find(x => x.UserID == user.ID) == null ? false : true);
}
public Task<bool> IsInRole(User user, string roleName, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task<IList<Utilisateur>> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken)
{
Profile currentProfile = db.Profiles.Include(profile => profile.ProfileUsers).ThenInclude(pu => pu.User).ToList().Find(x => x.Active && x.Name.Equals(roleName, StringComparison.OrdinalIgnoreCase));
if (currentProfile == null)
{
return Task.FromResult((IList<User>)null);
}
IList<User> ret = new List<User>();
currentProfile.ProfileUsers.ToList().ForEach(x => ret.Add(x.User));
return Task.FromResult(ret);
}
So when I try to do
var result1 = _userManager.AddToRoleAsync(userObject, "WorkOrderViewer");
var res = _userManager.GetUsersInRoleAsync("WorkOrderViewer");
var test = await _userManager.IsInRoleAsync(userObject, "WorkOrderViewer");
It works and my user gets the choosen Role. However, when I try to set up the controller authorizations or the controls visibility in the view it doesn't work. For the controller authorization I have an access denied for exemple.
When I put this in my code :
var test = User.IsInRole("WorkOrderViewer");
It returns "false" even if I am log in the app (I try to log out and log in) and if the UserManager.IsInRoleAsync returns me "true".
I think it fails because UserManager.AddToRoleAsync() is not synchronized with User.IsInRole() but I don't know how to resolve it.
Have you an idea of what I am doing wrong ?
When you say you use the code User.IsInRole I assume you mean in a Controller.
User in a controller is of type ClaimsPrincipal.
And the method ClaimsPrincipal.IsInRole has the following documentation:
The IsInRole method checks whether an identity that this claims principal possesses contains a claim of type ClaimsIdentity.RoleClaimType where the value of the claim is equal to the value specified by the role parameter.
This can be confirmed by looking at the source for ClaimsIdentity.cs.
if (_identities[i].HasClaim(_identities[i].RoleClaimType, role))
So User.IsInRoleis checking to see if the User has a Claim of type RoleClaimType (which defaults to "http://schemas.microsoft.com/ws/2008/06/identity/claims/role.").
The claim type can be configured as needed.

Custom AuthorizeAttribute OnAuthorizationAsync Function

I'm trying to make my own custom System.Web.Http.AuthorizeAttribute. Everything was working until I needed to run some async function inside of the overridden OnAuthorizationfunction.
I found the OnAuthorizationAsync method and I use that, however still having issues running an async method. I need to ensure that a header attached to the HttpActionContext is in the db.
public override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
IEnumerable<string> values;
if (actionContext.Request.Headers.TryGetValues("Authentication", out values))
{
var token = new Token() { TokenString = values.First() };
if (await _tg.ValidateToken(token))
{
return base.OnAuthorizationAsync(actionContext, cancellationToken);
}
}
base.HandleUnauthorizedRequest(actionContext);
}
_tg.ValidateToken(token) is an async method that returns a bool. If I try to await it and make OnAuthorizationAsync an async method I then apparently cant return a Task:
Since OnAuthorizeAsync is an async method that returns 'Task', a return keyword must not be followed by an object expression.
Is there a way you can see out of this mess?
Because you use await, you need to add the async modifier to the method's declaration. Then change the return you have there to await. The compiler will do the rest - i.e. returning the Task for you.
Based on #sellotape's answer:
public async override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
IEnumerable<string> values;
if (actionContext.Request.Headers.TryGetValues("Authentication", out values))
{
var token = new Token() { TokenString = values.First() };
if (await _tg.ValidateToken(token))
{
return base.OnAuthorizationAsync(actionContext, cancellationToken);
}
}
await base.HandleUnauthorizedRequest(actionContext);
}

Merge asyc method into one task

Not sure if this is possible or if it already does something simpler with chained tasks internally, could not work it out from the info I read.
public async Task<ClaimsIdentity> CreateIdentity(string userid )
{
Guid gid = Guid.Parse(userid);
Avatar avatar = await _dbContext.Users.Where(d => d.Id == gid).FirstOrDefaultAsync();
return await CreateIdentity(avatar);
}
public async Task<ClaimsIdentity> CreateIdentity(Avatar avatar)
{
var identity = new ClaimsIdentity(await GetClaims(avatar));
return identity;
}
public async Task<List<Claim>> GetClaims(Avatar avatar)
{
var claims = new List<Claim>();
claims = async //(database call to get claims)
return claims;
}
With above code or any other similar async code, what I'm wondering is there any way to say, continue into, or continue with. So, instead of ending up with three tasks, they could be combined so there is a single state machine either two of methods or even all three?
I'm not sure if it matters that much, just looking for correct way of do it.
eg. (not valid code i know)
public async Task<ClaimsIdentity> CreateIdentity(string userid )
{
Guid gid = Guid.Parse(userid);
Avatar avatar = await _dbContext.Users.Where(d => d.Id == gid).FirstOrDefaultAsync();
return contiuneinto CreateIdentity(avatar);
}
^ say something like that. so the next method would be in same task. seems such waste to create another task for something so little.
Each async method gets its own state-machine and task, you can't automagically combine them.
You can remove both the async and await and use Task.ContinueWith which removes the state-machine, but it does still create a new task:
public Task<ClaimsIdentity> CreateIdentity(string userid )
{
Guid gid = Guid.Parse(userid);
return _dbContext.Users.Where(d => d.Id == gid).FirstOrDefaultAsync().
ContinueWith(avatarTask => CreateIdentity(avatarTask.GetAwaiter().GetResult()));
}
The simplest way to cut back on these tasks and state-machines is to simply join these async methods together, for example:
public async Task<ClaimsIdentity> CreateIdentity(string userid )
{
Guid gid = Guid.Parse(userid);
Avatar avatar = await _dbContext.Users.Where(d => d.Id == gid).FirstOrDefaultAsync();
var claims = new List<Claim>();
claims = async //(database call to get claims)
return new ClaimsIdentity(claims);
}
In any case, you don't need to worry about it. The cost of that task creation is probably negligble comapred to your actual asynchronous operations.

Categories

Resources