ASP.NET vNext Identity Part 3 – Registration

In the first part of this series of articles, I set up the database. In the second part, I logged in. Now I want to create some additional users through registration. It turns out that this is a basic MVC problem and ultimately has very little to do with the Identity framework except where you insert the new user into the database (which is one line). I leverage the UserManager for that. Let’s take a look.

I want to be able to get to the login screen and then click on something like Register a new user?. To do that, edit the Views/Account/Login.cshtml file and add the following.

    <p>
        @Html.ActionLink("Register a new user?", "Register")
    </p>

I placed this right before the closing DIV on my page. I also need a registration screen. Create a new file called Views/Account/Register.cshtml and place the following there:

@model AspNetIdentity.ViewModels.RegisterViewModel

@{
    Layout = "~/Views/Layout/LoginPage.cshtml";
    ViewBag.Title = "Register";
}

<h2>@ViewBag.Title.</h2>

@using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    @Html.ValidationSummary()
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-4 control-label" })
        <div class="col-md-8">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-4 control-label" })
        <div class="col-md-8">
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-4 control-label" })
        <div class="col-md-8">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-4 col-md-4">
            <input type="submit" class="btn btn-default" value="Register">
        </div>
    </div>
}

Before I go on, I also need a RegisterViewModel to hold the form submission. I’ve already referenced the model at the top of the page. I place my view-models in the ViewModels directory. Here are the contents of ViewModels/RegisterViewModel.cs:

using System.ComponentModel.DataAnnotations;

namespace AspNetIdentity.ViewModels
{
    public class RegisterViewModel
    {
        [Required]
        [Display(Name = "Email")]
        public string Email { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
}

The next step is to add to our Account Controller to support registration. The first action is when a user clicks on the registration link on the login form. In this case a HTTP GET is done of /Account/Register (per the @Html.ActionLink). I just need to output the registration form in this case, which is a normal “display a view” code snippet:

        /**
         * GET: /Account/Register
         */
         [HttpGet]
         [AllowAnonymous]
         public IActionResult Register()
        {
            return View();
        }

When the user submits the form, then the same controller and action is called but this time with a POST. When this happens, we want to create the user (or at least try) and if that succeeds, then redirect to the home page. Let’s take a look:

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Register(RegisterViewModel model)
        {
            if (ModelState.IsValid)
            {
                Debug.WriteLine("Register: Creating new ApplicationUser");
                var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
                Debug.WriteLine(string.Format("Register: New Application User = {0}", user.UserName));
                var result = await UserManager.CreateAsync(user, model.Password);
                Debug.WriteLine(string.Format("Register: Registration = {0}", result.Succeeded));
                if (result.Succeeded)
                {
                    // Success - redirect to the login page
                    return RedirectToAction("Index", "Home");
                }
                foreach (var error in result.Errors)
                {
                    Debug.WriteLine(string.Format("Register: Adding Error: {0}:{1}", error.Code, error.Description));
                    ModelState.AddModelError("", error.Description);
                }
                return View(model);
            }
            // Somethign went wrong, but we don't know what
            return View(model);
        }

Note that I’m using debug writelines so I can see the errors in the console as well as in the web page. It also tells me where in the code it is failing rather than just having an error. I am done with this code so it’s ready to test.

I did bump into some issues with this version (which I will correct in the next article). Firstly, you can put literally anything in the email address and it will just accept it. It doesn’t have to be an email address. Secondly, there is no logout button. I use a Web Developer extension to remove domain cookies to log out, but that’s not a good long term option.

To fix the log out button, let’s create a log out action within our AccountController:

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Logout()
        {
            SignInManager.SignOut();
            return RedirectToAction("Index", "Home");
        }

I can integrate that into any page I want. For example, I added the following to the ~/Views/Layout/MainSite.cshtml page:

<body>
    <div class="container">
        <div class="navbar-header">
            @using (Html.BeginForm("Logout", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
            {
                @Html.AntiForgeryToken()
                <ul class="nav navbar-nav navbar-right">
                    <li><a href="javascript:document.getElementById('logoutForm').submit()">Log Out</a></li>
                </ul>
            }
        </div>
    </div>
    <section id="body">
        @RenderBody()
    </section>
    <section id="scripts">
        <script src="~/lib/requirejs/require.js" data-main="Scripts/application"></script>
    </section>
</body>

This will put a link at the top of the page to click for signing out.

I will deal with the other problem – registration fraud – in my next post. In the mean time, you can check out the latest code at my GitHub Repository.