I am using AspNet.Identity for User Management in my MVC project because I believe it is a great start, now that I have it working (with little changes), I wanted to add an Audit Trail (Track Changes) like my other DbContext that I use.
In IdentityModel.cs, I have the following code that works, but only in certain situations:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
//Can't recall if this was there by default
Configuration.AutoDetectChangesEnabled = true;
}
public override int SaveChanges()
{
//Tell EF to Track Changes
ChangeTracker.DetectChanges();
//More code once I get this working
//
}
}
In my Controller, I have the following:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(EditUserViewModel editUser)
{
var user = await UserManager.FindByIdAsync(editUser.Id);
//Update a property within the User object
user.FirstName = "Updated First Name";
//Save to database
var result = UserManager.Update(user);
//The above saves to database, but doesn't trigger SaveChanges()
//SaveChanges() will be triggered if I call
HttpContext.GetOwinContext().Get<ApplicationDbContext>().SaveChanges();
}
When the above HttpContext.GetOwinContext().Get<ApplicationDbContext>().SaveChanges(); is called, the updated ApplicationUser has an EntityState of Unchanged. Makes sense as it was already saved.
However, I am trying to see how I can utilize the UserManager and still work with SaveChanges().
I also understand that I could write a class that would log all of this myself, but as I expand the ApplicationUser (or ApplicationRole) I would like to avoid the extra coding for the Audit Log.
Any advice or links would help!
Set AutoSaveChanges to false in the constructor of your UserStore (which you would pass to the constructor of the UserManager).
public class MyUserStore : UserStore<User, Role, int, UserLogin, UserRole, UserClaim>
{
private MyDbContext _context;
public MyUserStore(MyDbContext context) : base(context)
{
//Set this to false
AutoSaveChanges = false;
_context = context;
}
}
Now you can call save changes as you normally would and the change tracker will contain the changes you're expecting.
I did however encounter an OptimisticConcurrencyException when I tried to make more than one call to UserManager between calls to SaveChanges. I just called SaveChanges after each UserManager operation.
Related
I am currently developing a backend with ABP (AspNetBoilerplate) AspNetCore 3.6.2 and Entity framework
I want to know if it is possible to prevent the modification of the LastModifierUserTime and LastModifierUserId of an AbpUser for a specific method.
I guess these properties are modified by ABP Auditing because the method LoginAsync() modifies the User entity. I am just trying to know how to disable the Audit in a scope or for a method.
private async Task<AbpLoginResult<Tenant, User>> GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName)
{
// Some code here
if (tenant != null)
{
// TODO: Cancel auditing for this method
loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenant.TenancyName);
}
// Some code here
}
Everytime I try to log in by calling this method in my service, the user entity is modified:
the LastModifierUserId is set to null
the LastModifierUserTime is set to now
I tried to put the [DisableAuditing] attribute on the GetLoginResultAsync method but it didn't do anything
Using [DisableAuditing] attribute won't help you to prevent the setting of LastModifierUserId & LastModifierUserTime properties. These two attributes are set through EF interceptors and EntityAuditingHelper class. You can take a look at the source here
While these properties are automatically set you can consider this as LastLoginTime property. Hopefully, according to its opensource policy you can find the internal procedure at their repository here
All in all, if you need to disable it, you need to overrride LoginAsync method in LogInManager and simply ignore setting those properties. All repositories are injected in the constructor so you might probably need nothing to be injected as a dependency.
public class LogInManager : AbpLogInManager<Tenant, Role, User>
{
public LogInManager(
UserManager userManager,
IMultiTenancyConfig multiTenancyConfig,
IRepository<Tenant> tenantRepository,
IUnitOfWorkManager unitOfWorkManager,
ISettingManager settingManager,
IRepository<UserLoginAttempt, long> userLoginAttemptRepository,
IUserManagementConfig userManagementConfig,
IIocResolver iocResolver,
IPasswordHasher<User> passwordHasher,
RoleManager roleManager,
UserClaimsPrincipalFactory claimsPrincipalFactory)
: base(
userManager,
multiTenancyConfig,
tenantRepository,
unitOfWorkManager,
settingManager,
userLoginAttemptRepository,
userManagementConfig,
iocResolver,
passwordHasher,
roleManager,
claimsPrincipalFactory)
{
}
public override Task<AbpLoginResult<Tenant, User>> LoginAsync(string userNameOrEmailAddress, string plainPassword, string tenancyName = null, bool shouldLockout = true)
{
//Here you can comment this line below and write your own code !
return base.LoginAsync(userNameOrEmailAddress, plainPassword, tenancyName, shouldLockout);
}
}
I have the following code:
public void someMethod(){
...
var accounts = myRepo.GetAccounts(accountId)?.ToList();
...
foreach (var account in accounts)
{
account.Status="INACTIVE";
var updatedAccount = myRepo.AddOrUpdateAccounts(account);
}
}
public Account AddOrUpdateAccounts(Account account){
//I want to compare account in the Db and what is passed in. So get the account from DB
var accountFromDb = myRepo.GetAccounts(account.Id); //this doesn't return whats in the database.
//here accountFromDb.Status is returned as INACTIVE, but in the database the column value is ACTIVE
...
...
}
public IEnumerable<Account> GetAccounts(int id){
return id <= 0 ? null : m_Context.Accounts.Where(x => x.Id == id);
}
Here, inside someMethod() I am calling GetAccounts() that returns data from the Accounts table.
Then I am changing the Status of the account, and calling AddOrUpdateAccounts().
Inside AddOrUpdateAccounts(), I want to compare the account that was passed in and whats in the database. When I call GetAccounts(), it returned a record with STATUS="INACTIVE". I haven't done SaveChanges(). Why didn't GetAccounts() returned the data from the database? In the Db the status is still "ACTIVE"
The repository method should return IQueryable<Account> rather than IEnumerable<Account> as this will allow the the consumer to continue to refine any criteria or govern how the account(s) should be consumed prior to any query executing against the database:
I would consider:
public IQueryable<Account> GetAccountsById(int id){
return m_Context.Accounts.Where(x => x.Id == id);
}
Don't return #null, just the query. The consumer can decide what to do if the data is not available.
From there the calling code looks like:
var accounts = myRepo.GetAccounts(accountId).ToList();
foreach (var account in accounts)
{
account.Status="INACTIVE";
}
Your addOrUpdate wouldn't work:
public Account AddOrUpdateAccounts(Account account){
...
var account = myRepo.GetAccounts(account.Id); //this doesn't return whats in the database.
You pass in the Account as "account" then try declaring a local variable called "account". If you remove the var keyword you would load the DbContext's record over top your modified account and your changes would be lost. Loading the account into another variable isn't necessary as long as the account is still associated with the DbContext.
Edit: After changing the var account = ... statement to look like:
public Account AddOrUpdateAccounts(Account account){
...
var accountToUpdate = myRepo.GetAccounts(account.Id); //this doesn't return whats
accountToUpdate will show the modified status rather than what is in the database because that DbContext is still tracking the reference to the entity that you modified. (account) For instance if I do this:
var account1st = context.Accounts.Single(x => x.AccountId == 1);
var account2nd = context.Accounts.Single(x => x.AccountId == 1);
Console.WriteLine(account1st.Status); // I get "ACTIVE"
Console.WriteLine(account2nd.Status); // I get "ACTIVE"
account1st.Status = "INACTIVE";
Console.WriteLine(account2nd.Status); // I get "INACTIVE"
Both references point to the same instance. It doesn't matter when I attempt to read the Account the 2nd time, as long as it's coming from the same DbContext and the context is tracking instances. If you read the row via a different DbContext, or use AsNoTracking() with all of your reads then the account can be read fresh from the database. You can reload an entity, but if those variables are pointing at the same reference it will overwrite your changes and set the entity back to Unmodified. This can be a little confusing when watching an SQL profiler output because in some cases you will see EF run a SELECT query for an entity, but the entity returned has different, modified values than what is in the database. Even when loading from the tracking cache, EF can still execute queries against the DB in some cases, but it returns the tracked entity reference.
/Edit
When it comes to saving the changes, it really just boils down to calling the SaveChanges on the DbContext that the account is associated. The "tricky" part is scoping the DbContext so that this can be done. The recommended pattern for this is the Unit of Work. There are a few different ones out there, and the one I recommend for EF is Mehdime's DbContextScope, however you can implement simpler ones that may be easier to understand and follow. Essentially a unit of work encapsulates the DbContext so that you can define a scope that repositories can access the same DbContext, then commit those changes at the end of the work.
At the most basic level:
public interface IUnitOfWork<TDbContext> : IDisposable where TDbContext : DbContext
{
TDbContext Context { get; }
int SaveChanges();
}
public class UnitOfWork : IUnitOfWork<YourDbContext>
{
private YourDbContext _context = null;
TDbContext IUnitOfWork<YourDbContext>.Context
{
get { return _context ?? (_context = new YourDbContext("YourConnectionString"); }
}
int IUnitOfWork<YourDbContext>.SaveChanges()
{
if(_context == null)
return 0;
return _context.SaveChanges();
}
public void Dispose()
{
try
{
if (_context != null)
_context.Dispose();
}
catch (ObjectDisposedException)
{ }
}
}
With this class available, and using dependency injection via an IoC container (Autofac, Unity, or MVC Core) you register the unit of work as Instance per Request so that when the controller and repository classes request one in their constructor, they receive the same instance.
Controller / Service:
private readonly IUnitOfWork<YourDbContext> _unitOfWork = null;
private readonly IYourRepository _repository = null;
public YourService(IUnitOfWork<YourDbContext> unitOfWork, IYourRepository repository)
{
_unitOfWork = unitOfWork ?? throw new ArgumentNullException("unitOfWork");
_repository = repository ?? throw new ArgumentNullException("repository");
}
Repository
private readonly IUnitOfWork<YourDbContext> _unitOfWork = null;
public YourService(IUnitOfWork<YourDbContext> unitOfWork)
{
_unitOfWork = unitOfWork ?? throw new ArgumentNullException("unitOfWork");
}
private YourDbContext Context { get { return _unitOfWork.Context; } }
Big Disclaimer: This is a very crude initial implementation to explain roughly how a Unit of Work can operate, it is no way production suitable code. It has limitations, specifically around disposing the DbContext but should serve as a demonstration. Definitely look to implement a library that's already out there and addresses these concerns. These implementations properly manage the DbContext disposal and will manage a scope beyond the context, like a TransactionScope so that their SaveChanges is required even if the unitOfWork.Context.SaveChanges() is called.
With a unit of work available to the Controller/Service and Repository, the code to use the repository and update your changes becomes:
var accounts = myRepo.GetAccountsById(accountId).ToList();
foreach (var account in accounts)
{
account.Status="INACTIVE";
}
UnitOfWork.SaveChanges();
With a proper unit of work it will look more like:
using (var unitOfWork = UnitOfWorkFactory.Create())
{
var accounts = myRepo.GetAccountsById(accountId).ToList(); // Where myRepo can resolve the unit of work via locator.
foreach (var account in accounts)
{
account.Status="INACTIVE";
}
unitOfWork.SaveChanges();
}
This way if you were to call different repos to fetch data, perform a number of different updates, the changes would be committed all in one call at the end and rolled back if there was a problem with any of the data.
In my C# Web API application I've added CreatedDate and CreatedBy columns in all of the tables. Now I want to populate these columns whenever a new record is added in any of the tables.
For this purpose I've overridden SaveChanges and SaveChangesAsync functions in the DbContext class like below:
public class AuthDbContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public override int SaveChanges()
{
AddTimestamps();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync()
{
AddTimestamps();
return await base.SaveChangesAsync();
}
private void AddTimestamps()
{
var entities = ChangeTracker.Entries().Where(x => (x.State == EntityState.Added));
var currentUsername = !string.IsNullOrEmpty(HttpContext.Current?.User?.Identity?.Name)
? HttpContext.Current.User.Identity.Name
: "SYSTEM";
foreach (var entity in entities)
{
foreach (var propName in entity.CurrentValues.PropertyNames)
{
if (propName == "CreatedBy" && entity.State == EntityState.Added)
{
entity.CurrentValues[propName] = currentUsername;
}
else if (propName == "CreatedDate" && entity.State == EntityState.Added)
{
entity.CurrentValues[propName] = DateTime.Now;
}
}
}
}
}
Now when I call SaveChanges or SaveChangesAsync from anywhere in my controllers, HttpContext.Current is assigned and I can get the User name from it by using ttpContext.Current.User.Identity.Name. But when I use UserManager.UpdateAsync function (which internally calls SaveChangesAsync function in our DbContext class) to make changes to underlying user table, HttpContext.Current is set to null.
How can I access HttpContext in this particular case to fetch the User name?
The problem is that with SaveChangesAsync you do not know if you will have access to the HttpContext.Current because you might not be executing on the thread that the request is being served on.
The best way to solve this problem is with DI. You can create an interface and matching class where the implementation relies on HttpContextBase. Configure the DI framework to inject an IUserContext instance into your DbContext and to create a new instance of UserContext per request.
As far as which DI framework to use I am partial to Autofac but there are plenty out there to choose from and mostly have similar functionality.
public interface IUserContext {
bool IsAuthenticated {get;}
// additional properties like user id / name / etc
}
public class UserContext : IUserContext
{
public UserContext(HttpContextBase httpContext) {
this.IsAuthenticated = httpContext.User.Identity.IsAuthenticated;
// any other properties that you want to use later
}
}
I am new with C# and MVC aplications.
I have weird issue with password change in MVC aplication.
The thing is next, when i fill my form and enter old and new password and hit enter, everything seams to went fine but the password is not updated in databse. Then when I do from second try it went fine.
[Authorize]
[HttpPost]
public ActionResult ChangePassword(ChangePasswordModel model)
{
if (ModelState.IsValid)
{
bool changePasswordSucceeded;
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
changePasswordSucceeded = currentUser.ChangePassword(model.OldPassword, model.NewPassword);
if (changePasswordSucceeded)
{
var user = GetUser();
user.PasswordEntropyScore = model.PasswordEntropyScore;
var userRepo = new UserRepository(MvcApplication.DbSession);
userRepo.Update(user);
return this.RedirectToAction("ChangePasswordSuccess");
}
else
{
ModelState.AddModelError(string.Empty, "The current password is incorrect or the new password is invalid.");
}
}
return this.View(model);
}
UserRepository Update() method is here:
public class UserRepository : Repository<User>
{
private Logger logger;
public UserRepository(ISession sesh) : base(sesh)
{
this.logger = LogManager.GetCurrentClassLogger();
}
public new bool Update(User user)
{
if (string.IsNullOrWhiteSpace(user.UserName) || string.IsNullOrWhiteSpace(user.Email))
{
var ex = new ArgumentException("Username or Email cannot be blank");
this.logger.Error("User.Update - username or email was null/empty string.", LoggerHelper.GetErrorString(ex, user));
throw ex;
}
return base.Update(user);
}
}
And repository base.Update() method:
public class Repository<T> : IRepository<T>
where T : Entity
{
protected readonly ISession session;
protected static Logger logger;
public Repository()
{
throw new NotImplementedException("Must instantiate repositories with an ISession");
}
public Repository(ISession sesh)
{
this.session = sesh;
logger = new LogFactory().GetCurrentClassLogger();
}
public bool Update(T entity)
{
this.session.SaveOrUpdate(entity);
this.session.Flush();
return true;
}
}
So all this code went fine when first time I tried to change password but it doens't udapted it in database. Then when I try the second time to change the password then it update in database.
If I change the code to directly call base.Update() method instead to call first UserRepostirory.Update() method as wrapper then it is fine.
Does anyone has idea what the issue is.
there is not much comment in your code to understand what you really did but if you use entity framework you can simply do like below:
entitymodel model= new entitymodel();
var query= model.users.first(o=>userID==inputID);
query.password=inputPassword;
model.SaveChanges();
As per the code I do not see any issues. Is it possible to add try catch block to Repository class Update Method and check if any exception is occurred when you are trying to save for the first time.
Can you check if MvcApplication.DBSession is set before hitting Update method for the first time
There is a possibility of having entity framework local cache which may be updating password to older one. Perform below steps.
Remove below lines from your code.
Change password
Restart application and check if password is changed or not.
If password is changed then clear entity framework local cache(by code).
NOTE: There is no need of updating password in db again. ChangePassword function makes changes to DB directly.
I've encountered what seems to be a common problem: I am updating values in my database, but EF is using its original in-memory copy of the object and these changed values are not reflected in the displayed data. I understand why this is, but I can't figure out a way around it.
The most common solution seems to be to set MergeOptions.NoTracking to turn off change tracking completely (or use the AsNoTracking() extension method when querying) and force a refresh every time the object is accessed, which is fine for my purposes.
I've got a generic base repository which my other repositories inherit from:
public abstract class RepositoryBase<T> where T : class
{
private readonly IDbSet<T> _dbset;
private readonly IUnitOfWork _unitOfWork;
protected RepositoryBase(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_dbset = _unitOfWork.Database.Set<T>();
}
public virtual IQueryable<T> All()
{
return _dbset;
}
// Some other IQueryable methods here (Query, GetByProductCode etc)
public virtual T Get(long id)
{
return _dbset.Find(id);
}
}
And a DbContext like this:
public class Db : DbContext
{
private IDbSet<Product> _products;
public IDbSet<Product> Products
{
get { return _products ?? (_products = DbSet<Product>()); }
}
public virtual IDbSet<T> DbSet<T>() where T : class
{
return Set<T>();
}
public virtual void Commit()
{
base.SaveChanges();
}
}
If I change the All() method of my repository thus:
public virtual IQueryable<T> All()
{
return _dbset.AsNoTracking();
}
I get the desired result - an update in the database is reflected when the page displaying the products is refreshed. However, I can't do this in the Get() method, as that extension method only works on an IQueryable.
Ideally I'd like to turn this off at the DbContext level as I will never need change tracking, but there doesn't seem to be an obvious way to do this, and there is pretty much zero documentation on the subject (unless someone can point me to some? Please!).
I tried adding a constructor to the DbContext with these configuration options disabled:
public Db()
{
base.Configuration.ProxyCreationEnabled = false;
base.Configuration.AutoDetectChangesEnabled = false;
}
But I must admit I'm only guessing as to what they really do (I only found them through looking at the source code), and they don't seem to have any effect anyway.
Any help would be greatly appreciated. If more info/code would help, please let me know.
If you want to force context to get fresh data each time you don't want to use Find method. Find method always query internal storage first. Use this instead:
public virtual T Get(long id)
{
return All().SingleOrDefault(e => e.Id == id);
}
But I don't understand what do you need this? What do you mean by:
an update in the database is reflected
when the page displaying the products
is refreshed
Context is unit of work. It should be used as unit of work - in web application or web service it means creating new context instance per request. In winforms / wpf application it means using context per logical block (per presenter etc). Because of that you should need this only in very specific scenarios but you want it globally. Your description seems like you are reusing context among requests which is completely bad solution. There are no performance costs in recreating context for each request.