如何禁止直接访问我的数据库上下文并通过单点汇集所有访问权限?

时间:2017-08-05 14:54:46

标签: c# asp.net-core-mvc entity-framework-core single-responsibility-principle

方案

我有一个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内部实现所有数据库访问方法,它将变得非常大并且有多重责任。

问题

  1. 还有另一种阻止其他类直接访问数据库对象的方法吗?
  2. 我建议的解决方案是将所有访问方法置于UserContext违反单一责任原则吗?或者这样可以,因为在这种情况下它只有一个责任:允许访问数据库吗?

3 个答案:

答案 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();
    }
}
然后,

控制器将通过其构造函数接受IUserRepositoryIPasswordChange接口。通过这些接口,他们将访问具体的实现,这将确保始终进行审计,并且控制器无法直接访问数据库。

为防止控制器构建UserContext或完全引用它,UserContext和相关数据库类(如SqlUserRepositorySqlPasswordChange)可以移动到单独的程序集。然后可以将类或它们的构造函数标记为内部,以防止从控制器访问它们。