.net core Identity, getting specific signup error conditions - c#

I wish to test/deal with specific error conditions returned from UserManager, eg: registration failed due to username already on file, etc
var user = new SiteUser() { UserName = username, Email = RegisterViewModel.Email };
var result = await _userManager.CreateAsync(user, RegisterViewModel.Password);
if (!result.Succeeded)
{
// here I want to test for specific error conditions
// eg: username already on file, etc
// how can I do this?
}

IdentityResult contains an Errors property, which is of type IEnumerable<IdentityError>. IdentityError itself contains both a Code property and a Description property. This means that your result variable in your OP has an Errors property that describes the specific errors that occurred.
IdentityErrorDescriber is used for generating instances of IdentityError. Here's an example from the source:
public virtual IdentityError DuplicateUserName(string userName)
{
return new IdentityError
{
Code = nameof(DuplicateUserName),
Description = Resources.FormatDuplicateUserName(userName)
};
}
IdentityErrorDescriber is injected into the DI system in the same way that UserManager is. This means you can take it as a dependency in your controller's construtor (for example) and use it later, like so (assuming _errorDescriber has been created and set in your constructor):
if (!result.Succeeded)
{
// DuplicateUserName(...) requires the UserName itself so it can add it in the
// Description. We don't care about that so just provide null.
var duplicateUserNameCode = _errorDescriber.DuplicateUserName(null).Code;
// Here's another option that's less flexible but removes the need for _errorDescriber.
// var duplicateUserNameCode = nameof(IdentityErrorDescriber.DuplicateUserName);
if (result.Errors.Any(x => x.Code == duplicateUserNameCode))
{
// Your code for handling DuplicateUserName.
}
}
There are a number of different ways to get the Code value you want to test against and to do the check itself - this is just one example that's fairly safe against customisations you might want to make to the codes and errors themselves.
If you're interested, here's a link to the source for where the DuplicateUserName error is added to the IdentityResult you get back.
I've only talked about DuplicateUserName here, but there are other IdentityErrorDescriber values for e.g. InvalidEmail that you might also want to check for.

Related

Creating ASP.NET MVC users using ApplicationUserManager throws DbContext has been disposed error

Part of my application requires the importing of users at a large scale, this information is supplied via an excel document which is uploaded via a View.
Due to the size of this batch and several other processes which need to be kicked off for each user, including welcome emails that take quite a long time to complete I have opted to use threads to do this to avoid any long loading screens to the user.
In order to use threads I have needed to make several variables accessible to the threaded function, including ApplicationUserManager, HttpRequestBase and UrlHelper which all get populated in the ActionResult function that starts this Thread
This is the controller function that populates this information:
public ActionResult importExternalUsers()
{
BulkUserImportProcessHelperModel helperModel = new BulkUserImportProcessHelperModel();
helperModel.userManager = this.HttpContext.ApplicationInstance.Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); ;
helperModel.urlContext = this.Url;
helperModel.httprequestBase = this.Request;
helperModel.filePath = #"C:\Users\tiaan\Desktop\MultipleUserCreation.xlsx";
Thread thread = new Thread(new ParameterizedThreadStart(bulkUserImportProcess));
thread.Start(helperModel);
return View();
}
As you can see the models get populated then a thread is created. I made use of a "Helper model" in that case just to pass the parameters to the function I started in a Thread as I could not find an easy way, if any at all, to pass multiple parameters to a function designed for threaded use.
This is the threaded function which imports the Excel document and starts the loop that registers the users:
public static void bulkUserImport(object objectParams)
{
BulkUserImportProcessHelperModel helperModel = (BulkUserImportProcessHelperModel)objectParams;
List<MedicalProfessionalRegistration> usersToReg = IO.InputOutput.ImportExcelDocument(helperModel.filePath);
foreach (var user in usersToReg)
{
AccountController.registerUser(user, helperModel.userManager, helperModel.urlContext, helperModel.httprequestBase);
}
}
So far, although probably a little unconventional, all is working. Adding a breakpoint in this function after the creation of the helperModel variable reveals that all the fields were populated as expected.
My problems start at the static AccountController function responsible for the creation of the users.
public static bool registerUser(MedicalProfessionalRegistration model, ApplicationUserManager userManager, UrlHelper urlHelper, HttpRequestBase request)
{
string password = membershipPasswordGenerator(10, 10, 5, 1);
model.Password = password;
model.ConfirmPassword = password;
var user = new ApplicationUser { UserName = model.UserName, Email = model.Email };
var result = userManager.Create(user, model.Password);
if (result.Succeeded)
{
IO.Identities.registerUser(user.Id, IO.IdentityRank.User);
IO.Identities.storeUserIdentityMeta(user.Id, model);
string code = userManager.GenerateEmailConfirmationToken(user.Id);
string callbackUrl = urlHelper.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code, rst = "T" }, protocol: request.Url.Scheme);
IO.Email.confirmEmailAccountRegister(user.Email, callbackUrl);
return true;
}
else
{
return false;
}
}
Currently, I am getting an exception on
var result = userManager.Create(user, model.Password);
that reads:
This exception was originally thrown at this call stack:
System.Data.Entity.Internal.LazyInternalContext.Connection.get()
What's weird to me is that sometimes it actually populates the database with a single user from this list then seems to fail on the next iteration of the loop in bulkUserImport. While other times it fails on the first one almost immediately.
I believe it has something to do with UserManager being disposed as the stack trace says, as well as the userManager being null
This thread could run upwards of an hour (and the IIS server has been configured appropriately for this)
So ultimately what I think this comes down to, is there any way that I can prevent these fields from being disposed, or is there something else that's wrong with my logic here that I have become oblivious to after spending the last day tinkering with it?
Thanks in advance

Strange anomoly with: Attaching an entity of type 'X' failed because another entity of the same type already has the same primary key value

I had a similar issue with this question, when updating a record using EF6.
I really thought I had cracked the whole updating thing, but now have to almost identical functions updating in what I think was an identical way. One works, the other doesn't. I have fixed the one that doesn't work by using Jamie's comment in the above question, but I'd like to understand if the function that works, really shouldn't and so is on borrowed time and I should make more like the 'fixed' one. Or, why the 'fixed' one didn't work in the first place. I even moved them into the same controller so that the database (DB) context was guaranteed the same. Have I missed something and they are not identical (functionally) at all?
It might also help some others out there that struggle with this as I did.
The function that works (cut down) is:
[HttpPost]
[Route("UpdateAddBusinessService")]
public async Task<IHttpActionResult> UpdateAddBusinessService(BusinessServiceDTO servicetoupdateoradd)
{
... pre check stuff...
try
{
if (servicetoupdateoradd.Id != null) // This is an existing service to be updated - if Is Null then create new
{
BusinessService businessService = await db.BusinessServices.FindAsync(servicetoupdateoradd.Id);
if (businessService != null)
{
Mapper.Map(servicetoupdateoradd, businessService);
db.Entry(businessService).State = EntityState.Modified;
await db.SaveChangesAsync();
return Ok("Service Updated");
}
else
The function that doesn't work is:
[HttpPost]
[Route("UpdateImage")]
public async Task<IHttpActionResult> UpdateImage(ImageDTO imageDTO)
{
... pre check stuff ...
try
{
// First find the image
// Image imagetoupdate = await db.Images.FindAsync(imageDTO.Id); <<-This FAILS.
Image imagetoupdate = db.Images.AsNoTracking().Single(x => x.Id == imageDTO.Id); <<- This WORKS
if (imagetoupdate != null)
{
imagetoupdate = Mapper.Map<ImageDTO, Image>(imageDTO); // Move the stuff over..
db.Entry(imagetoupdate).State = EntityState.Modified;
await db.SaveChangesAsync();
return Ok();
}
I wondered (as you will no doubt), if my Mapper function was doing anything, but I suspect not (without digging too deep, but I guess it could be), my Mapper.Config functions for the two DTO's are very similar:
cfg.CreateMap<Image, ImageDTO>();
cfg.CreateMap<ImageDTO, Image>();
and:
cfg.CreateMap<BusinessService, BusinessServiceDTO>();
cfg.CreateMap<BusinessServiceDTO, BusinessService>();
I would really just like to understand the 'correct' way of doing this so it doesn't bite me again. Thanks in advance.
EDIT: I was asked (quite reasonably) if the 'pre-check stuff' does anything to fetch the data, it doesn't, but there is a subtle difference, that I might have missed...
This is from the BusinessService function that works:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
string userid = User.Identity.GetUserId(); //Check user is valid
if (servicetoupdateoradd.UserId != userid)
{
var message = "User Id Not found - Contact support";
HttpResponseMessage err = new HttpResponseMessage() { StatusCode = HttpStatusCode.ExpectationFailed, ReasonPhrase = message };
return ResponseMessage(err);
}
This is from the UpdateImage function that didn't work:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
string userid = User.Identity.GetUserId();
SGGUser user = db.Users.Find(userid); // Use this find and not UserManager becuase its a different context and buggers up the file save
if (user == null)
{
var message = "User Id Not found - Contact support";
HttpResponseMessage err = new HttpResponseMessage() { StatusCode = HttpStatusCode.ExpectationFailed, ReasonPhrase = message };
return ResponseMessage(err);
}
I see that in this one, though I don't fetch the relevant data, I do use the 'db' context.. could that be it?? The Image object does contain a reference to the user, so maybe that does some magic in the background code?
Appologies, I just didn't want to clutter up the question too much...
This line:
Mapper.Map(servicetoupdateoradd, businessService);
and this line:
imagetoupdate = Mapper.Map<ImageDTO, Image>(imageDTO); // Move the stuff over..
look similar, but do two different things.
The first line will tell Automapper to copy values from the first object over to the second object reference using the mapping rules.
The second line will tell Automapper to make a completely new reference to the entity with the mapped over values from the provided object and return it.
So in the first case, the entity reference is preserved to the one the DbContext knows about. The reference was loaded from a DbContext and should be tracking changes so you shouldn't even need to set it's entity state. In the second case, Automapper is creating an entirely new reference and assigning it over top the original reference. EF is treating that as a completely new instance and trying to attach it, resulting in it complaining because the context had already loaded that entity, you just overwrote the reference.
It should work if you change the second instance to:
Mapper.Map(imageDTO, imagetoupdate);

ASP.NET Core validate Azure Table Entity already taken

I'm trying to trigger a data validation error back to my view on a lookup to a database back end.
// Perform lookup to see if domain has been taken already
var domainResults = await _context.TenantEntity.SingleOrDefaultAsync(x => x.Office365DomainName == Input.Office365DomainName);
if (domainResults.Office365DomainName == Input.Office365DomainName)
{
// duplicate domain name attempted
user.Office365DomainName = "AlreadyTaken";
return Page();
}
Here is my field:
[Required]
[Display(Name = "Office 365 Domain Name")
public string Office365DomainName { get; set; }
I'd prefer to use a DataAnnotation so I can send back a custom message to the view/user but I'm at a loss on how to build this in.
I've tried changing my property validation to a regex and watching for "AlreadyTaken" as I'm setting this inside my class which contains the same object. My thought was to perform a regex match on something obscure (like a GUID) then have my regex match that GUID for a validation error.
I'm probably over thinking all this and I hope someone has some insight.
As suggested, there was a very easy answer to this:
// Perform lookup to see if domain has been taken already
var domainResult = await _context.TenantEntity.SingleOrDefaultAsync(x => x.Office365DomainName == Input.Office365DomainName);
if (domainResult != null && domainResult.Office365DomainName == Input.Office365DomainName)
{
// duplicate domain name attempted
ModelState.AddModelError("Office365DomainName", "This domain has been registered already.");
return Page();
}
I didn't have to modify my field at all. The following article was a great help: https://exceptionnotfound.net/asp-net-mvc-demystified-modelstate/

error occurred when requesting data via Entity Framework

I'm building an N-tier application which has to send JSON data, which is read from SQL Server 2012 through Enity Framework.
When I try to request a collection of users I get an "An error has occurred" page. It works with hardcoded data.
This is my code:
public IEnumerable<User> Get()
{
IUserManager userManager = new UserManager();
return userManager.GetUsers();
}
public IEnumerable<User> GetUsers()
{
return repo.ReadUsers();
}
public IEnumerable<User> ReadUsers()
{
IEnumerable<User> users = ctx.Users.ToList();
return users;
}
"ctx" is a reference to a DbContext-object.
EDIT: This works:
public IEnumerable<User> Get()
{
IList<User> users = new List<User>();
users.Add(new User() { FirstName = "TestPerson1" });
users.Add(new User() { FirstName = "TestPerson2" });
return users;
}
Browser screenshot: http://i.imgur.com/zqG0qe0.png
EDIT: Full error (screenshot): http://i.imgur.com/dt48tRG.png
Thanks in advance.
If your website returns internal error and no call stack you are not seeing the full exception(which makes it kind of hard to exactly point out your problem).
So first of all to get to the actual exception with call stack you have 2 methods.
Debug the website : start the website locally or attach your debugger to a locally running website. While stepping through the code the debugger will stop when it hits an exception and you'll see the exception details then.
Disable custom errors : IIS wants to protect your internal workings so standard behavior is not to show full exceptions. To disable this behavior edit your web.config and add the xml node under
After you get the actual exception please update your question with call stack & the real internal server error. My guess is that you have a serialization issue, maybe a circular reference of some sort. You're fix would be to either make a simpel viewModel(and not return the entity directly) or add serialization settings(json.net support circular references for example).
Edit
As suspected the serialization is giving you a hard time. The cause is the proxy creation used by the lazy loading.
You can disable the proxy creation with the following code(make note that this also disables lazy loading).
public IEnumerable<User> ReadUsers()
{
ctx.Configuration.ProxyCreationEnabled = false;
IEnumerable<User> users = ctx.Users.ToList();
return users;
}
if this works you might consider disabling proxy creation during the context initialization.
Try to return plain model, smth like
var logins = ctx.Users.Select(user => new FlatUserModel
{
Id = user.Id,
Name = user.UserName,
Email = user.Email
})
.ToArray();
return logins;
Also look in browser what do you get in response.
As it works with hard coded data there is possibility of lazyloading enabled on context.
If so disable lazy loading in the context or like this so that serialization to JSON does not load the entities from database.
public IEnumerable<User> ReadUsers()
{
ctx.Configuration.LazyLoadingEnabled = false;
IEnumerable<User> users = ctx.Users.ToList();
return users;
}

Users able to see others' details. FirstOrDefault returning the wrong record? Or caching issue? Help! :)

Users of my site have experienced some strange behaviour yesterday (first time I've seen this issue), and unfortunately I don't have much in the way of error logs to try to figure out what's going on. The site had a higher-than-normal number of people online at once, albeit not a large number in the grand scheme of things (maybe 50 to 100 users all trying to perform similar functions). I can't recreate the issue in my development environment, haven't seen it before, and don't really know why it is happening.
The crux of the problem is that users can register or log on successfully, but a small number of them could see other users' data.
The site is ASP.NET MVC 3.
Users are logging on and I set an authentication cookie - here's the LogOn action:
[HttpPost]
public ActionResult LogOn(AccountLogOnViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (!Membership.ValidateUser(model.UserName, model.Password))
{
ModelState.AddModelError("login-message", "Incorrect username or password");
}
}
if (ModelState.IsValid)
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
Session.Remove("MenuItems");
return Redirect(returnUrl ?? Url.Action("Index", "Home"));
}
else
{
model.ReturnUrl = returnUrl;
return View(model);
}
}
AccountLogOnViewModel is a simple object with two string properties, UserName and Password.
From what I can gather, this is fine - if you log in as NickW then doing something like User.Identity.Name correctly gives you "NickW" (when users were seeing other users' data, they reported that that "Welcome, NickW" text on screen was showing them the correct value - this is written out using User.Identity.Name)
The site also uses a custom membership provider. It overrides the ValidateLogin method, and the GetUser method. ValidateLogin appears to be working just fine so I'm not concerned about it.
The overridden GetUser method is as follows:
public override MembershipUser GetUser(string username, bool userIsOnline)
{
User user = _userRepository.Users.FirstOrDefault(u => u.UserName == username);
MembershipUser membershipUser = null;
if (user == null)
return membershipUser;
membershipUser = new MembershipUser(this.Name,
user.UserName,
user.Id,
user.Email,
null,
user.Comments,
user.IsActivated,
user.IsLockedOut,
user.CreatedDate,
user.LastLoginDate,
user.LastLoginDate,
user.LastModifiedDate,
Convert.ToDateTime(user.LastLockedOutDate));
return membershipUser;
}
So I'm attempting to retrieve a User object from my database, and using that to create a new MembershipUser object. My database User table has additional columns on top of those required by the membership provider - e.g. name, address, phone number etc.
At various points in the rest of the website (for example if you go to the Profile page), I retrieve a user object from the database and use it to populate the screen. The line I use to retrieve the User object is:
User user = userRepository.Users.FirstOrDefault(u => u.UserName == Membership.GetUser().UserName);
Here is a cut down version of the userRepository (i.e. just removing unrelated code).
public class SqlUserRepository : IUserRepository
{
private Table<User> usersTable;
private string _connectionString;
public SqlUserRepository(string connectionString)
{
_connectionString = connectionString;
usersTable = (new DataContext(connectionString)).GetTable<User>();
}
public IQueryable<User> Users
{
get { return usersTable; }
}
public void CreateUser(AccountRegisterViewModel user)
{
User newUser = new User();
newUser.UserName = user.UserName;
newUser.Salutation = user.Salutation;
newUser.PhoneNumber = user.PhoneNumber;
newUser.SecondaryPhoneNumber = user.SecondaryPhoneNumber;
newUser.FirstName = user.FirstName;
newUser.LastName = user.LastName;
newUser.PasswordSalt = CreateSalt();
newUser.Password = CreatePasswordHash(user.Password, newUser.PasswordSalt);
newUser.Email = user.Email;
newUser.CreatedDate = DateTime.UtcNow;
newUser.Comments = "Created from web registration";
newUser.LastModifiedDate = DateTime.UtcNow;
newUser.LastLoginDate = DateTime.UtcNow;
newUser.IsActivated = true;
newUser.IsLockedOut = false;
newUser.MayContact = user.MayContact;
usersTable.InsertOnSubmit(newUser);
usersTable.Context.SubmitChanges();
}
}
So it appears to me as if the auth cookie I'm setting is fine, but either:
When I first go in to the membership provider's GetUser() method, it retrieves the wrong record from the database and therefore sets up a MembershipUser object with the wrong username; subsequently when I look in the database for "this" user I'm actually looking for the wrong username.
Or: Intermittently when I do userRepository.FirstOrDefault(x => x.UserName == Membership.GetUser().Name) it retrieves the wrong record.
Or: something else is going wrong that I haven't thought of.
As I say, this seems to be a problem when the site was under load, so I'm wondering if it's some sort of caching issue somewhere? But I really don't know.
One thought I had was to change the way I retrieve the user in case the problem lies with the membership provider, and use this instead:
userRepository.FirstOrDefault(x => x.UserName == User.Identity.Name)
// or HttpContext.Current.User.Identity.Name if not within a controller
But really I'm not even sure what's going on so have no idea whether this will resolve the issue. Could it be a caching problem somewhere? It appears (but I can't be 100% certain) that when user A could see user B's details, it was always the case that user B was also active in the system (or had been within the previous 20 minutes).
I know it's a long shot, but does anyone have any idea how this could happen? Obviously it's a major concern and needs to be fixed urgently, but without knowing why it's happening I can't fix it!
Thanks in advance for any help,
Nick
Some things to consider:
Instead of using FirstOrDefault, use SingleOrDefault. FirstOrDefault assumes there will be more than 1 record of data matching your query. Since you are querying by username, there should only be 1 matching row, correct? In that case, use SingleOrDefault instead. When there are multiple rows that match the query, SingleOrDefault will throw an exception.
To get the username, instead of invoking Membership.GetUser().UserName, use User.Identity.Name. The User property on an MVC controller references an IPrincipal that should match the user's forms authentication cookie value. Since you have a custom membership provider, this should help eliminate its methods as a source of the problem.
There could be a caching issue if you have caching set up for the MVC project. Do you use the OutputCacheAttribute ([OutputCache]) on any controllers or action methods? Do you have it set up as a global filter in the global.asax file? Or do you think there may be some kind of SQL-based caching going on?
Looking at your overridden GetUser method, I see it should take 2 parameters: string username and bool isOnline. However, when you invoke it with Membership.GetUser().UserName, you are passing no parameters. Do you have another overridden overload of this method that also takes no parameters? What does it look like? Does it use System.Threading.CurrentPrincipal.Identity.Name to sniff out the current username when none is passed?

Categories