I'm trying to find a way that would allow me to update a UserProfile entity along with a list of Roles that the user is assigned to. I've written the code below, but it doesn't work.
public void UpdateUserProfile(UserProfile userProfile)
{
context.Entry(userProfile).State = EntityState.Added;
var databaseRoleIds = context.Roles
.Where(r => r.UserProfiles
.Any(u => u.UserId == userProfile.UserId))
.Select(r => r.RoleId).ToList();
var clientRoleIds = userProfile.Roles.Select(r => r.RoleId).ToList();
var removedRoleIds = databaseRoleIds.Except(clientRoleIds).ToList();
var addedRoleIds = clientRoleIds.Except(databaseRoleIds).ToList();
var unchangedRoleIds = removedRoleIds.Union(addedRoleIds).ToList();
foreach (var roleId in unchangedRoleIds)
{
var role = context.Roles.Find(roleId);
context.Entry(role).State = EntityState.Unchanged;
}
foreach (var roleId in removedRoleIds)
{
userProfile.RemoveRole(context.Roles.Find(roleId));
}
foreach (var roleId in addedRoleIds)
{
userProfile.AddRole(context.Roles.Find(roleId));
}
context.Entry(userProfile).State = EntityState.Modified;
}
Here is the unitOfWork
namespace MvcWebsite.WorkUnits
{
public class WorkUnit : IWorkUnit, IDisposable
{
private MvcContext context = new MvcContext();
private RoleRepository roleRepository;
private UserProfileRepository userProfileRepository;
public IRoleRepository RoleRepository
{
get
{
if (this.roleRepository == null)
{
roleRepository = new RoleRepository(context);
}
return roleRepository;
}
}
public IUserProfileRepository UserProfileRepository
{
get
{
if (this.userProfileRepository == null)
{
userProfileRepository = new UserProfileRepository(context);
}
return userProfileRepository;
}
}
public void Save()
{
context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
... and here is the HttpPost Edit method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserProfileEditViewModel model)
{
try
{
if (ModelState.IsValid)
{
var clientUserProfile = new UserProfile();
clientUserProfile.UserId = model.UserId;
clientUserProfile.UserName = model.UserName;
clientUserProfile.FirstName = model.FirstName;
clientUserProfile.LastName = model.LastName;
clientUserProfile.Email = model.Email;
clientUserProfile.RowVersion = model.RowVersion;
clientUserProfile.Roles = new List<Role>();
foreach(var role in model.Roles)
{
if (role.Assigned)
{
clientUserProfile.Roles.Add(new Role
{
RoleId = role.RoleId,
RoleName = role.RoleName,
RowVersion = role.RowVersion,
});
}
}
unitOfWork.UserProfileRepository.UpdateUserProfile(clientUserProfile);
unitOfWork.Save();
return RedirectToAction("Details", new { id = clientUserProfile.UserId });
}
}
catch (DataException ex)
{
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
return View(model);
}
Does anyone have any idea why this isn't working? Or could suggest a tutorial somewhere that actually works. Any help, as always, is greatly appreciated.
Instead of creating a new UserProfile in your controller, get the UserProfile from the repository, modify its fields, then send it back to UpdateUserProfile and call Save.
Finally found that I had it totally wrong in the first place. I wasn't changing the relationships at all. I've included the code below, which allows me to attach the parent entity as modified and then mark the relationships as added and deleted as required
public void UpdateUserProfile(UserProfile userProfile)
{
context.Entry(userProfile).State = EntityState.Modified;
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
foreach (var role in userProfile.Roles)
{
context.Entry(role).State = EntityState.Unchanged;
}
var databaseRoleIds = context.Roles
.Where(r => r.UserProfiles
.Any(u => u.UserId == userProfile.UserId))
.Select(r => r.RoleId)
.ToList();
var clientRoleIds = userProfile.Roles.Select(r => r.RoleId).ToList();
var removedRoleIds = databaseRoleIds.Except(clientRoleIds).ToList();
var addedRoleIds = clientRoleIds.Except(databaseRoleIds).ToList();
foreach (var roleId in removedRoleIds)
{
var role = context.Roles.Find(roleId);
objectContext
.ObjectStateManager
.ChangeRelationshipState(userProfile, role, u => u.Roles, EntityState.Deleted);
}
foreach (var roleId in addedRoleIds)
{
var role = context.Roles.Find(roleId);
objectContext
.ObjectStateManager
.ChangeRelationshipState(userProfile, role, u => u.Roles, EntityState.Added);
}
}
Related
I have created an API that is using EF Core with a repository pattern and I have few questions:
Post method receives an email address and verify whether user exists on not.
If an email address does not exist in the User table, get the guest access details from the AccessManagement table and save in Entitlement table and return the details
If the entry exists, get the user access details and return them
IGeneralRepository:
public interface IGenrealRepository<TEntity> where TEntity : class , new()
{
IQueryable<TEntity> GetAll();
Task<TEntity> AddAsync(TEntity entity);
Task<TEntity[]> AddRangeAsync(TEntity[] entity);
TEntity Update(TEntity entity);
Task<int> CompleteAsync();
}
General repository:
public class GeneralRepository<TEntity> : IGenrealRepository<TEntity> where TEntity : class, new()
{
private MyDbContext _myDbContext;
public GeneralRepository(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
public async Task<TEntity> AddAsync(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(AddAsync)} entity must not be null");
}
try
{
await _myDbContext.AddAsync(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be saved: {ex.Message}");
}
}
public async Task<TEntity[]> AddRangeAsync(TEntity[] entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(AddRangeAsync)} entity must not be null");
}
try
{
await _myDbContext.AddRangeAsync(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be saved: {ex.Message}");
}
}
public async Task<int> CompleteAsync()
{
return await _myDbContext.SaveChangesAsync();
}
public IQueryable<TEntity> GetAll()
{
try
{
return _myDbContext.Set<TEntity>();
}
catch (Exception ex)
{
throw new Exception($"Couldn't retrieve entities: {ex.Message}");
}
}
public TEntity Update(TEntity entity)
{
try
{
_myDbContext.Update<TEntity>(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be updated: {ex.Message}");
}
}
}
IUserService:
public interface IUserService
{
Task<User> CreateUser(string emailId);
Task<int> Complete();
}
UserService implementation:
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
private readonly IAccessManagementRepository _accessManagementRepository;
public UserService(IUserRepository userRepository, IAccessManagementRepository accessManagementRepository)
{
_userRepository = userRepository;
_accessManagementRepository = accessManagementRepository;
}
public async Task<int> Complete()
{
return await _userRepository.CompleteAsync();
}
public async Task<User> CreateUser(string emailId)
{
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
if (user == null)
{
var entitlements = await _userAccessRepository.GetAll()
.Where( x => x.Default == true)
.Select( x => new UserEntitlement() {
Id = x.Id,
AccessName = x.AccessName
}).ToListAsync();
//saving User and Entitlement
user = new User()
{
EmailId = emailId,
UserEntitlements = entitlements
};
user = await _userRepository.AddAsync(user);
}
else
{
// Getting current User Entitlement
var entitlements = await _userRepository.GetAllUserEntitilements();
var entitlement = entitlements.Find(x => x.UserId == user.UserId);
user.UserEntitlements = entitlements;
}
return user;
}
}
API call:
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] User user)
{
var result = await _userService.CreateUser(user.EmailId);
await _userService.Complete();
return CreatedAtAction(nameof(GetUser), new { emailId = result.EmailId }, result);
}
Questions:
Is my method UserService.CreateUser() implementation correct? Any better approach?
Is the below code is the best approach to filter?
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
How to get data from User and Entitlement table at one stretch? Something like below Include but can not use include because of an error
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.Include<UserEntitlement>()
.FirstOrDefault();
How to do insert to one table and update to another table in a single transaction?
Leo,
I prefer doing the validation of the email outside the CreateUser function
This comes with another function where you could add to IUserService where you can get the user by email GetUserByEmail.
Doing that you can possibly return a proper error or validation message before invoking the CreateUser at the API Call
For example
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] User user)
{
var user = await _userService.GetUserByEmail(user.EmailId);
// or var userRegistered = await _userService.UserExistsByEmail(user.EmailId) returning a bool
// user registered?
if (user)
{
// The user already exists, return an error or
// You could update the UserEntitlements here or you could
// make an HttpPut where the user is updated do nothing here
}
....
}
An example
var user = _userRepository.GetAll()
.Include(x => x.UserEntitlements)
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
You can do it using UnitOfWork
Repository Pattern and Unit of Work
What I want is create caching proxies for all my components decorated with some attributes. So, I made Autofac Module like this:
public class CachingModule : Autofac.Module
{
private readonly ProxyGenerator generator;
public CachingModule()
{
generator = new ProxyGenerator();
}
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
var type = registration.Activator.LimitType;
if (type.GetCustomAttribute<CachedAttribute>(true) != null
|| type.GetMethods().Any(m => m.GetCustomAttribute<CachedAttribute>(true) != null))
{
registration.Activating += (s, e) =>
{
var proxy = generator.CreateClassProxyWithTarget(e.Instance.GetType(),
e.Instance,
interceptors: e.Context.Resolve<IEnumerable<CacheInterceptor>>().ToArray());
e.ReplaceInstance(proxy);
};
}
}
}
What I can't get to work is: I can't create proxy instances with parametrized constructors, is there any way to do this?
Ok, I've managed to get it all working, so for anyone interested here is a sample of proxy generation with non-default constructors
public class CachingModule : Autofac.Module
{
private readonly ProxyGenerator generator;
public CachingModule()
{
generator = new ProxyGenerator();
}
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
var type = registration.Activator.LimitType;
if (type.GetCustomAttribute<CachedAttribute>(true) != null
|| type.GetMethods().Any(m => m.GetCustomAttribute<CachedAttribute>(true) != null))
{
var ctors = type.GetConstructors();
registration.Activating += (s, e) =>
{
if (e.Instance is IProxyTargetAccessor)
{
return;
}
var param = new List<object>();
var infos = ctors.First().GetParameters();
if (ctors.Length > 0 && infos.Length > 0)
{
foreach (var p in e.Parameters)
{
foreach (var info in infos)
{
if (p.CanSupplyValue(info, e.Context, out var valueProvider))
{
param.Add(valueProvider());
}
}
}
}
var instance = e.Instance;
var toProxy = instance.GetType();
var proxyGenerationOptions = new ProxyGenerationOptions(new CacheProxyGenerationHook());
var proxy = generator.CreateClassProxyWithTarget(toProxy,
instance,
proxyGenerationOptions,
param.ToArray(),
interceptors: e.Context.Resolve<IEnumerable<CacheInterceptor>>().ToArray());
e.ReplaceInstance(proxy);
};
}
}
}
I want to seed the default DB with an admin user before I start the project on .NET Core Default MVC application. The code is as below:
public void SeedDb(ApplicationDbContext Context, IServiceProvider ServiceProvider, IConfiguration Configuration)
{
if (Context.Users.Count() > 0) return;
var UserManager = ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var ApplicationUser = new ApplicationUser()
{
Email = Configuration["Email"],
NormalizedEmail = Configuration["Email"],
LockoutEnabled = false,
NormalizedUserName = Configuration["Email"],
SecurityStamp = "579355dd - a64c - 498d - a0b5 - 9e55754c9109",
EmailConfirmed = true,
ConcurrencyStamp = null,
Id = "977ec1a5-1ae7-4658-952a-6b5dccd75a85",
PasswordHash ="",
PhoneNumber = "333333333333",
LockoutEnd = null,
AccessFailedCount = 1,
PhoneNumberConfirmed = true,
TwoFactorEnabled = false,
UserName = Configuration["Email"]
};
var Password = HashPassword(ApplicationUser, Configuration["Password"]);
if (VerifyHashedPassword(ApplicationUser, Password, Configuration["Password"]) == PasswordVerificationResult.Success)
{
ApplicationUser.PasswordHash = Password;
}
Context.Users.Add(ApplicationUser);
Context.SaveChanges();
var RoleManager = ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
string[] Roles = { "Admin", "Manager", "User" };
foreach (string RoleName in Roles) {
RoleManager.CreateAsync(new IdentityRole(RoleName));
}
var Admin = Context.Users.SingleOrDefault(m => m.Email == Configuration["Email"]);
var Role = Context.Roles.SingleOrDefault(m => m.Name == Configuration["Role"]);
IdentityUserRole<string> UserRole = new IdentityUserRole<string>() { UserId = Admin.Id, RoleId = Role.Id };
Context.UserRoles.Add(UserRole);
Context.SaveChanges();
}
Everything runs perfect except I can't seed the UserRole DB with Data. From DBContext I add IdentityUserRole entity and save the changes to DB. Although nothing passed under the DB. Any suggestion?
Create a class named StartupDbInitializer:
using System;
using System.Collections.Generic;
using System.Linq;
using Core.Entities;
using Microsoft.AspNetCore.Identity;
namespace Core.Startups
{
public class StartupDbInitializer
{
private const string AdminEmail = "admin#admin.com";
private const string AdminPassword = "StrongPasswordAdmin123!";
private static readonly List<IdentityRole> Roles = new List<IdentityRole>()
{
new IdentityRole {Name = "Admin", NormalizedName = "ADMIN", ConcurrencyStamp = Guid.NewGuid().ToString()}
};
public static void SeedData(ApplicationDbContext dbContext, UserManager<User> userManager)
{
dbContext.Database.EnsureCreated();
AddRoles(dbContext);
AddUser(dbContext, userManager);
AddUserRoles(dbContext, userManager);
}
private static void AddRoles(ApplicationDbContext dbContext)
{
if (!dbContext.Roles.Any())
{
foreach (var role in Roles)
{
dbContext.Roles.Add(role);
dbContext.SaveChanges();
}
}
}
private static async void AddUser(ApplicationDbContext dbContext, UserManager<User> userManager)
{
if (!dbContext.Users.Any())
{
var user = new User {
UserName = AdminEmail,
Email = AdminEmail,
IsEnabled = true,
EmailConfirmed = true,
};
await userManager.CreateAsync(user, AdminPassword);
}
}
private static void AddUserRoles(ApplicationDbContext dbContext, UserManager<User> userManager)
{
if (!dbContext.UserRoles.Any())
{
var userRole = new IdentityUserRole<string>
{
UserId = dbContext.Users.Single(r => r.Email == AdminEmail).Id,
RoleId = dbContext.Roles.Single(r => r.Name == "Admin").Id
};
dbContext.UserRoles.Add(userRole);
dbContext.SaveChanges();
}
}
}
}
Then call it in your Startup's Configure method:
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ApplicationDbContext dbContext,
UserManager<User> userManager,
)
{
// rest of code...
StartupDbInitializer.SeedData(dbContext, userManager);
}
Above, I inject my DbContext and UserManager<T>.
Try this line... it must work.
ApplicationUser user = await _usermanager.FindByEmailAsync("your.email#mymail.com");
if (!await _usermanager.IsInRoleAsync(user, "Admin"))
{
await _usermanager.AddToRoleAsync(user, "Admin");
}
When you tried it and it works, change it to your config parameters if you prefer them. It's not that hard to get it to work, you have everything you need in UserManager and RoleManager classes.
I still say you have to check if the role exist in table before you insert it, I got all my roles populated every time I run the application before I added the check.
if ((await _roleManager.FindByNameAsync("Admin")) == null)
{
await _roleManager.CreateAsync(new IdentityRole { Name = "Admin" });
}
I have two entities (TaskStep and Role) with many to many relation, I use a view model (named StepViewModel) to display taskstep and it's allowed Roles, StepViewModel is :
public class StepViewModel
{
public StepViewModel()
{
}
public TaskStep TaskStep { get; set; }
public virtual ICollection<AllowedRole> AllowedRole { get; set; }
}
and I use this method to display taskstep:
public static StepViewModel GetTaskStepData(int stepId)
{
using (var context = new WFContext())
{
return context.TaskSteps.Where(ts => ts.ID == stepId).Include(ts => ts.Roles)
.Select(ts => new StepViewModel()
{
AllowedRole = context.Roles.Select(r => new AllowedRole()
{
Role = r,
Allowed = ts.Roles.Select(x => x.ID).Contains(r.ID)
}).ToList(),
TaskStep = ts
})
.SingleOrDefault();
}
}
but when I save the taskstep, it's roles changes don't save. My save method is :
public static void SaveTaskStep(StepViewModel stepViewModel)
{
using (var context = new WorkFlowContext())
{
//TaskStep taskStep = context.TaskSteps.Where(ts => ts.ID == stepViewModel.TaskStep.ID).Include(ts => ts.Roles).SingleOrDefault();
TaskStep taskStep = stepViewModel.TaskStep;
List<int> aRolesIDs = stepViewModel.AllowedRole.Where(ar => ar.Allowed == true).Select(ar => ar.Role.ID).ToList();
List<Role> roles = context.Roles.Where(r => aRolesIDs.Contains(r.ID)).Include(r => r.AllowedTaskSteps).ToList();
taskStep.Roles.Clear();
foreach (Role role in roles)
{
if (context.Entry(role).State == EntityState.Detached)
context.Roles.Attach(role);
taskStep.Roles.Add(role);
}
if (taskStep.ID == 0)
context.TaskSteps.Add(taskStep);
else
context.Entry(taskStep).State = EntityState.Modified;
context.SaveChanges();
}
}
if I get taskstep from db with include roles (like first -comment- line in save method), it work's well. but in this way, I have to make all taskstep field's changes manually. In addition when I assign taskstep to stepviewmodel,I use "include" but it's still doesn't work.what can I do to save stepViewModel roles?
context.TaskSteps.Attach(taskStep) should work in your case
SaveTaskStep method:
using (var context = new WorkFlowContext())
{
TaskStep taskStep = stepViewModel.TaskStep;
if (taskStep.ID == 0)
context.TaskSteps.Add(taskStep);
else
context.TaskSteps.Attach(taskStep); //from this point EF start change tracking
//I just copy-pasted this part your code assuming it's correct
List<int> aRolesIDs = stepViewModel.AllowedRole.Where(ar => ar.Allowed == true).Select(ar => ar.Role.ID).ToList();
List<Role> roles = context.Roles.Where(r => aRolesIDs.Contains(r.ID)).Include(r => r.AllowedTaskSteps).ToList();
taskStep.Roles.Clear();
foreach (Role role in roles)
{
if (context.Entry(role).State == EntityState.Detached)
context.Roles.Attach(role);
taskStep.Roles.Add(role);
}
context.SaveChanges();
} // end of using (var context
I have a generic repository for my Entities, all my entities (generated by the Code generation Item) have a personalized partial that implements an IID interface, at this point all of my entites must have a Int32 Id property.
So, my problems is with the update, here is my code
public class RepositorioPersistencia<T> where T : class
{
public static bool Update(T entity)
{
try
{
using (var ctx = new FisioKinectEntities())
{
// here a get the Entity from the actual context
var currentEntity = ctx.Set<T>().Find(((BLL.Interfaces.IID)entity).Id);
var propertiesFromNewEntity = entity.GetType().GetProperties();
var propertiesFromCurrentEntity = currentEntity.GetType().GetProperties();
for (int i = 0; i < propertiesFromCurrentEntity.Length; i++)
{
//I'am trying to update my current entity with the values of the new entity
//but this code causes an exception
propertiesFromCurrentEntity[i].SetValue(currentEntity, propertiesFromNewEntity[i].GetValue(entity, null), null);
}
ctx.SaveChanges();
return true;
}
}
catch
{
return false;
}
}
}
Someone can help me? this driving me crazy.
You can use the EF API to update the values of an entity as follows.
public static bool Update(T entity)
{
try
{
using (var ctx = new FisioKinectEntities())
{
var currentEntity = ctx.Set<T>().Find(((BLL.Interfaces.IID)entity).Id);
var entry = ctx.Entry(currentEntity);
entry.CurrentValues.SetValues(entity);
ctx.SaveChanges();
return true;
}
}
catch
{
return false;
}
}