Entity Framework Samples

The following samples provide detailed information on how to build applications using BrightstarDB. If there are classes of applications for which you would like to see other tutorials please let us know.

Tweetbox

The source code for this example can be found in [INSTALLDIR]\Samples\EntityFramework\EntityFrameworkSamples.sln

Overview

The TweetBox sample is a simple console application that shows the speed in which BrightstarDB can load content. The aim is not to create a Twitter style application, but to show how objects with various relationships to one another are loading quickly, in a structure that will be familiar to developers.

The model consists of 3 simple interfaces: IUser, ITweet and IHashTag. The relationships between the interfaces mimic the structure on Twitter, in that Users have a many to many relationship with other Users (or followers), and have a one to many relationship with Tweets. The tweets have a many to many relationship with Hashtags, as a Tweet can have zero or more Hashtags, and a Hashtag may appear in more than one Tweet.

The Interfaces

IUser

The IUser interface represents a user on twitter, with simple string properties for the username, bio (profile text) and date of registration. The 'Following' property shows the list of users that this user follows, the other end of this relationship is shown in the 'Followers' property, this is marked with the 'InverseProperty' attribute to tell BrightstarDB that Followers is the other end of the Following relationship. The final property is a list of tweets that the user has authored, this is the other end of the relationship from the ITweet interface (described below)

[Entity]
public interface IUser
{
    string Id { get; }
    string Username { get; set; }
    string Bio { get; set; }
    DateTime DateRegistered { get; set; }
    ICollection<IUser> Following { get; set; }
    [InverseProperty("Following")]
    ICollection<IUser> Followers { get; set; }
    [InverseProperty("Author")]
    ICollection<ITweet> Tweets { get; set; }        
}

ITweet

The ITweet interface represents a tweet on twitter, and has simple properties for the tweet content and the date and time it was published. The Tweet has an IUser property ('Author') to relate it to the user who wrote it (the other end of this relationship is described above). ITweet also contains a collection of Hashtags that appear in the tweet (described below).

[Entity]
public interface ITweet
{
    string Id { get; }
    string Content { get; set; }
    DateTime DatePublished { get; set; }
    IUser Author { get; set; }
    ICollection<IHashTag> HashTags { get; set; }
}

IHashTag

A hashtag is a keyword that is contained in a tweet. The same hashtag may appear in more than one tweet, and so the collection of Tweets is marked with the 'InverseProperty' attribute to show that it is the other end of the collection of HashTags in the ITweet interface.

[Entity]
public interface IHashTag
{
    string Id { get; }
    string Value { get; set; }
    [InverseProperty("HashTags")]
    ICollection<ITweet> Tweets { get; set; } 
}

Initialising the BrightstarDB Context

The BrightstarDB context can be initialised using a connection string:

var connectionString = "Type=http;endpoint=http://localhost:8090/brightstar;StoreName=Tweetbox";
var context = new TweetBoxContext(connectionString);

If you have added the connection string into the Config file:

<add key="BrightstarDB.ConnectionString" value="Type=http;endpoint=http://localhost:8090/brightstar;StoreName=Tweetbox" />

then you can initialise the content with a simple:

var context = new TweetBoxContext();

For more information about connection strings, please read the "Connection Strings" topic

Creating a new User entity

Method 1:

var jo = context.Users.Create();
jo.Username = "JoBloggs79";
jo.Bio = "A short sentence about this user";
jo.DateRegistered = DateTime.Now;
context.SaveChanges();

Method 2:

var jo = new User {
                 Username = "JoBloggs79",
                 Bio = "A short sentence about this user",
                 DateRegistered = DateTime.Now
             };
context.Users.Add(jo);
context.SaveChanges();

Relationships between entities

The following code snippets show the creation of relationships between entities by simply setting properties.

Users to Users

var trevor = context.Users.Create();
trevor.Username = "TrevorSims82";
trevor.Bio = "A short sentence about this user";
trevor.DateRegistered = DateTime.Now;
trevor.Following.Add(jo);
context.SaveChanges();

Tweets to Tweeter

var tweet = context.Tweets.Create();
tweet.Content = "My first tweet";
tweet.DatePublished = DateTime.Now;
tweet.Tweeter = trevor;
context.SaveChanges();

Tweets to HashTags:

var nosql = context.HashTags.Where(ht => ht.Value.Equals("nosql").FirstOrDefault();
if (nosql == null)
{
    nosql = context.HashTags.Create();
    nosql.Value = "nosql";
}
var  brightstardb = context.HashTags.Where(ht => ht.Value.Equals("brightstardb").FirstOrDefault();
if (brightstardb == null)
{
    brightstardb = context.HashTags.Create();
    brightstardb.Value = "brightstardb";
}
var tweet2 = context.Tweets.Create();
tweet.Content = "New fast, scalable NoSQL database for the .NET platform";
tweet.HashTags.Add(nosql);
tweet.HashTags.Add(brightstar);
tweet.DatePublished = DateTime.Now;
tweet.Tweeter = trevor;
context.SaveChanges();

Fast creation, persistence and indexing of data

In order to show the speed at which objects can be created, persisted and index in BrightstarDB, the console application creates 100 users, each with 500 tweets. Each of those tweets has 2 hashtags (chosen from a set of 10,000 hash tags).

  1. Creates 100 users
  2. Creates 10,000 hashtags
  3. Saves the users and hashtags to the database
  4. Loops through the existing users and adds followers and tweets (each tweet has 2 random hashtags)
  5. Saves the changes back to the store
  6. Writes out the time taken to the console

MVC Nerd Dinner

The source code for this example can be found in [INSTALLDIR]\Samples\EntityFramework\EntityFrameworkSamples.sln

To demonstrate the ease of using BrightstarDB with MVC, we can use the well-known “Nerd Dinner” tutorial used by .NET Developers when they first learn MVC. We won’t recreate the full Nerd Dinner application, but just a small section of it, as used in the tutorial “Code-First Development with Entity Framework 4”. The code-first tutorial demonstrated how to build the NerdDinner model layer first, and have the database schema automatically created by the Entity Framework. This tutorial will show how to do the same thing using BrightstarDB rather than SQL server, and show how it not only matches the ease of creating applications from scratch, but surpasses Entity Framework by introducing pain free model changes (more on that later). The Brightstar.NerdDinner sample application shows a simple model layer, using MVC 3 for the CRUD application and Brightstar for data storage.

Part I: Creating the data model.

Step 1: Create a New Empty ASP.NET MVC 3 Application

Choose “ASP.NET MVC 3 Web Application” from the list of project types in Visual Studio 2010. If you do not already have MVC 3 installed you can download it from http://www.asp.net/mvc/mvc3. You must also install the "Visual Web Developer" feature in Visual Studio to be able to open and work with MVC3 projects.

Choose a name for your project and select “Empty” for the template type on the dialog that opens, this mean that the project will not be pre-filled with any controllers, models or views. Choose “Razor” as the View Engine, and leave the “Create a unit test project” unchecked, for the purposes of this quick sample it’s not needed.

Step 2: Add references to BrightstarDB

Add a reference in your project to the BrightstarDB DLL from the SDK

Step 3: Add a connection string to your BrightstarDB location

Open the web.config file in your new MVC 3 project, and add a connection string to the location of your BrightstarDB store. There is no setup required, an empty folder will do just fine.

  <appSettings>
    ...
    <add key="BrightstarDB.ConnectionString" value="Type=http;endpoint=http://localhost:8090/brightstar;StoreName=NerdDinner" />
  </appSettings>

For more information about connection strings, please read the "Connection Strings" topic

Step 4: Add the Brightstar Entity Context into your project

Select Add > New Item on the Models folder, and select "Brightstar Entity Context" from the Data category. Rename it to NerdDinnerContext.tt

Step 5: Creating the data model interfaces

BrightstarDB data models are defined by a number of standard .NET interfaces with certain attributes set. The NerdDinner model is very simple (especially for this tutorial) and only consists of a set of “Dinners” that refer to specific events that people can attend, and also a set of “RSVP”s that are used to track a person’s interest in attending a dinner.

We create the two interfaces as shown below in the Models folder of our project:

[Entity]
public interface IDinner
{
   [Identifier("http://nerddinner.com/dinners/")]
    string Id { get; }
    string Title { get; set; }
    string Description { get; set; }
    DateTime EventDate { get; set; }
    string Address { get; set; }
    string HostedBy { get; set; }

    ICollection<IRSVP> RSVPs { get; set; } 
}

[Entity]
public interface IRSVP
{
    [Identifier("http://nerddinner.com/rsvps/")]
    string Id { get; }
    string AttendeeEmail { get; set; }

    [InverseProperty("RSVPs")]
    IDinner Dinner { get; set; }
}

By default the Id properties are URIs that are automatically generated by BrightstarDB. In order to work with simpler values for our Ids we decorate the Id property with an identifier attribute. This adds a prefix for BrightstarDB to use when generating and querying the Ids.

We add an InverseProperty attribute to the Dinner property, and set it to the name of the .NET property on the referencing type ("RSVPs"). This shows that these two properties reflect different sides of the same association.

Step 6: Creating a context class to handle database persistence

Right click on the Brightstar Entity Context and select “Run Custom Tool”. This updates the .cs file contained within the .tt file with the most up to date persistence code needed for your interfaces.

PART II: Creating the interface with MVC controllers and views

Step 1: The controller

Right click on the controller folder and select “Add > Controller”. Name it “HomeController” and select “Controller with empty Read/Write Actions”. This adds a Controller class to the folder, with empty actions for Index(), Details(), Create(),  Edit() and Delete().

By default the HttpPost actions accept FormCollection parameters. We’ll change this to accept our data model’s classes.

Before we start amending the Actions, we add the following line to the HomeController class:

public class HomeController : Controller
{        
        NerdDinnerContext _nerdDinners = new NerdDinnerContext();
...
}

Index

This view will show a list of all dinners in the system, it’s a simple case of using LINQ to return a list of all dinners:

public ActionResult Index()
{
    var dinners = from d in _nerdDinners.Dinners
                  select d;
    return View(dinners.ToList());
}

Details

This view shows all the details of a particular dinner, so we use LINQ again to query the store for a dinner with a particular Id:

public ActionResult Details(string id)
{
    var dinner = _nerdDinners.Dinners.Where(d => d.Id.Equals(id)).FirstOrDefault();
    return dinner == null ? View("404") : View(dinner);
}

Edit

The controller has two methods to deal with the Edit action, the first handles a get request and is similar to the Details method above, but the view loads the property values into a form ready to be edited.

public ActionResult Edit(string id)
{
    var dinner = _nerdDinners.Dinners.Where(d => d.Id.Equals(id)).FirstOrDefault();
    return dinner == null ? View("404") : View(dinner);
}

The method that accept the HttpPost that is sent back after a user clicks “Save” on the view, deals with updating the property values in the store

 

[HttpPost]
public ActionResult Edit(Dinner dinner)
{
    if(ModelState.IsValid)
    {
        dinner.Context = _nerdDinners;
        _nerdDinners.SaveChanges();
        return RedirectToAction("Index");
    }
            
    return View();
}

Create

Like the Edit method, Create displays a form on the initial view, and then accepts the HttpPost that gets sent back after a user clicks “Save”. To make things slight easier for the user we are pre-filling the “EventDate” field with a date one week in the future.

public ActionResult Create()
{
   var dinner = new Dinner() {EventDate = DateTime.Now.AddDays(7)};
   return View(dinner);
}

When the user has entered the rest of the dinner details, we add the Dinner object to the context and SaveChanges()

[HttpPost]
public ActionResult Create(Dinner dinner)
{
    if(ModelState.IsValid)
    {
        _nerdDinners.Dinners.Add(dinner);
        _nerdDinners.SaveChanges();
        return RedirectToAction("Index");
    }
    return View();
}

Delete

The first stage of the Delete method displays the dinner about to be deleted to the user for confirmation:

public ActionResult Delete(string id)
{
    var dinner = _nerdDinners.Dinners.Where(d => d.Id.Equals(id)).FirstOrDefault();
    return dinner == null ? View("404") : View(dinner);
}

When the user has confirmed the object is Deleted from the store:

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(string id)
{
    var dinner = _nerdDinners.Dinners.Where(d => d.Id.Equals(id)).FirstOrDefault();
    _nerdDinners.DeleteObject(dinner);
    _nerdDinners.SaveChanges();
    return RedirectToAction("Index");
}

Step 2: Adding views

We can add Views for each of these actions. To Add a view, right click on the “Home” folder within “Views” and select “Add > View”.

Index:

Details:

Edit:

Create:

Adding strongly typed views in this way prepopulates the HTML with tables, forms and text where needed to display information and gather data from the user.

We have now implemented all of the code we need to write within our Controller and Views to implement the Dinner listing and Dinner creation functionality within our web application.

Part III: Running the application

Hit F5 to start up the application in Debug mode. This opens a browser window that by default starts in the Index action of the HomeController. As we have not yet added any dinners yet, the list is empty, but we can click on “Create New” to go to the Create view to add some dinners.

After entering some data we can see them in the list on the index page

We can also easily view the details of a dinner, edit the details or delete the dinner by using the links next to each item on the list

Part IV: Model changes with NoSQL

Step 1: Adding a new property

Open the IDinner interface and add a property for City

    [Entity]
    public interface IDinner
    {
        [Identifer("http://nerddinner.com/dinners#")]
        string Id { get; }
        string Title { get; set; }
        string Description { get; set; }
        DateTime EventDate { get; set; }
        string Address { get; set; }
        string City { get; set; }
        string HostedBy { get; set; }

        ICollection<IRSVP> RSVPs { get; set; } 
    }

To update the context, right click on the NerdDinnerContext.tt and select “Run Custom Tool”

Step 2: Update the views

Open the Index, Create, Delete, Details and Edit views to add the new City property to the HTML so that you will be able to view and amend its data.

Step 3: Run the application

There is no need to change anything on the database, you can run the application with the changes to the model immediately.The index view now shows the list of previously entered dinners, but with the new column for the “City” field. Go into edit view if you want to change one of your previously entered dinners to use the new field, or use the same Create New link to add more data to the system.

Adding a Custom Membership Provider

The source code for this example can be found in [INSTALLDIR]\Samples\EntityFramework\EntityFrameworkSamples.sln

Custom Membership Providers are a quick and straightforward way of managing membership information when you wish to store that membership data in a datasource that is not supported by the membership providers included within the .NET framework. Often developers will need to implement custom membership providers even when storing the data in a supported datasource, because the schema of that membership information differs from that in the default providers.

In this topic we are going to add a Custom Membership Provider to the Nerd Dinner sample so that users can register and login.

Part 1: Adding the Custom Membership Provider and login Entity

  1. Add a new class to your project and name it BrightstarMembershipProvider.cs
  2. Right click on the MembershipProvider class name and choose “Implement abstract class” from the context menu, this automatically creates all the override methods that your custom class can implement.
  3. Add a new interface to the Models directory and name it INerdDinnerLogin.cs
  4. Add the [Entity] attribute to the interface, and add the properties shown below:
  5. The Id property is decorated with the Identifier attribute to allow us to work with simpler string values rather than the full URI that is generated by BrightstarDB (for more information, please read the Entity Framework Documentation).
[Entity]
public interface INerdDinnerLogin
{
   [Identifier("http://nerddinner.com/logins/")]
   string Id { get; }
   string Username { get; set; }
   string Password { get; set; }
   string PasswordSalt { get; set; }
   string Email { get; set; }
   string Comments { get; set; }
   DateTime CreatedDate { get; set; }
   DateTime LastActive { get; set; }
   DateTime LastLoginDate { get; set; }
   bool IsActivated { get; set; }
   bool IsLockedOut { get; set; }
   DateTime LastLockedOutDate { get; set; }
   string LastLockedOutReason { get; set; }
   int? LoginAttempts { get; set; } 
}

To update the Brightstar Entity Context, right click on the NerdDinnerContext.tt file and select “Run Custom Tool” from the context menu.

Part 2: Configuring the application to use the Brightstar Membership Provider

To configure your web application to use this custom Membership Provider, we simply need to change the configuration values in the Web.config file in the root directory of the application. Change the membership node contained within the <system.web> to the snippet below:

    <membership defaultProvider="BrightstarMembershipProvider">
      <providers>
        <clear/>
        <add name="BrightstarMembershipProvider" type="BrightstarDB.Samples.NerdDinner.BrightstarMembershipProvider, BrightStarDB.Samples.NerdDinner" enablePasswordReset="true" 

maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
      </providers>
    </membership> 

Part 3: Adding functionality to the Custom Membership Provider

For the purpose of keeping this example simple, we will leave some of these methods to throw NotImplementedException, but you can add in whatever logic suits your business requirements once you have the basic functionality up and running.

The full code for the BrightstarMembershipProvider.cs is given below, but can be broken down as follows:

Initialization

We add an Initialize() method along with a GetConfigValue() helper method to handle retrieving the configuration values from Web.config, and setting default values if it is unable to retrieve a value.

Private helper methods

We add three more helper methods: CreateSalt() and CreatePasswordHash() to help us with user passwords, and ConvertLoginToMembershipUser() to return a built in .NET MembershipUser object when given the BrightstarDB INerdDinnerLogin entity.

CreateUser()

The CreateUser() method is used when a user registers on our site, the first part of this code validates based on the configuration settings (such as whether an email must be unique) and then creates a NerdDinnerLogin entity, adds it to the NerdDinnerContext and saves the changes to the BrightstarDB store.

GetUser()

The GetUser() method simply looks up a login in the BrightstarDB store, and returns a .NET MembershipUser object with the help of the ConvertLoginToMembershipUser() method mentioned above.

GetUserNameByEmail()

The GetUserNameByEmail() method is similar to the GetUser() method but looks up by email rather than username. It’s used by the CreateUser() method if the configuration settings specify that new users must have unique emails.

ValidateUser()

The ValidateUser() method is used when a user logs in to our web application. The login is looked up in the BrightstarDB store by username, and then the password is checked. If the checks pass successfully then it returns a true value which enables the user to successfully login.

using System;
using System.Collections.Specialized;
using System.Linq;
using System.Security.Cryptography;
using System.Web.Security;
using BrightstarDB.Samples.NerdDinner.Models;

namespace BrightstarDB.Samples.NerdDinner
{
    public class BrightstarMembershipProvider : MembershipProvider
    {

        #region Configuration and Initialization

        private string _applicationName;
        private const bool _requiresUniqueEmail = true;
        private int _maxInvalidPasswordAttempts;
        private int _passwordAttemptWindow;
        private int _minRequiredPasswordLength;
        private int _minRequiredNonalphanumericCharacters;
        private bool _enablePasswordReset;
        private string _passwordStrengthRegularExpression;
        private MembershipPasswordFormat _passwordFormat = MembershipPasswordFormat.Hashed;

        private string GetConfigValue(string configValue, string defaultValue)
        {
            if (string.IsNullOrEmpty(configValue))
                return defaultValue;

            return configValue;
        }

        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null) throw new ArgumentNullException("config");

            if (string.IsNullOrEmpty(name)) name = "BrightstarMembershipProvider";

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "BrightstarDB Membership Provider");
            }

            base.Initialize(name, config);

            _applicationName = GetConfigValue(config["applicationName"],
                          System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
            _maxInvalidPasswordAttempts = Convert.ToInt32(
                          GetConfigValue(config["maxInvalidPasswordAttempts"], "10"));
            _passwordAttemptWindow = Convert.ToInt32(
                          GetConfigValue(config["passwordAttemptWindow"], "10"));
            _minRequiredNonalphanumericCharacters = Convert.ToInt32(
                          GetConfigValue(config["minRequiredNonalphanumericCharacters"], "1"));
            _minRequiredPasswordLength = Convert.ToInt32(
                          GetConfigValue(config["minRequiredPasswordLength"], "6"));
            _enablePasswordReset = Convert.ToBoolean(
                          GetConfigValue(config["enablePasswordReset"], "true"));
            _passwordStrengthRegularExpression = Convert.ToString(
                           GetConfigValue(config["passwordStrengthRegularExpression"], ""));

        }
        
        #endregion

        #region Properties

        public override string ApplicationName
        {
            get { return _applicationName; }
            set { _applicationName = value; }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { return _maxInvalidPasswordAttempts; }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return _minRequiredNonalphanumericCharacters; }
        }

        public override int MinRequiredPasswordLength
        {
            get { return _minRequiredPasswordLength; }
        }

        public override int PasswordAttemptWindow
        {
            get { return _passwordAttemptWindow; }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { return _passwordFormat; }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { return _passwordStrengthRegularExpression; }
        }

        public override bool RequiresUniqueEmail
        {
            get { return _requiresUniqueEmail; }
        }
        #endregion

        #region Private Methods

        private static string CreateSalt()
        {
            var rng = new RNGCryptoServiceProvider();
            var buffer = new byte[32];
            rng.GetBytes(buffer);
            return Convert.ToBase64String(buffer);
        }

        private static string CreatePasswordHash(string password, string salt)
        {
            var snp = string.Concat(password, salt);
            var hashed = FormsAuthentication.HashPasswordForStoringInConfigFile(snp, "sha1");
            return hashed;

        }
       
        /// <summary>
        /// This helper method returns a .NET MembershipUser object generated from the supplied BrightstarDB entity
        /// </summary>
        private static MembershipUser ConvertLoginToMembershipUser(NerdDinnerLogin login)
        {
            if (login == null) return null;
            var user = new MembershipUser("BrightstarMembershipProvider",
                login.Username, login.Id, login.Email,
                "", "", login.IsActivated, login.IsLockedOut,
                login.CreatedDate, login.LastLoginDate,
                login.LastActive, DateTime.UtcNow, login.LastLockedOutDate);
            return user;
        }

        #endregion

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            var args = new ValidatePasswordEventArgs(email, password, true);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (string.IsNullOrEmpty(email))
            {
                status = MembershipCreateStatus.InvalidEmail;
                return null;
            }

            if (string.IsNullOrEmpty(password))
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }

            var u = GetUser(username, false);

            try
            {
                if (u == null)
                {
                    var salt = CreateSalt();
                    
                    //Create a new NerdDinnerLogin entity and set the properties
                    var login = new NerdDinnerLogin
                    {
                        Username = username,
                        Email = email,
                        PasswordSalt = salt,
                        Password = CreatePasswordHash(password, salt),
                        CreatedDate = DateTime.UtcNow,
                        IsActivated = true,
                        IsLockedOut = false,
                        LastLockedOutDate = DateTime.UtcNow,
                        LastLoginDate = DateTime.UtcNow,
                        LastActive = DateTime.UtcNow
                    };
                    //Create a context using the connection string in the Web.Config
                    var context = new NerdDinnerContext();
                    //Add the entity to the context
                    context.NerdDinnerLogins.Add(login);
                    //Save the changes to the BrightstarDB store
                    context.SaveChanges();

                    status = MembershipCreateStatus.Success;
                    return GetUser(username, true /*online*/);
                }
            }
            catch (Exception)
            {
                status = MembershipCreateStatus.ProviderError;
                return null;
            }

            status = MembershipCreateStatus.DuplicateUserName;
            return null;
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            if (string.IsNullOrEmpty(username)) return null;
            //Create a context using the connection string in Web.config
            var context = new NerdDinnerContext();
            //Query the store for a NerdDinnerLogin that matches the supplied username
            var login = context.NerdDinnerLogins.Where(l => l.Username.Equals(username)).FirstOrDefault();
            if (login == null) return null;
            if(userIsOnline)
            {
                //if the call states that the user is online, update the LastActive property of the NerdDinnerLogin
                login.LastActive = DateTime.UtcNow;
                context.SaveChanges();
            }
            return ConvertLoginToMembershipUser(login);
        }

        public override string GetUserNameByEmail(string email)
        {
            if (string.IsNullOrEmpty(email)) return "";
            //Create a context using the connection string in Web.config
            var context = new NerdDinnerContext();
            //Query the store for a NerdDinnerLogin that matches the supplied username
            var login = context.NerdDinnerLogins.Where(l => l.Email.Equals(email)).FirstOrDefault();
            if (login == null) return string.Empty;
            return login.Username;
        }
        
        public override bool ValidateUser(string username, string password)
        {
            //Create a context using the connection string set in Web.config
            var context = new NerdDinnerContext();
            //Query the store for a NerdDinnerLogin matching the supplied username
            var logins = context.NerdDinnerLogins.Where(l => l.Username.Equals(username));
            if (logins.Count() == 1)
            {
                //Ensure that only a single login matches the supplied username
                var login = logins.First();
                //Check the properties on the NerdDinnerLogin to ensure the user account is activate and not locked out
                if (login.IsLockedOut || !login.IsActivated) return false;
                //Validate the password of the NerdDinnerLogin against the supplied password
                var validatePassword = login.Password == CreatePasswordHash(password, login.PasswordSalt);
                if (!validatePassword)
                {
                    //return validation failure
                    return false;
                }
                //return validation success
                return true;
            }
            return false;
        }

        #region MembershipProvider properties and methods not implemented for this tutorial
...
        #endregion

        
    }
}

Part 4: Running the application

All the models, views and controllers needed to implement the logic logic are generated automatically when creating a new MVC3 Web Application if the option for "Internet Application" is selected. This adds:

  • An AccountController class with ActionResult methods for logging in, logging out and registering
  • AccountModels.cs which contains classes for LogonModel and RegisterModel
  • LogOn and Register views (in the Views/Account directory) that use the models to display form fields and validate input from the user
  • A _LogOnPartial view that is used in the main _Layout view to display a login link, or the username if the user is logged in

These files can be found in [INSTALLDIR]\Samples\EntityFramework\EntityFrameworkSamples.sln

Press F5 to run the application. You will notice a [Log On] link in the top right hand corner of the screen. You can navigate to the registration page via the logon page.

Register

Choosing a username, email and password will create a login entity for you in the BrightstarDB store, and automatically log you in.

Logged In

The partial view that contains the login link code recognises that you are logged in and displays your username and a [Log Off] link. Clicking the links clears the cookies that keep you logged in to the website.

LogOn

You can log on again at any time by entering your username and password.

Summary

In this tutorial we have walked through some simple steps to use a Custom Membership Provider to allow BrightstarDB to handle the authentication of users on your MVC3 Web Application.

For simplicity, we have kept the same structure of membership information as we would find in a default provider, but you can expand on this sample to include extra membership information by simply adding more properties to the BrightstarDB entity.

Adding a Custom Role Provider

The source code for this example can be found in [INSTALLDIR]\Samples\EntityFramework\EntityFrameworkSamples.sln

As with Custom Membership Providers, Custom Role Providers allow developers to use role management within application when either the role information is stored in a data source other than that supported by the default providers, or the role information is managed in a schema which differs from that set out in the default providers.

In this topic we are going to add a Custom Role Provider to the Nerd Dinner sample so that we can restrict certain areas from users who are not members of the appropriate role.

Part 1: Adding the Custom Role Provider

  1. Add the following line to the INerdDinnerLogin interface's propertiesICollection<string> Roles { get; set; }
    To update the Brightstar Entity Context, right click on the NerdDinnerContext.tt file and select “Run Custom Tool” from the context menu.
  2. Add a new class to your project and name it BrightstarRoleProvider.cs
  3. Make this new class inherit from the RoleProvider class (System.Web.Security namespace)
  1. Right click on the RoleProvider class name and choose "Implement abstract class" from the context menu, this automatically creates all the override methods that your custom class can implement.

Part 2: Configuring the application to use the Brightstar Membership Provider

To configure your web application to use the Custom Role Provider, add the following section to your Web.config, under the membership node:

<roleManager  enabled="true" defaultProvider="BrightstarRoleProvider">
  <providers>
<clear/>
<add name="BrightstarRoleProvider" type="BrightstarDB.Samples.NerdDinner.BrightstarRoleProvider" applicationName="/" />
  </providers>
</roleManager>

Part 3: Adding functionality to the Custom Role Provider

The full code for the BrightstarRoleProvider.cs is given below, but can be broken down as follows:

Initialization

We add an Initialize() method along with a GetConfigValue() helper method to handle retrieving the configuration values from Web.config, and setting default values if it is unable to retrieve a value.

GetRolesForUser()

This method returns the contents of the Roles collection that we added to the INerdDinnerLogin entity as a string array.

AddUsersToRoles()

AddUsersToRoles() loops through the usernames and role names supplied, and looks up the logins from the BrightstarDB store. When found, the role names are added to the Roles collection for that login.

RemoveUsersFromRoles()

RemoveUsersFromRoles() loops through the usernames and role names supplied, and looks up the logins from the BrightstarDB store. When found, the role names are removed from the Roles collection for that login.

IsUserInRole()

The BrightstarDB store is searched for the login who matches the supplied username, and then a true or false is passed back depending on whether the role name was found in that login's Role collection. If the login is inactive or locked out for any reason, then a false value is passed back.

GetUsersInRole()

BrightstarDB is queried for all logins that contain the supplied role name in their Roles collection.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using BrightstarDB.Samples.NerdDinner.Models;

namespace BrightstarDB.Samples.NerdDinner
{
    public class BrightstarRoleProvider : RoleProvider
    {
        #region Initialization
        
        private string _applicationName;

        private static string GetConfigValue(string configValue, string defaultValue)
        {
            if (string.IsNullOrEmpty(configValue))
                return defaultValue;

            return configValue;
        }

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (config == null) throw new ArgumentNullException("config");

            if (string.IsNullOrEmpty(name)) name = "NerdDinnerRoleProvider";

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Nerd Dinner Membership Provider");
            }
            base.Initialize(name, config);
            _applicationName = GetConfigValue(config["applicationName"],
                          System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
        }
        
        #endregion

        /// <summary>
        /// Gets a list of the roles that a specified user is in for the configured applicationName.
        /// </summary>
        /// <returns>
        /// A string array containing the names of all the roles that the specified user is in for the configured applicationName.
        /// </returns>
        /// <param name="username">The user to return a list of roles for.</param>
        public override string[] GetRolesForUser(string username)
        {
            if (string.IsNullOrEmpty(username)) throw new ArgumentNullException("username");
            //create a new BrightstarDB context using the values in Web.config
            var context = new NerdDinnerContext();
            //find a match for the username
            var login = context.NerdDinnerLogins.Where(l => l.Username.Equals(username)).FirstOrDefault();
            if (login == null) return null;
            //return the Roles collection
            return login.Roles.ToArray();
        }

        /// <summary>
        /// Adds the specified user names to the specified roles for the configured applicationName.
        /// </summary>
        /// <param name="usernames">A string array of user names to be added to the specified roles. </param><param name="roleNames">A string array of the role names to add the specified user names to.</param>
        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            //create a new BrightstarDB context using the values in Web.config
            var context = new NerdDinnerContext();
            foreach (var username in usernames)
            {
                //find the match for the username
                var login = context.NerdDinnerLogins.Where(l => l.Username.Equals(username)).FirstOrDefault();
                if (login == null) continue;
                foreach (var role in roleNames)
                {
                    //if the Roles collection of the login does not already contain the role, then add it
                    if (login.Roles.Contains(role)) continue;
                    login.Roles.Add(role);
                }
            }
            context.SaveChanges();
        }

        /// <summary>
        /// Removes the specified user names from the specified roles for the configured applicationName.
        /// </summary>
        /// <param name="usernames">A string array of user names to be removed from the specified roles. </param><param name="roleNames">A string array of role names to remove the specified user names from.</param>
        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            //create a new BrightstarDB context using the values in Web.config
            var context = new NerdDinnerContext();
            foreach (var username in usernames)
            {
                //find the match for the username
                var login = context.NerdDinnerLogins.Where(l => l.Username.Equals(username)).FirstOrDefault();
                if (login == null) continue;
                foreach (var role in roleNames)
                {
                    //if the Roles collection of the login contains the role, then remove it
                    if (!login.Roles.Contains(role)) continue;
                    login.Roles.Remove(role);
                }
            }
            context.SaveChanges();
        }

        /// <summary>
        /// Gets a value indicating whether the specified user is in the specified role for the configured applicationName.
        /// </summary>
        /// <returns>
        /// true if the specified user is in the specified role for the configured applicationName; otherwise, false.
        /// </returns>
        /// <param name="username">The username to search for.</param>
        /// <param name="roleName">The role to search in.</param>
        public override bool IsUserInRole(string username, string roleName)
        {
            try
            {
                //create a new BrightstarDB context using the values in Web.config
                var context = new NerdDinnerContext();
                //find a match for the username
                var login = context.NerdDinnerLogins.Where(l => l.Username.Equals(username)).FirstOrDefault();
                if (login == null || login.IsLockedOut || !login.IsActivated)
                {
                    // no match or inactive automatically returns false
                    return false;
                }
                //if the Roles collection of the login contains the role we are checking for, return true
                return login.Roles.Contains(roleName.ToLower());
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Gets a list of users in the specified role for the configured applicationName.
        /// </summary>
        /// <returns>
        /// A string array containing the names of all the users who are members of the specified role for the configured applicationName.
        /// </returns>
        /// <param name="roleName">The name of the role to get the list of users for.</param>
        public override string[] GetUsersInRole(string roleName)
        {
            if (string.IsNullOrEmpty(roleName)) throw new ArgumentNullException("roleName");
            //create a new BrightstarDB context using the values in Web.config
            var context = new NerdDinnerContext();
            //search for all logins who have the supplied roleName in their Roles collection
            var usersInRole = context.NerdDinnerLogins.Where(l => l.Roles.Contains(roleName.ToLower())).Select(l => l.Username).ToList();
            return usersInRole.ToArray();
        }
        
        /// <summary>
        /// Gets a value indicating whether the specified role name already exists in the role data source for the configured applicationName.
        /// </summary>
        /// <returns>
        /// true if the role name already exists in the data source for the configured applicationName; otherwise, false.
        /// </returns>
        /// <param name="roleName">The name of the role to search for in the data source.</param>
        public override bool RoleExists(string roleName)
        {
            //for the purpose of the sample the roles are hard coded
            return roleName.Equals("admin") || roleName.Equals("editor") || roleName.Equals("standard");
        }
        
        /// <summary>
        /// Gets a list of all the roles for the configured applicationName.
        /// </summary>
        /// <returns>
        /// A string array containing the names of all the roles stored in the data source for the configured applicationName.
        /// </returns>
        public override string[] GetAllRoles()
        {
            //for the purpose of the sample the roles are hard coded
            return new string[] { "admin", "editor", "standard" };
        }

        /// <summary>
        /// Gets an array of user names in a role where the user name contains the specified user name to match.
        /// </summary>
        /// <returns>
        /// A string array containing the names of all the users where the user name matches <paramref name="usernameToMatch"/> and the user is a member of the specified role.
        /// </returns>
        /// <param name="roleName">The role to search in.</param><param name="usernameToMatch">The user name to search for.</param>
        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            if (string.IsNullOrEmpty(roleName)) throw new ArgumentNullException("roleName");
            if (string.IsNullOrEmpty(usernameToMatch)) throw new ArgumentNullException("usernameToMatch");

            var allUsersInRole = GetUsersInRole(roleName);
            if (allUsersInRole == null || allUsersInRole.Count() < 1) return new string[] { "" };
            var match = (from u in allUsersInRole where u.Equals(usernameToMatch) select u);
            return match.ToArray();
        }

        #region Properties

        /// <summary>
        /// Gets or sets the name of the application to store and retrieve role information for.
        /// </summary>
        /// <returns>
        /// The name of the application to store and retrieve role information for.
        /// </returns>
        public override string ApplicationName
        {
            get { return _applicationName; }
            set { _applicationName = value; }
        }

        #endregion

        #region Not Implemented Methods
        
        /// <summary>
        /// Adds a new role to the data source for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to create.</param>
        public override void CreateRole(string roleName)
        {
            //for the purpose of the sample the roles are hard coded
            throw new NotImplementedException();
        }

        /// <summary>
        /// Removes a role from the data source for the configured applicationName.
        /// </summary>
        /// <returns>
        /// true if the role was successfully deleted; otherwise, false.
        /// </returns>
        /// <param name="roleName">The name of the role to delete.</param><param name="throwOnPopulatedRole">If true, throw an exception if <paramref name="roleName"/> has one or more members and do not delete <paramref name="roleName"/>.</param>
        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            //for the purpose of the sample the roles are hard coded
            throw new NotImplementedException();
        }

        #endregion
    }
}

Part 4: Adding secure sections

To display the functionality of the new Custom Role Provider, add 2 new ViewResult methods to the Home Controller. Notice that the [Authorize] MVC attribute has been added to each of the methods to restrict access to users in those roles only.

[Authorize(Roles = "editor")]
public ViewResult SecureEditorSection()
{
return View();
}

[Authorize(Roles = "admin")]
public ViewResult SecureAdminSection()
{
return View();
}

Right click on the View() methods, and select "Add View" for each. This automatically adds the SecureEditorSection.cshtml and SecureAdminSection.cshtml files to the Home view folder.

To be able to navigate to these sections, open the file Views/Shared/_Layout.cshtml and add two new action links to the main navigation menu:

<div id="menucontainer">
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("Query SPARQL", "Index", "Sparql")</li>
<li>@Html.ActionLink("Editors Only", "SecureEditorSection", "Home")</li>
<li>@Html.ActionLink("Admin Only", "SecureAdminSection", "Home")</li>
</ul>
</div>

Add the following line of code to the Register() ActionResult method in the AccountController, before SetAuthCookie:

Roles.AddUserToRole(user.UserName, "editor");

In a real world application, you would manage roles within your own administration section, but for the purpose of this sample we are going with an overly simplistic way of adding a user to a role.

Part 5: Running the application

Run the application and register a new user, this user has now been given the Editor role. Clicking on the navigation links to "Secure Editor Section" will allow access to that view. Whereas the "Secure Admin Section" will not pass autorization - by default MVC redirects the user to the login view.

Adding An OData Provider

The source code for this example can be found in [INSTALLDIR]\Samples\EntityFramework\EntityFrameworkSamples.sln

The Open Data Protocol (OData) is an open web protocol for querying and updating data. An OData provider can be added to BrightstarDB Entity Framework projects to allow OData consumers to query the underlying data.

The following steps describe how to create an OData provider to an existing project (in this example we add to the NerdDinner MVC Web Application project).

1. Select Add New Item > Web, and select WCF Data Service. Rename this to OData.svc and click 'add'.

2. Change the class inheritance from DataService to EntityDataService, and add the name of the BrightstarEntityContext to the type argument.

3. Uncomment the configuration settings. The code you are left with is as below:

public class OData : EntityDataService<NerdDinnerContext>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.SetEntitySetAccessRule("NerdDinnerLogin", EntitySetRights.None); 
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}

The NerdDinnerLogin set has been given EntitySetRights of None. This hides the set (which contains sensitive login information) from the OData service

4. Rebuild and run the project. Navigation to /OData.svc and you will see the standard OData metadata page displaying the entity sets from BrightstarDB

5. The OData service can now be queried using the standard OData conventions. There are a few restrictions when using OData services with BrighstarDB.

Consuming OData in PowerPivot

The data in BrighstarDB can be consumed by various OData consumers. In this topic we look at consuming the data using PowerPivot (a list of recommended OData consumers can be found odata.org/consumers).

Open Excel, click the PowerPivot tab and open the PowerPivot window

If you do not have PowerPivot installed, you can download it from powerpivot.com

To consume data from Brightstar, click the 'From Data Feeds' button in the 'Get External Data' section:

Add a name for your feed, and enter the URL of the OData service file for your BrightstarDB application.

Click "Test Connection" to make sure that you can connect to your OData service and then click "Next"

Select the sets that you wish to consume and click "Finish"

This then shows all the data that is consumed from the OData service in the PowerPivot window. When any data is added or edited in the BrightstarDB store, the data in the PowerPivot windows can be updated by clicking the "Refresh" button.

Mapping to Existing RDF Data

The source code for this example can be found in [INSTALLDIR]\Samples\EntityFramework\EntityFrameworkSamples.sln

One of the things that makes BrightstarDB unique is the ability to map multiple object models onto the same data and to map an object model onto existing RDF data. An example of this could be when some contact data in RDF foaf form is imported into BrightstarDB and an application wants to make use of that data. Using the BrightstarDB annotations it is possible to map object classes and properties to existing types and property types.

The following FOAF RDF triples are added to the data store.

<http://www.brightstardb.com/people/david> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .
<http://www.brightstardb.com/people/david> <http://xmlns.com/foaf/0.1/nick> ""David"" .
<http://www.brightstardb.com/people/david> <http://xmlns.com/foaf/0.1/name> ""David Summers"" .
<http://www.brightstardb.com/people/david> <http://xmlns.com/foaf/0.1/Organization> ""Microsoft"" .
<http://www.brightstardb.com/people/simon> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .
<http://www.brightstardb.com/people/simon> <http://xmlns.com/foaf/0.1/nick> ""Simon"" .
<http://www.brightstardb.com/people/simon> <http://xmlns.com/foaf/0.1/name> ""Simon Williamson"" .
<http://www.brightstardb.com/people/simon> <http://xmlns.com/foaf/0.1/Organization> ""Microsoft"" .
<http://www.brightstardb.com/people/simon> <http://xmlns.com/foaf/0.1/knows> <http://www.brightstardb.com/people/david> .

Triples can be loaded into the BrightStarDB using the following code:

var triples = new StringBuilder();
triples.AppendLine(@"<http://www.brightstardb.com/people/simon> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .");
triples.AppendLine(@"<http://www.brightstardb.com/people/simon> <http://xmlns.com/foaf/0.1/nick> ""Simon"" .");
triples.AppendLine(@"<http://www.brightstardb.com/people/simon> <http://xmlns.com/foaf/0.1/name> ""Simon Williamson"" .");
triples.AppendLine(@"<http://www.brightstardb.com/people/simon> <http://xmlns.com/foaf/0.1/Organization> ""Microsoft"" .");
triples.AppendLine(@"<http://www.brightstardb.com/people/simon> <http://xmlns.com/foaf/0.1/knows> <http://www.brightstardb.com/people/david> .");
client.ExecuteTransaction(storeName, null, triples.ToString());

Defining Mappings

To access this data from the Entity Framework, we need to define the mappings between the RDF predictates and the properties on an object that represents an entity in the store.

The properties are marked up with the PropertyType attribute of the RDF predicate. If the property "Name" should match the predicate "http://xmlns.com/foaf/0.1/name", we add the attribute [PropertyType("http://xmlns.com/foaf/0.1/name")].

We can add a Namespace Declaration to the project's AssemblyInfo.cs file to shorted the URIs used in the attributes, in the format:

[assembly: NamespaceDeclaration("foaf", "http://xmlns.com/foaf/0.1/")]

This means the PropertyType attributes can be shortened to [PropertyType("foaf:name")]

The RDF example given above would be mapped to an entity as given below:

[Entity("http://xmlns.com/foaf/0.1/Person")]
public interface IPerson
{
    [Identifier("http://www.brightstardb.com/people/")]
    string Id { get; }

    [PropertyType("foaf:nick")]
    string Nickname { get; set; }

    [PropertyType("foaf:name")]
    string Name { get; set; }

    [PropertyType("foaf:Organization")]
    string Organisation { get; set; }

    [PropertyType("foaf:knows")]
    ICollection<IPerson> Knows { get; set; }

    [InversePropertyType("foaf:knows")]
    ICollection<IPerson> KnownBy { get; set; }
}

Adding the [Identifier("http://www.brightstardb.com/people/")] to the ID of the interface, means that when we can query and retrieve the Id without the entire prefix

Example

Once there is RDF data in the store, and an interface that maps an entity to the RDF data, the data can then be accessed easy using the Entity Framework by using the correct connection string to directly access the store.

var connectionString = "Type=http;endpoint=http://localhost:8090/brightstar;StoreName=Foaf";
var context = new FoafContext(connectionString);

If you have added the connection string into the Config file:

<add key="BrightstarDB.ConnectionString" value="Type=http;endpoint=http://localhost:8090/brightstar;StoreName=Foaf" />

then you can initialise the content with a simple:

var context = new FoafContext();

For more information about connection strings, please read the "Connection Strings" topic

The code below connects to the store to access all the people in the RDF data, it then writes their name and place of employment, along with all the people they know or are known by.

var context = new FoafContext(connectionString);
var people = context.Persons.ToList();
var count = people.Count;
Console.WriteLine(@"{0} people found in raw RDF data", count);
Console.WriteLine();
foreach(var person in people)
{
    var knows = new List<IPerson>();
    knows.AddRange(person.Knows);
    knows.AddRange(person.KnownBy);

    Console.WriteLine(@"{0} ({1}), works at {2}", person.Name, person.Nickname, person.Organisation);
    Console.WriteLine(knows.Count == 1 ? string.Format(@"{0} knows 1 other person", person.Nickname)
                       : string.Format(@"{0} knows {1} other people", person.Nickname, knows.Count));
    foreach(var other in knows)
    {
        Console.WriteLine(@"    {0} at {1}", other.Name, other.Organisation);
    }
    Console.WriteLine();
}