Shortly after Microsoft introduced their Identity system as a replacement for the ASP.NET Membership API, there was some documentation published about it's architecture and a nice sample showed up at the Microsoft web-site on how to use MySQL instead of MS SQL Server to store the information by creating a custom storage provider.
I immediately though about creating something similar based with our own great ORM tool eXpress Persistent Objects (XPO).
One of the big advantages of XPO is that it supports more than a dozen database back-ends out of the box like: Advantage, SQL Anywhere, Sybase, DB2, Interbase/Firebird, MS-Access, Oracle, Pervasive, PostgreSQL, SQLite, VistaDB and yes; MS SQL Server and MySQL!
You can read more about the supported DB systems and their versions here.
I started this little project quite some time ago, but only recently had some time to finish it off and share it with you.
A quick overview on the architecture and what needs to be implemented
As I mentioned before, the documentation about ASP.NET Identity is well done and you can read all about it here, so I will not dive too deep into that.
To understand which parts of the architecture of Identity are being handled by the XPO based storage provider, you can take a look at the following diagram which describes the global architecture of Identity:
ASP.NET Application | Your project |
Managers | UserManager, RoleManager etc. |
Stores | UserStore, RoleStore etc. |
Data Access Layer | XPO |
Data Source | Database |
As you can see, the Data Access Layer will be implemented with XPO, and we need to create Store and Manager classes to facilitate the XPO based data layer.
The last step is to make a couple of smaller changes in your project's ~/App_Start/IdentityConfig.cs file to instantiate the correct Manager objects for you application.
Some goals I want to achieve
There are a couple of things I want to achieve with this package.
Easy to get
The end-result for me would be that the changes you need to make to your project should be as little as possible, and I want this all being packed up in a NuGet package, so the storage provider can be easily included in your project.
This last point raises a question: "This Nuget package will be released on NuGet, but XPO is neither open-source nor free, but it is available for all of our paying customers. How do we solve this?"
Fortunately, that is (since a couple of months) fairly simple: Remember this blogpost about the DevExpress NuGet feed?
This means that if you have configured the DevExpress NuGet feed in Visual Studio or in the NuGet.targets file of your solution, this Storage Provider package can have a dependency to the DevExpress.Xpo package.
Easy to setup
I want this to be setup as easy as possible. This means that even if you want to include it in a project which doesn't use XPO, you should not be bothered with the initialization of XPO. Also if you do use XPO, it should not get in your way.
This means there is a small package as a dependency which contains some generic helper code for XPO which I use through-out projects. If you think this is convenient, feel free to use it as well.
What needs to be done?
It all starts with a small data model which will be represented by the XPO classes in this project. Below, there is a schematic overview of this model:
This model is pretty much the same as what is being used in the Entity Framework storage provider where I would like to point out one interesting detail; all the persistent classes descend from the XpoDxBase class. Besides the fact that all descendants will have an Id, AddStampUTC and ModStampUTC field, there is another reason for this. It will become clear later in this post.
A quick look at the original provider
Let's create a new project with individual authentication in Visual Studio.
If we now take a look at ~/App_Start/IdentityConfig.cs, there is the ApplicationUserManager class being generated which descends from a generic base class UserManager<ApplicationUser>.
public class ApplicationUserManager : UserManager<ApplicationUser> { public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store) { } // ... more code ... }
The generic class determines which class to use to represent users in the manager. This ApplicationUser can be found in the ~/Models/IdentityModels.cs file:
public class ApplicationUser : IdentityUser { public ClaimsIdentity GenerateUserIdentity(ApplicationUserManager manager) { // Note the authenticationType must match the one defined // in CookieAuthenticationOptions.AuthenticationType var userIdentity = manager.CreateIdentity(this, DefaultAuthenticationTypes.ApplicationCookie); // Add custom user claims here return userIdentity; } public Task<ClaimsIdentity> GenerateUserIdentityAsync( ApplicationUserManager manager) { return Task.FromResult(GenerateUserIdentity(manager)); } }
The same approach is used for the SignIn manager:
public class ApplicationSignInManager : SignInManager<ApplicationUser, string> { public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager) { } public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user) { return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager); } public static ApplicationSignInManager Create( IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context) { return new ApplicationSignInManager( context.GetUserManager<ApplicationUserManager>(), context.Authentication); } }
The reason for this approach is that you can easily use your own classes in case you want to store additional account information like address or day of birth. This is something I want to incorporate in the XPO Storage provider as well.
There is one interesting thing to notice in the ApplicationUser class; it is inherited from the IdentityUser class which in its turn is inherited from a generic IdentityUser class.
The idea behind this is very nice, since this gives us the possibility to customize a couple of things like the type of the identity field and the types for Role, Login and Claim entities.
In coorporation with Entity Framework, you don’t need to do a lot of additional things because these classes can be directly used in the DbContext of Entity Framework.
We could use the same approach for XPO by following these steps, but a better way is to use the DTO design pattern which gives us more flexibility in the end.
The DX.Data.Xpo package
This package contains some code I use through-out different projects and it will serve as a base for the Identity Storage provider as well. Let’s go quickly through the files.
XpoDatabase
This class has the XPO related initialization code as described in the documentation. It also has some experimental features which are not used in the Identity Storage provider like working with different datalayers to copy data.
The most important code in this class are some helper methods to by used in the Async methods of the storage provider.
public class XpoDatabase : IDisposable { //... more code ... public virtual void Execute(Action<XpoDatabase, Session> work, bool transactional = true, bool commit = true) { using (Session s = transactional ? GetUnitOfWork() : GetSession()) { work(this, s); if (transactional && commit && (s is UnitOfWork)) ((UnitOfWork)s).CommitChanges(); } } public virtual T Execute(Func<XpoDatabase, Session, T> work, bool transactional = true, bool commit = true) { T result = default(T); using (Session s = transactional ? GetUnitOfWork() : GetSession()) { result = work(this, s); if (transactional && commit && (s is UnitOfWork)) ((UnitOfWork)s).CommitChanges(); } return result; } //... more code ... }
XpoStore
This generic helper class makes it easy to select a bunch of objects en perform updates on that collection.
XpoDtoDatasource
The XpoDtoDatasource class deals with the XPO entities and the DTO classes and serves as a (good old) ObjectDatasource implementation at the same time.
Since the XpoDatasource class is an abstract generic class, you will need to implement it for all entities. This class relies on the XpoDatabase class.
DX.Data.Xpo.Identity
This is what the blogpost is all about, the universal storage provider for Identity.
The project is setup quite straightforward. There are a number of DTO classes which are inherited from the Identy classes, but implement an interface to support the DTO mechanism built in the DX.Data.Xpo project.
The DTO classes are prefixed with XP like shown in the image below:
The structure of these files are pretty much the same, there is a generic class which allows us to fine-tune certain things like key type and some other type depending on the class.
If we take a look at the XPIdentityRole.cs file, there is the generic class:
public abstract class XPIdentityRole<TKey, TXPORole> : XpoDtoBaseEntity<TKey, TXPORole>, IRole<TKey>, IDxRole<TKey> where TKey : IEquatable<TKey> where TXPORole : XPBaseObject, IDxRole<TKey> { public XPIdentityRole(TXPORole source, int loadingFlags) : base(source, loadingFlags) { Users = new List<IDxUser<TKey>>(); } public XPIdentityRole(TXPORole source) : this(source, 0) { } public XPIdentityRole() : this(null, 0) { } public virtual ICollection<IDxUser<TKey>> Users { get; protected set; } public virtual IList UsersList { get { return Users.ToList(); } } public override TKey Key { get { return Id; } } public TKey Id { get; set; } public string Name { get; set; } public override void Assign(object source, int loadingFlags) { var src = CastSource(source); if (src != null) { this.Id = src.Key; this.Name = src.Name; } } }
And for easy and standard use, there is the non-generic class:
public class XPIdentityRole : XPIdentityRole<string, XpoDxRole> { public XPIdentityRole(XpoDxRole source) : base(source) { } public XPIdentityRole(XpoDxRole source, int loadingFlags) : base(source, loadingFlags) { } public XPIdentityRole() { } }
Instead of doing the Assign implementation manually, we could use something like Automapper instead, but I like to have the full control here.
The UserStore and RoleStore
With all the preparations described earlier, the actual implementation of the stores for the Storage provider is pretty straightforward. Again, I've created a full generic class which implements all the Identity interfaces including Claims and Logins support. Next there is a more simplyfied generic class, which only has the generic types which are going to be used in the ~/App_Start/IdentityConfig.cs of your project.
Below is the code for the RoleStore which also shows how to deal with the Async methods by using the code from the DX.Data.Xpo library.
public class XPRoleStore<TKey, TRole, TXPORole> : XpoStore<TXPORole, TKey>, IQueryableRoleStore<TRole, TKey> where TKey : IEquatable<TKey> where TRole : XPIdentityRole<TKey, TXPORole>, IRole<TKey> where TXPORole : XPBaseObject, IDxRole<TKey>, IRole<TKey> { public XPRoleStore() : base() { } public XPRoleStore(string connectionName) : base(connectionName) { } public XPRoleStore(string connectionString, string connectionName) : base(connectionString, connectionName) { } public XPRoleStore(XpoDatabase database) : base(database) { } #region Generic Helper methods and members protected static Type XPORoleType { get { return typeof(TXPORole); } } protected static TXPORole XPOCreateRole(Session s) { return Activator.CreateInstance(typeof(TXPORole), s) as TXPORole; } #endregion public IQueryable<TRole> Roles { get { XPQuery<TXPORole> q = new XPQuery<TXPORole>(GetSession()); var result = from u in q select Activator.CreateInstance(typeof(TRole), u as TXPORole) as TRole; return result; } } public Task CreateAsync(TRole role) { ThrowIfDisposed(); if (role == null) throw new ArgumentNullException("role"); return Task.FromResult(XPOExecute<object>((db, wrk) => { var xpoRole = XPOCreateRole(wrk); xpoRole.Assign(role, 0); return null; })); } public Task DeleteAsync(TRole role) { ThrowIfDisposed(); if (role == null) throw new ArgumentNullException("role"); return Task.FromResult(XPOExecute<object>((db, wrk) => { wrk.Delete(wrk.FindObject(XPORoleType, CriteriaOperator.Parse($"{KeyField} == ?", role.Id))); return null; })); } public Task<TRole> FindByIdAsync(TKey roleId) { ThrowIfDisposed(); return Task.FromResult(XPOExecute((db, wrk) => { var xpoRole = wrk.FindObject(XPORoleType, CriteriaOperator.Parse($"{KeyField} == ?", roleId)); return xpoRole == null ? null : Activator.CreateInstance(typeof(TRole), xpoRole, 0) as TRole; })); } public Task<TRole> FindByIdAsync(string roleId) { ThrowIfDisposed(); return Task.FromResult(XPOExecute((db, wrk) => { var xpoRole = wrk.FindObject(XPORoleType, CriteriaOperator.Parse($"{KeyField} == ?", roleId)); return xpoRole == null ? null : Activator.CreateInstance(typeof(TRole), xpoRole, 0) as TRole; })); } public Task<TRole> FindByNameAsync(string roleName) { ThrowIfDisposed(); if (String.IsNullOrEmpty(roleName)) throw new ArgumentException("roleName is null or empty"); return Task.FromResult(XPOExecute((db, wrk) => { var xpoRole = wrk.FindObject(XPORoleType, CriteriaOperator.Parse("NameUpper == ?", roleName.ToUpperInvariant())); return xpoRole == null ? null : Activator.CreateInstance(typeof(TRole), xpoRole, 0) as TRole; })); } public Task UpdateAsync(TRole role) { ThrowIfDisposed(); if (role == null) throw new ArgumentNullException("roleName"); return Task.FromResult(XPOExecute<object>((db, wrk) => { TXPORole r = wrk.FindObject(XPORoleType, CriteriaOperator.Parse($"{KeyField} == ?", role.Id)) as TXPORole; if (r != null) { r.Assign(role, 0); } return null; })); } }
The second less generic class can look as simple as below:
public class XPRoleStore<TRole, TXPORole> : DxRoleStore<string, TRole, TXPORole> where TRole : XPIdentityRole<string, TXPORole>, IRole<string> where TXPORole : XPBaseObject, IDxRole<string>, IRole<string> { public XPRoleStore() : base() { } public XPRoleStore(string connectionName) : base(connectionName) { } public XPRoleStore(string connectionString, string connectionName) : base(connectionString, connectionName) { } public XPRoleStore(XpoDatabase database) : base(database) { } }
For the UserStore, there is a bit more code involved. You can check that out by cloning or forking the project. Once you go through the code, you'll see that the approach is quite the same as the RoleStore.
How to use this in your own WebApp
I have mentioned a couple of directions on where to change things after you've completed the project template. Let's summarize the steps below:
- First make sure you have your personal DevExpress NuGet feed configured as described here
- Next, we can add the DX.Data.Xpo.Indentity package
- Now there are a couple of things we need to change in code:
In ~/App_Start/IdentityConfig.cs and ~/Models/IdentityConfig.cs we need to replace:
using Microsoft.AspNet.Identity.EntityFramework;with
using DX.Data.Xpo.Identity;
- Now we also need to change some code in the ApplicationUser which is located in ~/Models/IdentityConfig.cs. Replace the class with:
public class ApplicationUser : XPIdentityUser<string, XpoApplicationUser> { // don't forget the constructors !!! public ApplicationUser(XpoApplicationUser source) : base(source) { } public ApplicationUser(XpoApplicationUser source, int loadingFlags) : base(source, loadingFlags) { } public ApplicationUser() : base() { } public override void Assign(object source, int loadingFlags) { base.Assign(source, loadingFlags); //XpoApplicationUser src = source as XpoApplicationUser; //if (src != null) //{ // // additional properties here // this.PropertyA = src.PropertyA; // // etc. //} } public ClaimsIdentity GenerateUserIdentity(ApplicationUserManager manager) { // Note the authenticationType must match the one defined in // CookieAuthenticationOptions.AuthenticationType var userIdentity = manager.CreateIdentity(this, DefaultAuthenticationTypes.ApplicationCookie); // Add custom user claims here return userIdentity; } public Task<ClaimsIdentity> GenerateUserIdentityAsync( ApplicationUserManager manager) { return Task.FromResult(GenerateUserIdentity(manager)); } }
- Since the ApplicationUser is a DTO class, we also need to add the persistent counterpart. This is the class which will be stored in the database by XPO. You can add it to the ~/Models/IdentityConfig.cs.
// This class will be persisted in the database by XPO // It should have the same properties as the ApplicationUser [MapInheritance(MapInheritanceType.ParentTable)] public class XpoApplicationUser : XpoDxUser { public XpoApplicationUser(Session session) : base(session) { } public override void Assign(object source, int loadingFlags) { base.Assign(source, loadingFlags); //ApplicationUser src = source as ApplicationUser; //if (src != null) //{ // // additional properties here // this.PropertyA = src.PropertyA; // // etc. //} } }
- Next we need to initialize the ApplicationUserManager by using the XPUserStore. Because of the generic setup, we can easily specify the XPO persistent class together with the DTO class:
public class ApplicationUserManager : UserManager<ApplicationUser> { public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store) { } public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) { var manager = new ApplicationUserManager(new XPUserStore<ApplicationUser, XpoApplicationUser>(context.Get<XpoDatabase>())); // ... } // ... }
- And the last thing we need to change is the ApplicationDbContex class. It is also in ~/Models/IdentityConfig.cs. In XPO there is no DBContext class so:
public class ApplicationDbContext { public static DX.Data.Xpo.XpoDatabase Create() { return new DX.Data.Xpo.XpoDatabase("DefaultConnection"); } }
- And finally we can remove the Microsoft.AspNet.Identity.EntityFramework NuGet package as well as the EntityFramework package from the project.
Getting the packages and source
The NuGet packages are already available on NuGet.org so you can just add them to your project.
Do note that building your project does not work if you don't have your personal DevExpress NuGet feed configured!
If you want to check out all of the code, feel free to fork or clone the repo from my GitHub account at: https://github.com/donwibier/DXWeb. This code is free to use and you can contact me by replying on this blog post. You can even do a pull-request in case you found something in my code.
Let me know if this is interesting and/or usefull for you.