I have the following code:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("AssetContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static ApplicationUserManager Create(
IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context)
{
ApplicationDbContext dbContext = context.Get<ApplicationDbContext>();
return Create(options, dbContext);
}
.....
}
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
then I have the controller method:
public async Task<ActionResult> EditUser(UserEditViewModel model)
{
var user = mapper.Map<UserEditViewModel, AspNetUser>(model, _db.AspNetUsers.Where(p => p.UserName == model.UserName).FirstOrDefault());
user.CompanyId = null;
user.LockoutEnabled = false;
user.LockoutEndDateUtc = null;
_db.SaveChanges();
var roles = UserManager.GetRoles(user.Id);
UserManager.AddToRole(user.Id, model.RoleName);
UserManager.RemoveFromRoles(user.Id, roles.ToArray());
return RedirectToAction("UserList");
}
(please, don't say me about DI, I'm not an owner of code :) )
Problem is that all changes in user :
user.CompanyId = null;
user.LockoutEnabled = false;
user.LockoutEndDateUtc = null;
is rewritten when I call
UserManager.AddToRole(user.Id, model.RoleName);
why it happens?
UserManager directly depends on UserStore and that directly depends on IdentityDbContext. Every operation in UserManager that involves access to db will be using DbContext. Especially the write operations in UserManager will be calling dbContext.SaveChangesAsync().
If you have the same instance of DbContext used, you will get SaveChangesAsync() called on every identity-related write and that will commit all other uncommited changes related to this context.
So what you describe is a consequence of having the same instance of DbContext and calling to save changes (though deep inside of Identity framework).
UPD I think I've misread your question. Seems like you have 2 instances of DbContext that play a tug-of-war. You have your own instance and another instance that is part of Identity. When you call _db.SaveChanges() you are saving changes in your context and it is flushed to a DB. At the same time you have another instance of DbContext that does not know anything about modifications that you have done already. And it tracks instance of that user (somewhere else) and keeps internal information about that user, but it does not query for an updated state of the related record in DB. And when you call UserManager.AddToRole that state is flushed back to DB again, but with the old values.
So your issue is really having 2 DbContexts.
Related
I am working on adding Entity Framework to our web app, asp.net MVC 5, but I am having a hardtime saving changes and adding to the database. I set up UnitOfWork with a generic BaseRepository, and I have tried a few things attempting to get this to work. first, I thought I could inject, with AutoFac, my repo in UnitOfWork like so
public UnitOfWork(IServiceItem serviceItem
, ITechServiceItem techServiceItem
, ITechnicianTime technicianTime
, ISproc sproc
, IRepairOrder repairOrder
, ICustomer customer
, IRepairOrderStatus repairOrderStatus
, IRepairOrderUnit repairOrderUnit
, IFiles files
, IPartInventory partInventory
, IRepairOrderItems repairOrderItems
)
{
RepairOrderItems = repairOrderItems;
PartInventory = partInventory;
Files = files;
RepairOrderUnit = repairOrderUnit;
RepairOrderStatus = repairOrderStatus;
RepairOrder = repairOrder;
Customer = customer;
Sproc = sproc;
ServiceItem = serviceItem;
TechServiceItem = techServiceItem;
TechnicianTime = technicianTime;
}
and my BaseRepo is like
public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected DataDbContext _db;
public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected DataDbContext _db;
internal void GetData()
{
if (_db == null)
{
string accountNumber = HttpContext.Current.User.Identity.GetCompanyAccountNumber();
var connectionToken = ConfigurationManager.AppSettings["LoginSplitToken"];
_db = new DataDbContext(ConfigurationManager.ConnectionStrings["NameOfConnString"].ConnectionString.Replace(connectionToken, accountNumber));
}
}
public TEntity Get(int id)
{
return _db.Set<TEntity>().Find(id);
}
public IEnumerable<TEntity> GetAll()
{
return _db.Set<TEntity>().ToList();
}
public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
{
return _db.Set<TEntity>().Where(predicate);
}
public void Add(TEntity entity)
{
_db.Set<TEntity>().Add(entity);
}
public void AddRange(IEnumerable<TEntity> entities)
{
_db.Set<TEntity>().AddRange(entities);
}
public void Remove(TEntity entity)
{
_db.Set<TEntity>().Remove(entity);
}
public void RemoveRange(IEnumerable<TEntity> entities)
{
_db.Set<TEntity>().RemoveRange(entities);
}
public int CompleteData()
{
return _db.SaveChanges();
}
public TEntity Get(int id)
{
return _db.Set<TEntity>().Find(id);
}
public IEnumerable<TEntity> GetAll()
{
return _db.Set<TEntity>().ToList();
}
public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
{
return _db.Set<TEntity>().Where(predicate);
}
public void Add(TEntity entity)
{
_db.Set<TEntity>().Add(entity);
}
public void AddRange(IEnumerable<TEntity> entities)
{
_db.Set<TEntity>().AddRange(entities);
}
public void Remove(TEntity entity)
{
_db.Set<TEntity>().Remove(entity);
}
public void RemoveRange(IEnumerable<TEntity> entities)
{
_db.Set<TEntity>().RemoveRange(entities);
}
public int CompleteData()
{
return _db.SaveChanges();
}
}
and my StartUp.Configuration
public void Configuration(IAppBuilder app)
{
var builder = new ContainerBuilder();
HttpConfiguration config = GlobalConfiguration.Configuration;
// REGISTER DEPENDENCIES
builder.RegisterType<EverLogicDbContext>().AsSelf().InstancePerRequest();
builder.RegisterType<ApplicationUserManager>().AsSelf().InstancePerRequest();
builder.RegisterType<ApplicationSignInManager>().AsSelf().InstancePerRequest();
builder.Register(c => HttpContext.Current.GetOwinContext().Authentication).InstancePerRequest();
builder.Register(c => HttpContext.Current.User).InstancePerRequest();
builder.Register(c => app.GetDataProtectionProvider()).InstancePerRequest();
builder.RegisterType<ApplicationUserStore>().As<IUserStore<EverLogicMamber, int>>()
.WithParameter(new TypedParameter(typeof(ISecurityOfWork), new SecurityOfWork(new SecurityDbContext())))
.InstancePerRequest();
//Database
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
builder.RegisterType<SecurityOfWork>().As<ISecurityOfWork>().InstancePerRequest();
//Service
builder.RegisterType<TechnicianTimeService>().As<ITechnicianTimeService>().InstancePerRequest();
builder.RegisterType<PartService>().As<IPartService>().InstancePerRequest();
builder.RegisterType<TechServiceItemService>().As<ITechServiceItemService>().InstancePerRequest();
//Repo
builder.RegisterType<Company>().As<ICompany>().InstancePerRequest();
builder.RegisterType<Views>().As<IViews>().InstancePerRequest();
builder.RegisterType<RepairOrderItems>().As<IRepairOrderItems>().InstancePerRequest();
builder.RegisterType<PartInventory>().As<IPartInventory>().InstancePerRequest();
builder.RegisterType<Files>().As<IFiles>().InstancePerRequest();
builder.RegisterType<TechDashboardService>().As<ITechDashboardService>().InstancePerRequest();
builder.RegisterType<RepairOrderUnit>().As<IRepairOrderUnit>().InstancePerRequest();
builder.RegisterType<RepairOrderStatus>().As<IRepairOrderStatus>().InstancePerRequest();
builder.RegisterType<Customer>().As<ICustomer>().InstancePerRequest();
builder.RegisterType<ServiceItem>().As<IServiceItem>().InstancePerRequest();
builder.RegisterType<RepairOrder>().As<IRepairOrder>().InstancePerRequest();
builder.RegisterType<Sproc>().As<ISproc>().InstancePerRequest();
builder.RegisterType<TechServiceItem>().As<ITechServiceItem>().InstancePerRequest();
builder.RegisterType<TechnicianTime>().As<ITechnicianTime>().InstancePerRequest();
// REGISTER CONTROLLERS SO DEPENDENCIES ARE CONSTRUCTOR INJECTED
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(config);
builder.RegisterWebApiModelBinderProvider();
var container = builder.Build();
// REPLACE THE MVC DEPENDENCY RESOLVER WITH AUTOFAC
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
app.UseAutofacMiddleware(container);
app.UseAutofacMvc();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
ConfigureAuth(app);
}
But with this set up, the database does not update or add new entitys.
Then i tryed removing Dependcy injection from UnitOfWork and set UnitOfWork up like
protected DataDbContext _db;
public UnitOfWork(DataDbContext context)
{
GetData();
RepairOrderItems = new RepairOrderItems(_db);
PartInventory = new PartInventory(_db);
Files = new Files(_db);
RepairOrderUnit = new RepairOrderUnit(_db);
RepairOrderStatus = new RepairOrderStatus(_db);
RepairOrder = new RepairOrder(_db);
Customer = new Customer(_db);
Sproc = new Sproc(_db);
ServiceItem = new ServiceItem(_db);
TechServiceItem = new TechServiceItem(_db);
TechnicianTime = new TechnicianTime(_db);
}
internal void GetData()
{
if (_db == null)
{
string accountNumber = HttpContext.Current.User.Identity.GetCompanyAccountNumber();
var connectionToken = ConfigurationManager.AppSettings["LoginSplitToken"];
_db = new DataDbContext(ConfigurationManager.ConnectionStrings["NameOfConnString"].ConnectionString.Replace(connectionToken, accountNumber));
}
}
and moving SaveChanges from the BaseRepo to UnitOfWork, but still nothing is saving or adding to the database.
What am i missing????
TL;DR the problem is that all your repositories are using separate, independent DbContexts, so the DbContext injected into your UnitOfWork has no pending changes when you call SaveChanges on it, so that's why you aren't seeing any change to the database.
In order for the Unit of Work to function correctly, your UnitOfWork class, and all the repository classes which your code needs to perform data persistence, must all share the same DbContext instance. In your code, it's clear that each repository has a factory method to create it's own, independent DbContext instance.
Remove the GetData() factory method from your BaseRepository class, and instead, require an instance of your EverLogicDbContext instance to injected to the constructor of BaseRepository by AutoFac. This will require that all your Repository subclasses also need to have a constructor accepting this same EverLogicDbContext.
As per your last edit, the UnitOfWork class must accept the same, shared EverLogicDbContext that the repositories use. Since you've tagged with asp.net-mvc then RequestPerInstance lifetime scope is correct for your scenario.
Your UnitOfWork class needs to control the SaveChanges(Async) method, so remove the CompleteData method from the BaseRepository class.
As you already seem to have done, the DbContext needs to be registered InstancePerRequest:
builder.RegisterType<EverLogicDbContext>().AsSelf().InstancePerRequest();
If all this is tied together correctly:
AutoFac will create an instance of your concrete DbContext the first time it is needed during processing of each Request.
All Repositories will then share the same DbContext instance for the lifetime of the Request, and the DbContext will track interim changes made by your services.
The UnitOfWork injected into your main "business logic" (e.g. Controller, or Orchestrator / Handler) will then be able to Commit the actions taken by simply calling SaveChangesAsync on the shared DbContext. This will all happen under a single database connection, so will be a lightweight transaction.
As per other comments above, IMO Entity Framework is a already high level framework with transactional support built-in, so there's little point in over-engineering a "UnitOfWork" pattern if all the ACID activity will be conducted against the same Database (and can be wrapped into the same DbContext).
I have looked around on StackOverflow for a solution to my problem. Though I don't think this is a unique problem, I haven't been able to find a good solution.
In my WPF application, in my viewmodels, I need to call some services to return some data. These services get injected with UnitOfWork which in turn gets injected with the DbContext. This dbcontext that get injected into the UnitOfWork should differ based on some criteria.
I am having trouble doing the IoC container registrations the right way and injecting the right DbContext at runtime. So, if someone can please fill in the blanks (in the unity registrations as well as it's usage). I have some inline comments in the following code where I am in trouble and need help. Thanks.
If someone can replace my Registration code the right way and also educate me how to use it in my WPF ViewModel class, that would be truly great! Thanks.
One final note: If you find coding errors in this code, please don't start wondering how does this even compile? The code here is not my real code. To simplify things, I just wrote them up. But it does resemble very closely to my real app code.
public interface IDBContext{}
public interface IUnitOfWork{}
public interface ISomeEntityService{}
public interface IRepository<T> where T : class
{ T GetSingle( Expression<Func<T, bool>> predicate ); }
public class DBContext1 : IDBContext
{
public DBContext1(connString) : base(connString){}
}
public class DBContext2 : IDBContext
{
public DBContext2(connString) : base(connString){}
}
public class Repository<T> : IRepository<T> where T : class
{
private readonly IDBContext context;
private readonly IDbSet<T> dbSet;
public Repository(IDBContext ctx)
{
context = ctx;
dbSet = ((DbContext)context).Set<T>();
}
public T GetSingle( Expression<Func<T, bool>> predicate )
{
return ((DbContext)context).Set<T>().SingleOrDefault(predicate);
}
}
public class UnitOfWork : IUnitOfWork
{
IDBContext ctx;
private Dictionary<string, dynamic> repositories;
public UnitOfWork(IDBContext context)
{
ctx = context;
}
public IRepository<T> Repository<T>() where T : class
{
if (repositories == null)
repositories = new Dictionary<string, dynamic>();
var type = nameof(T);
if (repositories.ContainsKey(type))
return (IRepository<T>)repositories[type];
var repositoryType = typeof(Repository<>);
repositories.Add(type, Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), ctx));
return repositories[type];
}
public int SaveChanges()
{
return ctx.SaveChanges();
}
}
public class MyUnityBootstrapper : UnityBootstrapper
{
protected override void ConfigureContainer()
{
Container.RegisterType<IDBContext, DBContext1>("Context1");
Container.RegisterType<IDBContext, DBContext2>("Context2");
Container.RegisterType(typeof(IRepository<>), typeof(Repository<>));
Container.RegisterType<IUnitOfWork, UnitOfWork>();
}
}
public class SomeEntityService : ISomeEntityService
{
private IUnitOfWork uow;
public ConsumerService( IUnitOfWork _uow )
{ uow = _uow; }
public SomeEntity GetSomeData( int id )
{
return uow.Repository<SomeEntity>().GetSingle( x => x.Id == id);
}
}
public class SomeViewModel : BindableBase
{
private readonly ISomeEntityService someService;
public SomeViewModel( ISomeEntityService _someService)
{
// when I call someService, I want to make sure it is using either
// DBContext1 or DBContext2 based on some condition I can set here.
// This is where I am totally stuck.
someService = _someService;
}
// get the repository instance with an id of 1000
someService.GetSomeData( 1000 );
}
/*
I could do something like this. But I am afraid, I am violating
two of the best practices recommendations.
1. I am creating a dependency to my IoC Container here.
2. I am using the container as a Service Locator
*/
public class SomeViewModel : BindableBase
{
private readonly ISomeEntityService someService;
public SomeViewModel()
{
var container = SomeHowGetTheContainer();
/*
1. Call Container.Resolve<IDBContext>(with the required context);
2. Use the retrieved context to inject into the UnitOfWork
3. Use the retrieved UnitOfWork to inject into the service
But that would be like throwing everything about best practices to the wind!
*/
someService = container.Resolve<ISomeEntityService>( /*do some magic here to get the right context*/)
}
// get the repository instance with an id of 1000
someService.GetSomeData( 1000 );
}
Add a factory like this that resolves your ISomeEntityService:
public MySomeEntityServiceFactory
{
public MySomeEntityServiceFactory( IUnityContainer container )
{
_container = container;
}
public ISomeEntityService CreateSomeEntityService( bool condition )
{
return _container.Resolve<ISomeEntityService>( condition ? "VariantA" : "VariantB" );
}
private readonly IUnityContainer _container;
}
and add two named bindings like:
_container.RegisterType<ISomeEntityService, SomeEntityService>( "VariantA", new InjectionConstructor( new ResolvedParameter<IDBContext>( "VariantA" ) ) );
_container.RegisterType<ISomeEntityService, SomeEntityService>( "VariantB", new InjectionConstructor( new ResolvedParameter<IDBContext>( "VariantB" ) ) );
For IUnitOfWork, you can add a similar factory that resolves the unit of work, and call it in SomeEntityService's constructor passing in the IDBContext...
Those factories are additional dependencies themselves, btw...
I'm developing multitenant application. I use separate databases for each tenant. UserData are for each tenant in separate database TOO.
My problem is how can i create admin account for each tenant in "custom" database independently on DI. In MVC 5 was possible to instantiate UserManager base on UserStore(connection string). But UserManager in mvc6 depends on HttpContext... No documentation found...
Exist please some way how to do it??? I need in mvc 6 something like this in mvc 5:
UserStore<TenantUser> store = new UserStore<TenantUser>(new TenantDbContext("CONNECTION STRING")); //!!! NO POSSIBLE CREATE USER IN CUSTOM DATABASE
UserManager<TenantUser> t = new UserManager<TenantUser>(store);
t.CreateAsync(user, password);
Update:
public class TenantDbContext : IdentityDbContext<TenantUser, TenantRole, Guid>
{
private string _connectionString { get; set; }
private readonly IHttpContextAccessor _contextAccessor;
private readonly ApplicationDbContext _applicationDbContext;
//THIS SUB UNCOMENT ONLY IF CREATE MIGRATIONS (dnx ef...)
/*
public TenantDbContext(DbContextOptions<TenantDbContext> options) : base(options)
{
this._connectionString = "CONNECTION STRING";
}
*/
public TenantDbContext(DbContextOptions<TenantDbContext> options, IHttpContextAccessor contextAccessor, ApplicationDbContext applicationDbContext) : base(options) {
_contextAccessor = contextAccessor;
_applicationDbContext = applicationDbContext;
TenantResolver resolver = new TenantResolver(_contextAccessor, _applicationDbContext);
string con = resolver.GetConnectionString();
if (con != string.Empty)
{
this._connectionString = con; }
else
{
this._connectionString = "CONNECTION STRING"; //Development connection string
}
}
public TenantDbContext() //Posibility to create TenantDbContext migration and development database with no connectionString in constructor
{
//this._connectionString = "CONNECTION STRING";
}
public TenantDbContext(string ConnectionString)
{
this._connectionString = ConnectionString;
}
public static TenantDbContext Create(string ConnectionString)
{
return new TenantDbContext(ConnectionString);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
I hope that I correctly understand you. Let us we forget about the performance and the caching of multiple connections, which already opened to the databases. I suppose that you have multiple databases, which have the same schema. You need to access the databases using (sharing) the same database context.
I can suggest you two solutions.
The first solution consists from registering one context and reopening it if the one opened co change the connection string.
Let us you have TenantDbContext, which could be opened with different destination databases. For example with
#"Server=(localdb)\mssqllocaldb;Database=TenantDb1;Trusted_Connection=True;"
or
#"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;"
First of all you remove OnConfiguring like
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=TenantDb;Trusted_Connection=True;");
}
which could exist in the definition of TenantDbContext and you use the following code in ConfigureServices of Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
var connection1 = #"Server=(localdb)\mssqllocaldb;Database=TenantDb1;Trusted_Connection=True;";
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<TenantDbContext>(options => options.UseSqlServer(connection1));
services.AddMvc();
...
}
In the way you inject TenantDbContext with one from the database (TenantDb1). Let us the TenantDbContext contains some entity set like Blog for example. Thus your can define some MVC controller in the following way
public class TenantsController : Controller
{
private TenantDbContext _context;
public TenantsController (TenantDbContext context)
{
_context = context;
}
public IActionResult Index() {
var con = _context.Database.GetDbConnection();
// now the con uses either TenantDb2 or TenantDb2
// con.ConnectionString can be used to get or set the
// connection string
string needConStr = #"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;";
if (con.ConnectionString != needConStr) { // can be compared more carefully
_context.Database.CloseConnection();
con.ConnectionString = needConStr;
}
// get some data from the TenantDbContext
var blog = _context.Blog.ToList();
return View(blog);
}
}
The second solution don't need to inject any TenantDbContext using DependencyInjection. Instead of that you need just add one simple constructor to TenantDbContext:
public TenantDbContext(DbContextOptions optionsBuilder): base (optionsBuilder)
{
}
Such simple constructor will allows you to create the context at any time when you need it:
public class TenantsController : Controller
{
public IActionResult Index() {
var contextOptions = new DbContextOptionsBuilder();
contextOptions.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;");
var context = new BloggingContext(contextOptions.Options);
context.Database.OpenConnection();
// get some data from the TenantDbContext
var blog = context.Blog.ToList();
return View(blog);
}
}
I used all the connection strings directly in the code. You can easy modify the above code to get all connection strings from the config file appsettings.json.
Solved.
1. CreateCustomUserStore
public class TenantUserStore : UserStore<TenantUser, TenantRole, TenantDbContext, Guid>
{
public TenantUserStore(TenantDbContext context, IdentityErrorDescriber describer = null): base(context, describer)
{
}
}
And here is code how to instantiate UserManager with custom database:
IUserStore<TenantUser> CustomStore = new TenantUserStore(new TenantDbContext(coonection), null);
UserManager<TenantUser> manager = new UserManager<TenantUser>(CustomStore, _optionsAccessor, _passwordHasher, _userValidators,
_passwordValidators, _keyNormalizer, _errors, _services, _logger, _contextAccessor);
And DI used only for rest of UserManager Constructor:
public class TenantsController : Controller
{
private readonly IHttpContextAccessor _contextAccessor;
private readonly IOptions<IdentityOptions> _optionsAccessor;
private readonly IPasswordHasher<TenantUser> _passwordHasher;
private readonly IEnumerable<IUserValidator<TenantUser>> _userValidators;
private readonly IEnumerable<IPasswordValidator<TenantUser>> _passwordValidators;
private readonly ILookupNormalizer _keyNormalizer;
private readonly IdentityErrorDescriber _errors;
private readonly IServiceProvider _services;
private readonly ILogger<UserManager<TenantUser>> _logger;
public TenantsController(IHttpContextAccessor contextAccessor,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<TenantUser> passwordHasher,
IEnumerable<IUserValidator<TenantUser>> userValidators,
IEnumerable<IPasswordValidator<TenantUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<TenantUser>> logger
)
{
_optionsAccessor = optionsAccessor;
_passwordHasher = passwordHasher;
_userValidators = userValidators;
_passwordValidators = passwordValidators;
_keyNormalizer = keyNormalizer;
_errors = errors;
_services = services;
_logger = logger;
_contextAccessor = contextAccessor;
}
I want to pass the UserID (Forms Authentication) to the DB Context class which will call an overload of SaveChanges(). This over loaded function would first calla stored proc to set DBSession context for auditing and then will call the base SaveChanges().
The question here is what is a good approach to send the User id to this DBContext class (I have made UserId as a property of this class) from the Controller without explicitly calling it every time.
I want something which would be just fired every time and pass on the UserId to DBContext class without writing code for it each time.
public partial class DataContext : DbContext, IDataContext
{
public DataContext()
: base("name=DataContext")
{
}
public DataContext(DbConnection existingConnection, bool contextOwnsConnection) : base(existingConnection, contextOwnsConnection)
{
}
public string UserID { get; set; }
public override int SaveChanges()
{
SetDbSession();
return base.SaveChanges();
}
private void SetDbSession()
{
//Open a connection to the database so the session is set up
this.Database.Connection.Open();
//Set the db session
//ExecuteSqlCommand ot to be used as it will close the connection
using (var cmd = this.Database.Connection.CreateCommand())
{
var parm = cmd.CreateParameter();
parm.ParameterName = "#userId";
parm.Value = UserID ;
cmd.CommandText = "SetDbSession";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add(parm);
cmd.ExecuteNonQuery();
};
}
This is the DB Context class, all I want is to pass UserId into it, in a way which doesn't make it cumbersome to be used across.
You could inject it into the constructor:
public partial class DataContext : DbContext, IDataContext
{
private string _userID;
public DataContext(string userID)
: base("name=DataContext")
{
_userID = userID;
}
public DataContext(string userID, DbConnection existingConnection, bool contextOwnsConnection) : base(existingConnection, contextOwnsConnection)
{
_userID = userID;
}
public override int SaveChanges()
{
SetDbSession();
return base.SaveChanges();
}
private void SetDbSession()
{
//Open a connection to the database so the session is set up
this.Database.Connection.Open();
//Set the db session
//ExecuteSqlCommand ot to be used as it will close the connection
using (var cmd = this.Database.Connection.CreateCommand())
{
var parm = cmd.CreateParameter();
parm.ParameterName = "#userId";
parm.Value = _userID;
cmd.CommandText = "SetDbSession";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add(parm);
cmd.ExecuteNonQuery();
};
}
When creating your DB Context in a controller:
var context = new DataContext(User.Identity.Name);
If you're using ASP.NET MVC and Autofac, one way would be to inject HttpContextBase to your service layers through an abstraction.
public class MyHttpAbstraction :IMyHttpAbstraction {
public MyHttpAbstraction (HttpContextbase httpContextBase) { _httpContextBase = httpContextBase; }
public string GetUserId() {
return _httpContextBase.User.Identity.GetUserId();
}
}
and in your type registrations
builder.Register(c => new HttpContextWrapper(HttpContext.Current)).As<HttpContextBase>().InstancePerHttpRequest();
This way the concrete implementations of your service can depend on MyHttpAbstraction rather than know HttpContextBase directly. You can string along this dependency all the way down to your IDataContext. If you want to go crazy, feel free to abstract DbContext and IDataContext out and create an intermediary class that takes DbContext as a dependency and implements IDataContext:
public class MyDataContext : IDataContext {
public MyDataContext(DbContext context, IMyHttpAbstraction myHttpAbstraction) { _context = context; _myHttpAbstraction = myHttpAbstraction; }
//...feel free to implement
}
Not sure if this is what you're looking for.
I am trying to set the value of a column in aspnetuser table from a different controller (not accountcontroller). I have been trying to access UserManager but I can't figure our how to do it.
So far I have tried the following in the controller I want to use it in:
ApplicationUser u = UserManager.FindById(User.Identity.GetUserId());
u.IsRegComplete = true;
UserManager.Update(u);
This would not compile (I think because UserManager has not been instantiated the controller)
I also tried to create a public method in the AccountController to accept the value I want to change the value to and do it there but I can't figure out how to call it.
public void setIsRegComplete(Boolean setValue)
{
ApplicationUser u = UserManager.FindById(User.Identity.GetUserId());
u.IsRegComplete = setValue;
UserManager.Update(u);
return;
}
How do you access and edit user data outside of the Account Controller?
UPDATE:
I tried to instantiate the UserManager in the other controller like so:
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db));
ApplicationUser u = userManager.FindById(User.Identity.GetUserId());
I the project complied (got a little excited) but when I ran the code I get the following error:
Additional information: The entity type ApplicationUser is not part of the model for the current context.
UPDATE 2:
I have moved the function to the IdentityModel (don't ask I am clutching at straws here) like so:
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public Boolean IsRegComplete { get; set; }
public void SetIsRegComplete(string userId, Boolean valueToSet)
{
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>());
ApplicationUser u = new ApplicationUser();
u = userManager.FindById(userId);
u.IsRegComplete = valueToSet;
return;
}
}
However I am still getting the following:
The entity type ApplicationUser is not part of the model for the current context.
There is also the following class in IdentitiesModels.cs:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
What am I doing wrong here? It feels like I am completely barking up the wrong tree. All I am trying to do is update a column in aspnetuser table from the action of a different controller (i.e not the AccountsController).
If you're using the default project template, the UserManager gets created the following way:
In the Startup.Auth.cs file, there's a line like this:
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
that makes OWIN pipeline instantiate an instance of ApplicationUserManager each time a request arrives at the server. You can get that instance from OWIN pipeline using the following code inside a controller:
Request.GetOwinContext().GetUserManager<ApplicationUserManager>()
If you look carefully at your AccountController class, you'll see the following pieces of code that makes access to the ApplicationUserManager possible:
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
Please note, that in case you need to instantiate the ApplicationUserManager class, you need to use the ApplicationUserManager.Create static method so that you have the appropriate settings and configuration applied to it.
If you have to get UserManager's instance in another Controller just add its parameter in Controller's constructor like this
public class MyController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
public MyController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;;
}
}
But I have to get UserManager in a class that is not controller !
Any help would be appreciated.
UPDATE
I am considering you are using asp.net core
I ran into this same problem and modified my code to pass a reference to the UserManager class from the Controller to the Model:
//snippet from Controller
public async Task<JsonResult> UpdateUser(ApplicationUser applicationUser)
{
return Json(await UserIdentityDataAccess.UpdateUser(UserManager, applicationUser));
}
//snippet from Data Model
public static async Task<IdentityResult> UpdateUser(ApplicationUserManager userManager, ApplicationUser applicationUser)
{
applicationUser.UserName = applicationUser.Email;
var result = await userManager.UpdateAsync(applicationUser);
return result;
}
FOR MVC 5
The steps to access usermanger or createUser outside Account controller is easy. Follow the below steps
Create a controller, consider SuperAdminController
Decorate the SuperAdminController same as the AccountController as below,
private readonly IAdminOrganizationService _organizationService;
private readonly ICommonService _commonService;
private ApplicationSignInManager _signInManager;
private ApplicationUserManager _userManager;
public SuperAdminController()
{
}
public SuperAdminController(ApplicationUserManager userManager, ApplicationSignInManager signInManager)
{
UserManager = userManager;
SignInManager = signInManager;
}
public SuperAdminController(IAdminOrganizationService organizationService, ICommonService commonService)
{
if (organizationService == null)
throw new ArgumentNullException("organizationService");
if (commonService == null)
throw new ArgumentNullException("commonService");
_organizationService = organizationService;
_commonService = commonService;
}
public ApplicationSignInManager SignInManager
{
get
{
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
private set
{
_signInManager = value;
}
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
Create User in Action method
[HttpPost]
public async Task<ActionResult> AddNewOrganizationAdminUser(UserViewModel userViewModel)
{
if (!ModelState.IsValid)
{
return View(userViewModel);
}
var user = new ApplicationUser { UserName = userViewModel.Email, Email = userViewModel.Email };
var result = await UserManager.CreateAsync(user, userViewModel.Password);
if (result.Succeeded)
{
var model = Mapper.Map<UserViewModel, tblUser>(userViewModel);
var success = _organizationService.AddNewOrganizationAdminUser(model);
return RedirectToAction("OrganizationAdminUsers", "SuperAdmin");
}
AddErrors(result);
return View(userViewModel);
}
If you need access to the UserManager outside of a controller you can use the following:
var userStore = new UserStore<ApplicationUser>(new ApplicationDbContext());
var applicationManager = new ApplicationUserManager(userStore);