ASP.NET Identity Caché Provider - working with Identity through InterSystems Caché
With the advent of ASP.NET Identity technology from Microsoft .NET, developers are increasingly using it to create web applications. For a brief digression into technology, we suggest reading an article . This technology is present in the standard project template and allows using the standard implementation of authorization and user authentication functionality.

Out of the box, the data provider for ASP.NET Identity is MSSQL, but since the Identity authorization system can interact with any other relational DBMS, we explored and implemented this feature for InterSystems Caché.
First, what is all this for? Imagine that your project uses Caché DBMS on .NET and you need a complete and reliable authorization system. Writing such a system from scratch with your hands is extremely impractical, of course, that you want to use the existing analogue in .NET - ASP.NET Identity. But in its pure form, the framework is able to work only with its native Microsoft DBMS - MS SQL. Our task was to implement an adapter that allows porting Identity to Intersystems Cache DBMS with a slight movement of the hand. The task was implemented in ASP.NET Identity Caché Provider.
The essence of the ASP.NET Identity Caché Provider project is to implement the Caché data provider for ASP.NET Idenity. The main task was to store and provide access to AspNetRoles tables, AspNetUserClaims , AspNetUserLogins , AspNetUserRoles and AspNetUsers , without violating the standard logic of working with these tables.
Let's move on to the Caché data provider implementation for ASP.NET Identity. It went through two stages:
- Implementation of data storage classes (which will be responsible for storing state) and the IdentityDbContext class , which encapsulates all the low-level logic of working with the data warehouse. The IdentityDbInitializer class was also implemented , which adapts the Caché database to work with Identity.
- Implementation of classes UserStore and RoleStore (together with integration
tests). Demonstration project.
During the first stage, the following classes were implemented:
- IdentityUser - implementation of the IUser interface.
- IdentityUserRole - an associative entity for User – Role communication.
- IdentityUserLogin - data about user logins.
An extensible version of the UserLoginInfo class .
- IdentityUserClaim - user brand information.
- IdentityDbContext- Entity Framework database context.
Consider in more detail the identity of IdentityUser , which is a repository for users, roles, logins, brands and user-role relationships. An example of the implementation of a common and generic version of IdentityUser .
To implement the restriction of access rights in Identity, special objects are designed - Roles. The role in the configuration may correspond to the positions or activities of various user groups.
IdentityDbContext - an entity that encapsulates the creation of a connection, loading entities from the database, validating the correspondence of user objects to the structure of related tables and field values. As an example, consider the OnModelCreating method , which validates tables according to Identity requirements.
DbModelBuilder is used to map CLR classes to a database schema. This code-based approach to building an EDM model is called Code First. DbModelBuilder is typically used to customize a model by overriding OnModelCreating (DbModelBuilder) . However, DbModelBuilder can also be used independently of DbContext to build the model and then construct the DbContext or ObjectContext .
The IdentityDbInitializer class prepares the Caché database for use with Identity .
The CreateTableIfNotExists methods create the necessary tables, if they do not already exist. Checking for the existence of a table is done by querying the Cache table - Dictionary.CompiledClass , which stores information about existing tables. If any table has not yet been created, it is created.
At the second stage, such entities as IdentityUserStore and IdentityRoleStore were implemented, which encapsulate the logic of adding, editing, and deleting users and roles. For these entities, one hundred percent coverage with unit tests was required.
To summarize: we implemented a data provider for working with Caché DBMS with Entity Framework in the context of ASP.NET Identity technology. The application is designed in a separate Nuget package, and now, if necessary, work with the Caché DBMS, and at the same time use standard Microsoft authorization, it is enough to simply integrate the Identity Caché Provider assembly into the project through the Nuget Package Manager.
The implementation of the project with source code, example and tests is available on GitHub .

Out of the box, the data provider for ASP.NET Identity is MSSQL, but since the Identity authorization system can interact with any other relational DBMS, we explored and implemented this feature for InterSystems Caché.
First, what is all this for? Imagine that your project uses Caché DBMS on .NET and you need a complete and reliable authorization system. Writing such a system from scratch with your hands is extremely impractical, of course, that you want to use the existing analogue in .NET - ASP.NET Identity. But in its pure form, the framework is able to work only with its native Microsoft DBMS - MS SQL. Our task was to implement an adapter that allows porting Identity to Intersystems Cache DBMS with a slight movement of the hand. The task was implemented in ASP.NET Identity Caché Provider.
The essence of the ASP.NET Identity Caché Provider project is to implement the Caché data provider for ASP.NET Idenity. The main task was to store and provide access to AspNetRoles tables, AspNetUserClaims , AspNetUserLogins , AspNetUserRoles and AspNetUsers , without violating the standard logic of working with these tables.
A Few Words About ASP.NET Identity Architecture
The key objects in Asp.Net Identity are users and roles. All functionality for creating and deleting users, interacting with the user repository is stored in the UserManager class . To work with roles and manage them, the RoleManager class is defined in Asp.Net Identity . The following is a Microsoft.AspNet.Identity.Core class diagram.

Each user for UserManager provides an IUser interface object . At the same time, all user management operations are performed through the storage represented by the IUserStore object . Each role represents an implementation of the IRole interface , and role manipulations (addition, modification, deletion) are performed using RoleManager. The direct implementation of the IUser , IRole , IUserStore and IRoleStore interfaces provides the Microsoft.AspNet.Identity EntityFramework namespace, where classes such as IdentityUser , UserStore , are available for use ,IdentityRole , RoleStore , IdentityDbContext .

If you need to store additional information about a user that is not in the specified tables by default, there is an IdentityUserClaim class (hallmarks) that allows you to add the necessary fields and then use them, for example, when registering a user.

Each user for UserManager provides an IUser interface object . At the same time, all user management operations are performed through the storage represented by the IUserStore object . Each role represents an implementation of the IRole interface , and role manipulations (addition, modification, deletion) are performed using RoleManager. The direct implementation of the IUser , IRole , IUserStore and IRoleStore interfaces provides the Microsoft.AspNet.Identity EntityFramework namespace, where classes such as IdentityUser , UserStore , are available for use ,IdentityRole , RoleStore , IdentityDbContext .

If you need to store additional information about a user that is not in the specified tables by default, there is an IdentityUserClaim class (hallmarks) that allows you to add the necessary fields and then use them, for example, when registering a user.
Let's move on to the Caché data provider implementation for ASP.NET Identity. It went through two stages:
- Implementation of data storage classes (which will be responsible for storing state) and the IdentityDbContext class , which encapsulates all the low-level logic of working with the data warehouse. The IdentityDbInitializer class was also implemented , which adapts the Caché database to work with Identity.
- Implementation of classes UserStore and RoleStore (together with integration
tests). Demonstration project.
During the first stage, the following classes were implemented:
- IdentityUser - implementation of the IUser interface.
- IdentityUserRole - an associative entity for User – Role communication.
- IdentityUserLogin - data about user logins.
An extensible version of the UserLoginInfo class .
- IdentityUserClaim - user brand information.
- IdentityDbContext
Consider in more detail the identity of IdentityUser , which is a repository for users, roles, logins, brands and user-role relationships. An example of the implementation of a common and generic version of IdentityUser .
namespace InterSystems.AspNet.Identity.Cache
{
///
/// IUser implementation
///
public class IdentityUser : IdentityUser, IUser
{
///
/// Constructor which creates a new Guid for the Id
///
public IdentityUser()
{
Id = Guid.NewGuid().ToString();
}
///
/// Constructor that takes a userName
///
///
public IdentityUser(string userName)
: this()
{
UserName = userName;
}
}
///
/// IUser implementation
///
///
///
///
///
public class IdentityUser : IUser
where TLogin : IdentityUserLogin
where TRole : IdentityUserRole
where TClaim : IdentityUserClaim
{
///
/// Constructor
///
public IdentityUser()
{
Claims = new List();
Roles = new List();
Logins = new List();
}
///
/// Email
///
public virtual string Email { get; set; }
To implement the restriction of access rights in Identity, special objects are designed - Roles. The role in the configuration may correspond to the positions or activities of various user groups.
namespace InterSystems.AspNet.Identity.Cache
{
///
/// EntityType that represents a user belonging to a role
///
public class IdentityUserRole : IdentityUserRole
{
}
///
/// EntityType that represents a user belonging to a role
///
///
public class IdentityUserRole
{
///
/// UserId for the user that is in the role
///
public virtual TKey UserId { get; set; }
///
/// RoleId for the role
///
public virtual TKey RoleId { get; set; }
}
}
IdentityDbContext - an entity that encapsulates the creation of a connection, loading entities from the database, validating the correspondence of user objects to the structure of related tables and field values. As an example, consider the OnModelCreating method , which validates tables according to Identity requirements.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Mapping and configuring identity entities according to the Cache tables
var user = modelBuilder.Entity()
.ToTable("AspNetUsers");
user.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId);
user.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId);
user.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
user.Property(u => u.UserName)
.IsRequired()
.HasMaxLength(256)
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("UserNameIndex") { IsUnique = true }));
user.Property(u => u.Email).HasMaxLength(256);
modelBuilder.Entity()
.HasKey(r => new { r.UserId, r.RoleId })
.ToTable("AspNetUserRoles");
modelBuilder.Entity()
.HasKey(l => new { l.LoginProvider, l.ProviderKey, l.UserId })
.ToTable("AspNetUserLogins");
modelBuilder.Entity()
.ToTable("AspNetUserClaims");
var role = modelBuilder.Entity()
.ToTable("AspNetRoles");
role.Property(r => r.Name)
.IsRequired()
.HasMaxLength(256)
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("RoleNameIndex") { IsUnique = true }));
role.HasMany(r => r.Users).WithRequired().HasForeignKey(ur => ur.RoleId);
}
DbModelBuilder is used to map CLR classes to a database schema. This code-based approach to building an EDM model is called Code First. DbModelBuilder is typically used to customize a model by overriding OnModelCreating (DbModelBuilder) . However, DbModelBuilder can also be used independently of DbContext to build the model and then construct the DbContext or ObjectContext .
The IdentityDbInitializer class prepares the Caché database for use with Identity .
public void InitializeDatabase(DbContext context)
{
using (var connection = BuildConnection(context))
{
var tables = GetExistingTables(connection);
CreateTableIfNotExists(tables, AspNetUsers, connection);
CreateTableIfNotExists(tables, AspNetRoles, connection);
CreateTableIfNotExists(tables, AspNetUserRoles, connection);
CreateTableIfNotExists(tables, AspNetUserClaims, connection);
CreateTableIfNotExists(tables, AspNetUserLogins, connection);
CreateIndexesIfNotExist(connection);
}
}
The CreateTableIfNotExists methods create the necessary tables, if they do not already exist. Checking for the existence of a table is done by querying the Cache table - Dictionary.CompiledClass , which stores information about existing tables. If any table has not yet been created, it is created.
At the second stage, such entities as IdentityUserStore and IdentityRoleStore were implemented, which encapsulate the logic of adding, editing, and deleting users and roles. For these entities, one hundred percent coverage with unit tests was required.
To summarize: we implemented a data provider for working with Caché DBMS with Entity Framework in the context of ASP.NET Identity technology. The application is designed in a separate Nuget package, and now, if necessary, work with the Caché DBMS, and at the same time use standard Microsoft authorization, it is enough to simply integrate the Identity Caché Provider assembly into the project through the Nuget Package Manager.
The implementation of the project with source code, example and tests is available on GitHub .