DEV Community

Cover image for Using the new EF Core Provider For MongoDB with ASP.NET Core Identity
Miiro Ashirafu
Miiro Ashirafu

Posted on

Using the new EF Core Provider For MongoDB with ASP.NET Core Identity

In this guide, I will show you how to use the current version (8.2.1) of the EF Core Provider for mongoDB with its limitations.

The limitations that prevent this provider from directly being used for ASP.NET Core Identity include lack of support for the Select projection and some other Linq operations, which are used in Identity. Therefore some extra steps are needed to achieve an operational program.

Prerequisites

You need the following to complete follow along.

  1. Mongo Atlas account and a database URL for your cluster
  2. Configured development Environment that can develop Blazor Server App (I will be using Visual Studio 2022)
  3. An active internet connection (I guess it's obvious but anyway)

Creating the project

In Visual studio, Create a Blazor Server application (I have named min WeatherApp) and choose .NET version to 8.0 LTS and Athentication type to Individual Account. For Interactive render mode, select Server and choose your Global Interactivity location

Image description

After creating the application, you will notice that it has been configured to use SQL server by Default as the storage for EF Core in the Program.cs file. We shall change this shortly

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
Enter fullscreen mode Exit fullscreen mode

Adding MongoDB related configurations

We shall start by installing the required nugget packages.
Use any preferred method to install the package MongoDB.EntityFrameworkCore version 8.2.
In the appsettings.json file define an object to store the MongoDB settings as shown below

  "MongoDBSettings": {
    "AtlasURI": "mongodb+srv://<username>:<password>@cluster0.abc.mongodb.net/?retryWrites=true&w=majority",
    "DatabaseName": "weatherapp"
  }
Enter fullscreen mode Exit fullscreen mode

For the AtlasURI key, use the URI from the cluster that you created in your MongoDB Atlas account.
Also create a MongoDBSettings class in the Data folder in the root of the project to be used in mapping the settings.

 public class MongoDBSettings
 {
     public string AtlasURI { get; set; } = string.Empty;
     public string DatabaseName { get; set; } = string.Empty;
 }
Enter fullscreen mode Exit fullscreen mode

In the Program.cs file, there is a section of code used to configure the database context to use SQL server as shown below.

Image description
Replace that code with the code below, that uses MongoDB provider.

builder.Services.Configure<MongoDBSettings>(
    builder.Configuration.GetSection("MongoDBSettings"));

var mongoDBSettings = builder.Configuration.GetSection("MongoDBSettings").Get<MongoDBSettings>();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
            options.UseMongoDB(mongoDBSettings!.AtlasURI ?? "", mongoDBSettings.DatabaseName ?? "")
                .EnableSensitiveDataLogging());
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
Enter fullscreen mode Exit fullscreen mode

Using the MongoDB ObjectId as the primary key type for ASP.NET Core Identity

By default, ASP.NET Core Identity uses a GUID string as the primary key while storing users in the SQL databases. However, it allows you to customize this and use another type like long, ulong, int, etc. In this case, since our databases uses its on unique identifier as the ObjectId, we shall use this for identity. Let do the necessary modifications to ensure this.

Note: Remember to add the necessary using statements in each file where we have modified the code.

Firstly, modify the ApplicationUser class in the Data folder to use the ObjectId as the primary key type for the identity user.

public class ApplicationUser : IdentityUser<ObjectId>
{
}
Enter fullscreen mode Exit fullscreen mode

In the same Data folder, modify the ApplicationDbContext class to also inherit from a version of IdentityDbContext that allows specifying a different type for the primary key of the user and roles. We shall use ObjectId for both.

public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) 
        : IdentityDbContext<ApplicationUser, IdentityRole<ObjectId>, ObjectId>(options)
{
}
Enter fullscreen mode Exit fullscreen mode

At this moment, we are able to run the application and a user can be registered. But when we try some other operations like the default account confirmation, we get exceptions notifying us that the conversion from the string type to the ObjectId type is not supported.

Image description

This prevents us from using other identity features. In order to handle this, and the other limitations of the EF Core Provider for MongoDB, we shall need to use a custom store for both users and roles.

Creating the custom stores for identity

In the project, create a new folder and name it CustomIdentityStores and create 2 files, namely UserStore.cs and RoleStore.cs

Get the starter code from the identity repository for each of these files using the links below (from the official identity repository)

  1. UserStore.cs
  2. RoleStore.cs

Apart from the namespace declaration, paste the rest of the code into the corresponding file and we deal with the modifications (use local namespace).

In the UserStore.cs we shall have one error corresponding to some resource that we don't have access to.

Image description

For simplicity, just replace it to a constant string(e.g. "Role Not Found")

Modify the custom stores to handle Id conversion and other limitations of the provider

UserStore.cs

In this file, replace the FindByIdAsync method by the code below, which uses the ObjectId.Parse method instead of the identity one.

    public override Task<TUser?> FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken))
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        var id = ObjectId.Parse(userId);// ConvertIdFromString(userId);
        return UsersSet.FindAsync(new object?[] { id }, cancellationToken).AsTask();
    }
Enter fullscreen mode Exit fullscreen mode

Also modify the default User Store type and the corresponding other implementations to use ObjectId as the type of the unique identifier for both User and Role entities.
Make sure that you replace the DBContext injected in the default implementation to use the one ApplicationDbContext of our application.

Image description

In addition, methods such as GetRolesAsync where unsupported features such as the select projection are used, we can use client-side evaluation if we don't have a lot of roles or use any techniques of your choice to convert the Linq to SQL logic to something that is supported by the provider.
For example the code shown below:

Image description

can be converted to.

Image description

Do this for all such cases (e.g. in the GetClaimsAsync method) and end up with the changes as shown in the complete file here.

RoleStore.cs

In this file, follow the same steps as in the UserStore.cs. The example result is shown the completed file here.

Register the new Stores

In the Program.cs file, register these classes to be used by identity instead of the default stores by using the code below.

builder.Services.AddTransient<IUserStore<User>, UserStore>();
builder.Services.AddTransient<IRoleStore<IdentityRole<ObjectId>>, RoleStore<IdentityRole<ObjectId>>>();
Enter fullscreen mode Exit fullscreen mode

Once Everything is wired correctly, You should be able to run the application, register, login and view the authorized resources.

Image description

Find the completed code in a git hub repository here.

Enjoy coding without any limits.

Top comments (0)