Posts in  mongodb

6.4.2014

Building a mongodb provider for the new ASP.NET Identity framework - Part 2 RoleStore And Sample

Several people have requested help understanding how to use the MongoDB provider in a real application. Turns out there's a sample available as a NuGet package Microsoft ASP.NET Identity Samples.

The sample works with EntityFramework. I took the sample, and commit by commit adapted it to use the MongoDB provider. Checkout the modified sample.

One thing the sample had, that the mongodb provider had not yet implemented was a RoleStore. I hesitated to add this to the mongodb provider simply because I don't run across too many use cases for dynamic roles in an application. When I do see this, it's often very custom to the application and providing a generic means to handle this isn't that valuable. With that said, I went ahead and added a simple implementation if someone wants to see how this works with the identity sample and how they may want to adapt it for their own application.

One of the design decisions in doing this was not to update user documents when roles are deleted and when role names were updated. Instead of trying to provide an implementation for unknown use cases, I decided to make the RoleStore operations virtual so they could be extended by consumers. I had a couple of thoughts I wanted to share:

  • I intended the original implementation of the roles array on user documents to store role names.
  • When a role is deleted, leaving the role on the user document shouldn't be a problem. Yes it's orphaned data, but this is the world of denormalized data and is typical when working with NoSQL databases. If that won't work in your use case, consider a multi update to remove the role from user documents.
  • When a role name is changed, you may or may not need to update user documents. If you just made a mistake in the process of setting up a new role, no big deal if it's not yet assigned to any users. This is the use case I targeted. If you're renaming a role after it's been in use, you'll need to modify the user documents accordingly. You could override the RoleStore's UpdateAsync to do this before the role is renamed, just be aware that there's no easy way to ensure consistency of this operation across documents.
  • If you want to avoid issues with renaming roles, you could store the id of a role, instead of the name, in the user document's roles array.

Enjoy!

5.1.2014

Pluralsight Course: Using MongoDB with ASP.NET MVC

After months of hard work, I've finished my first Pluralsight course! I labored to distill what I've learned about MongoDB, specifically within the .NET platform, to help others get a jump start. My favorite part was sharing how document databases remove many of the constraints that usually hamper good design within our applications.

Using MongoDB with ASP.NET MVC

Interested in using MongoDB to store information in your ASP.NET MVC applications? This course covers the decisions you will face and the tools available to incrementally build an MVC application with MongoDB. You will learn how to connect to MongoDB using the official C# driver, create documents and customize serialization, overcome the object relational impedance mismatch and start creating rich domain models, store and modify documents, query documents with both LINQ and Mongo query styles, and store files with GridFS. At the end of this course, you will have the skills necessary to begin using MongoDB in your .NET applications.

1.13.2014

Building a mongodb provider for the new ASP.NET Identity framework - Part 1

Microsoft is hard at work on a new ASP.NET Identity framework. I thought it would be fantastic to start working on a mongodb provider. In a series of posts I'll discuss adapting the framework to work with mongodb, the decisions I made and hopefully spark some interest and get some feedback.

I'm not going to spend a lot of time re-hashing the details of the framework, instead here are some links for background:

I setup a nuget package: Install-Package AspNet.Identity.MongoDB -Pre

Designing IdentityUser

One of the first things we need is a model of a user, IUser is the interface we must implement.

Naming it

The first decision I had to make was what to name this thing? I decided to follow the Entity Framework implementation's IdentityUser to make it easier for users to get up and running with the mongo implementation.

String v ObjectId in .net

In the latest pre-release of Microsoft.AspNet.Identity.Core 2.0.0-alpha1, the IUser interface has been updated to support a TKey generic parameter. Prior versions used a string for the user's Id. We could go ahead and use ObjectId for TKey, but to be honest, working with strings in .net applications is often easier, especially when model binding and serializing/deserializing objects. I'm going to stick with string for TKey:

public class IdentityUser : IUser<string>
{
    public string Id { get; private set; }

    public string UserName { get; set; }
}

ObjectId representation in mongo

Even though we're using strings in our application, I want the Id to be stored as an ObjectId. Mongo is optimized to deal with ObjectId and it's much more familiar to people who work with mongodb. To do this I decorated the Id property with a BsonRepresentation attribute:

public class IdentityUser : IUser<string>
{
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; private set; }

    public string UserName { get; set; }
}

Distributed Id generation

The client driver will assign an id if the Id is not set on an insert, but I wanted to save developers the time of waiting until after an insert to get the Id. In the spirit of decentralized creation of ids which is one of many reasons to use a doc db, I setup the IdentityUser type to generate an id whenever it's created. During de-serialization from mongo, this will be overwritten with an actual id when loading existing user documents.

public class IdentityUser : IUser<string>
{
    public IdentityUser()
    {
        Id = ObjectId.GenerateNewId().ToString();
    }

    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; private set; }

    public string UserName { get; set; }
}

Implementing IUserStore

The first service interface to implement in the new identity framework is IUserStore. This provides basic CRUD operations on user storage. In the spirit of keeping with EF naming, I named this UserStore. I created an IdentityContext to decide where users are stored, this may change as I develop the provider, let me know if you have ideas. There are lots of tests of this in the repository, I wrote the majority as integration tests around the UserManager type in the identity framework. UserManager orchestrates using the different identity service interfaces, like IUserStore, to provide a facade to consuming applications and frameworks. Anyways, here is the UserStore:

public class UserStore<TUser> : IUserStore<TUser>
    where TUser : IdentityUser
{
    private readonly IdentityContext _Context;

    public UserStore(IdentityContext context)
    {
        _Context = context;
    }

    public void Dispose()
    {
        // no need to dispose of anything, mongodb handles connection pooling automatically
    }

    public Task CreateAsync(TUser user)
    {
        return Task.Run(() => _Context.Users.Insert(user));
    }

    public Task UpdateAsync(TUser user)
    {
        return Task.Run(() => _Context.Users.Save(user));
    }

    public Task DeleteAsync(TUser user)
    {
        var remove = Query<TUser>.EQ(u => u.Id, user.Id);
        return Task.Run(() => _Context.Users.Remove(remove));
    }

    public Task<TUser> FindByIdAsync(string userId)
    {
        return Task.Run(() => _Context.Users.FindOneByIdAs<TUser>(ObjectId.Parse(userId)));
    }

    public Task<TUser> FindByNameAsync(string userName)
    {
        var byName = Query<TUser>.EQ(u => u.UserName, userName);
        return Task.Run(() => _Context.Users.FindOneAs<TUser>(byName));
    }
}

And here is the IdentityContext:

public class IdentityContext
{
    public MongoCollection Users { get; private set; }

    public IdentityContext(MongoCollection users)
    {
        Users = users;
        EnsureUniqueIndexOnUserName(users);
    }

    private void EnsureUniqueIndexOnUserName(MongoCollection users)
    {
        var userName = new IndexKeysBuilder().Ascending("UserName");
        var unique = new IndexOptionsBuilder().SetUnique(true);
        users.EnsureIndex(userName, unique);
    }
}

The consuming application passes a MongoCollection instance to store users. MongoCollection is thread safe so we're ok to share a single instance of it. The IdentityContext then ensures there is a unique index on the UserName field so we don't get duplicate users! This will create the index if it doesn't exist. This functionality may move at some point, but this seemed like a good place to start.

Extending IdentityUser

Part of the purpose of the new identity framework is to allow users to tack on their own data in a strongly typed fashion. This is where mongo shines, I added tests in EnsureWeCanExtendIdentityUserTests to verify this works, here's how one would do this:

public class ExtendedIdentityUser : IdentityUser
{
    public string MyUserField { get; set; }

    public void MyUserBehavior(){ ... }
}

And to use it:

var context = new IdentityContext(Users);
var userStore = new UserStore<ExtendedIdentityUser>(context);
var userManager = new UserManager<ExtendedIdentityUser>(userStore);

Next

Let me know what you think of the decisions so far. In subsequent posts I'll show how to implement more of the service interfaces and talk about the decisions I encounter. Check out the repository on github, it's full of unit and integration tests.