Send Email to Selected Recipients from your ASP.NET MVC Web Application Part II

Posted on January 18 2014 08:10 AM by jatten in ASP.NET MVC, ASP.Net, C#, CodeProject, Web   ||   Comments (0)

tidepool-320This is the second part of an article demonstrating how to build out an application for sending personalized email to recipients selected from a list.

In the first part, we put together the basic structure of our ASP.NET MVC application, according to a simple list of requirements.

Now, we will add the email functionality, such that the user may select one or more recipients from a list using checkboxes, then generate and send a personalize email to each.

Image by Sergio Quesada | Some Rights Reserved

Review Part I --> Send Email to Selected Recipients from your ASP.NET MVC Web Application

The Send Mail Method

In the previous post, we created a stub for the SendMail method and threw some code in there to emulate a long-running process such as sending a list of emails:

The Send Mail Method Stub:
[HttpPost]
[Authorize]
public ActionResult SendMail(MailRecipientsViewModel recipients)
{
    // Mail-sending code will happen here . . .
    System.Threading.Thread.Sleep(2000);
    return RedirectToAction("Index");
}

 

We did this so that we could run our application, and all the front end functionality would work as expected. Now let's see what we need to do in order to actually send some mail.

The Problem to Solve

Let's take a look at what we need to accomplish here by breaking the problem into steps. In order to send a personalized message to each of the recipients selected by the user, we need to:

  • Retrieve the recipient data for each of the recipients selected by the user
  • Compose a message personalized for each recipient by inserting the recipient's name into some sort of message template, and addressed to the recipient's email.
  • Retrieve the current User's email address to use as the "From" email address, and the current user's name to use in the signature
  • Represent the above as a "Message" which can be aggregated into a list of messages to be sent, addressed to each recipient.
  • Add a record to the SentMail table representing the key points for each email sent (basically a log)
  • When sending is complete, redirect to the Index page, and refresh, displaying updated records which include a filed for the date mail was most recently sent to each recipient.
  • Pass the list to some sort of Mail Sender, which can iterate over the list and send each message.

Pseudo-Code the New and Improved SendMail Method

Given the above, it looks like we might want our SendMail method to do something like this:

Pseudo-Code for Steps in Sending Mail:
[HttpPost]
[Authorize]
public ActionResult SendMail(MailRecipientsViewModel recipients)
{
    // Retrieve the ids of the recipients selected:
  
    // Grab the recipient records:
  
    // Build the message container for each:
  
    // Send the mail:
  
    // Save a record of each mail sent:
  
    // Reload the index form:
}

So, lets make that happen!

In order to keep our Action method clean and simple, we are going to make each of these steps a call to a locally defined method. The code for each step could then also be easily moved out of the controller into another class or classes, depending on the needs of your application and/or fussiness about how much work should be done within the controller itself. We aren't going to get all pedantic about it here.

Retrieve the Selected Recipients

We can start by thinking about what we actually receive in the recipients argument passed to SendMail from the HTTP request body. We will get back an instance of MailRecipientsViewModel, which provides a method getSelectedRecipientIds(). This returns an IEnumerable<int> representing the Ids of the recipients selected by the user on our form.

Reviewing our MailRecipientsViewModel class:

The Get Selected Recipient Ids Method:
public class MailRecipientsViewModel
{
    public List<SelectRecipientEditorViewModel> MailRecipients { get; set; }
    public MailRecipientsViewModel()
    {
        this.MailRecipients = new List<SelectRecipientEditorViewModel>();
    }
  
  
    public IEnumerable<int> getSelectedRecipientIds()
    {
        return (from r in this.MailRecipients 
                where r.Selected 
                select r.MailRecipientId).ToList();
    }
}

 

Private Implementation Code

Now That we have our Ids, lets fill in the rest of our private helper methods. Add the following code the the controller after the SendMail stub:

Adding Code to the Controller to Implement the Send Mail Method:
IEnumerable<MailRecipient> LoadRecipientsFromIds(IEnumerable<int> selectedIds)
{
    var selectedMailRecipients = from r in db.MailRecipients
                                 where selectedIds.Contains(r.MailRecipientId)
                                 select r;
    return selectedMailRecipients;
}
  
  
IEnumerable<Message> createRecipientMailMessages(
    IEnumerable<MailRecipient> selectedMailRecipients)
{
    var messageContainers = new List<Message>();
    var currentUser = db.Users.Find(User.Identity.GetUserId());
    foreach (var recipient in selectedMailRecipients)
    {
        var msg = new Message()
        {
            Recipient = recipient,
            User = currentUser,
            Subject = string.Format("Welcome, {0}", recipient.FullName),
            MessageBody = this.getMessageText(recipient, currentUser)
        };
        messageContainers.Add(msg);
    }
    return messageContainers;
}
  
  
void SaveSentMail(IEnumerable<SentMail> sentMessages)
{
    foreach (var sent in sentMessages)
    {
        db.SentMails.Add(sent);
        db.SaveChanges();
    }
}
  
  
string getMessageText(MailRecipient recipient, ApplicationUser user)
{
    return ""
    + string.Format("Dear {0}, ", recipient.FullName) + Environment.NewLine
    + "Thank you for your interest in our latest product. "
    + "Please feel free to contact me for more information!"
    + Environment.NewLine
    + Environment.NewLine
    + "Sincerely, "
    + Environment.NewLine
    + string.Format("{0} {1}", user.FirstName, user.LastName);
}

 

Abstracting an Email Message - the Message Class

In the code above, we see we create an instance of a class Message. This is another Model we need to add to our Models folder. We are using the Message class to represent everything needed to send an email:

Add the following class to the Models folder:

The Message Class:
public class Message
{
    public MailRecipient Recipient { get; set; }
    public ApplicationUser User { get; set; }
    public string Subject { get; set; }
    public string MessageBody { get; set; }
}

 

Also, in the createRecipientMailMessages method, we grab the current logged-in User with the following call:

Get the Current Logged-in User:
var currentUser = db.Users.Find(User.Identity.GetUserId());

 

In order for this to work we need to add a reference to the Microsoft.AspNet.Identity namespace in the usings at the top of our code file, or this code won't work.

Call Implementation Code from Send Mail Method

Now that we have broken out each of our steps into discrete private method calls, we can call these from within the SendMail method:

[HttpPost]
[Authorize]
public ActionResult SendMail(MailRecipientsViewModel recipients)
{
    // Retrieve the ids of the recipients selected:
    var selectedIds = recipients.getSelectedRecipientIds();
  
    // Grab the recipient records:
    var selectedMailRecipients = this.LoadRecipientsFromIds(selectedIds);
  
    // Build the message container for each:
    var messageContainers = this.createRecipientMailMessages(selectedMailRecipients);
  
    // Send the mail:
    var sender = new MailSender();
    var sent = sender.SendMail(messageContainers);
  
    // Save a record of each mail sent:
    this.SaveSentMail(sent);
  
    // Reload the index form:
    return RedirectToAction("Index");
}

 

In the above, we have working code for everything except step 4, in which we initialize an instance of MailSender, and then actually send the mail. Now we get to the nitty-gritty of our application.

The Mail Sender Class

In our SendMail code, we build up a list of Message instances, which we then pass to a new class we haven't looked at yet - the MailSender class.

Add a new class to the project, name it MailSender, and paste in the following code:

The Mail Sender Class:
public class MailSender
{
    public IEnumerable<SentMail> SendMail(IEnumerable<Message> mailMessages)
    {
        var output = new List<SentMail>();
  
        // Modify this to suit your business case:
        string mailUser = "youremail@outlook.com";
        string mailUserPwd = "password";
        SmtpClient client = new SmtpClient("smtp.host.com");
        client.Port = 587;
        client.DeliveryMethod = SmtpDeliveryMethod.Network;
        client.UseDefaultCredentials = false;
        System.Net.NetworkCredential credentials = 
            new System.Net.NetworkCredential(mailUser, mailUserPwd);
        client.EnableSsl = true;
        client.Credentials = credentials;
  
        foreach (var msg in mailMessages)
        {
            var mail = new MailMessage(msg.User.Email.Trim(), msg.Recipient.Email.Trim());
            mail.Subject = msg.Subject;
            mail.Body = msg.MessageBody;
  
            try
            {
                client.Send(mail);
                var sentMessage = new SentMail()
                {
                    MailRecipientId = msg.Recipient.MailRecipientId,
                    SentToMail = msg.Recipient.Email,
                    SentFromMail = msg.User.Email,
                    SentDate = DateTime.Now
                };
                output.Add(sentMessage);
            }
            catch (Exception ex)
            {
                throw ex;
                // Or, more likely, do some logging or something
            }
        }
        return output;
    }
}

 

You will need to make sure you import the following namespaces for the code to work:

Required Namespaces for the Mail Sender Class:
using AspNetEmailExample.Models;
using System;
using System.Collections.Generic;
using System.Net.Mail;

 

Mail Client Configuration Settings

I discuss the details of setting up the mail client for Outlook.com or Gmail in another post. For most mail hosts, the client configuration should resemble the above. However, pay attention. For one, as discussed in the post linked above, if you have some sort of two-step authorization in place on your mail host, you will likely need to use an Application-Specific Password for this to work. Also note, you can send mail using your Outlook.com account as a host, but unlike most other mail hosting accounts, the Outlook.com host name for SMTP is:

smtp-mail.outlook.com

Whereas Gmail is simply:

smtp.gmail.com

 

For other mail hosts, you may have to experiment a little, or consult the provider documentation.

Walking Through the Execution of the Send Mail Method

With all of our pieces in place, we can now walk through the execution of SendMail() and take an high-level look at what is going on in all these small, refactored methods, and how they align with the steps we defined to send mail to each recipient,

First, we use our list of selected Ids to retrieve a corresponding list of fully instantiated recipient instances. This list is then returned to the call in SentMail, whereupon it is passed to the createMailRecipientMessages() method.

This next method iterates the list of recipients, and creates a new Message instance for each, supplying the property values needed to send an email. Two of these, the User and MessageBody properties, involve additional calls. Retrieving the current user requires a call into the Microsoft.AspNet.Identity library.

The getMessageText method, from which we retrieve the actual text for each mail message, represents a crude, "just make it work" implementation of what, in a real application, should probably be a template-based system. I have kept things simple here, but in reality we would probably like to be able to retrieve a message template from some resource or another, and populate the template properly from code without having to re-write and recompile.

How you implement this would depend significantly on your application requirements and is beyond the scope of this article (this article is already long, considering the topic is not all that advanced!). If you have either questions, or brilliant ideas for implementing such a system in your own application, I would love to hear either. This might become the topic of another article.

Once we have constructed our list of Message objects, we pass that to the MailSender.SendMail method, and, well, send the damn mail. We can see that each Message object is used to create a System.Net.Mail.MailMessage object, which is then sent using our properly configured mail client.

Once each Message is sent, we create a SentMail object, and then return the list of List<SentMail> back to the SendMail controller method, at which point we persist the SentMail objects, and redirect back to the Index method.

Running the Project and Sending Mail

Now, we likely have our test data from before, when we entered some examples to test out our front-end. You may want to go ahead and change the example.com email addresses to an actual mail account you can access, to ensure all is working properly. Then, run the application, log in, and try the "Email Selected" button again. you may want to deselect one or two of the potential recipients in the list, just to see the difference:

Try Sending Some Mail:

try-sending-mail-before

This time, we should see our "Busy" spinner for a moment, and then be redirected back to a refreshed Index view, now updated with the last date we sent mail to the selected recipients:

The Updated Index View After Sending Mail:

try-sending-mail-after

As we can see, the two items selected for sending email have now been updated with a Last Sent date.

What Went Wrong?

If you have been following along, building this out as you go, and something doesn't work, I strongly recommend cloning the example project from source and trying to run that. For what appears to be a simple application, there are actually a lot of places where I may have missed some small but critical item in posting the code here on the blog. I've tried to balance providing everything you need to build this out yourself with keeping the article length manageable (and still semi-failed on the length part!).

If you clone from source, and still have an issue, please do describe it in the comments section and/or shoot me an email. Also, if you see somewhere I have made a mistake, or taken the "dumb way" to doing something, I am ALL EARS.

I've tried to combine providing a useful tutorial on the mechanics of sending mail from an application, with my own steps in thinking through the problem. Obviously, some of the content here is aimed at folks new to ASP.NET and/or Web Development in general.

Thanks for reading, and your feedback is always appreciated.

Additional Resources and Items of Interest

 

Posted on January 18 2014 08:10 AM by jatten     

Comments (0)

Send Email to Selected Recipients from your ASP.NET MVC Web Application Part I

Posted on January 15 2014 08:26 PM by jatten in ASP.NET MVC, ASP.Net, C#, CodeProject, Web   ||   Comments (0)

message-in-a-bottle.-500I recently had to throw together an application for work allow users to send email to recipients selected from a list. The application in question is used to manage attendees at trainings, and, when the training is complete, send an email to each attendee containing a link to download a personalized certificate.

In this article we will look at a building out a simplified version of the mail-sending component.

The goal here is two-fold. For one, if you have a specific need to send email from your web application, this will at least get you pointed in the right direction. Also, I am going to attempt to look at the specific problems we need to solve, and some possible approaches to solving them.

Image by Sergio Quesada | Some Rights Reserved

Because this article got a little long, I am breaking it up into two parts. in this first installment, we will examine our requirements, and build out our basic application structure, including necessary Models, Views, and creating our database using EF Migrations.

In the next installment, we will build out the SendMail() controller action, and create the additional pieces needed to send a personalized message to multiple recipients from within our MVC application.

You can follow along with the article, and you can also grab the source from my Github repo.

The Problem

Again, I have simplified things here to keep focused. Our example application has the following requirements:

  • Access to the application is restricted to authenticated, internal users only.
  • The application should be able to maintain a database of Mail Recipients, and allow the basic CRUD operations to be performed.
  • Users should be able to update the list of mail recipients, then select one or more to which they wish to send a personalized email, and, er, send email to those recipients.
  • The mail sent should contain a personalized message for each recipient, based on a template of some sort.
  • The application should track the messages sent, and display the most recent date that each recipient was sent a mail message.

Like I said, pretty simple. In fact, so simple that in and of itself, the application we create here is not good for much except demonstrating the concepts involved. But I will endeavor to point out where functionality might be added to make a real-world implementation more functional.

All right, let’s get started, and look at our requirements one at a time.

Create an ASP.NET Application with Restricted, Internal-Only Access

We need an ASP.NET MVC application which is essentially closed to all but authenticated (logged-in) users authorized by our application administrator. We have looked at this in a previous post, in which we created an MVC 5 application, removed all of the external registration options, and implemented some basic Role-Based Authentication. In that post, we also extended the basic ASP.NET Identity model to include some custom user properties such as email addresses.

In building out the example project for this post, I simply cloned the ASP.NET Role-Based Security Example project and used that as my starting point for this example. Once I had cloned the basic project, I had to go through the relatively painless (but never-the-less annoying) process of renaming the VS project, namespaces, and such.

If you are following along instead of simply cloning the source for this article, you will need to do the same. Also, don’t go and run the EF Migrations to build out the database yet. We will be adding to our models, and updating migrations can be painful.

Note that if you clone the either the source for this project, or the original ASP.NET Role-Based Security Example project, you will need to Enable Nuget Package Restore in order to get things working properly (you should have this set up anyway!).

Pull the Db Context Out into its Own Class

We are already utilizing a Code-First approach here from setting up our custom Identity Management. We will go ahead and continue along those lines. However, we want to do a little housekeeping first.

Our original project (and indeed, the default ASP.NET MVC Project) is set up so that the Entity Framework ApplicationDbContext is tucked into a file named IdentityModels.cs. ApplicationDbContext is the primary point of data access for our application, and I don’t like it tucked into the IdentityModels.cs file. So the first thing I am going to do is move it our of there and into its own class.

Here is what the IdentityModels.cs file looks like before we modify the code:

The Identity Models Code File Before Modification:
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
  
namespace AspNetRoleBasedSecurity.Models
{
    public class ApplicationUser : IdentityUser
    {
        [Required]
        public string FirstName { get; set; }
  
        [Required]
        public string LastName { get; set; }
  
        [Required]
        public string Email { get; set; }
    }
  
  
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
  
        }
    }
  
  
    public class IdentityManager
    {
        public bool RoleExists(string name)
        {
            var rm = new RoleManager<IdentityRole>(
                new RoleStore<IdentityRole>(new ApplicationDbContext()));
            return rm.RoleExists(name);
        }
  
        // ...
        // A bunch of other code related to Identity Management . . .
        // ...
    }
}

 

As we can see, right there in the middle is our ApplicationDbContext class. Let’s pull that out into its own class file, just so we can keep better track of things. Add a new class, name it ApplicationDbContext.cs, then add the following code (we need to bring all the required namespaces along with us):

The new ApplicationDbContext Class File:
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
namespace AspNetEmailExample.Models
{
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection2")
        {
  
        }
    }
}

 

Adding Required Model Classes

Now, let’s get down to business. First, our requirements indicate that we should probably have a MailRecipient model, to persist our target mail recipient data. Also, it looks like we will need a SentMail model, since we are also going to be persisting a record of each mail sent. Each sent mail will refer to a parent MailRecipient, and each MailRecipient will have zero or more SentMail items.

Following is the code for a basic Mail Recipient model class:

Create the Mail Recipient Model

The Mail Recipient Model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
  
namespace AspNetEmailExample.Models
{
    public partial class MailRecipient
    {
        public MailRecipient()
        {
            this.SentMails = new HashSet<SentMail>();
        }
         
        [Key]
        [Required]
        public int MailRecipientId { get; set; }
  
        [Required]
        public string LastName { get; set; }
  
        [Required]
        public string FirstName { get; set; }
  
        public string Email { get; set; }
  
        public string Company { get; set; }
  
        public string FullName
        {
            get
            {
                return string.Format("{0} {1}", this.FirstName, this.LastName);
            }
        }
  
  
        public DateTime? getLastEmailDate()
        {
            var top = (from m in this.SentMails
                       orderby m.SentDate descending
                       select m).Take(1);
            if (top.Count() > 0)
            {
                return top.ElementAt(0).SentDate;
            }
            else
            {
                return null;
            }
        }
    
        public virtual ICollection<SentMail> SentMails { get; set; }
    }
}

 

In the above notice that we have added a method, getLastEmailDate(). This is going to provide for our requirement that we display the last date on which a particular recipient was sent mail. We have also added a FullName property, which return a concatenation of the first and last names of the recipient as a convenience. Also note, we have included the appropriate attribute decorations so that Entity Framework can build out our database based on this model.

Create the Sent Mail Model

Now we need to build out the SentMail model (another fairly simple exercise):

The Sent Mail Model:
namespace AspNetEmailExample.Models
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    public partial class SentMail
    {
        [Key]
        [Required]
        public int MailId { get; set; }
  
        [Required]
        public int MailRecipientId { get; set; }
  
        [Required]
        public string SentToMail { get; set; }
  
        [Required]
        public string SentFromMail { get; set; }
  
        [Required]
        public System.DateTime SentDate { get; set; }
      
        public virtual MailRecipient Recipient { get; set; }
    }
}

 

Once again, we have made sure to add all of the EF-required attribute decorations for a successful code-first scaffolding.

Create the Mail Recipients Controller and Associated Views

In thinking through what we need our simple application to do, our requirements indicate that, at a minimum, we need to be able to:

  • View a list of mail recipients, and select one or more specific recipients to send a personalized message to
  • Add new recipients to the database
  • Edit/Update recipient information
  • Delete recipients from the database
  • Send Email to the recipients selected by the user.

So we need a controller with the following methods:

  • Index (Displays a list of recipients with a checkbox to select for mailing)
  • Create
  • Edit
  • Delete
  • SendMail

We can let Visual Studio do a lot of the heavy lifting for us here, although we will need to modify the generated code afterwards. But let’s go ahead an build out our controller and views using VS and Entity Framework.

First, right click on the Controllers folder, and select Add => Controller. Then, from the dialog, select MVC 5 Controller with Views, using Entity Framework:

Add the Mail Recipients Controller Using Entity Framework:

Add Scaffold

Next, Name the Controller MailRecipientsController, and select the MailRecipient model from the dropdown list of available models. Select the ApplicationDbContext class from the dropdown for the Data Context class, and hit Add:

Set the Controller Configuration:

Add Controller

Once the controller and Views are created, open the newly created MailRecipientsController class. You should see code which looks something like this (I removed all the comments VS automatically includes, because I don’t like the noise in my code):

The Generated MailRecipientsController:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Net;
using System.Web;
using System.Web.Mvc;
using AspNetEmailExample.Models;
  
namespace AspNetEmailExample.Controllers
{
    public class MailRecipientsController : Controller
    {
        private ApplicationDbContext db = new ApplicationDbContext();
  
  
        public async Task<ActionResult> Index()
        {
            return View(await db.MailRecipients.ToListAsync());
        }
  
  
        public async Task<ActionResult> Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            MailRecipient mailrecipient = await db.MailRecipients.FindAsync(id);
            if (mailrecipient == null)
            {
                return HttpNotFound();
            }
            return View(mailrecipient);
        }
  
  
        public ActionResult Create()
        {
            return View();
        }
  
  
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Create([Bind(Include="MailRecipientId,LastName,FirstName,Email,Company")] MailRecipient mailrecipient)
        {
            if (ModelState.IsValid)
            {
                db.MailRecipients.Add(mailrecipient);
                await db.SaveChangesAsync();
                return RedirectToAction("Index");
            }
            return View(mailrecipient);
        }
  
  
        public async Task<ActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            MailRecipient mailrecipient = await db.MailRecipients.FindAsync(id);
            if (mailrecipient == null)
            {
                return HttpNotFound();
            }
            return View(mailrecipient);
        }
  
  
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Edit([Bind(Include="MailRecipientId,LastName,FirstName,Email,Company")] MailRecipient mailrecipient)
        {
            if (ModelState.IsValid)
            {
                db.Entry(mailrecipient).State = EntityState.Modified;
                await db.SaveChangesAsync();
                return RedirectToAction("Index");
            }
            return View(mailrecipient);
        }
  
  
        public async Task<ActionResult> Delete(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            MailRecipient mailrecipient = await db.MailRecipients.FindAsync(id);
            if (mailrecipient == null)
            {
                return HttpNotFound();
            }
            return View(mailrecipient);
        }
  
  
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> DeleteConfirmed(int id)
        {
            MailRecipient mailrecipient = await db.MailRecipients.FindAsync(id);
            db.MailRecipients.Remove(mailrecipient);
            await db.SaveChangesAsync();
            return RedirectToAction("Index");
        }
  
  
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

 

As we can see, VS created some basic code for four of the five controller methods we need. However, they don’t quite do what we need yet. As is most often the case, we will need to adapt them to our needs a little bit.

Modifying the Create Method for the Mail Recipients Controller

We will start by addressing the Create method. The first thing I notice is that the generated code includes a Binding for the MailRecipientId property of our model. The problem here is that MailRecipientId will be an auto-incrementing integer field in our database, and will not be a field on our input form. So we need to remove MailRecipientId from the Binding in the Create method signature.

Also note that, because access to our site is restricted to authenticated, internal users only, we have added the [Authorize] attribute to both overrides for the Create method. In fact, we will be adding this important attribute to all of the methods on MailRecipientController.

The modified Create method should now look like this:

The Modified Create Method for Mail Recipient Controller:
[Authorize]
public ActionResult Create()
{
    return View();
}
  
  
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include="LastName,FirstName,Email,Company")] MailRecipient mailrecipient)
{
    if (ModelState.IsValid)
    {
        db.MailRecipients.Add(mailrecipient);
        await db.SaveChangesAsync();
        return RedirectToAction("Index");
    }
  
    return View(mailrecipient);
}

 

The Edit and Delete methods work decently “out of the box” so far as our application is concerned, although we do still want to add the [Authorize] attribute to each of these as well.

Now let’s take a look at our Index method.

Editor Templates and View Models for the Index Method

We need to make some significant changes to the way our Index controller method works, but first, we need a few more items. In our Index View, we want to display a list of potential Mail Recipients, along with checkboxes for each allowing us to select one or more recipients from the list for a personalized email. For this, we will need to pass a list of Mail recipients to our view.

To achieve this we will need to implement an HTML form including a table with checkboxes, as well as the required View Models and Editor Templates this requires. For more detail on this, see Display an HTML Table with Checkboxes in ASP.NET.

As we learned in the article linked above, we will need to create a custom Editor Template in order to display rows in our table with checkboxes associated with the data in each row. Also, we will need an Editor View Model to match the Editor Template, and one more View Model to wrap the Editor View Model items in. Confused? It's not too bad.

First, let's create what we will call the SelectRecipientEditorViewModel. This will provide the model for our row Editor Template, and will include a Selected property to represent the checked status of the checkbox for each row.

Add a new class to the Models folder, and name it SelectRecipientEditorViewModel. Then add the following code:

The Select Recipient Editor View Model Class:
public class SelectRecipientEditorViewModel
{
    public bool Selected { get; set; }
    public SelectRecipientEditorViewModel() { }
    public int MailRecipientId { get; set; }
    public string FullName { get; set; }
    public string Company { get; set; }
    public string Email { get; set; }
    public string LastMailedDate { get; set; }
}

 

Next, we need a "wrapper" class, MailRecipientsViewModel to contain our list of recipients. Add another new class to the Models folder:

The Mail Recipients View Model Class:
public class MailRecipientsViewModel
{
    public List<SelectRecipientEditorViewModel> MailRecipients { get; set; }
    public MailRecipientsViewModel()
    {
        this.MailRecipients = new List<SelectRecipientEditorViewModel>();
    }
  
  
    public IEnumerable<int> getSelectedRecipientIds()
    {
        return (from r in this.MailRecipients 
                where r.Selected 
                select r.MailRecipientId).ToList();
    }
}

 

In a manner similar to the linked article about displaying checkboxes, we have added a method to our class which will conveniently return an IEnumerable<int> containing the Id's of the selected items in our table.

Now, we just need an Editor Template for our SelectRecipientEditorViewModel. If you don't already see a subfolder in the Views => Shared directory, add a new View named EditorTemplates.The naming here is critical, so no typos. Now, add a new empty View to Views => Shared => EditorTemplates, named SelectRecipientEditorViewModel (yes, it is named exactly the same as the View Model we created, except for the .cshtml suffix on the view file vs. the .cs suffix on the Model class file). Then add the following code:

The Select Recipient Editor Template View:
@model AspNetEmailExample.Models.SelectRecipientEditorViewModel
<tr>
    <td style="text-align:center">
        @Html.CheckBoxFor(model => model.Selected)
    </td>
    <td>
        @Html.DisplayFor(model => model.FullName)
    </td>
    <td>
        @Html.DisplayFor(model => model.Company)
    </td>
    <td>
        @Html.DisplayFor(model => model.Email)
    </td>
    <td>
        @Html.DisplayFor(model => model.LastMailedDate)
    </td>
    <td>
        @Html.HiddenFor(model => model.MailRecipientId)
    </td>
    <td>
        @Html.ActionLink("Edit", "Edit", new { id = Model.MailRecipientId }) |
        @Html.ActionLink("Details", "Details", new { id = Model.MailRecipientId }) |
        @Html.ActionLink("Delete", "Delete", new { id = Model.MailRecipientId })
    </td>
</tr>

 

For more details on how this works, see the linked article above.

Modifying the Code for the Index Controller Method

Now we have what we need to modify our Index() controller method, such that is can pass to the Index view a list of selectable mail recipient data. Update the code for the Index() method on MailRecipientsController to match the following:

Modified Code for the Index Controller Method:
[Authorize]
public async Task<ActionResult> Index()
{
    var model = new MailRecipientsViewModel();
  
    // Get a list of all the recipients:
    var recipients = await db.MailRecipients.ToListAsync();
    foreach(var item in recipients)
    {
        // Put the relevant data into the ViewModel:
        var newRecipient = new SelectRecipientEditorViewModel()
        {
            MailRecipientId = item.MailRecipientId,
            FullName = item.FullName,
            Company = item.Company,
            Email = item.Email,
            LastMailedDate = item.getLastEmailDate().HasValue ? item.getLastEmailDate().Value.ToShortDateString() : "",
            Selected = true
        };
  
        // Add to the list contained by the "wrapper" ViewModel:
        model.MailRecipients.Add(newRecipient);
    }
    // Pass to the view and return:
    return View(model);
}

 

As we can see, the Index() method now retrieves a list of all the recipients from the data store, iterates over the list, and creates an instance of SelectRecipientsViewModel for each (in this case, setting the Selected property to true for each, although this is optional. The default value of Selected may vary with the needs of your application).

Add a Send Mail Method Stub to the Index Controller

For the moment, we won't get into the nitty gritty of actually sending email - We'll look at that in the next post. For now, let's just add a method stub with a delay that simulates a long-running process such as sending a bunch of email, then returns the refreshed Index view again.

Add the following code to the end of the Index Controller:

Code Stub for the Send Mail Method:
[HttpPost]
[Authorize]
public ActionResult SendMail(MailRecipientsViewModel recipients)
{
    // Mail-sending code will happen here . . .
    System.Threading.Thread.Sleep(2000);
    return RedirectToAction("Index");
}

 

The Index View - Selecting Recipients and Sending Email

Here again, we refer to concepts discussed in Display an HTML Table with Checkboxes in ASP.NET. We need to modify the Index.cshtml file created by Visual Studio to accommodate our special need for checkboxes. We will also be adding a Select All option, and a "Send Mail" button to, well, send email once we have selected some recipients form the list.

The generated code produced by VS looks like this:

Code Generated by Visual Studio for the Index View:
@model IEnumerable<AspNetEmailExample.Models.MailRecipient>
 
@{
    ViewBag.Title = "Index";
}
  
<h2>Index</h2>
  
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.LastName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.FirstName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Email)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Company)
        </th>
        <th></th>
    </tr>
  
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.LastName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.FirstName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Email)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Company)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.MailRecipientId }) |
            @Html.ActionLink("Details", "Details", new { id=item.MailRecipientId }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.MailRecipientId })
        </td>
    </tr>
}
  
</table>

 

This view expects a Model of type MailRecipient, and will display an overly cluttered table, with a column for every property. Instead, we would like to simplify the table layout, as well as consume a different Model - our MailRecipientViewModel, and the list of potential recipients it contains.

Replace the code above with the following:

Replacement Code for the Index View:
@model AspNetEmailExample.Models.MailRecipientsViewModel
@{
    ViewBag.Title = "Email";
}
<h2>Send Email to Selected Recipients</h2>
<p>
    @Html.ActionLink("Create New", "Create")
</p>
@using (Html.BeginForm("SendMail", "MailRecipients", FormMethod.Post, new { encType = "multipart/form-data", name = "myform" }))
{
    // Add a "Check All" checkbox above the table:
    <div>
        <input type="checkbox" id="checkall" /><span>Check All</span>
    </div>
    
    // Wrap the table in a named <div> so we can refer to it from JQuery:
    <div id="checkboxes">
        <table class="table">
            <tr>
                <th>
                    @*This column will contain our checkboxes:*@
                    Select
                </th>
                <th>
                    @*This column will now hold a concatenation of the first/last names:*@
                    Name
                </th>
                <th>
                    Company
                </th>
                <th>
                    Email
                </th>
                <th>
                    Last Sent
                </th>
                <th></th>
            </tr>
            @* Our Table rows will be populated in the EditorTemplate: *@
            @Html.EditorFor(model => model.MailRecipients)
        </table>
    </div>
    <hr />
    <br />
    
    //Add a submit button to the bottom of the form:
    <input type="submit" name="operation" id="email" value="Email Selected" />
}
<div id="divProcessing">
    <p>Processing, please wait . . . <img src="../../Content/ajax-loader.gif"></p>
</div>
    @section Scripts {
        @Scripts.Render("~/bundles/jqueryval")
        <script type="text/javascript">
            function toggleChecked(status) {
                $("#checkboxes input").each(function () {
                    $(this).prop("checked", status);
                });
            }
            $(document).ready(function () {
                // Grab a reference to the checkall checkbox:
                var checkAllBox = $("#checkall");
                var divProcessing = $("#divProcessing");
                // Hide the animated Gif when page loads:
                divProcessing.hide();
                checkAllBox.prop('checked', true);
                // Attach a handler for the checkAllBox click event:
                checkAllBox.click(function () {
                    var status = checkAllBox.prop('checked');
                    toggleChecked(status);
                });
                $('#email').click(function () {
                    // Required hack to get animated gif to run in IE:
                    setTimeout(function () {
                        divProcessing.show();
                    }, 100);
                    $('myform').submit();
                });
            });
        </script>
    }

 

In the code above, we have made substantial changes to the View. First, the View is now based on our MailRecipientsViewModel, instead of a List<MailRecipient> as before. Also, we have added a new table column layout, with a checkbox as the first column, and a single column for displaying the name.

Instead of iterating over each item in a list to populate our table, we simply pass the MailRecipients property of our MailRecipientsViewModel to our Editor Template, which then takes care of rendering our list.

Also notice, we have added some JavaScript and additional HTML elements to provide a "Check All" checkbox at the top of the table. In addition, we have added some extra elements and some JavaScript to display an animated GIF Busy indicator after the user clicks the submit button to send mail (see the linked article for more about how this works - you will need to add an GIF to the Content folder if you did not clone this project from source).

Finally, note where we declare our HTML form. Specifically, in the BeginForm() method we set our submit arguments to create an HTTP POST request against the SendMail() action of the MailRecipients controller.

Add a Mail Recipients Tab to the Layout

Now, so that we can get to our Mail Recipient functionality, let's add a tab to the main layout. Open the Views => Shared => _Layout.cshtml file. In the middle of the file, add a tab that links to the Index method of our MailRecipientsController:

Add a Tab for Mail Recipients:
<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
        <li>@Html.ActionLink("Home", "Index", "Home")</li>
        <li>@Html.ActionLink("About", "About", "Home")</li>
        <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
        <li>@Html.ActionLink("Admin", "Index", "Account")</li>
    </ul>
    @Html.Partial("_LoginPartial")
</div>

 

For now, we are just leaving the default Home/About/Contact views as they are. in reality, we would remove these and replace them with our own and probably use [Authorize] to restrict access to most of our application in keeping with our requirement for internal, authorized-only access.

** If you cloned the project from Github, these are already removed**

At this point, we have everything we need to run our project, make sure everything is working properly, and make any adjustments if needed. Well, almost. First, we need to run Entity Framework Migrations to create our database.

Run EF Migrations to Create the Database

If you have cloned this project from Github, you should be all set to go. The migration is already defined, although you may want to open the Configuration file in the Migrations folder and swap out my sample user name for your own. Simply open the Package Manager Console and do:

Update Database -F

 

I have found I sometimes need to do this twice to get the Seed() method to run.

If you need more information on this part of the process, see Configuring Db Connection and Code-First Migration for Identity Accounts in ASP.NET MVC 5 and Visual Studio 2013.

Running the Application

Ok. If everything has gone well, you should be able to run the application. You will need to log in using the user name and password you created in the Seed() method, otherwise you won't be able to access the Mail Recipients tab (remember, this is an internal access application, with no public registration).

The Application Main Page at Startup:

application-start-page

Once logged in, navigate to the Mail Recipients tab. Not much to see here yet, as we have no recipients yet:

The Mail Recipients Page (Empty):

application-mail-page

Click the Create New link to add a recipient. When done, you should see something like this:

The Mail Recipients Page (with data):

application-after-add-recipient

Now, if we click the Email Selected button, we should see our "busy" spinner while our method stub runs down the clock on the Thread.Sleep() call we are using to mock sending our emails:

Pretending to Send Mail:

application-sending

Because this is getting a little long, I'm going to address the actual mail sending in the next post.

For now, we have built out a fairly basic front-end, providing a surface for the user interaction. At the same time, we have addressed our requirement the mailing list portion of the application be restricted to internal, authorized access.

Up Next: Adding the Mail Sender and Filling in the SendMail() Method

Additional Resources and Items of Interest

 

Posted on January 15 2014 08:26 PM by jatten     

Comments (0)

Extending Identity Accounts and Implementing Role-Based Authentication in ASP.NET MVC 5

Posted on November 11 2013 07:45 PM by jatten in ASP.NET MVC, ASP.Net, C#, CodeProject, Web   ||   Comments (25)

5555555-500In a previous post, we took a quick look at extending Identity Accounts in the context of the new Identity system under ASP.NET MVC 5. We also examined the basics of Entity Framework Code-First Migrations. If you haven't read that article, you might want to do so now, so we don't have to repeat some of the general ideas explained there.

 

Image by: Elif Ayiter | Some Rights Reserved

If you are using the Identity 2.0 Framework:

This article focuses on customizing and modifying version 1.0 of the ASP.NET Identity framework. If you are using the recently released version 2.0, this code in this article won't work. For more information on working with Identity 2.0, see ASP.NET Identity 2.0: Understanding the Basics and ASP.NET Identity 2.0: Setting Up Account Validation and Two-Factor Authentication.

Many of the customizations implemented in this article are included "ini the box" with the Identity Samples project. I discuss extending and customizing IdentityUser and IdentityRole in Identity 2.0 in a new article, ASP.NET Identity 2.0: Customizing Users and Roles

If you are using the Identity 1.0 Framework:

For the purpose of this post, we are going to look at a implementing relatively simple role-based authentication and identity management for an ASP.NET MVC 5 web application. The examples used will be deliberately simplified, and while they will effectively illustrate the basics of setting up role-based identity management, I can promise that the implementation here will lack certain things we would like to see in a production project (such as complete exception handling). Also, production application modeling would likely look a little different based upon business needs.

In other words, I'm keeping it simple, much like we ignored the effects of friction way back in high school physics. For a more advanced look at working with roles, and more granular application permissions management using Group - based permissions, see Extending and Modifying Roles and   ASP.NET MVC 5 Identity: Implementing Group-Based Permissions Management.

That said, there's a lot to cover. The article is not as long as it seems, because I am including some large code samples, and images to illustrate what's going on. The complete project source code for the project discussed in this article can be found at my Github Repo.

Download the Source Code

The complete project source code for this article is available at my Gihub Repo. NOTE: You will need to enable Nuget Package Restore in order to build the project properly. 

Basic Hypothetical Application Requirements

We will assume our identity management needs to do the following for a Line-of-Business web application used primarily by internal users or others suitably authorized by management. The application will have a minimal public-facing interface, and will require all users to log-in to access even minimal functionality (it is easy to extend what we will be doing to include public users, but this is derived from an actual application I needed to create rather quickly at work).

  • User accounts must be created by one or more Admin-level users. "Registration" as we know it from the ASP.NET Membership paradigm, does not exists (anonymous users cannot register and create accounts from the public site).
  • User Identity accounts will be extended to include an email address, and first/last names
  • For our purposes, there will be at least three Roles; Administrator (full access to everything), Editor (can perform most business functions of the application, but cannot access admin functions such as account management), and Read-Only User (what the name implies).
  • Each user may be a member of zero or more roles. Multiple roles will have the access rights of all the roles combines.
  • To keep things simple, roles are independently defined, and are not members of other roles.
  • All application roles are pre-defined. There is no administrative creation of roles. Also, Role permissions are integral to the application, and not manageable by administrators (this is for simplicity at this point).
  • Anonymous access to the site is not allowed, except to the log-in portal.
  • There will be no use of external log-ins or OAuth (code for this is included as part of the default MVC project. We will remove it to keep things clean).

Getting Started – Stripping Unneeded Items from the MVC Project Template

While we could start with what we created in the previous article on Extending Identity Accounts, we will be re-arranging things sufficiently it will be cleaner for our purposes to start fresh. Create a new ASP.NET MVC project in Visual Studio. Before we do anything else, let's clear out some unneeded clutter so we are left with only what we need.

We are going to be removing a bunch of code related to managing external log-ins, as well as clearing out some of the extraneous comments included with the default project (which to me, just add noise to our code).

We will start with the AccountController.

Simplifying AccountController – Remove the Clutter

There are a number of m on AccountController related to External Logins which we don't need. If you examine AccountController carefully, you can go through and delete the code for the following methods:

  • Dissociate()
  • ExternalLogin()
  • ExternalLoginCallback()
  • LinkLogin()
  • LinkLoginCallback()
  • ExternalLoginConfirmation()
  • ExternalLoginFailure()
  • RemoveAccountList()

Additionally, if you look closely, there is a code #region (I know. I hate #region and think it should be done away with) for helpers. From here, we can delete the following items:

  • The member constant XsrfKey
  • The entire class ChallengeResult

At this point, we can also right-click in the code file and select Remove and Sort Usings to clear out some of the unneeded clutter here as well.

At this point, our AccountController.cs file should contain the following methods (reduced to simple stubs here for brevity – we'll get to the code shortly:

The Cleaned Up AccountController.cs File (Stubs Only - Code Hidden for Brevity):
using AspNetRoleBasedSecurity.Models;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin.Security;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
  
namespace AspNetRoleBasedSecurity.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        public AccountController()
            : this(new UserManager<ApplicationUser>(
            new UserStore<ApplicationUser>(new ApplicationDbContext())))
        {
        }
  
  
        public AccountController(UserManager<ApplicationUser> userManager)
        {
            UserManager = userManager;
        }
  
  
        public UserManager<ApplicationUser> UserManager { get; private set; }
  
  
        [AllowAnonymous]
        public ActionResult Login(string returnUrl)
        {
            ViewBag.ReturnUrl = returnUrl;
            return View();
        }
  
  
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
        {
             // . . . Code Hidden for brevity
        }
  
  
        [AllowAnonymous]
        public ActionResult Register()
        {
            return View();
        }
  
  
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Register(RegisterViewModel model)
        {
             // . . . Code Hidden for brevity
        }
  
  
        public ActionResult Manage(ManageMessageId? message)
        {
             // . . . Code Hidden for brevity
        }
  
  
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Manage(ManageUserViewModel model)
        {
             // . . . Code Hidden for brevity
        }
  
  
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            AuthenticationManager.SignOut();
            return RedirectToAction("Index", "Home");
        }
   
  
        protected override void Dispose(bool disposing)
        {
             // . . . Code Hidden for brevity
        }
  
  
  
  
        #region Helpers
  
  
        private IAuthenticationManager AuthenticationManager
        {
             // . . . Code Hidden for brevity
        }
  
  
        private async Task SignInAsync(ApplicationUser user, bool isPersistent)
        {
             // . . . Code Hidden for brevity
        }
  
  
        private void AddErrors(IdentityResult result)
        {
             // . . . Code Hidden for brevity
        }
  
  
        private bool HasPassword()
        {
             // . . . Code Hidden for brevity
        }
  
  
        public enum ManageMessageId
        {
             // . . . Code Hidden for brevity
        }
  
  
        private ActionResult RedirectToLocal(string returnUrl)
        {
             // . . . Code Hidden for brevity
        }
  
  
        #endregion
    }
}

 

Remove Unneeded Views

Along with the unnecessary Controller methods we just removed, we can also remove the unnecessary views related to external logins. If we open the Views => Account folder in Solution Explorer, we find we can delete the highlighted views below from our project:

Solution Explorer – Remove Unneeded Views:

solution-explorer-remove-unneeded-views

Now that the totally unnecessary views are out of the way, let's remove the External Log-in code from the remaining views as well.

Clean Up Account-Related Views

There is some remaining clutter to clear out of our Account-related views. We don't want dead-end links on our site, and we want to keep only relevant code in our views.

We can start with the Login.cshtml file, which contains a section related to creating an external log-in from various social networks (highlighted in yellow).

Login.cshtml - Remove Social Network Login Option:
@{
    ViewBag.Title = "Log in";
}
<h2>@ViewBag.Title.</h2>
<div class="row">
    <div class="col-md-8">
        <section id="loginForm">
            @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
            {
                @Html.AntiForgeryToken()
                <h4>Use a local account to log in.</h4>
                <hr />
                @Html.ValidationSummary(true)
                <div class="form-group">
                    @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.UserName)
                    </div>
                </div>
                <div class="form-group">
                    @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.Password)
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <div class="checkbox">
                            @Html.CheckBoxFor(m => m.RememberMe)
                            @Html.LabelFor(m => m.RememberMe)
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <input type="submit" value="Log in" class="btn btn-default" />
                    </div>
                </div>
                <p>
                    @Html.ActionLink("Register", "Register") if you don't have a local account.
                </p>
            }
        </section>
    </div>
    <div class="col-md-4">
        <section id="socialLoginForm">
            @Html.Partial("_ExternalLoginsListPartial", new { Action = "ExternalLogin", ReturnUrl = ViewBag.ReturnUrl })
        </section>
    </div>
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

In the above, the section highlighted in yellow can be deleted.

Next, let's remove similar External Login functionality from the Manage.cshtml file (highlighted in yellow, again):

Manage.cshtml – Remove External Login Items:
@using AspNetRoleBasedSecurity.Models;
@using Microsoft.AspNet.Identity;
@{
    ViewBag.Title = "Manage Account";
}
  
<h2>@ViewBag.Title.</h2>
  
<p class="text-success">@ViewBag.StatusMessage</p>
<div class="row">
    <div class="col-md-12">
        @if (ViewBag.HasLocalPassword)
        {
            @Html.Partial("_ChangePasswordPartial")
        }
        else
        {
            @Html.Partial("_SetPasswordPartial")
        }
  
        <section id="externalLogins">
            @Html.Action("RemoveAccountList")
            @Html.Partial("_ExternalLoginsListPartial", new { Action = "LinkLogin", ReturnUrl = ViewBag.ReturnUrl })
        </section>
    </div>
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

As before, the highlighted area can be safely removed.

Remove Unneeded Model Classes

As with the previous sections, there are unneeded model classes we can safely dispose of in order to clean up our project. If we expand the Models folder in Solution Explorer, we find there is a single code file, AccountViewModels.cs,  containing several ViewModel classes related to Identity Management. Review the file carefully, and delete the ExternalLogInConfirmationViewModel item highlighted in yellow below:

Account View Models File – Remove Unneeded Classes:

 

using System.ComponentModel.DataAnnotations;
namespace AspNetRoleBasedSecurity.Models
{
    public class ExternalLoginConfirmationViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }
    }
  
    public class ManageUserViewModel
    {
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Current password")]
        public string OldPassword { get; set; }
  
        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "New password")]
        public string NewPassword { get; set; }
  
        [DataType(DataType.Password)]
        [Display(Name = "Confirm new password")]
        [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
  
    public class LoginViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }
        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }
  
    public class RegisterViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { 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; }
    }
}

 

Extending the Identity Management Models and View Models

As we have seen, the Model classes used by our application to manage identity and authorization are contained in the IdentityModels.cs file. Additionally, there are Identity-related ViewModel classes defined in the AccountViewModels.cs file used to manage the transfer of identity data between our views and controllers.

Important to note here that we would really like to get all of our model definitions correct before we run the application and create any new user accounts, or register using the normal (and soon-to-be-removed) MVC "registration" mechanism. We are going to use Entity Framework Migrations and Code-First to do the Database heavy-lifting for us. While it is not terribly difficult to update our models later (and hence, the database, through EF migrations), it is cleaner and smoother to get it right up front. 

In order to conform to our project specifications, one of the first things we need to do is extend the default ApplicationUser class to include Email, LastName, and FirstName properties. Open the IdentityModels.cs file. Currently, the code should look like this:

The Default IdentityModels File with Emply ApplicationUser Stub:
using Microsoft.AspNet.Identity.EntityFramework;
  
namespace AspNetRoleBasedSecurity.Models
{
    public class ApplicationUser : IdentityUser
    {
    }
  
  
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }
    }
}

 

In this step, we are going to extend our ApplicationUser class to include the properties required by our application specification. Also, we will add an IdentityManager class in which we consolidate our user and role management functions. We'll discuss how all this works in a moment. For now, add the following code to the IdentityModels.cs code file (note we have added some new using statements at the top as well):

Modified IdentityModels.cs File
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
  
namespace AspNetRoleBasedSecurity.Models
{
    public class ApplicationUser : IdentityUser
    {
        [Required]
        public string FirstName { get; set; }
   
        [Required]
        public string LastName { get; set; }
   
        [Required]
        public string Email { get; set; }
    }
    
  
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
            
        }
    }
  
  
    public class IdentityManager
    {
        public bool RoleExists(string name)
        {
            var rm = new RoleManager<IdentityRole>(
                new RoleStore<IdentityRole>(new ApplicationDbContext()));
            return rm.RoleExists(name);
        }
  
  
        public bool CreateRole(string name)
        {
            var rm = new RoleManager<IdentityRole>(
                new RoleStore<IdentityRole>(new ApplicationDbContext()));
            var idResult = rm.Create(new IdentityRole(name));
            return idResult.Succeeded;
        }
  
  
        public bool CreateUser(ApplicationUser user, string password)
        {
            var um = new UserManager<ApplicationUser>(
                new UserStore<ApplicationUser>(new ApplicationDbContext()));
            var idResult = um.Create(user, password);
            return idResult.Succeeded;
        }
  
  
        public bool AddUserToRole(string userId, string roleName)
        {
            var um = new UserManager<ApplicationUser>(
                new UserStore<ApplicationUser>(new ApplicationDbContext()));
            var idResult = um.AddToRole(userId, roleName);
            return idResult.Succeeded;
        }
  
  
        public void ClearUserRoles(string userId)
        {
            var um = new UserManager<ApplicationUser>(
                new UserStore<ApplicationUser>(new ApplicationDbContext()));
            var user = um.FindById(userId);
            var currentRoles = new List<IdentityUserRole>();
            currentRoles.AddRange(user.Roles);
            foreach(var role in currentRoles)
            {
                um.RemoveFromRole(userId, role.Role.Name);
            }
        }
    }
}

 

Yeah, I know. There is room for some refactoring here.We'll ignore that for now. In the above, we have extended ApplicationUser to include our new required properties, and added the IdentityManager class, which includes the methods required to create new users, and add/remove users from available roles.

We have also decorated our new ApplicationUser properties with the [Required] data annotation, which will be reflected both in our database (nulls will not be allowed) and our Model Validation for our views.

SIDE NOTE, REDUX: I am utilizing the methods available directly within the Microsoft.AspNet.Identity and Microsoft.AspNet.Identity.EntityFramework namespaces. I am content to let the ASP.NET team invent and provide the best practices for managing application security. Therefore, in the context of this application, I am not inventing my own. I strongly recommend you do as well. It is easy to spot ways to manage some of the Account/Identity stuff (including data persistence) which appear more direct or easier. I concluded that the team thought all this through more effectively than I can. Therefore, while we are creating an authorization management structure here, we are doing so using the core implementation provided by people who know better than we do.

Extending Account Management ViewModels

Now that we have expanded upon our basic Identity Models, we need to do the same with our Account ViewModels. ViewModels essentially represent a data exchange mechanism between our views and Controllers. Our goal here is to provide our Account management views with precisely the information required to perform the task at hand, and no more.

Also of note is that for the purpose of our presentation layer, I am not pushing User or Role id's out onto the page or the backing html. Instead I am relying on the unique nature of the Username and Role names to look up the proper Id value server-side.

Currently, before we do anything, our AccountVeiwModels.cs file looks like this:

The AccountViewModels.cs File Before Modification:
using System.ComponentModel.DataAnnotations;
namespace AspNetRoleBasedSecurity.Models
{
    public class ManageUserViewModel
    {
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Current password")]
        public string OldPassword { get; set; }
  
        [Required]
        [StringLength(100, ErrorMessage = 
            "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "New password")]
        public string NewPassword { get; set; }
  
        [DataType(DataType.Password)]
        [Display(Name = "Confirm new password")]
        [Compare("NewPassword", ErrorMessage = 
            "The new password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
  
    public class LoginViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }
  
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }
  
        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }
  
    public class RegisterViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { 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; }
    }
}

 

We are going to expand on this significantly. In fact, it may seem, redundantly. For, with a few minor differences, it might appear that one or two of the ViewModels in the following code are near-duplicates, and possible candidates for a refactoring into a single class. However, I decided that the purpose of the ViewModel is to represent the specific data required by a specific view. While in some cases these appear to be the same, that may change. I concluded that it is better, so far as Views and ViewModels go, to have some potential duplication, but preserve the ability of each view to evolve independently should the need arise, without having to fuss with the impact on other views dependent on the same ViewModel.

Modify the code above as follows (or simply replace it with the following). We'll take a closer look at the functionality in a moment, when we get to the Controller implementation:

Modified Code for AccountViewModels.cs File:
using System.ComponentModel.DataAnnotations;
  
// New namespace imports:
using Microsoft.AspNet.Identity.EntityFramework;
using System.Collections.Generic;
  
namespace AspNetRoleBasedSecurity.Models
{
    public class ManageUserViewModel
    {
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Current password")]
        public string OldPassword { get; set; }
  
        [Required]
        [StringLength(100, ErrorMessage = 
            "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "New password")]
        public string NewPassword { get; set; }
  
        [DataType(DataType.Password)]
        [Display(Name = "Confirm new password")]
        [Compare("NewPassword", ErrorMessage = 
            "The new password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
  
  
    public class LoginViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }
  
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }
  
        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }
  
  
    public class RegisterViewModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { 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; }
  
        // New Fields added to extend Application User class:
  
        [Required]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }
  
        [Required]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
  
        [Required]
        public string Email { get; set; }
  
        // Return a pre-poulated instance of AppliationUser:
        public ApplicationUser GetUser()
        {
            var user = new ApplicationUser()
            {
                UserName = this.UserName,
                FirstName = this.FirstName,
                LastName = this.LastName,
                Email = this.Email,
            };
            return user;
        }
    }
  
  
    public class EditUserViewModel
    {
        public EditUserViewModel() { }
  
        // Allow Initialization with an instance of ApplicationUser:
        public EditUserViewModel(ApplicationUser user)
        {
            this.UserName = user.UserName;
            this.FirstName = user.FirstName;
            this.LastName = user.LastName;
            this.Email = user.Email;
        }
  
        [Required]
        [Display(Name = "User Name")]
        public string UserName { get; set; }
  
        [Required]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }
  
        [Required]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
  
        [Required]
        public string Email { get; set; }
    }
  
  
    public class SelectUserRolesViewModel
    {
        public SelectUserRolesViewModel() 
        {
            this.Roles = new List<SelectRoleEditorViewModel>();
        }
  
  
        // Enable initialization with an instance of ApplicationUser:
        public SelectUserRolesViewModel(ApplicationUser user) : this()
        {
            this.UserName = user.UserName;
            this.FirstName = user.FirstName;
            this.LastName = user.LastName;
  
            var Db = new ApplicationDbContext();
  
            // Add all available roles to the list of EditorViewModels:
            var allRoles = Db.Roles;
            foreach(var role in allRoles)
            {
                // An EditorViewModel will be used by Editor Template:
                var rvm = new SelectRoleEditorViewModel(role);
                this.Roles.Add(rvm);
            }
  
            // Set the Selected property to true for those roles for 
            // which the current user is a member:
            foreach(var userRole in user.Roles)
            {
                var checkUserRole = 
                    this.Roles.Find(r => r.RoleName == userRole.Role.Name);
                checkUserRole.Selected = true;
            }
        }
  
        public string UserName { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public List<SelectRoleEditorViewModel> Roles { get; set; }
    }
  
    // Used to display a single role with a checkbox, within a list structure:
    public class SelectRoleEditorViewModel
    {
        public SelectRoleEditorViewModel() {}
        public SelectRoleEditorViewModel(IdentityRole role)
        {
            this.RoleName = role.Name;
        }
  
        public bool Selected { get; set; }
  
        [Required]
        public string RoleName { get; set;}
    }
}

 

Extending the Account Controller

Now that we have our Models and ViewModels mostly in place, let's look at how it all comes together in the Controller. Our current AccountController defines Controller Actions for the following:

  • Register (Essentially creates a new user)
  • Manage (Essentially allows the user to change their password)
  • Login
  • Log Off

Also, the above methods are focused upon allowing anonymous users to self-register, and create their own user account.

We are not planning to allow self-registration, and our requirements establish that user accounts are to be created by an administrator. Also, we have extended our ApplicationUser model to include some additional properties. From a functional perspective, we would like to se the following behavior implemented:

  • View a list of user accounts (Index), with links to various relevant functionality (Edit, Roles, Etc.)
  • Create a new User (We will co-opt the "Register" method for this, but we will extend it significantly).
  • Edit a User (Administrators need to be able to edit user accounts, assign roles, and such)
  • Delete a User (We want to be able to remove User accounts (or at least render them active or inactive)
  • Assign Roles to a User
  • Login
  • Log Off

Before we proceed, understand that there are countless possible major and minor variations we could consider for the above. I chose an application model which was simple, and for our purposes, rather arbitrary. For example, it could be your application requirements allow for self-registration of anonymous users into a default Role of some sort. The model I represent here is by purpose rather limited in scope, as we are trying to see concepts without getting too distracted by a complex implementation. I leave to you to expand from here into more complex (and more useful to you) application models.

In keeping with the above, I have added the [Authorize(Roles = "Admin")] attribute to all of the administrative methods, with the assumption that our administrative role will be called (wait for it . . .) "Admin." More on this later too.

Where was I?

Oh, yeah. So, looking at the functional needs in the list above, I am going to modify my AccountController to incorporate the above items. As mentioned parenthetically, I am simply going to co-opt the Register controller method and use it for what should probably be named Create (out of laziness at this point!).

Modifying The AccountController Register Method (Create User)

First, we will look at modifying our existing Register method(s) to accommodate our new ApplicationUser properties. We want to be able to create a new ApplicationUser in our View, and then persist the new record in our database.

Modified Register Method:
[Authorize(Roles = "Admin")]
public ActionResult Register()
{
    return View();
}
  
  
[HttpPost]
[Authorize(Roles = "Admin")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = model.GetUser();
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            return RedirectToAction("Index", "Account");
        }
    }
  
    // If we got this far, something failed, redisplay form
    return View(model);
}

 

In the above, we have not changed much except using the handy GetUser() method defined on our RegisterViewModel to retrieve an instance of ApplicationUser, populated and ready to persist in our database. Also, we are redirecting to a new Index method we will define momentarily on our AccountController.

Adding The AccountController Index Method (View List of Users)

Previously, the AccountController did not have an Index method. We need a way for our Administrators to view a list of users of our application, and access the functionality to edit, assign roles, and delete. Once again, accessing the user account data is an admin function, so we have added the [Authorize] attribute as well.

The New Index Method:
[Authorize(Roles = "Admin")]
public ActionResult Index()
{
    var Db = new ApplicationDbContext();
    var users = Db.Users;
    var model = new List<EditUserViewModel>();
    foreach(var user in users)
    {
        var u = new EditUserViewModel(user);
        model.Add(u);
    }
    return View(model);
}

 

Our Index method uses a List<EditUserViewModel> for now, as it contains all the information needed for display in our list. Contrary to what I said above, I have re-used a ViewModel here. I should probably fix that, but you can make your own decision on this point.

Notice that instead of performing the tedious mapping of properties from ApplicationUser instance to each EditUserViewModel within this method, I simply pass the ApplicationUser instance into the overridden constructor on EditUserViewModel. Our Index.cshtml View will expect a List<EditUserViewModel> as the model for display.

Adding The AccountController Edit Method

We have added an Edit method to facilitate administrative updating of User account data. There are some details to pay attention to in the Edit method implementation, at least in my version. First, while the method still accepts a parameter named id, what we will actually be passing to the method when a request is routed here will be a UserName. Why? I decided to follow the lead of the ASP.NET team on this. They are not passing User Id's (which are GUID's) out into the public HTML, so neither will I.

Also, by design and constraint, the UserName is unique in our database, and will already be a semi-public piece of information. Just something to keep in mind – the id parameter for public Account Action methods will be the User (or, as the case may be, Role) name, and not an integer. Lastly, I didn't want to add a whole new route just to rename a single route parameter which is serving essentially the same purpose as an int id.

That said, the following is the new Edit method, which will be used when an Administrator wishes to update user information:

The New Edit Method:
[Authorize(Roles = "Admin")]
public ActionResult Edit(string id, ManageMessageId? Message = null)
{
    var Db = new ApplicationDbContext();
    var user = Db.Users.First(u => u.UserName == id);
    var model = new EditUserViewModel(user);
    ViewBag.MessageId = Message;
    return View(model);
}
[HttpPost]
[Authorize(Roles = "Admin")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(EditUserViewModel model)
{
    if (ModelState.IsValid)
    {
        var Db = new ApplicationDbContext();
        var user = Db.Users.First(u => u.UserName == model.UserName);
        // Update the user data:
        user.FirstName = model.FirstName;
        user.LastName = model.LastName;
        user.Email = model.Email;
        Db.Entry(user).State = System.Data.Entity.EntityState.Modified;
        await Db.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    // If we got this far, something failed, redisplay form
    return View(model);
}

 

In the above, we use Linq to grab a reference to the specific user based on the UserName passed in as the id parameter in the first (GET) Edit method. We then populate an instance of EditUserViewModel by passing the ApplicationUser Instance to the constructor, and pass the ViewModel on to the Edit.cshtml View.

When the View returns our updated model to the second (POST) Edit method, we do much the same in reverse. We retrieve the User record from the database, then update with the model data, and save changes.

In our View, it will be important to remember that we cannot allow editing of the UserName property itself (at least, not under our current model, which considers the UserName to be an inviolate identifier).

Adding the AccountController Delete Method

We are adding a Delete (GET) method and a DeleteConfirmed (POST) method to the AccountController class. In my implementation, this method will actually delete the selected user from the database. You may decide instead to flag the database record as deleted, or implement some other method of managing unwanted user records.

You might also forego a delete method, and instead add a Boolean Inactive property to the ApplicationUser class, and manage Active/Inactive status through the Edit method discussed previously. Again, there are many permutations to the design model here. I went with the simplest for the sake of clarity.

The Delete method implementation here is straightforward so long as we remember, once again, that the id parameter passed to each of the two related methods below is actually a UserName.

The New Delete Methods:
[Authorize(Roles = "Admin")]
public ActionResult Delete(string id = null)
{
    var Db = new ApplicationDbContext();
    var user = Db.Users.First(u => u.UserName == id);
    var model = new EditUserViewModel(user);
    if (user == null)
    {
        return HttpNotFound();
    }
    return View(model);
}
  
  
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Admin")]
public ActionResult DeleteConfirmed(string id)
{
    var Db = new ApplicationDbContext();
    var user = Db.Users.First(u => u.UserName == id);
    Db.Users.Remove(user);
    Db.SaveChanges();
    return RedirectToAction("Index");
}

 

As we can see, the DeleteConfirmed method is decorated with a HttpPost attribute, and an ActionName attribute "Delete" which means POST requests routed to Delete will be routed here. Both methods use the UserName passed as the id parameter to look up the appropriate ApplicationUser in the database.

Once again, in contrast to what I said earlier, I re-used the EditUserViewModel to pass to the Delete.cshtml View.

Adding the UserRoles Method to the AccountController

Lastly, we add the UserRoles method pair. This is where we manage assignment of user accounts to various application roles.

The implementation here looks relatively simple, and pretty similar to the other controller methods we have examined so far. However, under the hood in the SelectUserRolesViewModel and in the IdentityManager class, there is a lot going on. First, the code:

The New UserRoles Method(s):
[Authorize(Roles = "Admin")]
public ActionResult UserRoles(string id)
{
    var Db = new ApplicationDbContext();
    var user = Db.Users.First(u => u.UserName == id);
    var model = new SelectUserRolesViewModel(user);
    return View(model);
}
  
  
[HttpPost]
[Authorize(Roles = "Admin")]
[ValidateAntiForgeryToken]
public ActionResult UserRoles(SelectUserRolesViewModel model)
{
    if(ModelState.IsValid)
    {
        var idManager = new IdentityManager();
        var Db = new ApplicationDbContext();
        var user = Db.Users.First(u => u.UserName == model.UserName);
        idManager.ClearUserRoles(user.Id);
        foreach (var role in model.Roles)
        {
            if (role.Selected)
            {
                idManager.AddUserToRole(user.Id, role.RoleName);
            }
        }
        return RedirectToAction("index");
    }
    return View();
}

 

As we can see above, and incoming GET request routed to the UserRoles method is handled similarly to those in previous methods. The UserName passed as the id parameter is used to retrieve the User record from the database, and then an instance of SelectUserRolesViewModel is initialized, passing the ApplicationUser instance to the constructor.

Here is where things get interesting. Let's take another look at the code for our SelectUserRolesViewModel from the AccountViewModels.cs file:

Code for SelectUserRolesViewModel – Revisited:
public class SelectUserRolesViewModel
{
    public SelectUserRolesViewModel() 
    {
        this.Roles = new List<SelectRoleEditorViewModel>();
    }
  
  
    // Enable initialization with an instance of ApplicationUser:
    public SelectUserRolesViewModel(ApplicationUser user) : this()
    {
        this.UserName = user.UserName;
        this.FirstName = user.FirstName;
        this.LastName = user.LastName;
  
        var Db = new ApplicationDbContext();
  
        // Add all available roles to the list of EditorViewModels:
        var allRoles = Db.Roles;
        foreach(var role in allRoles)
        {
            // An EditorViewModel will be used by Editor Template:
            var rvm = new SelectRoleEditorViewModel(role);
            this.Roles.Add(rvm);
        }
  
        // Set the Selected property to true for those roles for 
        // which the current user is a member:
        foreach(var userRole in user.Roles)
        {
            var checkUserRole = 
                this.Roles.Find(r => r.RoleName == userRole.Role.Name);
            checkUserRole.Selected = true;
        }
    }
  
    public string UserName { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public List<SelectRoleEditorViewModel> Roles { get; set; }
}

 

During initialization, we are populating a List<SelectRoleEditorViewModel>() with all the roles available in the application. First, this is a prime candidate for refactoring, as I am performing  data access within the object constructor (a general no-no). Second, SelectRoleEditorViewModel? What?

In my current implementation, we will see that the SelectUserRolesViewModel is passed to the UserRoles.cshtml view. We want to display the basic user details (so we know which user we are assigning roles to – always important to know), as well as a list of all available Roles. I have decided to facilitate role assignment using checkboxes, whereby roles are assigned to the user by checking one or more (or none!) checkboxes.

This is where the EditorViewModel comes in. We are going to use a common technique for adding checkboxes to a table and allowing the user to select from the list of items.

Let's revisit the code for SelectRoleEditorViewModel, which we defined in our AccountViewModels.cs file.

The SelectRoleEditorViewModel represents an individual role, and as we can see from the following code, includes a Boolean field used to indicate the Selected status for that role:

Code for SelectRoleEditorViewModel, Revisited:
public class SelectRoleEditorViewModel
{
    public SelectRoleEditorViewModel() { }
    public SelectRoleEditorViewModel(IdentityRole role)
    {
        this.RoleName = role.Name;
    }
  
    public bool Selected { get; set; }
  
    [Required]
    public string RoleName { get; set; }
}

 

This EditorViewModel will be used by a special View called an EditorTemplate, which we will look at shortly. For now, bear in mind that the SelectUserRolesViewModel contains a List of SelectRoleEditorViewModel objects (yes, the naming of these could be better and posed some challenges – I am open to suggestion here! For the moment, I try to think of them as "SelectUserRoles-ViewModel" and "SelectRole-EditorViewModel" if that helps).

That covers the modified or new items in our AccountController. Now let's look at our Views.

Basic Views for Role-Based Identity Management

We already have a few of the views we will need, we just need to modify them a bit. In addition, we need to add a few new ones. We'll start by modifying our existing views to suit our needs, beginning with the Register.cshmtl View.

Modifying the Register.cshtml View File

The Register view as it currently sits was designed to allow user self-registration. As we have co-opted the Register method on the AccountController for restricted administrative use, so we will co-opt the Register.cshtml View for our purposes.

Essentially, all we need to do is add some additional HTML and Razor-Syntax code to the file to accommodate the new properties we added to our ApplicationUser class:

Modifying the Register.cshtml File:
@model AspNetRoleBasedSecurity.Models.RegisterViewModel
@{
    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>
    <hr />
    @Html.ValidationSummary()
    <div class="form-group">
        @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>
  
    // Add the LastName, FirstName, and Email Properties:
    <div class="form-group">
        @Html.LabelFor(m => m.LastName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.LastName, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.FirstName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.FirstName, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
}

 

Next, we will create our edit view. To do this, we can right-click on the Edit Action method declaration in AccountController and let VS do the work for us:

Create View for the Edit Method:

add-view-edit-method

We next see the Add View Dialog. Choose the Edit template from the Template drop-down, and select the EditUserViewModel class from the Model Class drop-down. We already have a data context, so leave that blank.

Add View Dialog:

add-view-dialog

Repeat the process above for the Delete and Index methods. Choose the appropriate template for each (use the List template for the Index View, as we want to display a list of User Accounts), and use EditUserViewModel as the Model Class for both.

Fine-Tuning the Index View

We need to make a few minor changes to our index view.

Notice near the bottom, where the template has provided handy links for Edit, Details, and Delete. We will change the "Details" link to instead point to our UserRoles Action method. Also, we need to replace the commented out route parameters such that the Username is passed as the id Parameter.

Lastly, up near the top of the file the is some razor code for an Action link to create a new user. Replace the Action method parameter "Create" with our co-opted "Register" method.

After our modifications, the final Index.cshtml file should look like this:

Modified Index.cshtml File:
@model IEnumerable<AspNetRoleBasedSecurity.Models.EditUserViewModel>
  
@{
    ViewBag.Title = "Index";
}
  
<h2>Index</h2>
  
<p>
    @Html.ActionLink("Create New", "Register") 
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.UserName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.FirstName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.LastName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Email)
        </th>
        <th></th>
    </tr>
  
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.UserName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.FirstName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.LastName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Email)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id = item.UserName }) |
            @Html.ActionLink("Roles", "UserRoles", new { id = item.UserName }) |
            @Html.ActionLink("Delete", "Delete", new { id = item.UserName })
        </td>
    </tr>
}
  
</table>

 

Now we can get back to that whole User Roles issue.

Create the UserRoles.cshtml View

We can use the VS Add View method to create our UserRoles.cshtml View as we did with the previous views. However, we will be doing most of our work from scratch on this one. Right-Click on the UserRoles method of AccountController and select Add View. This time, however, choose the Empty template option from the Template drop-down, and choose SelectUserRolesViewModel from the Model Class drop-down.

You should now have a mostly empty UserRoles.cshtml file the looks like this:

The Empty UserRoles.cshtml File:
@model AspNetRoleBasedSecurity.Models.SelectUserRolesViewModel
  
@{
    ViewBag.Title = "UserRoles";
}
  
<h2>UserRoles</h2>

 

From here, we will add our code manually. We want to display the basic user information, followed by a list of the available roles to which the user can be assigned (or removed). We want the list of roles to feature checkboxes as the selection mechanism.

To accomplish the above, we will add our Razor code as follows:

Added Code to the UserRoles.cshtml File:
@model AspNetRoleBasedSecurity.Models.SelectUserRolesViewModel
  
@{
    ViewBag.Title = "User Roles";
}
  
<h2>Roles for user @Html.DisplayFor(model => model.UserName)</h2>
<hr />
  
@using (Html.BeginForm("UserRoles", "Account", FormMethod.Post, new { encType = "multipart/form-data", name = "myform" }))
{
    @Html.AntiForgeryToken()
  
    <div class="form-horizontal">
        @Html.ValidationSummary(true)
        <div class="form-group">
            <div class="col-md-10">
                @Html.HiddenFor(model => model.UserName)
            </div>
        </div>
  
        <h4>Select Role Assignments</h4>
        <br />
        <hr />
  
        <table>
            <tr>
                <th>
                    Select
                </th>
                <th>
                    Role
                </th>
            </tr>
            @Html.EditorFor(model => model.Roles)
        </table>
        <br />
        <hr />
  
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}

 

In the above, we have set up a display header featuring the UserName, and created an HTML Table with header elements for Select and Role. The Select column will contain the checkboxes for each role, and the Role column will, obviously, display the Role names.

Below the table header set up, notice the line:

@Html.EditorFor(model => model.Roles)

 

This is where we return to that EditorTemplate concept. An Editor template is a Shared View, and needs to be located in the Views => Shared => EditorTemplates folder in your project. You may need to create the folder yourself.

Create the SelectRoleEditorViewModel Editor Template by right-clicking on your new EditorTemplates folder and selecting Add View. Use the Empty template again, and name the view SelectRoleEditorViewModel (this is important). Choose SelectRoleEditorViewModel from the Model Classes drop-down. When you are done you should have a .cshtml file that looks like this:

The Empty SelectRoleEditorViewModel Editor Template File:
@model AspNetRoleBasedSecurity.Models.SelectRoleEditorViewModel
  
@{
    ViewBag.Title = "SelectRoleEditorViewModel";
}
  
<h2>SelectRoleEditorViewModel</h2>

 

From here, we will add a few lines, so that our file looks like this:

Modified SelectRoleEditorViewModel Editor Template File:
@model AspNetRoleBasedSecurity.Models.SelectRoleEditorViewModel
@Html.HiddenFor(model => model.RoleName)
<tr>
    <td style="text-align:center">
        @Html.CheckBoxFor(model => model.Selected)
    </td>
    <td>
        @Html.DisplayFor(model => model.RoleName)
    </td>
</tr>

 

Now we have an editor template for our SelectRoleEditorViewModel. The code in our UserRoles.cshtml View will use this template to render our list of roles, including the checkboxes. Selections made in the checkboxes will be reflected in our model and returned, with the Role name, to the controller when the form is submitted.

Adding the Admin Tab to the Main Site Page

We are almost there. However, none of these new views and functionality do us much good if we can't get to it from within our application. We need to add an Admin tab to our main site page, and also remove the ability for anonymous users to access the registration link included on the site by default.

To do this, we need to modify the _Layout.cshtml file in the Views => Shared folder.

Modified _Layout.cshtml File
<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
        <li>@Html.ActionLink("Home", "Index", "Home")</li>
        <li>@Html.ActionLink("About", "About", "Home")</li>
        <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
        <li>@Html.ActionLink("Admin", "Index", "Account")</li>
    </ul>
    @Html.Partial("_LoginPartial")
</div>

 

The code above is from just about the middle of the _Layout.cshtml View file. Add the highlighted line to create a tab link pointing to the Index method of our AccountController.

Remove the Register Link from the _LoginPartial.cshtml View

Last, we want to remove the link to the Register method from the main site layout. This link is found on the _LoginPartial.cshtml file, again located in the Views => Shared folder. At the bottom of this file, remove the code line highlighted below:

Remove the Register Link from _LoginPartial.cshtml:
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
        <li>@Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
    </ul>
}

 

Now all our views should be ready.

Set Up and Run Entity Framework Migrations

Now that we have most of the pieces in place, it's time to set up Code-First Migrations with Entity Framework. Also, because we are ostensibly building an application in which only users in an administrative role can create or edit users, we need to seed our application with an initial Admin user. Further, since we don't plan to allow creation or modification of roles, we need to seed the database with the roles we expect to use in our application, since we can't create them from within the application itself.

We covered EF Code-First Migrations fairly thoroughly in the previous article, so I am going to skim through it this time.

First, enable migrations by typing the following in the Package Manager Console:

Enable EF Migrations in Your Project:
PM> Enable-Migrations –EnableAutomaticMigrations

 

Now, open the Migrations => Configuration.cs file and add the following code (tune it up to suit your specifics. You probably don't want to add MY information as your admin user. Also note, whatever password you provide to start with must conform to the constraints of the application, which appears to require at least one capital letter, and at least one number:

Modify the EF Migrations Configuration File with Seed Data:
using AspNetRoleBasedSecurity.Models;
using System.Data.Entity.Migrations;
namespace AspNetRoleBasedSecurity.Migrations
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;
  
    internal sealed class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }
  
  
        protected override void Seed(ApplicationDbContext context)
        {
            this.AddUserAndRoles();
        }
  
  
        bool AddUserAndRoles()
        {
            bool success = false;
  
            var idManager = new IdentityManager();
            success = idManager.CreateRole("Admin");
            if (!success == true) return success;
  
            success = idManager.CreateRole("CanEdit");
            if (!success == true) return success;
  
            success = idManager.CreateRole("User");
            if (!success) return success;
  
  
            var newUser = new ApplicationUser()
            {
                UserName = "jatten",
                FirstName = "John",
                LastName = "Atten",
                Email = "jatten@typecastexception.com"
            };
  
            // Be careful here - you  will need to use a password which will 
            // be valid under the password rules for the application, 
            // or the process will abort:
            success = idManager.CreateUser(newUser, "Password1");
            if (!success) return success;
  
            success = idManager.AddUserToRole(newUser.Id, "Admin");
            if (!success) return success;
  
            success = idManager.AddUserToRole(newUser.Id, "CanEdit");
            if (!success) return success;
  
            success = idManager.AddUserToRole(newUser.Id, "User");
            if (!success) return success;
  
            return success;
        }
    }
}

 

Once that is done, run the following command from the Package Manager Console:

Add the Initial EF Migration
Add-Migration Init

 

Then create the database by running the Update-Database command:

Update Database Command:
Update-Database

 

If all went well, your database should be created as a SQL Server (local) database in the App_Data folder. If you want to point to a different Database Server, review the previous article where we discuss pointing the default connection string to a different server.

You can check to see if your database was created properly by opening the Server Explorer window in Visual Studio. Or, of course, you could simply run your application, and see what happens!

Use [Authorize] Attribute to Control Access

From this point, we can regulate access to different application functionality using the [Authorize] Attribute. We have already seen examples of this on the methods in our AccountController, where access to everything except the Login method is restricted to users of the Admin role.

Use [Authorize] Attribute to Control Access to Functionality:
[AllowAnonymous]
public MyPublicMethod()
{
    // Code
}
  
  
[Authorize(Role = "Admin, CanEdit, User")]
public MyPrettyAccessibleMethod()
{
    // Code
}
  
  
[Authorize(Role = "Admin, CanEdit")]
public MyMoreRestrictiveMethod()
{
    // Code
}
  
  
[Authorize(Role = "Admin")]
public MyVeryRestrictedMethod()
{
    // Code
}

In the code above, we see progressively more restricted method access based on Role access defined using the [Authorize] attribute.

At the moment, our role definitions are not "tiered" in a manner by which a higher-level role inherits the permissions associated with a more restricted role. For example, if a method is decorated with an [Authorize] attribute granting access to members of the User Role, it is important to note that ONLY members of that role will be able to access the method. Role access must be explicitly and specifically granted for each role under this scenario.

Role Permissions are not Inherited:
// Admins can't access this method:
[Authorize(Role = "Users")]
public SomeMethod()
{
    // Code
}
// Admins AND Users can access this method:
[Authorize(Role = "Users", Admins)]
public SomeMethod()
{
    // Code
}

 

Contrary to our experience with most operating system security, members of the Admin role do not automatically get all the permissions of the User role. We could probably achieve this, but such is beyond the scope of this article.

A Note About Roles and Role Naming

For our purposes here, I have used some rather generic role names, since we really don't have any business cases to consider when using roles to manage application access.

The ASP.NET team recommends (and I agree) the it is best to use descriptive and limiting role definitions which describe, to an extent, the permissions associated with that role. For example, instead of a generic "Admin" role, one might create an "IdentityManager" role specific to Account and Identity management, and other such descriptive role names as make sense in the business context of your application.

Wrapping It Up

In this article we have created a very simple implementation of Role-Based Identity Management. As I mentioned at the beginning, the model used here, outside of any business context, is a little basic. I have attempted to create show some of the basics involved with using the ASP.NET MVC Identity system, extending it to include some custom properties, and modifying the use to suit a basic business case.

There is a lot to know about Web Application security, and in my mind, it is not the place to re-invent any wheels. In the application discussed here, we have re-jiggered the components of the ASP.NET Identity model, but we have used the core pieces as designed instead of inventing our own authorization mechanism.

Hopefully, this rather long article has been helpful, and I have not propagated any bad information. Please do let me know, either in the comments, or by email if you find any significant issues here. I will correct them promptly.

Additional Resources and Items of Interest

 

Posted on November 11 2013 07:45 PM by jatten     

Comments (25)

About the author

My name is John Atten, and my "handle" on many of my online accounts is xivSolutions. I am Fascinated by all things technology and software development. I work mostly with C#, JavaScript/Node, and databases of many flavors. Actively learning always. I dig web development. I am always looking for new information, and value your feedback (especially where I got something wrong!). You can email me at:

jatten at typecastexception dot com

Web Hosting by