How to run code within Seed method during deployment? - c#

I have a Web Api solution that makes use of ASP.NET Identity (v2.1) and Entity Framework v6.1. Inside the Seed() method of the Configuration.cs file I have code that creates my first Identity user. This code makes use of the Identity framework to hash the password, create the security stamp, etc. These are all things I cannot do via SQL so adding to the Up() method does not seem like an option.
protected override void Seed(ApplicationDbContext context)
{
// Create the admin user
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
var user = new ApplicationUser()
{
UserName = "adminuser",
Email = "adminuser#mycompany.com",
EmailConfirmed = true,
FirstName = "John",
LastName = "Does",
JoinDate = DateTime.Now.AddYears(-1)
};
manager.Create(user, "SuperSecurePassword321!");
if (roleManager.Roles.Count() == 0)
{
roleManager.Create(new IdentityRole { Name = "Admin" });
roleManager.Create(new IdentityRole { Name = "Employee" });
roleManager.Create(new IdentityRole { Name = "Customer" });
}
var adminUser = manager.FindByName("adminuser");
manager.AddToRoles(adminUser.Id, new string[] { "Admin" });
}
I need to use FTP to publish this (no control over this). Any suggestions on how to run this code once it is deployed and the database is schema is setup?
Options I have considered:
I have thought about creating an API endpoint that when called can
kick off this code, however, this endpoint would have to allow
anonymous access since it would be creating this first user and
the roles used in the system. I would then need to somehow disable
or remove this endpoint later.
Script the database and include the data and then restore that to
the database server targeted for deployment.

Seed() gets called when the database is accessed the first time. What's wrong with that automatic behavior?
If you want to call it manually, try something like this in protected void Application_Start():
Database.SetInitializer(new YourInititalizer());
var dbContext = new TheContextYouAreUsing();
dbContext.Database.Initialize(force: true);

Related

How to make a single admin user for MVC .Net Core app

I'm building a web app that is essentially a store, but I want to put in an easy way for the admin of the site to add new products. However I want to restrict this part of the site so only the admin can access it. I have no use for other users at this moment.
How do I make it so that anybody with the admin username and password can access these pages and it will persist to know that they are logged in? I already have a system in place that accepts a user input and then continues to the admin pages if it's correct. But the problem is if someone decides to just go directly to the pages like Admin/AddProduct. I'd need my app to know that they're not allowed to access the AddProduct page yet and redirect them back to the login.
Here's how you go about it Joey
You could do this easily by creating a CreateRoles method in your startup class. This helps check if the roles are created, and creates the roles if they aren't; on application startup. Like so.
private async Task CreateRoles(IServiceProvider serviceProvider)
{
//initializing custom roles
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
string[] roleNames = { "Admin", "Store-Manager", "Member" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
var roleExist = await RoleManager.RoleExistsAsync(roleName);
// ensure that the role does not exist
if (!roleExist)
{
//create the roles and seed them to the database:
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
// find the user with the admin email
var _user = await UserManager.FindByEmailAsync("admin#email.com");
// check if the user exists
if(_user == null)
{
//Here you could create the super admin who will maintain the web app
var poweruser = new ApplicationUser
{
UserName = "Admin",
Email = "admin#email.com",
};
string adminPassword = "p#$$w0rd";
var createPowerUser = await UserManager.CreateAsync(poweruser, adminPassword);
if (createPowerUser.Succeeded)
{
//here we tie the new user to the role
await UserManager.AddToRoleAsync(poweruser, "Admin");
}
}
}
and then you could call the await CreateRoles(serviceProvider); method from the Configure method in the Startup class.
ensure you have IServiceProvider as a parameter in the Configure class.
Question 2: "How do I make it so that anybody with the admin username and password can access these pages "
You can do this easily, like so.
[Authorize(Roles="Admin")]
public class ManageController : Controller
{
//....
Return View();
}
You can also use role-based authorization in the action method like so. Assign multiple roles, if you will
[Authorize(Roles="Admin")]
public IActionResult Index()
{
/*
.....
*/
}
While this works fine, for a much better practice, you might want to read about using policy based role checks. You can find it on the ASP.NET core documentation here, or this article I wrote about it here
Once you add ASP.NET Identity to your project you can implement Role based Authorization in your application. Basically it allows you to setup [Authorize(Roles = "Administrator")] attribute for contollers which shall be available for admin users only.

Update existing Identity ApplicationUser properties via attaching new ApplicationUser to context

I'm trying to update my existing ApplicationUser, by adding new "non-core" properties e.g. FirstName, LastName. I've extented these properties already via ApplicationUser: IdentityUser, this works overall fine.
Now, what I try to do is create a new ApplicationUser in my controller, and then pass it to repository to update user existing in db, like:
public ActionResult Members_Update(MemberViewModel mvm){
ApplicationUser user = new ApplicationUser();
user.FirstName = mvm.FirstName;
user.LastName = mvm.LastName;
_repo.UpdateApplicationUser(user);
}
In repository ( constructed as _context = new ApplicationDbContext(); ) I do:
public void UpdateAppicationUser(ApplicationUser updatedUser){
_context.Users.Attach(updatedUser);
var entry = _context.Entry(updatedUser);
...
and then I continue via
entry.Property(x => x.AccessFailedCount).IsModified = false;
entry.Property(x => x.EmailConfirmed).IsModified = false;
entry.Property(x => x.LockoutEnabled).IsModified = false;
// same for rest of core identity properties
_context.SaveChanges();
}
I'm trying to do this based on this pattern, so that the db is only hit once (and overall looks like an optimal way), however there seems to be some Identity behaviour preventing this. After Attach the entry is just replacing the original user instead of modifying (so e.g. UserName ends up being null, which is naturally required).

Seed User with custom role in EF6 code-first

I am having trouble figuring out how to seed additional users and roles into my MVC5 application, using EF6 code first. In order to debug the Seed method from the Configure.cs since update-database was not working, I wrote this controller,
public ActionResult test() {
var context = new ApplicationDbContext();
var roleStore = new RoleStore<IdentityRole>(context);
var roleManager = new RoleManager<IdentityRole>(roleStore);
roleManager.Create(new IdentityRole { Name = "basic" });
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new UserManager<ApplicationUser>(userStore);
var adminthere = context.Users.Any(n => n.UserName == "Admin");
var basicthere = context.Users.Any(n => n.UserName == "Basic");
// Create Dummy basic account
if (!basicthere) {
var basicUser = new ApplicationUser { UserName = "Basic" };
userManager.Create(basicUser, "test");
var _id = basicUser.Id;
userManager.AddToRole(basicUser.Id, "basic");
}
return View();
}
The debugger throws an exception at the userManager.AddToRole(basicUser.Id, "basic"); call saying "UserID not found"? Here is a screenshot including variable values from the debug session:
What is the problem? Also, the exact same code (changing the words "basic" for "Admin") works for seeding the database with the Admin user in role "admin". Why?
EDIT EDIT: moved edit I posted here previoulsy to a real answer below.
As the comments suggested I will post my this as an answer:
The line of code userManager.Create(basicUser, "test"); didn't succeed - the passwort must at least have 6 characters. So while creating the basicUser ApplicationUser instance worked (and hence the _id was not null) I didn't have an IdentityUser of that _id. On admin it succeeded previously bc. I had a different pwd that I didn't want to post here ...

Understanding MVC-5 Identity

I created a new ASP.NET MVC-5 application with Individual User Accounts and then updated all the Nuget packages in the solution. Now I'm trying to follow some of the guidelines shown in some tutorials but I encountered some problems.
The first one is that a class called ApplicationRoleManager which is being used throughout the application wasn't created (the ApplicationUserManager was created).
The second problem is more about Entity-Framework: I've seen that for seeding the database with a user and role many people create a static constructor in the ApplicationDbContext class:
static ApplicationDbContext()
{
Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
}
So I added it, and the implementation of the ApplicationDbInitializer is:
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
InitializeIdentityForEF(context);
base.Seed(context);
}
//Create User=Admin#Admin.com with password=Admin#123456 in the Admin role
public static void InitializeIdentityForEF(ApplicationDbContext db)
{
var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
const string name = "admin#admin.com";
const string password = "Admin#123456";
const string roleName = "Admin";
//Create Role Admin if it does not exist
var role = roleManager.FindByName(roleName);
if (role == null)
{
role = new IdentityRole(roleName);
var roleresult = roleManager.Create(role);
}
var user = userManager.FindByName(name);
if (user == null)
{
user = new ApplicationUser { UserName = name, Email = name };
var result = userManager.Create(user, password);
result = userManager.SetLockoutEnabled(user.Id, false);
}
// Add user admin to Role Admin if not already added
var rolesForUser = userManager.GetRoles(user.Id);
if (!rolesForUser.Contains(role.Name))
{
var result = userManager.AddToRole(user.Id, role.Name);
}
}
After adding everything I opened the Package Manager Console and typed Enable-Migrations, then Add-Migration someName and then Update-Database.
the results were that the database was created successfully but no data was inserted to the database.
After noticing the data wasn't inserted I moved the Seed logic to the Index method of the home controller and the data was inserted after running the application.
I also needed to add this line: app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
to the Startup.Auth.cs file.
So my questions are:
Do I really need to enter the ApplicationRoleManager class
manually?
How do I make the seed method work?
UPDATE
I've changed the Seed method to:
protected override void Seed(ApplicationDbContext context)
{
var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
//since there is no ApplicationRoleManager (why is that?) this is how i create it
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
const string name = "admin#admin.com";
const string password = "Admin#123456";
const string roleName = "Admin";
//Create Role Admin if it does not exist
var role = roleManager.FindByName(roleName);
if (role == null)
{
role = new IdentityRole(roleName);
var roleresult = roleManager.Create(role);
}
//app hangs here...
var user = userManager.FindByName(name);
if (user == null)
{
user = new ApplicationUser { UserName = name, Email = name };
var result = userManager.Create(user, password);
result = userManager.SetLockoutEnabled(user.Id, false);
}
// Add user admin to Role Admin if not already added
var rolesForUser = userManager.GetRoles(user.Id);
if (!rolesForUser.Contains(role.Name))
{
var result = userManager.AddToRole(user.Id, role.Name);
}
base.Seed(context);
}
So now, the Admin role is created but when getting to var user = userManager.FindByName(name); the application hangs with no exception or any message...
When using migrations you can use the built in initializer and the Seed method:
Database.SetInitializer<ApplicationDbContext>(new
MigrateDatabaseToLatestVersion<ApplicationDbContext,
APPLICATION.Migrations.Configuration>());
and in APPLICATION.Migrations.Configuration (this was created by the Enable-Migrations command):
protected override void Seed(ApplicationDbContext context)
{
// seed logic
}
As a role manager you can also use the RoleManager<ApplicationRole> base implementation.
I also was a bit confused about hanging of application in this case. The problem can be solved in this way
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUserManager>(db));
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(db));
And for anyone using the ApplicationUser with Integer foreign key, the code is this one:
var userManager = new ApplicationUserManager(new ApplicationUserStore(context));
var roleManager = new ApplicationRoleManager(new ApplicationRoleStore(context));
This works great for default MVC 5 project.
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context));
It doesn't appear that the solutions posted address the issue of the app hanging on the call of userManager.FindByName(name). I'm running into the same problem. It worked a few hours ago on my local. I published to Azure and it started hanging. When I tested my local again it all of a sudden started hanging at that step. No error is returned and no timeout (at least after waiting 10-15 minutes). Does anyone have any tips to address Yoav's ultimate question?
I have some other very simple seeding processes that run before adding roles, and db.Foo.AddOrUpdate(foo) calls are running without error, but not actually saving anything to the database.
I just spent a deeply unpleasant half day dealing with this. I finally managed to get the damn thing to fire:
public static void InitializeIdentityForEF(ApplicationDbContext context)
{
context.Configuration.LazyLoadingEnabled = true;
//var userManager = HttpContext.Current
// .GetOwinContext().GetUserManager<ApplicationUserManager>();
//var roleManager = HttpContext.Current
// .GetOwinContext().Get<ApplicationRoleManager>();
var roleStore = new RoleStore<ApplicationRole, int, ApplicationUserRole>(context);
var roleManager = new RoleManager<ApplicationRole, int>(roleStore);
var userStore = new UserStore<ApplicationUser, ApplicationRole, int, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>(context);
var userManager = new UserManager<ApplicationUser, int>(userStore);
...
It's the end of an extremely long day, and I suspect someone's going to tell me why I shouldn't do this. The rest of my Seed method fires beautifully, however, using non-async methods (FindByName/Create).
Sir goobering,
You struggles have helped me get passed this problem, I had to do it a little different though.
context.Configuration.LazyLoadingEnabled = true;
//var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
//var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
const string name = "admin#example.com";
const string password = "Admin#123456";
const string roleName = "Admin";
***var userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(context));
var roleManager = new ApplicationRoleManager(new RoleStore<IdentityRole>(context));***
//Create Role Admin if it does not exist
var role = roleManager.FindByName(roleName);
if (role == null) {
role = new IdentityRole(roleName);
var roleresult = roleManager.Create(role);
}

How to delete users that were created with UserManager.CreateAsync

Using asp.net mvc5, my user management systems seems to work. I can login with google or with name/password..
but now I am working on a user management interface in which I need to be able to delete existing users. And this is starting to expose to me just how confusing the user management system is. There's so many different ways to deal with users.. and some of them don't work.
Most everywhere I read, it is talking about using the Membership.DeleteUser().
But that isn't working...
The users were created with.
var user = new ApplicationUser()
{
UserName = model.UserName,
Email = model.Email,
ConfirmationToken = confirmationToken,
IsConfirmed = false
};
var result = await UserManager.CreateAsync(user, model.Password);
Now later on.. how do I delete such a user? (given its name or userid)
I have tried what comes up most on various searches.. comes up with Membership as the solution. But this surely isn't right for MVC5?
For example
var allusers = Membership.GetAllUsers(); // allusers is empty
bool success = Membership.DeleteUser(model.name); // <-- success = false
I can get all the users using this method..
ApplicationDbContext db = new ApplicationDbContext();
foreach (var user in db.Users) { ... }
And I can find an individual user with..
ApplicationDbContext db = new ApplicationDbContext();
var um = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db));
ApplicationUser user = um.FindById(model.userId);
Now how do I delete one though? ....
Update
As of Microsoft.AspNet.Identity Version 2.0.0.0, you can now delete users with Identity using UserManager.Delete(user);.
For Posterity
You are referring to two different things, Identity and Membership. Newer versions of ASP.NET support Identity and Membership with Identity being the default, while older versions support only Membership (out of those two authentication systems).
When you create a user with UserManager.CreateAsync, you are doing so within the Microsoft.AspNet.Identity namespace. When you are attempting to delete a user with Membership.DeleteUser, you are doing so within the System.Web.Security namespace. They are living in two different worlds.
As another comment mentions, deleting users is not yet supported out of the box by Identity, but it is the first item on their roadmap for a Spring of 2014 release.
But why wait? Add another property to the ApplicationUser model like this:
public class ApplicationUser : IdentityUser
{
public string IsActive { get; set; }
}
Then, in your controller for deleting a user:
user.IsActive = false;
Do a check when the user logs in:
if (user.IsActive == false)
{
ModelState.AddModelError(String.Empty, "That user has been deleted.");
return View(model);
}
When an deleted user attempts to re-register, instead of UserManager.Create, use UserManager.Update with their new information on the registration page.
These steps will effectively delete the user. If you truly must clear their information from your database, you can use Entity Framework to do that more directly.
added to the previous response. If you have
public class ApplicationUser : IdentityUser
{
public string IsActive { get; set; }
}
Then, in your controller for deleting a user:
user.IsActive = false.ToString();
because your data type is a string and n ot a boolean

Categories

Resources