Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Identity scaffolding with AppplicationDbContext

ASP.NET Core 2.x now hides the Identity pages and logic inside of a library. In order to extend the IdentityUser entity with new properties and add UI and logic to deal with those new properties, we now get to run the "Identity scaffolder" (under MyProject -> Add -> New scaffolded item). Unfortunately, the docs are incomplete at best and, in more than a few instance, flat out wrong.

I'm struggling to figure out how to wrestle the scaffolder and the resultant code to achieve what I would assume is a pretty standard use case:

  1. I'm starting with a new ASP.NET Core MVC app
  2. I want to extend IdentityUser with my own properties. Since this involves creating an inherited class, I want the name of that new class to be ApplicationUser.
  3. I want to scaffold the Identity pages I need to fiddle with (Login, Register, Manage, etc.)
  4. I want to use the existing ApplicationDbContext

My problem is that, reading the docs and playing with the Identity scaffolder, I can't figure out how to get the scaffolder to let me extend IdentityUser and keep using the existing ApplicationDbContext.

like image 938
Bob.at.Indigo.Health Avatar asked Jan 28 '23 18:01

Bob.at.Indigo.Health


2 Answers

After staring at the docs and playing with the scaffolder and the resultant code, I think I've figure out most of it.

  1. Start with a new ASP.NET Core Web Application, with "Individual user accounts" authentication. Apparently, it doesn't really matter whether you start with a "Web Application" (i.e. Razor Pages) or "Web Application (Model-View-Controller)" (i.e. MVC). In the end, the Identity pages will be Razor Pages, but they function just fine within an MVC app.
  2. Build the solution. I'm not sure if this is necessary, but it doesn't hurt...
  3. Before you go any farther, make sure you like your DB connection string (in appsettings.json). In the Package Manager Console, enter the command update-database. This apparently updates the ApplicationDbContextModelSnapshot.cs file to include the initial migration that you got from the project template. If you skip this step then, when you add your first migration, it will include all of the migration steps you already have in 00000000000000_CreateIdentitySchema.cs. Also, this step is apparently needed in order for the scaffolder to recognize the existing ApplicationDbContext.
  4. Right-click the project and select Add -> New scaffolded item; Select the Identity item type
  5. Specify your existing _Layout.cshtml and select the pages you want to scaffold
  6. Next to Data context class, click the drop-down arrow and select the existing ApplicationDbContext
  7. Click the Add button to perform the scaffolding.

At this point, your project is wired up to the existing ApplicationDbContext and IdentityUser classes. Now, you can extend IdentityUser and wire up the Identity pages to use the new, extended entity:

Add a new ApplicationUser class that inherits from IdentityUser:

public class ApplicationUser : IdentityUser
{
    public string Nickname { get; set; }
}

Add the new ApplicationUser entity to the ApplicationDbContext class:

public class ApplicationDbContext : IdentityDbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    public DbSet<ApplicationUser> AppUsers { get; set; }
}

Replace all instances of <IdentityUser> with <ApplicationUser>. This means that you will change startup.cs to call services.AddDefaultIdentity<ApplicationUser>(), and you will replace all of the references to SigninManager and UserManager to specify the new <ApplicationUser> Type parameter.

Whew... now, you can go into things like Register.cshtml.cs and create (or fetch) a new ApplicationUser rather than an IdentityUser, and all of the plumbing will be wired up to ApplicationUser. For example, the code in Register.cshtml.cs might look like this:

var user = new ApplicationUser {
    UserName = Input.Email,
    Email = Input.Email,
    Nickname = Input.Nickname // New property
};
var result = await _userManager.CreateAsync(user, Input.Password);
like image 92
Bob.at.Indigo.Health Avatar answered Jan 31 '23 13:01

Bob.at.Indigo.Health


Normally, without Identity, ApplicationDbContext inherits from DbContext.

I have a solution with four projects. WEB, WebAPI, DATA and ENTITIES.

I have a single ApplicationDbContext located in DATA and have modified it to inherit from IdentityDbContext e.g.:

public class ApplicationDbContext : IdentityDbContext<IdentityUser>

Now, ENTITIES is where the models (POCOs) are defined.

And, AppliationDbContext in DATA contains references to these classes and contains the DbSet<T> property setter used by EF. The Person(aka, ApplicationUser) class in ENTITIES looks like this:

public class Person : IdentityUser { [PersonalData] public string LastName { get; set; }

In the WEB project, with the appropriate references to DATA and ApplicationDContext, I have only one services.AddDbContext<T> dependency reference; then use that context within AddDefaultIdentity<IdentityUser>() service addition.:

         services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddDefaultIdentity<IdentityUser>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

That seemed to do the trick to use only one DbContext service. It also extended the AspNetUsers table to include the [PersonalData] properties I had set in the POCO Person model class located within ENTITIES.

Now, since <IdentityUser> has been extended, in my case by Person, all references to IdentityUser should be updated to reflect that. So Startup.ConfigureServices now looks like this:

            services.AddDefaultIdentity<Person>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

And, within the UI, to gain access to the properties added by 'Person', references in ~/Identity/Account/Manage/Index.cshtml.cs need to be updated as well:

        private readonly UserManager<Person> _userManager;
    private readonly SignInManager<Person> _signInManager;
    private readonly IEmailSender _emailSender;

    public IndexModel(
        UserManager<Person> userManager,
        SignInManager<Person> signInManager,
        IEmailSender emailSender)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
    }

You will have to update the 'InputModel' in index.cshtml.cs as well as the 'new up' in OnGetAsync() method to map the properties -- similar to how Email is handled by the default settings.

And then, finally, there is the index.cshtml view file to give access to the UI/razor display.

This should get you on your way with items 1 - 4 in your question.

like image 39
SRQ Coder Avatar answered Jan 31 '23 11:01

SRQ Coder