如何在使用Forms身份验证时在ASP.NET MVC中实现可测试的基于自定义ID的用户系统?

时间:2011-03-10 07:19:45

标签: asp.net-mvc authentication forms-authentication

我有一个问题一直困扰着我一段时间(我离开了一段时间,但现在我又回来了,我仍然无法找到解决方案)。

设置如下:我没有使用默认会员资格,角色或资料提供程序。相反,我的应用程序仅使用OpenID登录,它不实现密码。此外,我的users表使用ID作为主键,而不是用户名。

就身份验证而言,表单身份验证很棒,但是,某些操作要求我知道当前登录用户的ID。这会产生一个问题,因为表单身份验证只设置User属性中的Name(可在控制器User属性中访问,特别是在User.Identity.Name中)。

我最好喜欢像User.Identity.ID这样的东西,以便它可以包含在cookie中(如果我没有错),因为这是我的用户表的主键。

我在AccountController的登录方法中使用的是下面的丑陋和笨拙的事情

user = _userRepository.GetByOpenId(identifier);
FormsAuthenticationTicket ticket = 
    new FormsAuthenticationTicket(1, user.DisplayName,
                                  DateTime.Now, 
                                  DateTime.Now.AddDays(10),
                                  false /*rememberMe*/,
                                  user.UserID.ToString());

稍后当我需要获取用户ID时:

int loggedUserID = Convert.ToInt32(((FormsIdentity)User.Identity).Ticket.UserData);

我基本上在Forms Cookie中对ID进行了编码,但除了让我感觉很脏之外,我还没有看到如何对此进行单元测试(如何在需要的地方提供假用户,包括其他控制器)。

总而言之,我在这里有两个问题:

  1. 如何使这更简单,更少kludgy(如在简单中设置User.Identity.ID或类似的东西)
  2. 如何使其可测试(特别是如何在需要时提供假用户)
  3. 请帮助我,谢谢!

    编辑:有没有办法将User.Identity.Name属性更改为User.Identity.ID或添加新属性以使其具有语义感?

2 个答案:

答案 0 :(得分:1)

您应该使用User.Identity.Name属性来存储用户的唯一标识符,在您的情况下,该标识符是用户ID而不是其实际名称。

您可以通过创建新的GenericPrincipal

来测试User.Identity
var principal = new GenericPrincipal(new GenericIdentity(user.ID.ToString()), user.Roles.ToArray());

答案 1 :(得分:1)

编辑:在MVC3的DI-able控制器和test helper's InitializeController之后不再需要这个东西


我找到了一种单元测试依赖于用户的操作的方法,如Scott Hanselman's post所示。

总结一下,您可以像这样创建一个IPrincipal模型绑定器:

public class IPrincipalModelBinder : IModelBinder{    
     public object BindModel(ControllerContext controllerContext, 
                             ModelBindingContext bindingContext) {        
          if (controllerContext == null) {            
               throw new ArgumentNullException("controllerContext");        
          }        
          if (bindingContext == null) {            
               throw new ArgumentNullException("bindingContext");        
          }        
          IPrincipal p = controllerContext.HttpContext.User;        
          return p;    
     }
} 

然后在global.asax中注册binder:

void Application_Start() {    
     RegisterRoutes(RouteTable.Routes); //unrelated, don't sweat this line.             
     ModelBinders.Binders[typeof(IPrincipal)] = new IPrincipalModelBinder();
} 

现在可以创建一个以IPrincipal为参数的动作:

public ActionResult Edit(int id, IPrincipal user) 

在测试时传递假用户:

[TestMethod]
public void EditAllowsUsersToEditDinnersTheyOwn()
{
    // Arrange
    DinnersController controller = new DinnersController(new TestDinnerRespository());

    // Act
    IPrincipal FakeUser = new GenericPrincipal(new GenericIdentity("Scott","Forms"),null);
    ViewResult result = controller.Edit(4, FakeUser) as ViewResult;

    // Yada yada yada assert etc etc etc
    Assert.IsTrue(result.ViewName != "InvalidOwner");
}