Inside an ASP.Net Core Web app, what is the correct way to pass a value to the DB Context class to use it as a filter?
I need to filter the data returned across all pages of my ASP.Net Core web app. The filter will be based on the logged in user. If the logged in user is from Branch 6108 then they should only see that branch's data. If I use the raw value I can filter by saying
modelBuilder.Entity<Branch>().HasQueryFilter(b => b.BranchID == 6108);
The problem is we have 50 branches so the number will be different for any logged in user.
So ideally I want it to say
modelBuilder.Entity<Branch>().HasQueryFilter(b => b.BranchID == branchFilter);
Where branchFilter is a value I discover and pass after getting the logged in user by using
@User.Identity.Name
Edit with suggested injection
After making the changes suggested below, I am receiving a new error when attempting to access a view.
InvalidOperationException: A named connection string was used, but the name 'AzureSql' was not found in the application's configuration.
It points to the GetUserProvider as the source
public GetUserProvider(IHttpContextAccessor accessor)
{
UserName = accessor.HttpContext?.User.Claims.SingleOrDefault(x => x.Type == UserName)?.Value;
using (var context = new TimeSheetContext())
{
var branch = context.Colleagues
.Single(c => 'the identifer' == UserName);
BranchID = branch.BranchID;
}
}
With var branch line highlighted.
Searches on Google for the error show it is usually caused during scaffolding so it is EF Core but unrelated to my problem as the connection string is found fine when not attempting the suggestion.
CodePudding user response:
When you initiate your DbContext you could inject the required user data, store it in a field and then use this in your query. For this you can create an interface for this user data and then inject the implementation of the user data through dependency injection. After that you can use the field in your HasQueryFilter
.
For example:
public class YourDbContext : DbContext
{
private string branchId;
private string userId;
public YourDbContext(DbContextOptions<YourDbContext> options, IGetUserProvider userData) : base(options)
{
tenantId = userData.BranchId;
userId = userData.UserId;
}
// ...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Branch>().HasQueryFilter(b => b.BranchID == branchId);
}
}
And an example on how to get the user from the HttpContext, where your user holds a claim to the branch. You should of course have an implementation of IGetUserProvider
that is relevant to your use case.
public interface IGetUserProvider
{
string UserId { get; }
string BranchId { get; }
}
public class GetUserProvider : IGetUserProvider
{
public string UserId { get; }
public string BranchId { get; }
public GetTenantClaimsProvider(IHttpContextAccessor accessor)
{
UserId = accessor.HttpContext?.User.Claims.SingleOrDefault(x => x.Type == UserIdentifierClaimName)?.Value;
BranchId = accessor.HttpContext?.User.Claims.SingleOrDefault(x => x.Type == BranchIdentifierClaimName)?.Value;
}
}
Alternatively you could create a public property or method on your implementation of DbContext
, but you would need to remember to always set the data before querying any dataset. That's a highly likely mistake with considerable consequence that can be avoided by injecting the required data.
For a full guid on this topic, please read Jon P. Smith's proposed design: https://www.thereformedprogrammer.net/part-4-building-a-robust-and-secure-data-authorization-with-ef-core/