Building a simple ToDo application with ASP.NET Identity and associating Users with ToDoes

Building a simple ToDo application with ASP.NET Identity and associating Users with ToDoes

Rate This
  • Comments 29

Hello everyone. I was prompted to write this post from a comment I received on http://blogs.msdn.com/b/webdev/archive/2013/10/17/announcing-release-of-asp-net-and-web-tools-for-visual-studio-2013.aspx. I am pasting the comment verbatim from the post

“I'm having a lot of difficulty integrating the IdentityDbContext with other DbContexts to create a comprehensive model and database for my applications. If the membership system always stood on its own, there wouldn't be an issue but the User ID typically gets used throughout other models and tables (think of a blog, for example, where some authenticated users are authorized to create blog posts whereas others are only authorized to leave comments - all users, though, need to have their User ID stored with blogs and/or comments). What are the recommended ways to combine these EF Code First models?”

While this requirement may seem fairly straight forward, it does highlight lots of key components of using any membership system in an application. I thank “Robert Gaut” for posting this comment which motivated me to write this post.

So this should set the context of this blog post :) This blog post shows how you can create a simple ToDo application and associate ToDoes with Users from ASP.NET Identity. In other words, this post shows how you can mix and match Entity Framework Code First Models for the application specific data with the Models of User from ASP.NET Identity.

Let us define the requirements of this application :-

  • Only authenticated users should be able to create, edit, delete and see their own ToDoes.
  • Users should not be able to view or edit ToDoes which were created by other users. D’Oh you would be thinking that this should be a given, but I figured I should mention that since this is what I hit while writing this sample.
  • Only Users who belong to Admin Role (I am going to call this User as Admin User in this post) should be authorized to see all the ToDoes in the application. This means that the Admin User can see the ToDoes for all the users. This requirement will highlight the authorization requirements mentioned in the comment.

Let us see how we can build an application using ASP.NET Identity which fulfills these requirements

I am going to write this post as a tutorial as well and use the ASP.NET Project templates in Visual Studio 2013 so hopefully it would be easy to follow and try out and later on you can implement this flow in your own application.

    • Create a File – New ASP.NET Project and select ASP.NET MVC with Individual User Accounts.

    • This project creates an application where a user can login by registering an account with the website or use Social Login providers such as Facebook, Twitter etc. For the purpose of this example we will register a user with the website.
    • Initialize ASP.NET Identity to create Admin User and Admin Role and Add Admin User to Admin Role
      • You can initialize ASP.NET Identity when the application starts. Since ASP.NET Identity is Entity Framework based in this sample,  you can create DatabaseInitializer which is configured to get called each time the app starts.
      • Set the Database Initializer in Global.asax

             1:  Database.SetInitializer<MyDbContext>(new MyDbInitializer());

      • Initialize the database to create Admin Role and Admin User
        • In Line 5 we are creating a UserManger from ASP.NET Identity system which will let us do operations on the User such as Create, List, Edit and Verify the user. You can think of the UserManager as being analogus to SQLMembershpProvider in ASP.NET 2.0
        • In Line 6, we are creating a RoleManager from ASP.NET Identity system which lets us operate on Roles. You can think of the RoleManager as being analogus to SQLRoleMembershpProvider in ASP.NET 2.0
        • In this example, my User type is called in MyUser. In the project template, it is called ApplicationUser.

 

             1:  public class MyDbInitializer : DropCreateDatabaseAlways<MyDbContext>
             2:      {
             3:          protected override void Seed(MyDbContext context)
             4:          {

          5: var UserManager = new UserManager<MyUser>(new

          UserStore<MyUser>(context));

             6:              var RoleManager = new RoleManager<IdentityRole>(new 
                                                    RoleStore<IdentityRole>(context));
             7:   
             8:              string name = "Admin";
             9:              string password = "123456";
           
            13:   
            14:              //Create Role Admin if it does not exist
            15:              if (!RoleManager.RoleExists(name))
            16:              {
            17:                  var roleresult = RoleManager.Create(new IdentityRole(name));
            18:              }
            19:   
            20:              //Create User=Admin with password=123456
            21:              var user = new MyUser();
            22:              user.UserName = name;
            23:              var adminresult = UserManager.Create(user, password);
            24:   
            25:              //Add User Admin to Role Admin
            26:              if (adminresult.Succeeded)
            27:              {
            28:                  var result = UserManager.AddToRole(user.Id, name);
            29:              }
            30:              base.Seed(context);
            31:          }
            32:      }

    • Create an Entity Framework Code First ToDo model in Models\AppModels,cs.
      • Since we are using Entity Framework Code First, EF will create the right keys between Users and ToDo table.
      •    1:  public class MyUser : IdentityUser
           2:      {
           3:          public string HomeTown { get; set; }
           4:          public virtual ICollection<ToDo>
           5:                               ToDoes { get; set; }
           6:      }
           7:   
           8:      public class ToDo
           9:      {
          10:          public int Id { get; set; }
          11:          public string Description { get; set; }
          12:          public bool IsDone { get; set; }
          13:          public virtual MyUser User { get; set; }
          14:      }

    • Use Scaffolding to generate a ToDo MVC controller along with Views to create, update, delete and list all the ToDoes. Follow this tutorial on how to use Scaffolding in ASP.NET http://www.asp.net/visual-studio/overview/2013/aspnet-scaffolding-overview
      • Please ensure that you reuse the existing database context so you can store ASP.NET Identity and ToDoes in the same database. This is a convenient way of managing application data and membership data. You DbContext should look something like this
      •    1:      public class MyDbContext : IdentityDbContext<MyUser>
           2:      {
           3:          public MyDbContext()
           4:              : base("DefaultConnection")
           5:          {
           6:          }
           7:   
           8:          protected override void OnModelCreating(DbModelBuilder modelBuilder)
           9:          {
          10:          public System.Data.Entity.DbSet<AspnetIdentitySample.Models.ToDo> 
                             ToDoes { get; set; }
          11:      }

    • Update the generated ToDo controller to associate a User with ToDoes. In this sample I am only going to show you Create and list, but you can follow the similar pattern for Edit and Detele
      • Add an overload of ToDo controller constructor which takes in a UserManager for ASP.NET Identity. UserManager allows you to manage the User in ASP.NET Identity system.
        •    1:          private MyDbContext db;
             2:          private UserManager<MyUser> manager;
             3:          public ToDoController()
             4:          {
             5:              db = new MyDbContext();
             6:              manager = new UserManager<MyUser>(new UserStore<MyUser>(db));
             7:          }
             8:          

      • Update Create Action
        • When you create a ToDo, we look up the logged in User in the ASP.NET Identity and associate the User object with the ToDoes.
             1:          public async Task<ActionResult> Create
             2:          ([Bind(Include="Id,Description,IsDone")] ToDo todo)
             3:          {
             4:              var currentUser = await manager.FindByIdAsync
                                                           (User.Identity.GetUserId()); 
             5:              if (ModelState.IsValid)
             6:              {
             7:                  todo.User = currentUser;
             8:                  db.ToDoes.Add(todo);
             9:                  await db.SaveChangesAsync();
            10:                  return RedirectToAction("Index");
            11:              }
            12:   
            13:              return View(todo);
            14:          }
              

      • Update List Action
        • We will only get the list of ToDoes created by the current logged in User
        •    1:          public ActionResult Index()
             2:          {

          3: var currentUser = manager.FindById(User.Identity.GetUserId());

             4:              return View(db.ToDoes.ToList().Where(
                                             todo => todo.User.Id == currentUser.Id));
             5:          }

      • Allow only Admins to view the ToDoes for all the users
          • We will add a new Action in the ToDo controller to list all the ToDoes, but we will only authorize Users in Role Admin to have access to these ToDoes. Again we are using the same [Authorize] attribute that we used before
          •    1:          [Authorize(Roles="Admin")]
               2:          public async Task<ActionResult> All()
               3:          {
               4:              return View(await db.ToDoes.ToListAsync());
               5:          }

      • Add support to view the User details from the ToDo table
          • Since we associate the ToDoes with a User object, the benefit that we get is when we get the list of ToDoes for all the users, we can easily access any profile specific data for the user from the ToDo model itself. For eg. when the Admin views all the ToDoes we can also get a profile property for the user if we added any. In this sample we have added HomeTowm so we ill display HomeTown next to the ToDoes.
          • The markup for the views looks like as follows
            •    1:  @model IEnumerable<AspnetIdentitySample.Models.ToDo>
                 2:   
                 3:  @{
                 4:      ViewBag.Title = "Index";
                 5:  }
                 6:   
                 7:  <h2>List of ToDoes for all Users</h2>
                 8:  <p>
                 9:      Notice that we can see the User info (UserName) and profile info such as HomeTown for the user as well.
                10:      This was possible because we associated the User object with a ToDo object and hence
                11:      we can get this rich behavior.
                12:  </p>
                13:   
                14:  <table class="table">
                15:      <tr>
                16:          <th>
                17:              @Html.DisplayNameFor(model => model.Description)
                18:          </th>
                19:          <th>
                20:              @Html.DisplayNameFor(model => model.IsDone)
                21:          </th>
                22:          <th>@Html.DisplayNameFor(model => model.User.UserName)</th>
                23:          <th>@Html.DisplayNameFor(model => model.User.HomeTown)</th>
                24:      </tr>
                25:   
                26:      @foreach (var item in Model)
                27:      {
                28:          <tr>
                29:              <td>
                30:                  @Html.DisplayFor(modelItem => item.Description)
                31:              </td>
                32:              <td>
                33:                  @Html.DisplayFor(modelItem => item.IsDone)
                34:              </td>
                35:              <td>
                36:                  @Html.DisplayFor(modelItem => item.User.UserName)
                37:              </td>
                38:              <td>
                39:                  @Html.DisplayFor(modelItem => item.User.HomeTown)
                40:              </td>
                41:          </tr>
                42:      }
                43:   
                44:  </table>

      • Update the Layout Page to add links for ToDoes
        •    1:  <li>@Html.ActionLink("ToDo", "Index", "ToDo")</li>
             2:  <li>@Html.ActionLink("ToDo for User In Role Admin", "All", "ToDo")</li>
             3:                      
      • Run the application
        • When you run the application you will the links at the top of the page as follows
        • image

      • Create ToDo as a normal user (non admin)
          • Click ToDo and you will be redirected to Login page since you are not authenticated
          • You can register a new account and create a ToDo
            • image
          • Once you create the ToDo, then you can view the ToDoes for yourself. Note you cannot view ToDoes for all users
          • image
      • View the ToDoes for all the users (admin only access)
        • Click the link “ToDo for User in Role Admin”. you will be redirected back to the login page since you are not an Admin and hence you are not authorized to view this page
        • Logout from this application and Login using the Admin you created when you were initializing the ASP.NET Identity system. User = Admin Password=123456
        • Once you login you can view the ToDoes for all users
        • image

    Conclusion

    I hope you will find this walkthrough useful. For a completed sample you can visit my project at https://github.com/rustd/AspnetIdentitySample. If you have any questions around ASP.NET Identity, please feel free to leave comments, ask them on asp.net/forums of stackoverflow. I can also be reached on twitter (@rustd)

    Leave a Comment
    • Please add 2 and 5 and type the answer here:
    • Post
    • Thanks for sharing this article. I am now able to understand ASP.NET Identity a lot more and put it to good use with my model classes. Thanks again.

    • Despite what some comments above say this sample does generally work as advertised.

      There is a new class that did not exist at the time of this writing IdentityUser.cs which defines ApplicationUser and ApplicationDbContext replacing MyUser and MyDbContext.

    • If you are having problem like this:

      "EntityType 'IdentityUserLogin' has no key defined.

      EntityType 'IdentityUserRole' has no key defined.

      IdentityUserLogins: EntityType: EntitySet 'IdentityUserLogins' is based on type 'IdentityUserLogin' that has no keys defined.

      IdentityUserRoles: EntityType: EntitySet 'IdentityUserRoles' is based on type 'IdentityUserRole' that has no keys defined.",

      you have to manually set Primary Keys in those Identity entities (tables) and keep original names of the tables (starting with AspNet...).

      Add following code to the OnModelCreating method in your DBContext class:

                 base.OnModelCreating(modelBuilder);

                 modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id).ToTable("AspNetRoles");

                 modelBuilder.Entity<IdentityUser>().ToTable("AspNetUsers");

                 modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId).ToTable("AspNetUserLogins");

                 modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId }).ToTable("AspNetUserRoles");

                 modelBuilder.Entity<IdentityUserClaim>().ToTable("AspNetUserClaims");

      I hope that it will be fixed in the next release.

    • Sorry, small mistake, this line:

      modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId).ToTable("AspNetUserLogins");

      should be replaced with:

      modelBuilder.Entity<IdentityUserLogin>().HasKey(l => new { l.UserId, l.LoginProvider, l.ProviderKey }).ToTable("AspNetUserLogins");

      AspNetUserLogins table should have a 3 columns PK.

    • I agree is SPADES  with Jeff  It would be really nice to have a tutorial that uses a Stable releases - I think it would be much more practical.  I am trying to learn something new  but it is not worth the time.....

      I just downloaded this tutorial and did all the NuGet updates however  it does not show the 'Microsoft.AspNet.Identity.EntityFramework as an installed package but when i go the to install it ,  it says it is installed ... and when try to build  the app I get the error ---unable to find version '1.1.0-alpha1-130930' of package

      I have also tried the SQLMembership-Identity-OWIN and it is also  full of errors . AND the SQL Query migration script that  the tutorial references is  garbage

    • Please give more detail explanation about UserManager, RoleManager, UserStore classes and IUser, IRole interfaces.

    • So when I have created users 'Admin' and 'User'. I create a ToDo for both.

      From User I can still access Admin's (other users ToDo's) from typing /Details/# in the adressbar. Anyway to stop this automaticly? Filters?

    • I have changed packages.config, and all has earned.

      <?xml version="1.0" encoding="utf-8"?>

      <packages>

       <package id="Antlr" version="3.5.0.2" targetFramework="net45" />

       <package id="bootstrap" version="3.0.3" targetFramework="net45" />

       <package id="EntityFramework" version="6.1.0-alpha1" targetFramework="net45" />

       <package id="jQuery" version="2.0.3" targetFramework="net45" />

       <package id="jQuery.Validation" version="1.11.1" targetFramework="net45" />

       <package id="Microsoft.AspNet.Identity.Core" version="2.0.0-alpha1" targetFramework="net45" />

       <package id="Microsoft.AspNet.Identity.EntityFramework" version="2.0.0-alpha1" targetFramework="net45" />

       <package id="Microsoft.AspNet.Identity.Owin" version="2.0.0-alpha1" targetFramework="net45" />

       <package id="Microsoft.AspNet.Mvc" version="5.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.AspNet.Razor" version="3.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.AspNet.Web.Optimization" version="1.1.2" targetFramework="net45" />

       <package id="Microsoft.AspNet.WebPages" version="3.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.jQuery.Unobtrusive.Ajax" version="3.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.jQuery.Unobtrusive.Validation" version="3.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.Owin" version="2.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.Owin.Host.SystemWeb" version="2.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.Owin.Security" version="2.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.Owin.Security.Cookies" version="2.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.Owin.Security.Facebook" version="2.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.Owin.Security.Google" version="2.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.Owin.Security.MicrosoftAccount" version="2.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.Owin.Security.OAuth" version="2.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.Owin.Security.Twitter" version="2.1.0-rc1" targetFramework="net45" />

       <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />

       <package id="Modernizr" version="2.7.1" targetFramework="net45" />

       <package id="Newtonsoft.Json" version="5.0.8" targetFramework="net45" />

       <package id="Owin" version="1.0" targetFramework="net45" />

       <package id="WebGrease" version="1.5.2" targetFramework="net45" />

      </packages>

    • Thank you so much Piotr, I moved my model to a repository project and got those errors. Your code fixed it right up!

    • Hi,thanks for the article,i would like to ask is it possible to achieve it by using pure asp.net web api application?And is it possible to change the table name too?Thanks a lot

    • why is this tutorial so badly laid out? just a touch of indent madness going on

    • Very very confused, i'm spending much time linking the points that you ignore

    • What if we are using VS2013 with forms and MVC? The solution has started with aspx fils not MVC. Is it still possible to have access to the same functionality? EF is unlikely to work, right?

    • Everything is great but can anyone submit the working code for deleting user and prior to that removing all roles the user is assigned to please.

    Page 2 of 2 (29 items) 12