How to implement a testable custom ID based user system in ASP.NET MVC while using Forms Authentication?
I have a problem that has been bugging me for quite some time now (I left it for a while but now that I'm back I still can't figure out a solution).
The setting is like this: I'm not using the default membership, role or profile pr开发者_开发问答oviders. Rather, my application uses only OpenID login, it does not implement passwords. Also, my users table uses ID as primary key, instead of username.
As far as authentication goes, Forms Authentication is great, however, some actions require me to know the ID of the currently logged user. This poses a problem as Forms Authentication sets just the Name in the User property (accesible inside the controllers User property, specifically in User.Identity.Name).
I would ideally like something like User.Identity.ID, so that it could be included inside the cookie (if I'm not wrong), since this is the primary key of my user table.
What I used in the login method of my AccountController was the ugly and kludgy thing below
user = _userRepository.GetByOpenId(identifier);
FormsAuthenticationTicket ticket =
new FormsAuthenticationTicket(1, user.DisplayName,
DateTime.Now,
DateTime.Now.AddDays(10),
false /*rememberMe*/,
user.UserID.ToString());
And later when I needed to get the User ID:
int loggedUserID = Convert.ToInt32(((FormsIdentity)User.Identity).Ticket.UserData);
I basically encoded the ID in the Forms Cookie, but beyond making me feel dirty, I don't see how to unit test this (how to supply a fake user where needed, including other controllers).
So to summarize, I have two problems here:
- How to make this simpler and less kludgy (like setting something as User.Identity.ID or similar in simplicity)
- How to make it testable (specifically how to supply a fake user where needed)
Please help me, thanks!
EDIT: Is there a way to change the User.Identity.Name property to User.Identity.ID or to add a new property so that it makes semantic sense?
You should use the the User.Identity.Name
property to store the unique identifier of your user, which in your case is the user ID and not their actual name.
You can test the User.Identity
by creating a new GenericPrincipal
var principal = new GenericPrincipal(new GenericIdentity(user.ID.ToString()), user.Roles.ToArray());
EDIT: No more need for this thing after MVC3's DI-able controllers and test helper's InitializeController
I found a way of unit testing an action that depends on User, as shown in Scott Hanselman's post.
Summarizing, you create an IPrincipal model binder like so:
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;
}
}
Then register the binder in global.asax:
void Application_Start() {
RegisterRoutes(RouteTable.Routes); //unrelated, don't sweat this line.
ModelBinders.Binders[typeof(IPrincipal)] = new IPrincipalModelBinder();
}
Now it's possible to create an action that takes an IPrincipal as parameter:
public ActionResult Edit(int id, IPrincipal user)
And pass a fake user when testing like so:
[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");
}
精彩评论