DbContext has been disposed - c#

I developed a web application with ASP.NET MVC 4 and SQL Server 2008, I create ContextManager class to have only one database context in all pages.
public static class ContextManager
{
public static HotelContext Current
{
get
{
var key = "Hotel_" + HttpContext.Current.GetHashCode().ToString("x")
+ Thread.CurrentContext.ContextID.ToString();
var context = HttpContext.Current.Items[key] as HotelContext;
if (context == null)
{
context = new HotelContext();
HttpContext.Current.Items[key] = context;
}
return context;
}
}
}
It works properly in most of the pages, but in registration page something goes wrong and my context gone deposed with following error:
The operation cannot be completed because the DbContext has been disposed.
public ActionResult Register ( RegisterModel model )
{
if ( ModelState.IsValid )
{
// Attempt to register the user
try
{
WebSecurity.CreateUserAndAccount( model.UserName, model.Password,
new
{
Email = model.Email,
IsActive = true,
Contact_Id = Contact.Unknown.Id
} );
//Add Contact for this User.
var contact = new Contact { Firstname = model.FirstName, LastName = model.Lastname };
_db.Contacts.Add( contact );
var user = _db.Users.First( u => u.Username == model.UserName );
user.Contact = contact;
_db.SaveChanges();
WebSecurity.Login( model.UserName, model.Password );
at the line _db.Contacts.Add( contact ); I got the exception.
But without using ContextManager by changing
HotelContext _db = ContextManager.Current;
into:
HotelContext _db = new HotelContext();
the problem was solved. But I need to use my own ContextManager. What is the problem?

Your context has been disposed somewhere else (not in the code you've shown), so basically when you access it from your Register action, it throws the exception.
Actually, you shouldn't use a static singleton to access to your context. Do instantiate a new DbContext instance for each request. See c# working with Entity Framework in a multi threaded server

In my case, my GetAll method was not calling ToList() method after where clause in lambda expression. After using ToList() my problem was solved.
Where(x => x.IsActive).ToList();

You are probably 'lazy-loading' a navigation property of User in your registration view. Make sure you include it by using the Include method on your DbSet before sending it to the view:
_db.Users.Include(u => u.PropertyToInclude);
Also, sharing DbContexts with a static property may have unexpected side effects.

I used to have the same problem. I solved it doing as it was said above. Instantiate a new instance of your context.
Try using this:
using (HotelContextProductStoreDB = new ProductStoreEntities())
{
//your code
}
This way it'll be created a new instance everytime you use your code and your context will not be disposed.

Why override the Dispose(bool)?
public partial class HotelContext : DbContext
{
public bool IsDisposed { get; set; }
protected override void Dispose(bool disposing)
{
IsDisposed = true;
base.Dispose(disposing);
}
}
And, then check IsDisposed
public static class ContextManager
{
public static HotelContext Current
{
get
{
var key = "Hotel_" + HttpContext.Current.GetHashCode().ToString("x")
+ Thread.CurrentContext.ContextID.ToString();
var context = HttpContext.Current.Items[key] as HotelContext;
if (context == null || context.IsDisposed)
{
context = new HotelContext();
HttpContext.Current.Items[key] = context;
}
return context;
}
}
}
Maybe, can be an option.

Related

How to dynamically choose a DbContext for API endpoint method

I developed and API that uses a helper class to get the database context for each endpoint function. Now I'm trying to write unit tests for each endpoint and I want to use an In-memory db in my unit test project.
The issue I'm running into is that in order to call the API functions I had to add a constructor to my API controller class. This would allow me to pass the dbContext of the in-memory db to the controller function for it to use. However, since the adding of the constuctor I got the following error when attempting to hit the endpoint:
"exceptionMessage": "Unable to resolve service for type 'AppointmentAPI.Appt_Models.ApptSystemContext' while attempting to activate 'AppointmentAPI.Controllers.apptController'."
UPDATE
controller.cs
public class apptController : Controller
{
private readonly ApptSystemContext _context;
public apptController(ApptSystemContext dbContext)
{
_context = dbContext;
}
#region assingAppt
/*
* assignAppt()
*
* Assigns newly created appointment to slot
* based on slotId
*
*/
[Authorize]
[HttpPost]
[Route("/appt/assignAppt")]
public string assignAppt([FromBody] dynamic apptData)
{
int id = apptData.SlotId;
string json = apptData.ApptJson;
DateTime timeStamp = DateTime.Now;
using (_context)
{
var slot = _context.AppointmentSlots.Single(s => s.SlotId == id);
// make sure there isn't already an appointment booked in appt slot
if (slot.Timestamp == null)
{
slot.ApptJson = json;
slot.Timestamp = timeStamp;
_context.SaveChanges();
return "Task Executed\n";
}
else
{
return "There is already an appointment booked for this slot.\n" +
"If this slot needs changing try updating it instead of assigning it.";
}
}
}
}
UnitTest.cs
using System;
using Xunit;
using AppointmentAPI.Controllers;
using AppointmentAPI.Appt_Models;
using Microsoft.EntityFrameworkCore;
namespace XUnitTest
{
public abstract class UnitTest1
{
protected UnitTest1(DbContextOptions<ApptSystemContext> contextOptions)
{
ContextOptions = contextOptions;
SeedInMemoryDB();
}
protected DbContextOptions<ApptSystemContext> ContextOptions { get; }
private void SeedInMemoryDB()
{
using(var context = new ApptSystemContext(ContextOptions))
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var seventh = new AppointmentSlots
{
SlotId = 7,
Date = Convert.ToDateTime("2020-05-19 00:00:00.000"),
Time = TimeSpan.Parse("08:45:00.0000000"),
ApptJson = null,
Timestamp = null
};
context.AppointmentSlots.Add(seventh);
context.SaveChanges();
}
}
[Fact]
public void Test1()
{
DbContextOptions<ApptSystemContext> options;
var builder = new DbContextOptionsBuilder<ApptSystemContext>();
builder.UseInMemoryDatabase();
options = builder.Options;
var context = new ApptSystemContext(options);
var controller = new apptController(context);
// Arrange
var request = new AppointmentAPI.Appt_Models.AppointmentSlots
{
SlotId = 7,
ApptJson = "{'fname':'Emily','lname':'Carlton','age':62,'caseWorker':'Brenda', 'appStatus':'unfinished'}",
Timestamp = Convert.ToDateTime("2020-06-25 09:34:00.000")
};
string expectedResult = "Task Executed\n";
// Act
var response = controller.assignAppt(request);
Assert.Equal(response, expectedResult);
}
}
}
InMemoryClass.cs
using System;
using System.Data.Common;
using Microsoft.EntityFrameworkCore;
using AppointmentAPI.Appt_Models;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace XUnitTest
{
public class InMemoryClass1 : UnitTest1, IDisposable
{
private readonly DbConnection _connection;
public InMemoryClass1()
:base(
new DbContextOptionsBuilder<ApptSystemContext>()
.UseSqlite(CreateInMemoryDB())
.Options
)
{
_connection = RelationalOptionsExtension.Extract(ContextOptions).Connection;
}
private static DbConnection CreateInMemoryDB()
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
return connection;
}
public void Dispose() => _connection.Dispose();
}
}
The exception suggests that you haven't registered your DBContext in your Startup.cs (as mentioned above). I'd also suggest that you change the name of your private readonly property to something other than DbContext (which is the class name and can get confusing)
Use something like this:
private readonly ApptSystemContext _context;
Besides that, your approach should be changed.
First, you will set the connection string when you register the DBContext. Just let dependency injection take care of that for you. Your controller should look like this:
public apptController(ApptSystemContext dbContext)
{
_context = dbContext;
}
The dbContext won't be null if you register it in Startup.
Next, unit testing is a tricky concept, but once you write your Unit test, you'll start to understand a little better.
You've said that you want to use the SQL In Memory db for unit testing, which is a good approach (be aware that there are limitations to SQL In Mem like no FK constraints). Next, I assume you want to test your Controller, so, since you MUST pass in a DBContext in order to instantiate your Controller, you can create a new DBContext instance that is configured to use the In Memory Database.
For example
public void ApptControllerTest()
{
//create new dbcontext
DbContextOptions<ApptSystemContext> options;
var builder = new DbContextOptionsBuilder<ApptSystemContext>();
builder.UseInMemoryDatabase();
options = builder.Options;
var context = new ApptSystemContext(options);
//instantiate your controller
var controller = new appController(context);
//call your method that you want to test
var retVal = controller.assignAppt(args go here);
}
Change the body of the method to this:
public string assignAppt([FromBody] dynamic apptData)
{
int id = apptData.SlotId;
string json = apptData.ApptJson;
DateTime timeStamp = DateTime.Now;
using (_context)
{
var slot = _context.AppointmentSlots.Single(s => s.SlotId == id);
// make sure there isn't already an appointment booked in appt slot
if (slot.Timestamp == null)
{
slot.ApptJson = json;
slot.Timestamp = timeStamp;
_context.SaveChanges();
return "Task Executed\n";
}
else
{
return "There is already an appointment booked for this slot.\n" +
"If this slot needs changing try updating it instead of assigning it.";
}
}
}
Another suggestion, don't use a dynamic object as the body of a request unless you are absolutely forced to do so. Using a dynamic object allows for anything to be passed in and you lose the ability to determine if a request is acceptible or not.

"No connection string named 'Entities' could be found in the application config file." error with Moq

I'm using Moq to provide a mocking context for my Oracle db. But when I call _context.Entry with the mocked context, I get an InvalidOperationException.
"No connection string named 'Entities' could be found in the application config file."
I'm already providing a mocked context, so not sure why it's still trying to read connection string to create the context.
// generated code for oracle db
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<ACTIVITY_CODE> ACTIVITY_CODE { get; set; }
}
// my code
public partial class Entities : System.Data.Entity.DbContext
{
public Entities(string scon) : base(scon) { }
}
// my code
public partial class ActivityCodeService
{
private Entities _context;
public ActivityCodeService(Entities context)
{
this._context = context;
}
public ACTIVITY_CODE Update(ACTIVITY_CODE item)
{
ACTIVITY_CODE ret = null;
var found = Read(item.ACT_ID);
if (found != null)
{
_context.Entry<ACTIVITY_CODE>(found).CurrentValues.SetValues(item); // throws InvalidOperationException "No connection string named 'Entities' could be found in the application config file."
_context.SaveChanges();
ret = item;
}
return ret;
}
}
// test code
[TestMethod]
public void activity_code_update_test()
{
// arrange
var mockSet = new Mock<DbSet<ACTIVITY_CODE>>();
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.Provider).Returns(testData.Provider);
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.Expression).Returns(testData.Expression);
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.ElementType).Returns(testData.ElementType);
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.GetEnumerator()).Returns(testData.GetEnumerator());
var mockContext = new Mock<Entities>();
mockContext.Setup(c => c.ACTIVITY_CODE).Returns(mockSet.Object);
var expected = new ACTIVITY_CODE() { ACT_ID = 1, ACT_CODE = "code 2", ACT_DESC = "desc 2" };
var target = new ActivityCodeService(mockContext.Object);
// act
target.Update(expected);
}
But if I don't use _context.Entry, then the test runs fine which is expected. So does that mean _context.Entry is creating another internal context and ignoring my mocked context?
// my code
public ACTIVITY_CODE Update(ACTIVITY_CODE item)
{
var ret = _context.ACTIVITY_CODE.FirstOrDefault(o => o.ACT_ID == item.ACT_ID);
if (ret != null)
{
ret.ACT_CODE = item.ACT_CODE;
ret.ACT_DESC = item.ACT_DESC;
_context.SaveChanges(); // this will work fine with Moq's mocked context
}
return ret;
}
Entry isn't, and can't be, mocked by Moq as it's not virtual so it is still going to try to use the database that it believes is there. That's why it's looking for a connection string.
What I have been able to do which has worked well is to abstract that function call into a virtual method that I had enough control over to actually mock.
Alternatives:
There are some tools based on answers to other questions that have the same base problem. Looks like TypeMock and JustMock may be able to work around the issue.
Additionally, it looks like MS Fakes should be able to shim it. After a little investigation it looks like it'd work something like this:
ShimDbEntityEntry<TestModel> entryMock = new ShimDbEntityEntry<TestModel>();
ShimDbPropertyValues mockValues = new ShimDbPropertyValues();
mockValues.SetValuesObject = (newValues) => { }; // Manually do something here
entryMock.CurrentValuesGet = () => mockValues;
ShimDbContext.AllInstances.EntryOf1M0<TestModel>((ctx, target) => entryMock);

Update all fields of an object using entity framework

I want to change all of an object properties using entity framwork.
after searching i got to have this:
Controller,action:
public ActionResult Test()
{
var user = GetCurrentUser();
user.FirstName = "BLAH BLAH";
new UserRepository().UpdateUser(user);
return RedirectToAction("Index");
}
and in my UserRepository:
public bool UpdateUser(ApplicationUser target)
{
using (var db = new AppDatabase())
{
db.Entry(target).State = EntityState.Modified;
db.SaveChanges();
return true;
}
}
but when i try execute i got this error
An entity object cannot be referenced by multiple instances of EntityChangeTracker.
so,any ways to fix or any better way?
using entity framework 6.0.0 and .net 4.5
public ApplicationUser GetCurrentUser()
{
return UserManager.FindById(User.Identity.GetUserId());
}
You should use same instance of db context for finding and updating, so you UserRepository can be:
class UserRepository : IDisposable //using IDisposable to dispose db context
{
private AppDatabase _context;
public UserRepository()
{
_context = new AppDatabase();
}
public ApplicationUser Find(string id)
{
return _context.Set<ApplicationUser>().Find(id);
}
public void Update(ApplicationUserentity entity)
{
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
You can use it in controller:
public ActionResult Test()
{
using (var repository = new UserRepository())
{
var user = repository.Find(User.Identity.GetUserId());
user.FirstName = "BLAH BLAH";
repository.Update(user);
}
return RedirectToAction("Index");
}
I also think using some dependency injection framework would be beneficial for you. So go for it!!
Be sure that all objects came from the same context!
var userContextOne = new MyDbContext();
var user = userContextOne.Users.FirstOrDefault();
var AppDbContextTwo = new MyDbContext();
// Warning when you work with entity properties here! Be sure that all objects came from the same context!
db.Entry(target).State = EntityState.Modified;
AppDbContextTwo.SaveChanges();
The scond problem (not related to the exception!):
db.Entry(target).State = EntityState.Modified;
Why you are doing that?! You dont not have Detached Scenario? did you have disabled your Changetracker? anyway just execute DetectChanges and this method will find the changed data you do not have to do it by your self.

Am I using and disposing Entity Framework's Object Context (per request) correctly?

I have a web application where I have just began to use Entity Framework. I read the beginners tutorials, and topics about benefits of object context per request for web apps.
However, I am not sure my context is at the right place...
I found this very useful post (Entity Framework Object Context per request in ASP.NET?) and used the suggested code :
public static class DbContextManager
{
public static MyEntities Current
{
get
{
var key = "MyDb_" + HttpContext.Current.GetHashCode().ToString("x")
+ Thread.CurrentContext.ContextID.ToString();
var context = HttpContext.Current.Items[key] as MyEntities;
if (context == null)
{
context = new MyEntities();
HttpContext.Current.Items[key] = context;
}
return context;
}
}
}
And in Global.asax :
protected virtual void Application_EndRequest()
{
var key = "MyDb_" + HttpContext.Current.GetHashCode().ToString("x")
+ Thread.CurrentContext.ContextID.ToString();
var context = HttpContext.Current.Items[key] as MyEntities;
if (context != null)
{
context.Dispose();
}
}
Then, I am using it in my pages :
public partial class Login : System.Web.UI.Page
{
private MyEntities context;
private User user;
protected void Page_Load(object sender, EventArgs e)
{
context = DbContextManager.Current;
if (Membership.GetUser() != null)
{
Guid guid = (Guid)Membership.GetUser().ProviderUserKey;
user = context.Users.Single(u => (u.Id == guid));
}
}
protected void _Button_Click(object sender, EventArgs e)
{
Item item = context.Items.Single(i => i.UserId == user.Id);
item.SomeFunctionThatUpdatesProperties();
context.SaveChanges();
}
}
I did read a lot but this is still a little bit confused for me.
Is the context getter okay in Page_Load ? Do I still need to use "using" or will disposal be okay with the Global.asax method ?
If I am confusing something I am sorry and I would be really, really grateful if someone could help me understand where it should be.
Thanks a lot !
Edits following nativehr answer and comments :
Here is the DbContextManager:
public static class DbContextManager
{
public static MyEntities Current
{
get
{
var key = "MyDb_" + typeof(MyEntities).ToString();
var context = HttpContext.Current.Items[key] as MyEntities;
if (context == null)
{
context = new MyEntities();
HttpContext.Current.Items[key] = context;
}
return context;
}
}
}
The page :
public partial class Login : System.Web.UI.Page
{
private User user;
protected void Page_Load(object sender, EventArgs e)
{
if (Membership.GetUser() != null)
{
Guid guid = (Guid)Membership.GetUser().ProviderUserKey;
user = UserService.Get(guid);
}
}
protected void _Button_Click(object sender, EventArgs e)
{
if (user != null)
{
Item item = ItemService.GetByUser(user.Id)
item.SomeFunctionThatUpdatesProperties();
ItemService.Save(item);
}
}
}
And the ItemService class :
public static class ItemService
{
public static Item GetByUser(Guid userId)
{
using (MyEntities context = DbContextManager.Current)
{
return context.Items.Single(i => (i.UserId == userId));
}
}
public static void Save(Item item)
{
using (MyEntities context = DbContextManager.Current)
{
context.SaveChanges();
}
}
}
I would not rely on Thread.CurrentContext property.
Firstly, Microsoft says, Context class is not intended to be used directly from your code:
https://msdn.microsoft.com/en-us/library/system.runtime.remoting.contexts.context%28v=vs.110%29.aspx
Secondly, imagine you want to make an async call to the database.
In this case an additional MyEntities instance will be constructed, and it will not be disposed in Application_EndRequest.
Furthermore, ASP.NET itself does not guarantee not to switch threads while executing a request.
I had a similar question, have a look at this:
is thread switching possible during request processing?
I would use "MyDb_" + typeof(MyEntities).ToString() instead.
Disposing db context in Application_EndRequest is OK, but it produces a bit performance hit, 'cause your context will stay not disposed longer than needed, it is better to close it as soon as possible (you actually don't need an open context to render the page, right?)
Context pre request implementation would make sense if it has to be shared between different parts of your code, insted of creating a new instance each time.
For example, if you utilize the Repository pattern, and several repositories share the same db context while executing a request.
Finally you call SaveChanges and all the changes made by different repositories are committed in a single transaction.
But in your example you are calling the database directly from your page's code, in this case I don't see any reason to not create a context directly with using.
Hope this helps.
Update: a sample with Context per request:
//Unit of works acts like a wrapper around DbContext
//Current unit of work is stored in the HttpContext
//HttpContext.Current calls are kept in one place, insted of calling it many times
public class UnitOfWork : IDisposable
{
private const string _httpContextKey = "_unitOfWork";
private MyContext _dbContext;
public static UnitOfWork Current
{
get { return (UnitOfWork) HttpContext.Current.Items[_httpContextKey]; }
}
public UnitOfWork()
{
HttpContext.Current.Items[_httpContextKey] = this;
}
public MyEntities GetContext()
{
if(_dbContext == null)
_dbContext = new MyEntities();
return _dbContext;
}
public int Commit()
{
return _dbContext != null ? _dbContext.SaveChanges() : null;
}
public void Dispose()
{
if(_dbContext != null)
_dbContext.Dispose();
}
}
//ContextManager allows repositories to get an instance of DbContext
//This implementation grabs the instance from the current UnitOfWork
//If you want to look for it anywhere else you could write another implementation of IContextManager
public class ContextManager : IContextManager
{
public MyEntities GetContext()
{
return UnitOfWork.Current.GetContext();
}
}
//Repository provides CRUD operations with different entities
public class RepositoryBase
{
//Repository asks the ContextManager for the context, does not create it itself
protected readonly IContextManager _contextManager;
public RepositoryBase()
{
_contextManager = new ContextManager(); //You could also use DI/ServiceLocator here
}
}
//UsersRepository incapsulates Db operations related to User
public class UsersRepository : RepositoryBase
{
public User Get(Guid id)
{
return _contextManager.GetContext().Users.Find(id);
}
//Repository just adds/updates/deletes entities, saving changes is not it's business
public void Update(User user)
{
var ctx = _contextManager.GetContext();
ctx.Users.Attach(user);
ctx.Entry(user).State = EntityState.Modified;
}
}
public class ItemsRepository : RepositoryBase
{
public void UpdateSomeProperties(Item item)
{
var ctx = _contextManager.GetContext();
ctx.Items.Attach(item);
var entry = ctx.Entry(item);
item.ModifiedDate = DateTime.Now;
//Updating property1 and property2
entry.Property(i => i.Property1).Modified = true;
entry.Property(i => i.Property2).Modified = true;
entry.Property(i => i.ModifiedDate).Modified = true;
}
}
//Service encapsultes repositories that are necessary for request handling
//Its responsibility is to create and commit the entire UnitOfWork
public class AVeryCoolService
{
private UsersRepository _usersRepository = new UsersRepository();
private ItemsRepository _itemsRepository = new ItemsRepository();
public int UpdateUserAndItem(User user, Item item)
{
using(var unitOfWork = new UnitOfWork()) //Here UnitOfWork.Current will be assigned
{
_usersRepository.Update(user);
_itemsRepository.Update(user); //Item object will be updated with the same DbContext instance!
return unitOfWork.Commit();
//Disposing UnitOfWork: DbContext gets disposed immediately after it is not longer used.
//Both User and Item updates will be saved in ome transaction
}
}
}
//And finally, the Page
public class AVeryCoolPage : System.Web.UI.Page
{
private AVeryCoolService _coolService;
protected void Btn_Click(object sender, EventArgs e)
{
var user = .... //somehow get User and Item objects, for example from control's values
var item = ....
_coolService.UpdateUserAndItem(user, item);
}
}
I think you should read a bit more about repository pattern for EntityFramework and UnitofWork pattern.
Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC
I know this is mvc and you are problably using web forms but you can get an idea of how to implement it.
Disposing the context on each request is a bit strange, because there might be requests where you will not touch the database, so you will be doing unnecessary code.
What you should do is get a layer for data access and implement a repository pattern that you will access on whatever method you will need on the code behind of your page.

Identity Framework seed method does not save newly created user when called from Global.asax.cs

I am trying to seed an "admin" account in Identity framework during application start up. The majority of our application is not set up through code-first Entity framework models, so I need to do this without extending one of the IDatabaseInitializer classes. I am using the same database as these database-first models.
This is an ASP.NET MVC 5 application.
In Global.asax.cs, I have the following relevant code.
using (var context = new IdentityContext(EnvironmentSettings.Current.DatabaseConnections.CreateDbConnection("Extranet").ConnectionString))
{
context.SeedAdminAccount(EnvironmentSettings.DefaultAdminAccount.UserName, EnvironmentSettings.DefaultAdminAccount.Password).Wait();
}
The connection string is an Azure SQL server. The username is an email address, and the password is a string of characters, including a bang.
The class IdentityContext looks like this.
public class IdentityContext : IdentityDbContext<IdentityUser>
{
public IdentityContext(string connectionString) : base(connectionString)
{
Debug.WriteLine(connectionString);
Initialize();
}
void Initialize()
{
Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
Database.SetInitializer<IdentityContext>(new CreateInitializer());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<IdentityUser>().ToTable("IdentityUser", "dbo");
modelBuilder.Entity<IdentityRole>().ToTable("IdentityRole", "dbo");
modelBuilder.Entity<IdentityUserClaim>().ToTable("IdentityUserClaim", "dbo");
modelBuilder.Entity<IdentityUserLogin>().ToTable("IdentityUserLogin", "dbo");
modelBuilder.Entity<IdentityUserRole>().ToTable("IdentityUserRole", "dbo");
}
}
context.SeedAdminAccount() is an extension of IdentityContext. It looks like this.
public static class IdentityContextExtensions
{
public static async Task SeedAdminAccount(this IdentityContext identityContext, string username, string password)
{
var userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(identityContext));
//var user = await userManager.FindAsync(EnvironmentSettings.DefaultAdminAccount.UserName, EnvironmentSettings.DefaultAdminAccount.Password);
var user = await userManager.FindAsync(username, password);
if (user != null) return;
user = new IdentityUser() { UserName = username };
var role = new IdentityUserRole { Role = new IdentityRole(Role.Admin) };
user.Roles.Add(role);
await userManager.CreateAsync(user, password);
identityContext.SaveChanges();
}
}
And lastly, CreateInitializer looks like this (although it is not called).
public class CreateInitializer : CreateDatabaseIfNotExists<IdentityContext>
{
protected override async void Seed(IdentityContext context)
{
var user = new
{
EnvironmentSettings.DefaultAdminAccount.UserName,
EnvironmentSettings.DefaultAdminAccount.Password
};
await context.SeedAdminAccount(user.UserName, user.Password);
base.Seed(context);
}
}
Okay, so with all of that out of the way, here's what works:
1) Application startup successfully creates an instance of IdentityContext.
2) Identity framework creates the correct tables with the modified table names.
3) SeedAdminAccount() is called with the correct parameters.
4) userManager.FindAsync() does not find the user (because it doesn't exist).
5) SeedAdminAccount() continues to each statement in its body and returns successfully.
Here's where I'm stuck:
1) Although it appears that my seed method is working correctly, no rows are saved to the IdentityUser table, or any other Identity framework tables.
2) If I use the same code from a controller action, a user is created and stored in the IdentityUser table successfully.
What am I missing here? Am I using the wrong context during application start up? Is there some sort of exception happening that I can't see?
Being at a complete loss, I decided to check the return value of userManager.CreateAsync(). I noticed the Errors field was non-empty, with the error being "The name specified contains invalid characters".
Turns out, I forgot to overload UserValidator to use my EmailUserValidator class in the SeedAdminAccount() method.
I also changed how I'm storing roles. This is what my seed method looks like now.
public static void SeedAdminAccount(this IdentityContext identityContext, string username, string password)
{
var userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(identityContext));
userManager.UserValidator = new EmailUserValidator<IdentityUser>(userManager);
var user = userManager.Find(username, password);
if (user != null) return;
SeedUserRoles(identityContext);
user = new IdentityUser() { UserName = username };
var result = userManager.Create(user, password);
if (result.Succeeded)
{
userManager.AddToRole(user.Id, Role.Administrator);
}
else
{
var e = new Exception("Could not add default account.");
var enumerator = result.Errors.GetEnumerator();
foreach(var error in result.Errors)
{
e.Data.Add(enumerator.Current, error);
}
throw e;
}
}
public static void SeedUserRoles(this IdentityContext identityContext)
{
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(identityContext));
foreach(var role in Role.Roles)
{
var roleExists = roleManager.RoleExists(role);
if (roleExists) continue;
roleManager.Create(new IdentityRole(role));
}
}

Categories