If I had asked my customers what they wanted, they would have said a faster horse. 

Henry Ford – Founder, Ford Motor Company 

Introduction 

I’ve been called stubborn, but I have never really thought of myself that way until last week. It began with a problem in ASP.NET Core that I just couldn’t let go. It’s a good thing it ended well because I was becoming married to my keyboard, stackoverflow.com, and coffee. 

It all started when I wanted to follow Navigation properties in my ASP.NET Core Identity User object. If I’ve lost you already, know that ASP.NET Core is the open-source .NET web framework and that this blog post might cause you to glaze over. This is a story about how I figured out how to get ASP.NET Core Identity to follow my Navigation properties so that I didn’t have to put all my User’s personal data into one table controlled by Identity. 

El Situation 

ASP.NET Core Identity is an API that supports User login functionality as well as maintains users, passwords, profile data, roles, claims, and more. This is taken more or less from the Microsoft docs, which has a great primer on Identity written by Rick Andersen here

It maintains all that User and Membership data, usually in a database, with the help of Entity Framework Core, which is (save me Microsoft docs!) a lightweight and extensible ORM (Object Relational Mapping) system that eliminates the need for most data access code. I use it in just about all my .NET applications because I just don’t feel like writing “SELECT * FROM blah_table WHERE id = @id” in my C# code anymore and passing that to an ADO.NET Command object. There, have I dated myself already?  

Anyways, for those new to EF Core, I would start with the MS docs again here. 

Let’s start with my User object, which inherits from IdentityUser. This is the main class used by Identity to represent the User:

As you can see, I have extended the IdentityUser class to include an AccountId field, as well as a Navigation property called Account. The Account object goes to a class named Account: 

As you can see, there is one more foreign key here in OrganizationId, as well as the Organization Navigation property, which goes to (you guess it) my Organization class: 

With me so far? It’s a fairly straightforward hierarchy: 

User > Account > Organization  

Why not just include all these properties on my WmtUser class? When you create the database using EntityFramework Core Migrations (as I did), the Identity framework creates a bunch of tables in your database just for its use: 

This is done because you have specified that you are using Identity, not only by using the IdentityUser class, but also by using the IdentityDbContext class. The DbContext is a key piece of Entity Framework Core that handles how classes in your projects get translated into database tables and vice versa. You can read more about the DbContext here.

ASP.NET Identity uses its own version of the DbContext class, called IdentityDbContext and it is responsible for creating all the AspNet* tables in your database when you run an EF Migration with this context. 

I’ve also setup my Account and Organization classes and Entity Framework migrations established these in the database as well: 

I did not want all these other personal fields crowding up my AspNetUsers table. In my mind, my User’s Account and Organization information needed to be separate. What if I had another client someday that used another form of authentication? In any case, it just bugged me to have to put everything into the AspNetUsers table just because. 

Now, don’t get me wrong, ASP.NET Core IdentityUser DOES SUPPORT Navigation properties. Moving from 1.x to 2.x in ASP.NET Core removed certain Navigation properties, such as Claims, Tokens, and UserRoles from the IdentityUser class. You can fairly easily put them back by following the steps outlined here.  

You can also use this method to add in your own Navigation properties. The article mainly deals with One To Many relationships such as User > UserRoles and tells you how to override the OnModelCreating method of your DbContext to prevent duplicate foreign keys when running EF migrations.  

Problem Solved, Right? Wrong!

Let’s say we did that and implemented our User > Account > Organization relationship this wayI’ve already showed you the WmtUser class, so the only thing left to do would be to put the correct entries into my OnModelBuilding method like this: 

Once this is in place, I can just run Users.Include(u => u.Account) and be good to go right? Sorry, but where do “Users” come from? Oh, right, it comes from the UserManager! The UserManager is a key part of ASP.NET Core Identity and is responsible for all the CRUD (Create, Retrieve, Update, Delete) operations for the framework. To get all Users with an included Account and Organization, we would need to call something like this: 

Nothing to it right? Now, I can see my User with an included Account: 

But wait, what if I don’t call userManager.Users? What if I call something else like: 

The first call is getting the WmtUser by passing in the ClaimsPrincipal, which is essentially the current User in the HttpContext. The second is just looking up a standard user (me) according to my username.  

Neither of these (or any other) calls against UserManager give me the opportunity to “Include” the Account Navigation property so it’s always null for Users returned by these methods: 

Now, ironically, IF you first ran this line of code: 

And then run either GetUserAsync or FindByNameAsync (or I imagine any of the others retrieving Users from the UserManager), then the Account information IS included. I can only conclude here that calling Users.Include first loaded everything into the EF cache or something but that is kind of cool. 😉 

My Issue: The Client Should Stay In El Dark 

While using the above could solve the problem, I didn’t want my website to have to deal with this. Why does it have to know that there is an unpopulated Navigation property named Account and have to deal with this? Why can’t my website just call:

And have Account and Organization populated out of the gate? For that matter, if the website retrieves Users at all from the UserManager, the Account and Organization information should already be loaded.

My WmtUser business object lives in an entirely different project. Like my other business objects, they should be handed up to the client via a Services layer (in yet another project) that can easily make sure Navigation properties are populated. However, because WmtUser inherits from IdentityUser, if you want to play by the Identity Framework rules, the UserManager is in charge. 

Ok, So Override UserManager Right? Not So Fast… 

I’ve seen the UserManager overridden a bunch and you can definitely solve this problem doing that. Robert Wray has a good blog post on extending the UserManager here.  

To me though, this is the place where Users are created, updated, or deleted. If I needed to call another Service whenever a new User was created, UserManager would be the place it with a simple override of the CreateAsync method. But here, I just want the Account information included whenever IdentityUsers are used. That meant going one level deeper, to the UserStore itself. 

But Wait! What about Lazy Loading? 

Yes, Lazy Loading would also solve this problem. Lazy Loading means that Entity Framework Core is configured to load Navigation properties only when they are requested and so to only hit the database when needed. EF Core does not support it by default because it can cause multiple database queries depending upon how the client is requesting the data. 

Let’s take an example: Pretend the application had 1000 Users and you wanted to list the Users on a web page. As long as you stick to first-level properties, such as Username or Email, this would be fine. However, if you accidentally did something like this on your ASP.NET View:

This would cause 1000 separate database queries just to get the Account Name! Not ideal when web requests are supposed to be quick. 😉 The main point is, with Lazy Loading, you lose control of how your objects are populated so this wasn’t an option for me.

If you do want to go down this route, see this article here.

UserStore, the Beginning for ASP.NET Core Identity

The UserStore is how ASP.NET Core Identity gets information about the User from the database. It is one level above the IdentityDbContext itself and calls operational methods on the context to manager User information, such as updating a User’s password, adding a User to a new Role, deleting Users, et. The UserStore class lives in the Microsoft.AspNetCore.Identity.EntityFrameworkCore namespace. If you look at its definition, you will see that it has dozens of methods and properties for performing database operations for the Identity framework.

In our case, we only need to make a couple changes. First, I created a WmtUserStore class that inherited from UserStore like this:

NOTE: UserStore has a lot of constructors and we are using this one because I only needed the DbContext object.

Next, I declare my own “UsersSet” object in the class. UsersSet is the DbSet<WmtUserthat represents Identity Users from the database standpoint. Now, the base UserStore already has a UserSet object, however, it is declared as private so I can’t use it in my classMy definition is pretty much the same though:

Then, we need our own Users property in the class. This is what is called when we call UserManager.Users and it is used by other Identity methods as well. Here, we are just ensuring that our Navigation properties are populated before handing back the IQuerable object:

Finally, we need to override the FindByIdAsync method. FindByIdAsync is called by almost all operations in the Identity Framework where we need to retrieve a single User. While many operations in Identity work with the UsersStore.Users property above, FindByIdAsync works with UsersSet directly and calls UsersSet.FindAsync in order to return a single User.

It does this for performance reasons. The first call to UsersSet.FindAsync for a specific User will result in a database hit; however, subsequent calls for the same User in the same Http Request will just return the User from the Entity Framework cache. Therefore, although this method might be called many times during the course of a web request, it is usually returning the requested User from memory.

On that first pass to the database; however, we need to make sure that we include our Account and Organization navigation properties. This is done a little differently against a single User than against a DbSet:

Notice how we’re calling Context.Entry(wmtUser).Reference(<nav property>).Load() here? This is because we already have a fetched User object and now we need to separately ask the DbContext to load up the other Navigation properties. Unlike UsersSet.Include, which uses a JOIN condition in SQL to make sure the Account data is included, Reference<nav property>.Load uses a separate SELECT statement to accomplish the same thing. You can see the difference here:

NOTE: The above images are only showing for User > Account not User > Account > Organization.

So it’s not as efficient, but again, this is only called during the first call of the Http Request. This is why this statement is so important:

This makes sure that I am only requesting the database call IF wmtUser.Account is empty, which it will be on the first call.

There’s Always One More Thing

Since I now have my WmtUserStore created in a separate Project (Services layer)the Identity Framework in the website can now just rely on it for User information. Just like a separate Service, I can make sure that the object being handed over is populated the way I want, without having to make the website jump through a bunch of unnecessary hoops

The last thing to do is make sure ASP.NET Core Identity uses my new WmtUserStore. In the Startup.cs class, I added this line at the back of my Identity setup in the ConfigureServices method:

And that’s it! It was satisfying to get ASP.NET Core Identity to behave the way I wanted without major surgery or by chucking out the framework altogether. I hope this helps you in your future ASP.NET Core adventures.

Ready to Learn More?

es_MXSpanish
en_USEnglish es_MXSpanish