Modified MVC AccountController for Preview 5

I just downloaded the ASP.NET MVC Preview 5 bits from Codeplex and started on my first experiment.

One of the first things I did was to modify the default AccountController to use the new Form Posting and Form Validation features of the Preview 5, somebody probably overlooked updating those :)

If anyone else wants the reworked code, feel free to copy paste.

Note this was something done during lunch break in a hurry, it seems to all work logically, but it's possible I'll have to tune it a bit later on.

Controller:

C#:
  1. [HandleError]
  2. [OutputCache(Location = OutputCacheLocation.None)]
  3. public class AccountController : Controller
  4. {
  5.     public AccountController()
  6.         : this(null, null)
  7.     {
  8.     }
  9.  
  10.     public AccountController(IFormsAuthentication formsAuth, MembershipProvider provider)
  11.     {
  12.         FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();
  13.         Provider = provider ?? Membership.Provider;
  14.     }
  15.  
  16.     public IFormsAuthentication FormsAuth
  17.     {
  18.         get;
  19.         private set;
  20.     }
  21.  
  22.     public MembershipProvider Provider
  23.     {
  24.         get;
  25.         private set;
  26.     }
  27.  
  28.     [Authorize]
  29.     [AcceptVerbs("GET")]
  30.     public ActionResult ChangePassword()
  31.     {
  32.         ViewData["Title"] = "Change Password";
  33.         ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;
  34.  
  35.         return View();
  36.     }
  37.  
  38.     [Authorize]
  39.     [AcceptVerbs("POST")]
  40.     public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword)
  41.     {
  42.         // Basic parameter validation
  43.         if (String.IsNullOrEmpty(currentPassword))
  44.         {
  45.             ViewData.ModelState.AddModelError("currentPassword", currentPassword, "You must specify a current password.");
  46.         }
  47.         if (newPassword == null || newPassword.Length <Provider.MinRequiredPasswordLength)
  48.         {
  49.             ViewData.ModelState.AddModelError("newPassword", newPassword, String.Format(CultureInfo.InvariantCulture,
  50.                      "You must specify a new password of {0} or more characters.",
  51.                      Provider.MinRequiredPasswordLength));
  52.         }
  53.         if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal))
  54.         {
  55.             ViewData.ModelState.AddModelError("newPassword", newPassword, "The new password and confirmation password do not match.");
  56.         }
  57.  
  58.         if (ViewData.ModelState.IsValid)
  59.         {
  60.             // Attempt to change password
  61.             MembershipUser currentUser = Provider.GetUser(User.Identity.Name, true /* userIsOnline */);
  62.             bool changeSuccessful = false;
  63.             try
  64.             {
  65.                 changeSuccessful = currentUser.ChangePassword(currentPassword, newPassword);
  66.             }
  67.             catch
  68.             {
  69.                 // An exception is thrown if the new password does not meet the provider's requirements
  70.             }
  71.  
  72.             if (changeSuccessful)
  73.             {
  74.                 return RedirectToAction("ChangePasswordSuccess");
  75.             }
  76.             else
  77.             {
  78.                 ViewData.ModelState.AddModelError("password", currentPassword, "The current password is incorrect or the new password is invalid.");
  79.             }
  80.         }
  81.  
  82.         // If we got this far, something failed, redisplay form
  83.         ViewData["Title"] = "Change Password";
  84.         ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;
  85.  
  86.         return View();
  87.     }
  88.  
  89.     public ActionResult ChangePasswordSuccess()
  90.     {
  91.         ViewData["Title"] = "Change Password";
  92.  
  93.         return View();
  94.     }
  95.  
  96.     [AcceptVerbs("GET")]
  97.     public ActionResult Login()
  98.     {
  99.         ViewData["Title"] = "Login";
  100.         ViewData["CurrentPage"] = "login";
  101.  
  102.         return View();
  103.     }
  104.  
  105.     [AcceptVerbs("POST")]
  106.     public ActionResult Login(string username, string password, bool? rememberMe)
  107.     {
  108.         // Basic parameter validation
  109.         if (String.IsNullOrEmpty(username))
  110.         {
  111.             ViewData.ModelState.AddModelError("username", username, "You must specify a username.");
  112.         }
  113.  
  114.         if (ViewData.ModelState.IsValid)
  115.         {
  116.             // Attempt to login
  117.             bool loginSuccessful = Provider.ValidateUser(username, password);
  118.  
  119.             if (loginSuccessful)
  120.             {
  121.                 FormsAuth.SetAuthCookie(username, rememberMe ?? false);
  122.                 return RedirectToAction("Index", "Home");
  123.             }
  124.             else
  125.             {
  126.                 ViewData.ModelState.AddModelError("*", username, "The username or password provided is incorrect.");
  127.             }
  128.         }
  129.  
  130.         // If we got this far, something failed, redisplay form
  131.         ViewData["Title"] = "Login";
  132.         ViewData["CurrentPage"] = "login";
  133.         ViewData["username"] = username;
  134.  
  135.         return View();
  136.     }
  137.  
  138.     public ActionResult Logout()
  139.     {
  140.         FormsAuth.SignOut();
  141.         return RedirectToAction("Index", "Home");
  142.     }
  143.  
  144.     protected override void OnActionExecuting(ActionExecutingContext filterContext)
  145.     {
  146.         if (filterContext.HttpContext.User.Identity is WindowsIdentity)
  147.         {
  148.             throw new InvalidOperationException("Windows authentication is not supported.");
  149.         }
  150.     }
  151.  
  152.     [AcceptVerbs("GET")]
  153.     public ActionResult Register()
  154.     {
  155.         ViewData["Title"] = "Register";
  156.         ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;
  157.  
  158.         return View();
  159.     }
  160.  
  161.     [AcceptVerbs("POST")]
  162.     public ActionResult Register(string username, string email, string password, string confirmPassword)
  163.     {
  164.         // Basic parameter validation
  165.         if (String.IsNullOrEmpty(username))
  166.         {
  167.             ViewData.ModelState.AddModelError("username", username, "You must specify a username.");
  168.         }
  169.  
  170.         if (String.IsNullOrEmpty(email))
  171.         {
  172.             ViewData.ModelState.AddModelError("email", email, "You must specify an email address.");
  173.         }
  174.  
  175.         if (password == null || password.Length <Provider.MinRequiredPasswordLength)
  176.         {
  177.             ViewData.ModelState.AddModelError("password", password, String.Format(CultureInfo.InvariantCulture,
  178.                      "You must specify a password of {0} or more characters.",
  179.                      Provider.MinRequiredPasswordLength));
  180.         }
  181.  
  182.         if (!String.Equals(password, confirmPassword, StringComparison.Ordinal))
  183.         {
  184.             ViewData.ModelState.AddModelError("confirmPassword", confirmPassword, "The password and confirmation do not match.");
  185.         }
  186.  
  187.         if (ViewData.ModelState.IsValid)
  188.         {
  189.  
  190.             // Attempt to register the user
  191.             MembershipCreateStatus createStatus;
  192.             MembershipUser newUser = Provider.CreateUser(username, password, email, null, null, true, null, out createStatus);
  193.  
  194.             if (newUser != null)
  195.             {
  196.                 FormsAuth.SetAuthCookie(username, false /* createPersistentCookie */);
  197.                 return RedirectToAction("Index", "Home");
  198.             }
  199.             else
  200.             {
  201.                 ViewData.ModelState.AddModelError("*", username, ErrorCodeToString(createStatus));
  202.             }
  203.         }
  204.  
  205.         // If we got this far, something failed, redisplay form
  206.         ViewData["Title"] = "Register";
  207.         ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;
  208.         ViewData["username"] = username;
  209.         ViewData["email"] = email;
  210.  
  211.         return View();
  212.     }
  213.  
  214.     public static string ErrorCodeToString(MembershipCreateStatus createStatus)
  215.     {
  216.         // See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
  217.         // a full list of status codes.
  218.         switch (createStatus)
  219.         {
  220.             case MembershipCreateStatus.DuplicateUserName:
  221.                 return "Username already exists. Please enter a different user name.";
  222.  
  223.             case MembershipCreateStatus.DuplicateEmail:
  224.                 return "A username for that e-mail address already exists. Please enter a different e-mail address.";
  225.  
  226.             case MembershipCreateStatus.InvalidPassword:
  227.                 return "The password provided is invalid. Please enter a valid password value.";
  228.  
  229.             case MembershipCreateStatus.InvalidEmail:
  230.                 return "The e-mail address provided is invalid. Please check the value and try again.";
  231.  
  232.             case MembershipCreateStatus.InvalidAnswer:
  233.                 return "The password retrieval answer provided is invalid. Please check the value and try again.";
  234.  
  235.             case MembershipCreateStatus.InvalidQuestion:
  236.                 return "The password retrieval question provided is invalid. Please check the value and try again.";
  237.  
  238.             case MembershipCreateStatus.InvalidUserName:
  239.                 return "The user name provided is invalid. Please check the value and try again.";
  240.  
  241.             case MembershipCreateStatus.ProviderError:
  242.                 return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
  243.  
  244.             case MembershipCreateStatus.UserRejected:
  245.                 return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
  246.  
  247.             default:
  248.                 return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
  249.         }
  250.     }
  251. }
  252.  
  253. // The FormsAuthentication type is sealed and contains static members, so it is difficult to
  254. // unit test code that calls its members. The interface and helper class below demonstrate
  255. // how to create an abstract wrapper around such a type in order to make the AccountController
  256. // code unit testable.
  257.  
  258. public interface IFormsAuthentication
  259. {
  260.     void SetAuthCookie(string userName, bool createPersistentCookie);
  261.     void SignOut();
  262. }
  263.  
  264. public class FormsAuthenticationWrapper : IFormsAuthentication
  265. {
  266.     public void SetAuthCookie(string userName, bool createPersistentCookie)
  267.     {
  268.         FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
  269.     }
  270.     public void SignOut()
  271.     {
  272.         FormsAuthentication.SignOut();
  273.     }
  274. }

Login View:

ASP:
  1. <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="GuildSite.Views.Account.Login" %>
  2.  
  3. <asp:Content ID="loginContent" ContentPlaceHolderID="MainContent" runat="server">
  4.     <h2>Login</h2>
  5.  
  6.     <p>
  7.         Please enter your username and password below. If you don't have an account,
  8.         please <%= Html.ActionLink("register", "Register") %>.
  9.     </p>
  10.  
  11.     <%= Html.ValidationSummary()%>
  12.  
  13.     <form method="post" action="<%= Html.AttributeEncode(Url.Action("Login")) %>">
  14.         <div class="form">
  15.             <table>
  16.                 <tr>
  17.                     <td>Username:</td>
  18.                     <td><%= Html.TextBox("username") %></td>
  19.                 </tr>
  20.                 <tr>
  21.                     <td>Password:</td>
  22.                     <td><%= Html.Password("password") %></td>
  23.                 </tr>
  24.                 <tr>
  25.                     <td></td>
  26.                     <td><input type="checkbox" name="rememberMe" value="true" /> Remember me?</td>
  27.                 </tr>
  28.                 <tr>
  29.                     <td></td>
  30.                     <td><input type="submit" value="Login" /></td>
  31.                 </tr>
  32.             </table>
  33.         </div>
  34.     </form>
  35. </asp:Content>

Register View:

ASP:
  1. <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Register.aspx.cs" Inherits="GuildSite.Views.Account.Register" %>
  2.  
  3. <asp:Content ID="registerContent" ContentPlaceHolderID="MainContent" runat="server">
  4.     <h2>Account Creation</h2>
  5.  
  6.     <p>
  7.         Use the form below to create a new account.
  8.     </p>
  9.     <p>
  10.         Passwords are required to be a minimum of <%=Html.Encode(ViewData["PasswordLength"])%> characters in length.
  11.     </p>
  12.  
  13.     <%= Html.ValidationSummary()%>
  14.  
  15.     <form method="post" action="<%= Html.AttributeEncode(Url.Action("Register")) %>">
  16.         <div class="form">
  17.             <table>
  18.                 <tr>
  19.                     <td>Username:</td>
  20.                     <td><%= Html.TextBox("username") %></td>
  21.                 </tr>
  22.                 <tr>
  23.                     <td>Email:</td>
  24.                     <td><%= Html.TextBox("email") %></td>
  25.                 </tr>
  26.                 <tr>
  27.                     <td>Password:</td>
  28.                     <td><%= Html.Password("password") %></td>
  29.                 </tr>
  30.                 <tr>
  31.                     <td>Confirm password:</td>
  32.                     <td><%= Html.Password("confirmPassword") %></td>
  33.                 </tr>
  34.                 <tr>
  35.                     <td></td>
  36.                     <td><input type="submit" value="Register" /></td>
  37.                 </tr>
  38.             </table>
  39.         </div>
  40.     </form>
  41. </asp:Content>

4 reacties op “Modified MVC AccountController for Preview 5”

  1. [...] more at http://blog.cumps.be/modified-accountcontroller-preview-5/ Filed under: C#, IT, ASP.NET, General Software Development, [...]

  2. Tod Thomson zegt:

    Hi David,

    I just stumbled upon your post while looking for something unrelated and it made me want to shoot myself…

    I just started on ASP.NET MVC on Monday, and I really didn’t like a bunch of things about the example code (which I have been using to create my own application).

    Namely:

    1. HTTP Method handing
    2. Validation

    So I decided to “roll my own” – anyhow, as you will have guessed already, your updated example above shows me that I don’t really need my stuff anymore…

    However, this has brought me to a conclusion, i.e. I know where I went wrong.

    I need better documentation on ASP.NET MVC. i.e. my lack of knowing where the documentation has lead me to re-invent the wheel.

    So my question is – where is the documentation? how did you find out about these new features? Just from the DLLs or are there any API docs etc?

    If you could give me any pointers around documentation, it would be much appreciated.

    Cheers!

    Tod.

  3. David Cumps zegt:

    Hi Tod,

    It’s not yet in beta, so don’t hope for a complete full documentation :)

    However, here is the power of blogs, I’ll link the most useful links about mvc:

    http://blog.wekeroad.com/mvc-storefront/ – Rob Conery, great series about a full blown mvc app

    http://weblogs.asp.net/scottgu/ – Scott Guthrie, you’ll find all info on new releases on his blog

    http://www.asp.net/mvc/ – Official downloads, videos and other stuff

    Enjoy :)

Reageer