Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Membership with Microsoft.AspNet.Identity - CreateLocalUser fails

Tags:

I've been trying to implement a custom version of the new Identity features in ASP.NET 4.5 (Microsoft.AspNet.Identity), using Visual Studio 2013. After many hours of playing around with this, I've simplified my code in an effort to get it running without errors. I've listed my code below. When doing a Local Registration, the database tables are created, but the CreateLocalUser method fails. I'm hoping that someone can help me identify the changes needed.

Models/MembershipModel.cs

using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity; using System.Linq; using System.Web;  namespace thePulse.web.Models {     public class PulseUser : IUser     {         public PulseUser() { }         public PulseUser(string userName)          {             UserName = userName;         }          [Key]         public string Id { get; set; }         [Required]         [StringLength(20)]         public string UserName { get; set; }         [StringLength(100)]         public string Email { get; set; }         [Column(TypeName = "Date")]         public DateTime? BirthDate { get; set; }         [StringLength(1)]         public string Gender { get; set; }     }      public class PulseUserClaim : IUserClaim     {         public PulseUserClaim() { }          [Key]         public string Key { get; set; }         public string UserId { get; set; }         public string ClaimType { get; set; }         public string ClaimValue { get; set; }      }      public class PulseUserSecret : IUserSecret     {         public PulseUserSecret() { }         public PulseUserSecret(string userName, string secret)         {             UserName = userName;             Secret = secret;         }          [Key]         public string UserName { get; set; }         public string Secret { get; set; }      }      public class PulseUserLogin : IUserLogin     {         public PulseUserLogin() { }         public PulseUserLogin(string userId, string loginProvider, string providerKey)          {             LoginProvider = LoginProvider;             ProviderKey = providerKey;             UserId = userId;         }          [Key, Column(Order = 0)]         public string LoginProvider { get; set; }         [Key, Column(Order = 1)]         public string ProviderKey { get; set; }         public string UserId { get; set; }     }      public class PulseRole : IRole     {         public PulseRole() { }         public PulseRole(string roleId)         {             Id = roleId;         }          [Key]         public string Id { get; set; }     }      public class PulseUserRole : IUserRole     {         public PulseUserRole() { }          [Key, Column(Order = 0)]         public string RoleId { get; set; }         [Key, Column(Order = 1)]         public string UserId { get; set; }     }      public class PulseUserContext : IdentityStoreContext     {         public PulseUserContext(DbContext db) : base(db)         {             Users = new UserStore<PulseUser>(db);             Logins = new UserLoginStore<PulseUserLogin>(db);             Roles = new RoleStore<PulseRole, PulseUserRole>(db);             Secrets = new UserSecretStore<PulseUserSecret>(db);             UserClaims = new UserClaimStore<PulseUserClaim>(db);         }     }      public class PulseDbContext : IdentityDbContext<PulseUser, PulseUserClaim, PulseUserSecret, PulseUserLogin, PulseRole, PulseUserRole>     {     } } 

Changes to Controllers/AccountController.cs

public AccountController()  {      IdentityStore = new IdentityStoreManager(new PulseUserContext(new PulseDbContext()));     AuthenticationManager = new IdentityAuthenticationManager(IdentityStore); }  // // POST: /Account/Register [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) {     if (ModelState.IsValid)     {         try         {             // Create a profile, password, and link the local login before signing in the user             PulseUser user = new PulseUser(model.UserName);             if (await IdentityStore.CreateLocalUser(user, model.Password))             {                 await AuthenticationManager.SignIn(HttpContext, user.Id, isPersistent: false);                 return RedirectToAction("Index", "Home");             }             else             {                 ModelState.AddModelError("", "Failed to register user name: " + model.UserName);             }         }         catch (IdentityException e)         {             ModelState.AddModelError("", e.Message);         }     }      // If we got this far, something failed, redisplay form     return View(model); } 

As I said above, this implementation fails when the CreateLocalUser method fails (Microsoft.AspNet.Identity.EntityFramework). I cannot figure out why.

like image 891
Vito Avatar asked Jul 28 '13 18:07

Vito


2 Answers

The issue here is that IdentityStoreManager has strong dependency on the default implementation of identity EF models. For example, the CreateLocalUser method will create UserSecret and UserLogin objects and save them to stores, which won't work if the store is not using the default model type. So if you customize the model type, it won't work smoothly with IdentityStoreManager.

Since you only customize the IUser model, I simplified the code to inherit custom user from default identity user and reuse other models from identity EF models.

using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity; using System.Linq; using System.Web;  namespace WebApplication11.Models {     public class PulseUser : User     {         public PulseUser() { }         public PulseUser(string userName) : base(userName)         {         }          [StringLength(100)]         public string Email { get; set; }         [Column(TypeName = "Date")]         public DateTime? BirthDate { get; set; }         [StringLength(1)]         public string Gender { get; set; }     }      public class PulseUserContext : IdentityStoreContext     {         public PulseUserContext(DbContext db) : base(db)         {             this.Users = new UserStore<PulseUser>(this.DbContext);         }     }      public class PulseDbContext : IdentityDbContext<PulseUser, UserClaim, UserSecret, UserLogin, Role, UserRole>     {     } } 

The code above should work with preview version of Identity API.

The IdentityStoreManager API in upcoming release is already aware of this issue and changed all the non-EF dependency code into a base class so that you can customize it by inheriting from it. It should solve all the problems here. Thanks.

like image 50
Hongye Sun Avatar answered Nov 06 '22 13:11

Hongye Sun


PulseUser.Id is defined as a string but doesn't appear to be set to a value. Were you meant to be using a GUID for the Id? If so, initialise it in the constructor.

    public PulseUser() : this(String.Empty) { }     public PulseUser(string userName)      {         UserName = userName;         Id = Guid.NewGuid().ToString();     } 

You will also want to perform a check that the user name doesn't already exist. Look at overriding DbEntityValidationResult in PulseDbContext. Do a new MVC project in VS2013 to see an example.

like image 36
Grant Avatar answered Nov 06 '22 12:11

Grant