Identityazuretable
This project provides a high performance cloud solution for ASP.NET Identity Core using Azure Table storage replacing the Entity Framework / MSSQL provider.
Technical Overview
ElCamino.AspNetCore.Identity.AzureTable > 1.7 <= 2.1
High Level Dependencies
ElCamino.AspNetCore.Identity.AzureTable.dll acts as the data access layer under the Microsoft.AspNetCore.Identity.dll classes UserManager and RoleManager by implementing the necessary interfaces on the UserStore and RoleStore respectively. These classes consume the IdentityCloudContext and the other Identity Model classes described below to persist the user and role information according to the business logic dictated by the respective UserManager and RoleManager classes.
UserStore and RoleStore Classes
IdentityCloudContext and Connections
IdentityCloudContext that enables access to the Azure Table storage service and exposes the tables.
The IdentityCloudContext default constructor will look for the IdentityConfiguration object that contains the table storage connection and other settings. The example is shown storing and loading these settings in a .json configuration file. Add the default local connection string to the Azure Storage Emulator or to an Azure Table Storage account. TablePrefix can be left blank, any text here will prefix the Azure table storage names of AspNetUser, AspNetRoles, and AspNetIndex. StorageConnectionString is the Azure table storage connection string. LocationMode can be left empty (defaults to PrimaryOnly). Other storage location modes are listed here and must be used with a RA-GRS storage account if changed from the default.
{
...
"IdentityAzureTable": {
"IdentityConfiguration": {
"TablePrefix": "mvc6"
"StorageConnectionString": "UseDevelopmentStorage=true;"
"LocationMode": "PrimaryOnly"
}
}...
}
Identity Model to Azure Table Relationships
AspNetUsers
Identity Model | PartitionKey | RowKey | Cardinality |
---|---|---|---|
IdentityUser | Escaped(UserName) | Escaped(UserName) | 1 UserName : 1 IdentityUser, UserName uniquely identifies IdentityUser. |
IdentityUserClaim | Escaped(UserName) | Hashed(ClaimType, ClaimValue) | 1 IdentityUser : 0,Many IdentityUserClaim(s) |
IdentityUserLogin | Escaped(UserName) | Escaped(LoginProvider, ProviderKey) | 1 IdentityUser : 0,Many IdentityUserLogin(s) |
IdentityUserRole | Escaped(UserName) | Escaped(RoleName) | 1 IdentityUser : 0,Many IdentityUserRole(s) |
IdentityUserToken | Escaped(UserName) | Hashed(LoginProvider, Name) | 1 IdentityUser : 0,Many IdentityUserToken(s) |
AspNetIndex
Identity Model | Type | PartitionKey | RowKey | Id | Cardinality |
---|---|---|---|---|---|
IdentityUserIndex | User Email Index | Escaped(User Email) | Escaped(UserName) | Escaped(UserName) | 1 Email : 1,Many IdentityUser(s) (unless the UserManager is set to enforce unique email addresses per user, then 1 Email : 1 IdentityUser) |
IdentityUserIndex | User Login Index | Escaped(LoginProvider, ProviderKey) | Escaped(LoginProvider, ProviderKey) | Escaped(UserName) | 1 IdentityUserLogin : 1 IdentityUser |
IdentityUserIndex | User Claim Index | Hashed(ClaimType, ClaimValue) | Escaped(UserName) | Escaped(UserName) | 1 IdentityUserClaim : 1,Many IdentityUser(s). Used to lookup users by claim. |
IdentityUserIndex | User Role Index | Escaped(RoleName) | Escaped(UserName) | Escaped(UserName) | 1 IdentityUserRole : 1,Many IdentityUser(s). Used to lookup users by role. |
AspNetRoles
Identity Model | PartitionKey | RowKey |
---|---|---|
IdentityRole | Escaped(First Char of RoleName) | Escaped(RoleName) |
The properties of the respective identity model class are stored as a field in the table unless marked with the ignore attribute. Inheriting and extending these classes one can add fields whenever your extended class is saved. Also, the Escaped() psydo-code is a reference to the respective methods found in the UriEncodeKeyHelper class. Hashed() psydo-code is a SHA256 hash of the inputfound in the HashKeyHelper class. The Hashed() algorithm was introduced to overcome the size limitation of the rowkey for claim values and overall is a better algorithm for setting deterministic key sizes.
Performance
The AspNetUsers table is designed to keep all of the user's data in a single table and in a table partition for each user. This results in a single query using the user's id (partition key) to get all of the data in a single query when the user's id is known. The user's id is an 'Escaped' version of the username and the traditional guid is not used. This makes the UserStore.FindByUserNameAsync() and UserStoreFindByUserNameAsync() use the same query. When the user id is not known, the AspNetIndex table is queried first either by user's email UserStore.FindByEmailAsync() or external login information UserStore.FindAsync() to get the user's id to then query by user id for the complete user information. The AspNextIndex is always queried by partition and rowkey making that query extremely fast. See above for the details of the AspNetIndex partition and rowkey strategy. In addition, Roles and Claims mapping to users are also indexed for use by GetUsersInRoleAsync() and GetUsersForClaimAsync(), respectively.
AspNetRoles table also uses a composite of the RoleName for the partition and row keys making it possible query the table by its primary key optimizing performance.
Testing
A full suite of integration tests against this assembly ElCamino.AspNetCore.Identity.AzureTable.Tests.csproj
Release Management
- Full suite of integration tests against this assembly at 100% pass rate against the Azure Local Emulator and against a live Azure Storage account. 100% pass rate is required before release. The tests are at about 90% code coverage currently and will work to keep the up.
- Releases available on NuGet and are strong name signed.
A blog for modern software development
Software as a Service
Optic Nerve AI
No code, first class import/publish with Azure, Google cloud custom vision AI services.