2015-03-17 7 views
5

Desidero sviluppare un'applicazione in MVC utilizzando Identity Framework 1.0 che consente agli utenti di registrarsi con lo stesso nome utente utilizzato da un altro utente.Come consentire all'utente di registrarsi con Nome utente duplicato utilizzando Identity Framework 1.0

Quando si elimina un utente, desidero impostare la proprietà personalizzata IsDeleted su true anziché eliminare l'utente dal database. In questo caso un altro utente può utilizzare UserName dell'utente il cui IsDeleted è impostato su true.

Ma il metodo predefinito UserManager.CreateAsync(user, password); impedisce di farlo.

avevo ignorato il metodo di IdentityDbContextValidateEntity come questo

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) 
{ 
    if ((entityEntry != null) && (entityEntry.State == EntityState.Added)) 
    { 
     ApplicationUser user = entityEntry.Entity as ApplicationUser; 
     if ((user != null) && this.Users.Any<ApplicationUser>(u => string.Equals(u.UserName, user.UserName) && u.IsDeleted==false)) 
     { 
      return new DbEntityValidationResult(entityEntry, new List<DbValidationError>()) { 
       ValidationErrors = { 
        new DbValidationError("User", string.Format(CultureInfo.CurrentCulture, 
         "", new object[] { user.UserName })) 
       } 
      }; 
     } 
     IdentityRole role = entityEntry.Entity as IdentityRole; 
     if ((role != null) && this.Roles.Any<IdentityRole>(r => string.Equals(r.Name, role.Name))) 
     { 
      return new DbEntityValidationResult(entityEntry, new List<DbValidationError>()) { 
       ValidationErrors = { 
        new DbValidationError("Role", string.Format(CultureInfo.CurrentCulture, 
         "", new object[] { role.Name })) } }; 
     } 
    } 
    return base.ValidateEntity(entityEntry, items); 
} 

Ecco il mio metodo di registro dove utente viene creato

metodo
public async Task<ActionResult> Register(RegisterViewModel model) 
{ 
    if (ModelState.IsValid) 
    { 
     var user = new ApplicationUser() { UserName = model.UserName, Email = model.Email.ToLower(), CreatedBy = model.UserName, CreatedDate = DateTime.UtcNow, }; 

     user.ConfirmedEmail = false; 
     var result = await _accountService.CreateAsync(user, model.Password); 
     if (result.Succeeded) 
     { 

      TempData["MessageConfirm"] = user.Email; 
      return RedirectToAction("Confirm", "Account"); 
     } 
     else 
     { 
      AddErrors(result); 
     } 
    } 
    // If we got this far, something failed, redisplay form 
    return View(model); 
} 

ValidateEntity dovrebbe essere eseguito quando await _accountService.CreateAsync(user, model.Password); esegue. Ma è in esecuzione dopo che il metodo register ha completato la sua esecuzione. Quindi il risultato genera errore.

Qualche suggerimento su come posso raggiungere questo obiettivo?

+2

I nomi utente ** devono ** essere unici! Che problema stai cercando di risolvere! –

+2

Non voglio che il nome utente sia univoco – Twix

+0

Poiché il nome utente è l'equivalente di una chiave primaria ed è l'unico identificatore per un utente, ** deve ** essere univoco. Non hai scelta. In quale altro modo ti distinguerai tra gli utenti. Quindi, qual è il problema di base che stai cercando di risolvere? –

risposta

3

Il mio cruccio è quando le persone decidono di dare un consiglio invece di rispondere alla domanda posta. i nomi utente non devono essere unici. La chiave db è userid e non il nome utente. Se si dispone di un'applicazione che fornisce servizi a più aziende, è possibile che i dipendenti di società diverse abbiano lo stesso nome utente. Per fare ciò devi estendere l'identità di aspnet.

https://www.scottbrady91.com/ASPNET-Identity/Quick-and-Easy-ASPNET-Identity-Multitenancy

IdentityUser Per cominciare abbiamo bisogno di aggiungere la pretesa di TenantId (è possibile rinominare questo come si adatta alle vostre esigenze di business) estendendo la classe IdentityUser. Anche se questo è concettualmente un reclamo, ci avvantaggeremo della tabella AspNetUser e aggiungeremo TenantId come proprietà, poiché faremo una query con questa proprietà in modo equo. Per semplicità ho aggiunto il TenantId come int, tuttavia un'alternativa non iterativa consisterebbe nell'usare una stringa.

public class ApplicationUser : IdentityUser { 
    public int TenantId { get; set; } 
} 

UserStore Successivo implementeremo l'UserStore per il nostro nuovo utente che è a conoscenza della nostra nuova proprietà. Qui stiamo usando una proprietà all'interno della nostra classe UserStore per impostare il nostro TenantId, permettendoci di scavalcare l'implementazione di base con la nostra implementazione multi-tenant.

public class ApplicationUserStore<TUser> : UserStore<TUser> 
    where TUser : ApplicationUser { 
    public ApplicationUserStore(DbContext context) 
     : base(context) { 
    } 

    public int TenantId { get; set; } 
} 

CreateUserAsync

public override Task CreateAsync(TUser user) { 
if (user == null) { 
    throw new ArgumentNullException("user"); 
} 

user.TenantId = this.TenantId; 
return base.CreateAsync(user); 

} 

FindByEmailAsync

public override Task<TUser> FindByEmailAsync(string email) { 
return this.GetUserAggregateAsync(u => u.Email.ToUpper() == email.ToUpper() 
    && u.TenantId == this.TenantId); 

} 

FindByNameAsync

public override Task<TUser> FindByNameAsync(string userName) { 
return this.GetUserAggregateAsync(u => u.UserName.ToUpper() == userName.ToUpper() 
    && u.TenantId == this.TenantId); 

} 

UserValidator Mentre l'UserValidator di default è hardcoded verifica la presenza di nomi utente duplicati, la nostra nuova implementazione dei metodi UserStore FindByNameAsync e FindByEmailAsync consentirà il comportamento multi-tenanted corretta (supponendo che è stata impostata una TenantId all'interno del UserStore). Ciò significa che possiamo sfruttare appieno l'UserValidator predefinito ed estenderlo se necessario.

IdentityDbContext Ora ecco un po 'difficile. Il team Identity ASP.NET ha nuovamente codificato un controllo per i nomi utente duplicati all'interno della classe IdentityDbContext, tuttavia questa volta è sia all'interno del metodo ValidateEntity sia nello stesso schema del database EF utilizzando un indice.

L'indice può essere risolto estendendo il metodo OnModelCreating per modificare l'indice univoco basato sul nome utente per cercare anche il nostro TenantId (un indice composito). Questo ci fa risparmiare questo indice utile e ottimizza il nostro database per la multitenancy. È possibile farlo con il seguente metodo di sostituzione:

public class ApplicationUserDbContext<TUser> : IdentityDbContext<TUser> 
    where TUser : ApplicationUser { 
    public ApplicationUserDbContext(string nameOrConnectionString) 
     : base(nameOrConnectionString) { 
    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) { 
     base.OnModelCreating(modelBuilder); 

     var user = modelBuilder.Entity<TUser>(); 

     user.Property(u => u.UserName) 
      .IsRequired() 
      .HasMaxLength(256) 
      .HasColumnAnnotation("Index", new IndexAnnotation(
       new IndexAttribute("UserNameIndex") { IsUnique = true, Order = 1})); 

     user.Property(u => u.TenantId) 
      .IsRequired() 
      .HasColumnAnnotation("Index", new IndexAnnotation(
       new IndexAttribute("UserNameIndex") { IsUnique = true, Order = 2 })); 
    } 
} 
The ValidateEntity method is a bit more tricky however, as we will have to reimplement the entire method in order to remove the hardcoded username checks: 

    protected override DbEntityValidationResult ValidateEntity(
     DbEntityEntry entityEntry, IDictionary<object, object> items) { 
     if (entityEntry != null && entityEntry.State == EntityState.Added) { 
      var errors = new List<DbValidationError>(); 
      var user = entityEntry.Entity as TUser; 

      if (user != null) { 
       if (this.Users.Any(u => string.Equals(u.UserName, user.UserName) 
        && u.TenantId == user.TenantId)) { 
        errors.Add(new DbValidationError("User", 
         string.Format("Username {0} is already taken for AppId {1}", 
         user.UserName, user.TenantId))); 
       } 

       if (this.RequireUniqueEmail 
        && this.Users.Any(u => string.Equals(u.Email, user.Email) 
        && u.TenantId == user.TenantId)) { 
        errors.Add(new DbValidationError("User", 
         string.Format("Email Address {0} is already taken for AppId {1}", 
         user.UserName, user.TenantId))); 
       } 
      } 
      else { 
       var role = entityEntry.Entity as IdentityRole; 

       if (role != null && this.Roles.Any(r => string.Equals(r.Name, role.Name))) { 
        errors.Add(new DbValidationError("Role", 
         string.Format("Role {0} already exists", role.Name))); 
       } 
      } 
      if (errors.Any()) { 
       return new DbEntityValidationResult(entityEntry, errors); 
      } 
     } 

     return new DbEntityValidationResult(entityEntry, new List<DbValidationError>()); 
    } 

client Tutto ciò che resta ora è quello di inizializzare le classi. Non dimenticare che sarà necessario fornire TenantId ogni volta che si rinnova il contesto. Vedere l'esempio di seguito (notare l'uso di 'esempio', queste classi sono tutte usa e getta ...).

var context = new ApplicationUserDbContext<ApplicationUser>("DefaultConnection"); 
var userStore = new ApplicationUserStore<ApplicationUser>(context) { TenantId = 1 }; 
var userManager = new UserManager<ApplicationUser, string>(userStore);