How to customize ASP.NET Identity to handle users scoped within applications?

Nov 7, 2014 at 9:13 PM
I am working on an application which records users who registers to access some resources on web pages. I'm using ASP.NET MVC 5 with ASP.NET Identity. I use the same value for Email and for a UserName. I have a table which store webpages. I added also a WebpageId column to AspNetUsers table to identify a webpage. Actually, between AspNetUsers table and Webpage table there is many to many relationship, but for some reason I wanted to have multiple records for a user in AspNetUsers table – one for each webpage where the user is registered.

If a user with user@examplemail.com is registered to webpage1 it does not mean that he is registered to webpage2. When he registers to webpage2 with the same e-mail it should be actually completely invisible for him that he is already registered in the system as a user of webpage1.

In order to have multiple records with the same UserName in AspNetUsers table I removed a unique index on UserName and instead I created a unique index on two columns: UserName and WebpageId.

But when I try to register to webpage2 with the e-mail address that is already registered to webpage1, I get DbEntityValidationException with the following validation message : „__Username user@examplemail.com has already been taken__”. This is thrown in AccountControler.Register() method when UserManger is used to create a new user.

My first try to solve the problem was to customize the UserManager class. I created a subclass of UserManager where I overriden FindByEmailAsync and FindByNameAsync methods in a way that they look for a pair email+webpage or name+webpage:
var existingUser = this.db.Users
                  .FirstOrDefault(x => x.UserName == userName && 
                                       x.WebpageId == webpageId);
return existingUser;
My second try was to additionally customize UserValidator of this UserManager. I do there something similar to the code above - try to get user with the same UserName and webpageId. When user is not found, I return IdentityResult.Success.

No success. Still having DbEntityValidationException with „Username user@examplemail.com has already been taken” validation error.

Any idea which ASP.NET Identity component still looks for a user with a user name (without checking the webpageId)? How should I customize ASP.NET Identity to handle such a scenario?

FYI, I also posted a question on stackoverflow about it: http://stackoverflow.com/questions/26810870/how-to-customize-asp-net-identity-to-handle-users-scoped-within-applications
Nov 7, 2014 at 9:24 PM
I paste also the exception callstack. Maybe it helps.
at System.Data.Entity.Internal.InternalContext.SaveChangesAsync(CancellationToken cancellationToken)
at System.Data.Entity.Internal.LazyInternalContext.SaveChangesAsync(CancellationToken cancellationToken)
at System.Data.Entity.DbContext.SaveChangesAsync(CancellationToken cancellationToken)
at System.Data.Entity.DbContext.SaveChangesAsync()
at Microsoft.AspNet.Identity.EntityFramework.UserStore6.<SaveChanges>d__31.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult()
at Microsoft.AspNet.Identity.EntityFramework.UserStore
6.<CreateAsync>d__c.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult()
at Microsoft.AspNet.Identity.UserManager2.<CreateAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable
1.ConfiguredTaskAwaiter.GetResult()
at Microsoft.AspNet.Identity.UserManager2.<CreateAsync>d__d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter
1.GetResult()
at UserRegistrationSystem.Web.Controllers.AccountController.<Register>d__11.MoveNext() in i:\Repo\RegistrationSystem\UserRegistrationSystem.Web\Controllers\AccountController.cs:line 126