Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Code First & Identity with Azure Table Storage

I'm working on a small web app and I've just hit the point in development where I need to start making database decisions. My original plan was to go EF Code First with MSSQL on Azure because it just simplifies the process of working with a database. However, when investigating my database hosting capabilities on Azure, I discovered Azure Table Storage which opened up the world of NoSQL to me.

While the Internet is ablaze with chatter about the features of NoSQL, one of the biggest reason I have managed to gather is that NoSQL stores entire objects as one in a database without breaking up the data into various tables which is good for performance. While this sounds appealing, EF Code First has effectively eliminated this problem by automatically pulling together objects and separating objects into a SQL database without a developer every having to worry about queries.

My main problem, however, is that I can't find any documentation to use things like EF Code First and ASP.NET Identity with NoSQL databases. Since my app currently uses Identity, I'd like to avoid having to switch to something else.

Q: Is it possible to use Code First and/or Identity with Azure Tables?


Edit: A little bit about my app As an extreme simplification, my app allows my users create custom profiles by mixing and matching preconfigured types of data. For example, a user can add any number of Quote objects to their profile and then define the value of the quote (i.e. "Be yourself; everyone else is already taken."). Or they can use a Movie object to define a collection of their favorite movies (i.e. "Title: Inception, Year: 2010"). On average, a user can easily have 50 or more such properties on their page; there is no limitation on the number of properties that they can have.

Using this example, I can easily see how I would go about implementing it using Code First (Profile has a list of Quote objects and a list of Movie objects). I'm not yet sure how this would map to a NoSQL database such as Azure Tables. So, with my app's needs, I'm not sure if switching from Code First to NoSQL a reasonable decision with the features and functionality I would lose.

like image 307
Dragonseer Avatar asked Oct 17 '13 20:10

Dragonseer


People also ask

What is the code first approach?

In the ASP.NET MVC framework, the code-first approach is a development model where you first write the code that creates the data access layer, then you write the code that creates the controllers and views. In the code-first approach, you create a model, which is a class that represents the data in the application.

What is code first and database first?

In the code first approach, the programmer has to write the classes with the required properties first, while in the database first approach, the programmer has to create first the database using GUI.

How do I use code first in an existing database?

To use code-first for an existing database, right click on your project in Visual Studio -> Add -> New Item.. Select ADO.NET Entity Data Model in the Add New Item dialog box and specify the model name (this will be a context class name) and click on Add. This will open the Entity Data Model wizard as shown below.


2 Answers

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);
    }
}
like image 143
Hao Kung Avatar answered Oct 14 '22 00:10

Hao Kung


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)

like image 33
Matt Whetton Avatar answered Oct 14 '22 00:10

Matt Whetton