Code First & Identity with Azure Table Storage Code First & Identity with Azure Table Storage azure azure

Code First & Identity with Azure Table Storage


So we will have a sample targeting exactly this scenario, using AzureTable storage as a no sql implementation of a UserStore. Basically you implement an IUserStore using the Azure Storage APIs. Here's a basic implementation that implements the login/password methods, but not everything:

public class AzureRole : TableEntity, IRole {    public string Id { get; set; }    public string Name { get; set; }}public class AzureLogin : TableEntity {    public AzureLogin() {        PartitionKey = Constants.IdentityPartitionKey;        RowKey = Guid.NewGuid().ToString();    }    public AzureLogin(string ownerId, UserLoginInfo info) : this() {        UserId = ownerId;        LoginProvider = info.LoginProvider;        ProviderKey = info.ProviderKey;    }    public string UserId { get; set; }    public string ProviderKey { get; set; }    public string LoginProvider { get; set; }}public class AzureUser : TableEntity, IUser {    public AzureUser() {        PartitionKey = Constants.IdentityPartitionKey;        RowKey = Guid.NewGuid().ToString();        Id = RowKey;        Roles = new List<string>();        Claims = new List<Claim>();        Logins = new List<AzureLogin>();    }    public AzureUser(string userName) : this() {        UserName = userName;    }    public string Id { get; set; }    public string UserName { get; set; }    public string PasswordHash { get; set; }    public string SecurityStamp { get; set; }    public IList<string> Roles { get; set; }    public IList<AzureLogin> Logins { get; set; }    public IList<Claim> Claims { get; set; }}public static class Constants {    public const string IdentityPartitionKey = "ASP.NET Identity";}public class AzureStore : IUserStore<AzureUser>, IUserClaimStore<AzureUser>, IUserLoginStore<AzureUser>, IUserRoleStore<AzureUser>, IUserPasswordStore<AzureUser> {    public AzureStore() {        // Retrieve the storage account from the connection string.        CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));        // CreateAsync the table client.        CloudTableClient tableClient = storageAccount.CreateCloudTableClient();        // CreateAsync the table if it doesn't exist.        CloudTable table = tableClient.GetTableReference("Identity");        table.CreateIfNotExists();        Table = table;        BatchOperation = new TableBatchOperation();    }    public TableBatchOperation BatchOperation { get; set; }    public CloudTable Table { get; set; }    public void Dispose() {    }    public Task<IList<Claim>> GetClaimsAsync(AzureUser user) {        return Task.FromResult(user.Claims);    }    public Task AddClaimAsync(AzureUser user, System.Security.Claims.Claim claim) {        return Task.FromResult(0);    }    public Task RemoveClaimAsync(AzureUser user, System.Security.Claims.Claim claim) {        return Task.FromResult(0);    }    Task IUserStore<AzureUser>.CreateAsync(AzureUser user) {        TableOperation op = TableOperation.Insert(user);        var result = Table.Execute(op);        return Task.FromResult(0);    }    Task IUserStore<AzureUser>.UpdateAsync(AzureUser user) {        TableOperation op = TableOperation.Replace(user);        var result = Table.Execute(op);        return Task.FromResult(0);    }    public Task<AzureUser> FindByIdAsync(string userId) {        TableOperation op = TableOperation.Retrieve<AzureUser>(Constants.IdentityPartitionKey, userId);        var result = Table.Execute(op);        return Task.FromResult<AzureUser>(result.Result as AzureUser);    }    public Task<AzureUser> FindByNameAsync(string userName) {        TableQuery<AzureUser> query = new TableQuery<AzureUser>().Where(TableQuery.GenerateFilterCondition("UserName", QueryComparisons.Equal, userName));        return Task.FromResult(Table.ExecuteQuery(query).FirstOrDefault());    }    public Task AddLoginAsync(AzureUser user, UserLoginInfo login) {        TableOperation op = TableOperation.Insert(new AzureLogin(user.Id, login));        var result = Table.Execute(op);        return Task.FromResult(0);    }    public Task RemoveLoginAsync(AzureUser user, UserLoginInfo login) {        var al = Find(login);        if (al != null) {            TableOperation op = TableOperation.Delete(al);            var result = Table.Execute(op);        }        return Task.FromResult(0);    }    public Task<IList<UserLoginInfo>> GetLoginsAsync(AzureUser user) {        TableQuery<AzureLogin> query = new TableQuery<AzureLogin>()            .Where(TableQuery.GenerateFilterCondition("UserId", QueryComparisons.Equal, user.Id))            .Select(new string[] { "LoginProvider", "ProviderKey" });        var results = Table.ExecuteQuery(query);        IList<UserLoginInfo> logins = new List<UserLoginInfo>();        foreach (var al in results) {            logins.Add(new UserLoginInfo(al.LoginProvider, al.ProviderKey));        }        return Task.FromResult(logins);    }    private AzureLogin Find(UserLoginInfo login) {        TableQuery<AzureLogin> query = new TableQuery<AzureLogin>()            .Where(TableQuery.CombineFilters(                TableQuery.GenerateFilterCondition("LoginProvider", QueryComparisons.Equal, login.LoginProvider),                TableOperators.And,                TableQuery.GenerateFilterCondition("ProviderKey", QueryComparisons.Equal, login.ProviderKey)))            .Select(new string[] { "UserId" });        return Table.ExecuteQuery(query).FirstOrDefault();    }    public Task<AzureUser> FindAsync(UserLoginInfo login) {        var al = Find(login);        if (al != null) {            return FindByIdAsync(al.UserId);        }        return Task.FromResult<AzureUser>(null);    }    public Task AddToRoleAsync(AzureUser user, string role) {        return Task.FromResult(0);    }    public Task RemoveFromRoleAsync(AzureUser user, string role) {        return Task.FromResult(0);    }    public Task<IList<string>> GetRolesAsync(AzureUser user) {        return Task.FromResult(user.Roles);    }    public Task<bool> IsInRoleAsync(AzureUser user, string role) {        return Task.FromResult(false);    }    public Task DeleteAsync(AzureUser user) {        throw new NotImplementedException();    }    public Task<string> GetPasswordHashAsync(AzureUser user) {        return Task.FromResult(user.PasswordHash);    }    public Task<bool> HasPasswordAsync(AzureUser user) {        return Task.FromResult(user.PasswordHash != null);    }    public Task SetPasswordHashAsync(AzureUser user, string passwordHash) {        user.PasswordHash = passwordHash;        return Task.FromResult(0);    }}


Realistically you cannot use EF Code First with Azure Table Storage. Saying that, working with table storage is generally done using a similar approach to code first - i.e. you create your classes and they create the tables on the fly.

Do note that with table storage there is no relationships or anything like that. Table storage is even simpler than other NoSQL solutions in that you cannot store complex objects in a single table "row".

You could probably create a .net identity provider that uses only table and/or blob storage, but I cant find any examples out there - I'm sure there used to be a codeplex project but I cant find it now.

What Gert Arnold means is use both SQL Azure and Table Storage (EF only with the sql azure part). This way you can use each for what they're best at - table storage at storing large amounts of simply structured data, sql azure for the parts of the data that are more complex (i.e. requires relationships)


For any future reference. There is a github project using Identity with Azure Table storage.James Randall's Accidental Fish. I am not sure if the roles are already implemented.