我有一个UserContext
类作为数据库的网关。
public class UserContext : DbContext
{
public DbSet<User> Users { get; set; }
}
我想限制对数据库的直接访问(即UserContext.Users
),因为我想强制执行审核。到目前为止,我有额外的模型类来执行审计并实现控制器使用的接口:
控制器 - &gt;界面 - &gt;型号类 - &gt;的DbContext
但没有什么可以阻止控制器(或者其他开发人员创建它们)直接访问DbContext。
据我所知,我不能仅允许访问某些类(模型)并拒绝其他人(控制器),因此我需要保护UserContext
免受所有类。
我能想到的唯一解决方案是将Users
设为私有,然后在UserContext
内实现访问方法:
public class UserContext : DbContext
{
private DbSet<User> Users { get; set; }
public changePassword(String username, String newPassword)
{
doAudit();
Users.Single(user => user.Username == username).Password = newPassword;
SaveChanges();
}
}
但是,如果我必须在UserContext
内部实现所有数据库访问方法,它将变得非常大并且有多重责任。
UserContext
违反单一责任原则吗?或者这样可以,因为在这种情况下它只有一个责任:允许访问数据库吗?答案 0 :(得分:1)
你可以通过在其中嵌套各种类来创建一个类并保护你的上下文,但它有点不寻常的模式。
// sealed public class
public sealed class SecuredUserService : ISecuredUserService
{
private readonly UserContext _context;
private readonly ILogger _logger;
public SecuredUserService(ILogger logger)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger;
_context = new UserContext()
}
// expose a secure interface / method
public bool TryChangePassword(IUser user, string value)
{
_logger.Log("password was NOT changed.");
return false;
}
// secure the context
private class UserContext : DbContext
{
public DbSet<User> Users { get; set; }
}
// secure the model
private class User : IUser
{
public int ReadOnlyInteger { get; set; }
public bool WriteOnlyBool { get; set; }
public DateTime ReadWriteDateTime { get; set; }
public string SecretString { get; set; }
}
}
这些将是上述的公共接口。
// public exposed interfaces
public interface IUser
{
int ReadOnlyInteger { get; }
bool WriteOnlyBool { set; }
DateTime ReadWriteDateTime { get; set; }
}
// public exposed interface
public interface ISecuredUserService
{
bool TryChangePassword(IUser user, string value);
}
注意:为了更进一步,您可以将该类放入单独的命名空间。
答案 1 :(得分:0)
创建一个仅显示您认为可接受的功能的repository。这样,您就可以拥有一个集中的,可测试的数据库网关。
答案 2 :(得分:0)
虽然尽可能偏离当前的解决方案,但考虑将UserContext
中的责任分成其他类UserContext
来控制和委托。例如,PasswordChange
类负责审核和执行密码更改。然后,UserContext
可以将密码更改委派给PasswordChange
类:
public class PasswordChange
{
private DbSet<User> Users { get; set; }
public PasswordChange(DbSet<User> users)
{
Users = users;
}
public void Execute(String username, String newPassword)
{
doAudit();
Users.Single(user => user.Username == username).Password = newPassword;
}
}
public class UserContext : DbContext
{
private DbSet<User> Users { get; set; }
public changePassword(String username, String newPassword)
{
new PasswordChange(Users).Execute(username, newPassword);
SaveChanges();
}
}
更清晰但更复杂的解决方案是使用IUserRepository
方法定义ChangePassword
接口,或使用IPasswordChange
方法定义更细粒度的Execute
接口尺寸是一个问题。这些接口的具体实现将使用UserContext
执行与数据库相关的任务,同时还执行审计。如果使用IUserRepository
:
public interface IUserRepository
{
public void ChangePassword(string username, string newPassword);
//other user related methods
}
public class SqlUserRepository : IUserRepository
{
UserContext UserContext { get; }
public SqlUserRepository(UserContext userContext)
{
UserContext = userContext;
}
public void ChangePassword(string username, string newPassword)
{
DoAudit();
UserContext.Users.Single(user => user.Username == username).Password = newPassword;
UserContext.SaveChanges();
}
//other user related methods
}
如果选择IPasswordChange
:
public interface IPasswordChange
{
public void Execute(string username, string newPassword);
}
public class SqlPasswordChange : IPasswordChange
{
UserContext UserContext { get; }
public SqlPasswordChange(UserContext userContext)
{
UserContext = userContext;
}
public void Execute(string username, string newPassword)
{
DoAudit();
UserContext.Users.Single(user => user.Username == username).Password = newPassword;
UserContext.SaveChanges();
}
}
然后,控制器将通过其构造函数接受IUserRepository
或IPasswordChange
接口。通过这些接口,他们将访问具体的实现,这将确保始终进行审计,并且控制器无法直接访问数据库。
为防止控制器构建UserContext
或完全引用它,UserContext
和相关数据库类(如SqlUserRepository
或SqlPasswordChange
)可以移动到单独的程序集。然后可以将类或它们的构造函数标记为内部,以防止从控制器访问它们。