在开发一个支持多种文档格式上传和搜索的Web页面项目时,为系统设计了三种不同的用户角色:“上传者”、“管理者”和“管理员”。为了实现这一功能,使用了ASP.NET MVC框架中的AuthorizeAttribute属性,它允许只有特定角色的用户才能查看网页的某些部分。然而,这需要在数据库中创建额外的表并插入数据来配置角色。由于已经使用ASP.NET Membership来创建安全的登录账户,但仅限于存储密码,所有的账户信息都存储在创建的“Accounts”表中。因此,希望角色信息也能存储在该表中。
ASP.NET Roles要求将角色存储在特定的表中。为了避免不必要的复杂性,选择了创建自定义的RoleProvider类,以便修改默认行为以满足需求。RoleProvider是一个抽象类,其派生类用于管理应用程序中的角色。检查用户是否属于某个角色也属于这个类。之前考虑过实现自己的RoleProvider几次,但每次都因为需要实现的方法数量而感到不知所措,所以每次都选择了默认行为。那是11个方法和一个属性需要实现。
此外,由于一直在尝试使用最佳实践来开发这个应用程序,并且一直在使用依赖注入,实现自定义RoleProvider带来了另一个问题:没有办法通过构造函数将依赖项传递给RoleProvider实现,因为该类是由ASP.NET隐式实例化的,使用的是Web.Config中的设置。
幸运的是,这两个问题似乎只是难以解决。对于第一个问题,实现所有方法,结果发现,如果想使用AuthorizeAttribute,只需要实现一个方法。这个方法是GetRolesForUser(..),它的作用正如其名。现在,为了获取系统中账户的角色列表,需要一个可以从数据库检索它们的存储库。由于在案例中,一个账户一次只能属于一个角色,它只需要使用有的接口IAccountRepository来检索账户:
public override string[] GetRolesForUser(string username)
{
Account account = accountRepository.GetByUsername(username);
if (account != null)
return new string[] { account.Role };
return new string[] { };
}
一切顺利,但需要实际获取IAccountRepository接口的某个实现的实例,因为项目没有直接引用它们。使用Unity IoC容器来创建接口的实现,并填充它们可能在构造函数中拥有的任何依赖项。通常,容器仅在应用程序的最高级别使用,例如ASP.NET中的Global.asax。但由于没有办法将任何东西注入到RoleProvider类中,决定在自定义实现的Initialize方法中直接干预容器。创建了一个UnityContainerFactory类来获取容器,以便不需要单例(被一些博客说服,认为单例应该被认为是反模式)。结果如下:
public override void Initialize(string name, NameValueCollection config)
{
base.Initialize(name, config);
IUnityContainer container = new UnityContainerFactory().Create();
accountRepository = container.Resolve();
}
UnityContainerFactory类实际上返回了IUnityContainer的同一个实例,该实例被自定义的IControllerFactory用来创建ASP.NET MVC控制器。它实际上是一个单例,即每个应用程序有一个类的实例,而不是一个单例,即在应用程序的每个部分都使用一个静态属性。