Is is possible to extend the UserStore/UserManager to have access to a custom table within an override method (without creating another dbcontext)?
I'm working on a legacy database which we integrated with Identity. Recently we added a new table, Clients, to the database that has a one to one relationship with the Users table. What would like to do is override the FindAsync method in the UserManager class to do call the base method, grab the user, and then based on the user's client Id stored in the table, fetch the client name and insert that into the user model.
The following is what I have attempted; however, to me I think this is wrong approach and would like some suggestions on how to correctly apply what I'm trying to do.
public override async Task<ApplicationUser> FindAsync(string userName, string password)
{
ApplicationUser user = await base.FindAsync(userName, password);
if (user != null && user.ClientID != null)
{
using (var ctx = new ApplicationDbContext(HostPage.ConnectionString))
{
user.Client = ctx.Clients.Where(p => p.ClientID == user.ClientID).FirstOrDefault();
}
}
return user;
}
You could modify SQL in base.FindAsync() to perform a join to your table thus executing a single query.
Related
I am trying to create a page in my MVC .NET CORE application in which a user can change the roles of other users. At this point the view and model bindings all work great, but when actually trying to save the roles back to the database I am getting an error saying that the roles don't exists. My approach is this:
Get list of roles from the model.
Delete existing roles for specified user.
Add roles back using list from model.
Do this all in a transaction in case of error.
My code in the controller is as follows:
var user = await _userManager.FindByIdAsync(id);
if (user == null)
{
return View("Error");
}
using (_context.Database.BeginTransaction())
{
var removeResult = await _userManager.RemoveFromRolesAsync(user, await _userManager.GetRolesAsync(user));
if (!removeResult.Succeeded)
{
return View("Error");
}
var addResult = await _userManager.AddToRolesAsync(user, model.MemberRoles);
if (!addResult.Succeeded)
{
return View("Error");
}
}
Where model.MemberRoles as a List<string> of roles. The strange part is is that this process is failing on _userManager.RemoveFromRolesAsync, even though I am passing the existing roles of the user directly into the function. I have tried ditching the UserManager class and going with ef but had no luck. I also verified that the roles in the model match what is in the database. Is there anything obviously wrong that would cause this to fail? Thanks.
Edit
Here is the error I am running into:
InvalidOperationException: Role ADMINISTRATOR does not exist.
Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`4.<AddToRoleAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
And here are the roles defined in my database:
When you try to add a user to a role, it first tries to find the role. In Sql Profiler, you can see that it is actually searching by the NormalizedName field rather than the Name field.
exec sp_executesql N'
SELECT TOP(2) [r].[Id], [r].[ConcurrencyStamp], [r].[Name], [r].[NormalizedName]
FROM [AspNetRoles] AS [r]
WHERE [r].[NormalizedName] = #__normalizedRoleName_0',N'#__normalizedRoleName_0 nvarchar(256)',#__normalizedRoleName_0=N'ADMINISTRATOR'
The solution is to set the NormalizedName field to the Uppercase of the Name field when you create the Roles:
So in the SeedData class:
/* Create Roles */
IEnumerable<UserRole> allRoles = Enum.GetValues(typeof(UserRole)).Cast<UserRole>();
foreach (UserRole role in allRoles)
{
if (!await roleManager.RoleExistsAsync(role.ToString()))
{
await roleManager.CreateAsync(new IdentityRole {
Name = role.ToString(),
NormalizedName = role.ToString().ToUpper()
});
}
}
I'm actually surprised you're not getting an InvalidCastException instead, as it's not possible to directly cast string[] to List<string>. Not really sure why you're doing it that way in the first place, since you could just as easily just call .ToList(), instead of .ToArray(), and call it a day, if you want a list. Or, since the return type of GetRolesAsync is IList<string>, anyways, you don't even really need to do that. Right now, that's the only problem I see with your code, though.
I've deleted one of the registered users from the data base manually, by right-clicking on the table row itself and selecting the delete option. Now I am trying to register the same user again with exactly the same email that this particular user use to be registered before. When I run the registration process, the email duplicate method is constantly returning true for some reason. If I try to use any other email for registering it works like charm. Any idea why is this happening?
Email validation method
private static bool EmailIsRegistered(string email)
{
using (var data = new HospitalDBDataContext())
{
if (data.Connection.State == ConnectionState.Closed) data.Connection.Open();
var match = data.Staffs.Any(x => x.Email.Equals(email));
if (data.Connection.State == ConnectionState.Open) data.Connection.Close();
data.Dispose();
return match;
}
}
I have two classes:
**User
UseP where** User have its id (PK) and UserP has the id (FK) of User.
I'm working on MVC 4. Using Fluent API.
The classes are mapped within the project to connect to the database. My project is split into controllers, services, models and views.
I have a User screen that uses a UserPr field.
How can I instantiate it in class without being the way down? (that way does not work because of the relationship, otherwise work):
**CLASS USER.CS**
public string Login { get { return this.UserP.login } set { login = value }}
inclusion screen to add the User of items, I should also change the status of the screen, this status is within UserP.
Controller
public ViewResultBase Add(User model)
{
if (this.ModelState.IsValid)
{
try
{
this.Service.SaveUserP(model);
return this.SuccessView(true);
}
catch (ValidationException exception)
{
base.AddValidationErrors(exception);
return base.PartialView(model);
}
}
else
{
return base.PartialView(model);
}
}
Service
public void SaveUserP(User item)
{
//save fields from User (Running ok)
base.context.Usuario.Add(item);
//Attempt to save the login and their status in UserP
foreach (var userP in base.context.UserP.Where(x => x.IdUser == item.Id))
{
item.login = userP.Login;
userP.ParticipantType = 3;
base.context.UsersP.Add(userP);
}
}
I've tried that way, but I could not.
If the item.login is only working because login stand as [notmapped] in User.cs
To summarize: In the inclusion screen (User) I have a field that should bring (UserP) login.
When I trigger the method of inclusion, he should save the fields and save the User log in using UsuarioP IdUsuario as key and also changing the status of login (ParticipantType = 3)
The error I get:
Invalid column login (because really there is no login in User)
Time to debug it includes only the User fields, and even through the foreach.
I do not know how to operate, can help me? What if I have not been clear, I put more details
You will need to save the User input first before your foreach loop.
base.context.SaveChanges();
I added a custom field to the UserProfile table named ClassOfYear and I'm able to get the data into the profile during registration like this:
var confirmationToken = WebSecurity.CreateUserAndAccount(model.UserName,
model.Password,
propertyValues: new { ClassOfYear = model.ClassOfYear },
requireConfirmationToken: true);
However, now I want to be able to update the profile when I manage it but I can't seem to find a method to do so. Do I need to simply update the UserProfile table myself? If not, what is the appropriate way of doing this?
FYI, I'm using Dapper as my data access layer, just in case it matters. But, like stated, I can just update the UserProfile table via Dapper if that's what I'm supposed to do, I just figured that the WebSecurity class, or something similar, had a way already since the custom user profile fields are integrated with the CreateUserAndAccount method.
Thanks all!
There is nothing in the SimpleMembershipProvider code that does anything with additional fields except upon create.
Simply query the values yourself from your ORM.
You can use the WebSecurity.GetUserId(User.Identity.Name) to get the user's id and then Dapper to query the UserProfile table.
Just in case anyone facing the same problem. After fighting a lot with the SimpleMembership I got a solution that populates both the webpages_Membership and my custom Users table. For clarification follow my code:
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
TUsuario userDTO= new TUSer()
{
Name = model.Name,
Login = model.Login,
Pass = model.Pass.ToString(CultureInfo.InvariantCulture),
Active = true,
IdCompany = model.IdCompany,
IdUserGroup = model.IdUserGroup,
};
try
{
WebSecurity.CreateUserAndAccount(model.Login, model.Pass, new { IdUser = new UserDAL().Seq.NextVal(), Name = userDTO.Name, Login = userDTO.Login, Active = userDTO.Active, Pass = userDTO.Pass, IdCompany = userDTO.IdCompany, IdUserGroup = userDTO.IdUserGroup });
WebSecurity.Login(model.Login, model.Pass);
After cursing the framework a lot, that gave me a bliss of fresh air :)
PS.: The users table is specified in the global.asax file using the WebSecurity.InitializeDatabaseConnection functon.
I'm having trouble getting a ModelUpdate or TryModelUpdate to work in my code.
I'm using the default Role Manager and Login system created by MVC when running the ASP.Net configuration tool. What I'm trying to do is add another column to the Users table so I can record if my users are also customers. So I want to record their CustomerID there.
I used the ADO Entity Data Model to generate all my model code based off my database. The code it created for the field I want to update is here:
public string CustomerID
{
get
{
return this._CustomerID;
}
set
{
this.OnCustomerIDChanging(value);
this.ReportPropertyChanging("CustomerID");
this._CustomerID = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, true);
this.ReportPropertyChanged("CustomerID");
this.OnCustomerIDChanged();
}
}
private string _CustomerID;
partial void OnCustomerIDChanging(string value);
partial void OnCustomerIDChanged();
In my controller Im trying to update the CustomerID field with this code:
var userToUpdate = dbu.aspnet_Users.First(u => u.UserName == User.Identity.Name);
UpdateModel(userToUpdate, new string[] { "CustomerID"}, txtID);
dbu.SaveChanges();
But I get an error saying the overload method has some invalid arguments.
I get that the issue is in assigning txtID to CustomerID based off the error, but whats the correct way to do it?
If I need to add more info please let me know.
I figured it out. Apparantly ModelUpdate won't let me pass in custom data and it needs to be passed in from the Form Collection. So using UpdateModel(userToUpdate, new string[] {"CustomerID"}, form.ToValueProvider()); worked.