У меня есть приложение с несколькими ролями пользователя (Admin, Dealer, Manager). Со следующей структурой базы данных
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Shop> Shops { get; set; }
}
public class Shop
{
public int Id { get; set; }
public string Name { get; set; }
public int CountryId { get; set; }
public virtual Country Country { get; set; }
public virtual ICollection<Machine> Machines { get; set; }
}
public class Machine
{
public int Id { get; set; }
public string Name { get; set; }
public int ShopId { get; set; }
public virtual Shop Shop { get; set; }
}
Каждый пользователь может видеть записи машины, указанные c только для его роли. Администратор может видеть все машины, дилер - только машины из своей страны и менеджер для своего магазина. Мне нужно убедиться, что пользователи не видят данные, которые им не нужны, когда список компьютеров или отдельная сущность извлекаются из базы данных. Опции, которые я пробовал:
- Использовать глобальный фильтр структуры сущности, передавая выражение на основе роли в HasQueryFilter во время инициализации DbContext. Но, очевидно, первое выражение кэшируется во время одного вызова OnModelCreating. Поэтому этот подход не будет работать для разных выражений, основанных на роли для одной и той же сущности.
public class DatabaseContext : DbContext
{
private Expression<Func<Machine, bool>> machineFilter;
public DatabaseContext(DbContextOptions<DatabaseContext> options, Expression<Func<Machine, bool>> machineFilter)
: base(options)
{
this.machineFilter = machineFilter;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Machine>().HasQueryFilter(machineFilter);
}
public virtual DbSet<Country> Countries { get; set; }
public virtual DbSet<Shop> Shops { get; set; }
public virtual DbSet<Machine> Machines { get; set; }
}
Использовать шаблон спецификации. Этот подход работает, но тогда мне нужно go пройти через все службы, где используется объект этого datacontext, и убедиться, что спецификация применяется в старом и новом коде. И я не уверен, насколько корректен этот подход для моего случая.
public class MachineService
{
private readonly DataContext _context;
public List<Machine> GetAll(ISpecification specification)
{
return _context.Machines.Where(specification.Predicate).ToList();
}
}
public class MachineSpecification : ISpecification<Machine>
{
public Expression<Func<Machine, bool>> Predicate { get; }
public MachineSpecification(Role role, int? countryId, int? shopId)
{
switch(role)
{
case Role.Admin:
Predicate = c => true;
break;
case Role.Dealer:
Predicate = c => c.Shop.CountryId == countryId;
break;
case Role.Manager:
Predicate = c => c.ShopId == shopId;
break;
default: throw new NoPermissionException();
}
}
}
Каков подходящий подход или шаблон для решения проблемы доступа к данным на основе роли? Я не хочу делать эти проверки в контроллерах. Есть ли способ решить это на уровне DataContext?