MVC Controllers和Forms验证

想想下,一组从Forms验证抽象出来的登入/登出Controllers行为特有的ActionResult实现。。

我已经使用ASP.NETMVC重新实现了我的个人网站(projects.nikhilk.net)的一部分。其中之一就是登入/登出功能的实现。当你创建一个新的ASP.NETMVC应用程序,默认的就会包含一个简单的AccountController。这个Controller中包含了登入/登出,并实现了IFormsAuthentication接口。其默认实现基于System.Web.Security.FormsAuthentication APIs(SetAuthCookie and SignOut)。可以仿照给这个接口的实现以便用于United测试。

尽管如此,当我在自己的Controller中实现登入/登出时,我发现一个奇怪现象,行为(actions)直接的访问FormsAuthentication(甚至是通过接口)。我以为行为应该简单地完成用户验证,而后返回一个处理生成响应的匹配的ActionResult,这该实例中就是设置或清除cookie,并重定向Url。为此,我创建了FormsLoginResult和FormsLogoutResult。很明显的他们是成对的出现的,but I thought I'd go ahead and share anyway。

首先,我将显示AccountController中修改过的Login和Logout方法。

[AcceptVerbs(HttpVerbs.Post)]

public ActionResult Login(string username, string password, bool rememberMe) {

    ...

    if (ViewData.ModelState.IsValid) {

        // Attempt to login

        bool loginSuccessful = Provider.ValidateUser(username, password);

        if (loginSuccessful) {

            return new FormsLoginResult(username, rememberMe);

        }

        else {

            ...

        }

    }



    // If we got this far, something failed, redisplay form

    ...

    return View();

}



public ActionResult Logout() {

    return new FormsLogoutResult();

}

现在Login和Logout行为不再和authentication是否通过cookie跟踪以及需要重定位到哪里(默认主页、参数关联的url等)相关联。

其次、建立这两个函数的测试单元。我使用Moq实现测试需要的MembershipProvider的模拟,当是不再需要IFormsAuthentication的实现。

[TestClass]

public class AccountControllerTest {



    [TestMethod]

    public void LoginSuccessful() {

        string testUserName = "TestUser";

        string testPassword = "TestPassword";



        Mock<MembershipProvider> mockMembership = new Mock<MembershipProvider>();

        mockMembership.Expect<bool>(m => m.ValidateUser(testUserName, testPassword))

           .Returns(true).AtMostOnce().Verifiable();



        AccountController controller = new AccountController(mockMembership.Object);

        ActionResult result = controller.Login(testUserName, testPassword, false);



        Assert.IsInstanceOfType(result, typeof(FormsLoginResult));



        FormsLoginResult loginResult = (FormsLoginResult)result;

        Assert.AreEqual<string>(loginResult.UserName, testUserName);

        Assert.AreEqual<bool>(loginResult.PersistentCookie, false);

    }



    [TestMethod]

    public void LoginFailure() {

        string testUserName = "TestUser";

        string testPassword = "TestPassword";



        Mock<MembershipProvider> mockMembership = new Mock<MembershipProvider>();

        mockMembership.Expect<bool>(m => m.ValidateUser(testUserName, testPassword))

           .Returns(true).AtMostOnce().Verifiable();



        AccountController controller = new AccountController(mockMembership.Object);

        ActionResult result = controller.Login(testUserName, "badPassword", false);



        Assert.IsInstanceOfType(result, typeof(ViewResult));

    }

}

下面是FormsLoginResult 和FormsLogoutResult的实现。这里使用了MVC提供的构建自定义action results可扩展性较好的实现。

public class FormsLoginResult : ActionResult {

    private string _userName;

    private string _userData;

    private bool _persistentCookie;



    public FormsLoginResult(string userName)

        : this(userName, /* persistentCookie */ false) {

    }



    public FormsLoginResult(string userName, bool persistentCookie) {

        if (String.IsNullOrEmpty(userName)) {

            throw new ArgumentNullException("userName");

        }

        _userName = userName;

        _persistentCookie = persistentCookie;

    }



    public bool PersistentCookie {

        get { return _persistentCookie; }

    }



    public string UserData {

        get { return _userData; }

        set { _userData = value; }

    }



    public string UserName {

        get { return _userName; }

    }



    public override void ExecuteResult(ControllerContext context) {

        HttpResponseBase response = context.HttpContext.Response;



        if (String.IsNullOrEmpty(_userData)) {

            FormsAuthentication.SetAuthCookie(_userName, _persistentCookie);

        }

        else {

            FormsAuthenticationTicket ticket =

                new FormsAuthenticationTicket(1, _userName,

                                              DateTime.Now, DateTime.Now.AddMinutes(30),

                                              _persistentCookie,

                                              _userData, 

                                              FormsAuthentication.FormsCookiePath);

            string encryptedTicket = FormsAuthentication.Encrypt(ticket);



            HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName,

                                               encryptedTicket);

            cookie.HttpOnly = true;

            cookie.Secure = FormsAuthentication.RequireSSL;

            cookie.Path = FormsAuthentication.FormsCookiePath;

            if (FormsAuthentication.CookieDomain != null) {

                cookie.Domain = FormsAuthentication.CookieDomain;

            }



            response.Cookies.Add(cookie);

        }



        response.Redirect(FormsAuthentication.GetRedirectUrl(_userName, _persistentCookie));

    }

}

令人庆幸的是,FormsLoginResult同样的在FormsAuth cookie内封装了一些自定义用户数据,这些数据可以通过HttpApplication的Authenticate事件获取,并可以使用FormsAuthentication.Decrypt将AuthCookie内数据转换为一个FormsAuthenticationTicket实例,以及从Ticket中获取的用户数据。我在我的网站中使用这个跟踪一些元数据以便于在Requests后重新创建代理实例。

第二个action result FormsLogoutResult非常简单。

 

public class FormsLogoutResult : ActionResult {

    private string _url;



    public FormsLogoutResult()

        : this(FormsAuthentication.DefaultUrl) {

    }



    public FormsLogoutResult(string url) {

        if (String.IsNullOrEmpty(url)) {

            throw new ArgumentNullException("url");

        }

        _url = url;

    }



    public string Url {

        get { return _url; }

    }



    public override void ExecuteResult(ControllerContext context) {

        FormsAuthentication.SignOut();

        context.HttpContext.Response.Redirect(_url);

    }

}



源:http://www.nikhilk.net/Default.aspx

你可能感兴趣的:(controller)