Monday, 4 August 2014

Use Cognitum ASP.NET Providers for Cassandra (Role, Membership, Session, Profile) in your application.


1.      About

My name is Karol and  I would like to show you how to implement ASP.NET providers (Role, Membership, Session, Profile) using Cassandra as a database. All necessary providers you can find on Cognitum ASP.NETProviders for Cassandra. What is important, you can easily use Cassandra Providers in your application to store providers’ data on Cassandra.
The solution implements Membership, Role, Profile and Session-State Store Providers. ASP.NET Membership enables you to manage user information and validate for your Web application. Role Provider mapping users to roles and provide methods to manage roles. Profile Provider expands information about user for new property. Session-State Provider serialize and deserialize session-state data and store them. Information about this providers you can find at MSDN:

2.      See solution

Requirements: 

2.1.  Run application

You can run my application using MVC package application or source code.

    MVC application package

Requirements: 

You can run my application locally. You should add Cognitum.CassandraProviders_*_package.zip to your IIS.
       Do that:
  • Download Cognitum.CassandraProviders_*_package.zip from download.
  • Run Internet Information Service (IIS) Manager.
  • In Connections Window find Sites.
  • Find Default Web Site or make that with target: localhost.
  • Add application to Default Web Site by right mouse click -> Deploy -> Import   Application.
  • Run application by right mouse click on them and select Manage Application -> Browse.

   Cassandra providers solution

 

      To run solution:
  • Download source code from download.
  • Open CassandraProviders.sln file.
  • In Web.config file set ConnectionString value to connection string to your Cassandra database.
  • Set WebPages project as startup project (right mouse click on them).
  • Build project.
  • Run project.
  • Find Cognitum.CassandraProviders_*_package file, click Next and Finish.

2.2.  Use application

First page, which you see, is Home Page. Firstly click link "To create Cassandra tables and Admin account. Click here" to create tables on database and admin account.

                Normal user

You can register new user:
or sign in:
On page with your profile you can choose your gender and file with picture (database write only name of picture).
You can change your password:

             Administrator

Admin account have more options:
See roles on application:
See users on application:
See statistics of application:


3.      Use Cassandra Providers in your application

To using ASP.NET Providers for Cassandra in your application, you can manage CassandraProviders package by NuGet or add CassandraProviders, CassandraTableStorage and EmailSender from source code.
Let's go:
  • Create an ASP.NET MVC4 Web application.
  • Select Internet Application template.
  • Add to project CassandraProviders , CassandraTableStorage and EmailSender and add reference from your application to CassandraProviders or manage to your application CassandraProviders package by NuGet .

    Membership Provider

In Web.config add membership provider like this:  


<system.web>
  <compilation debug="true" targetFramework="4.0" />
  <authentication mode="Forms">
    <forms loginUrl="~/Account/Login" timeout="2880" />
  </authentication>
  <membership defaultProvider="CassandraMembershipProvider">
    <providers>
      <clear />
      <add name="CassandraMembershipProvider"
           type="Cognitum.CassandraProviders.
             Cassandra. CassandraMembershipProvider"
           enablePasswordRetrieval="true"
           enablePasswordReset="true"
           requiresQuestionAndAnswer="true"
           requiresUniqueEmail="false"
           maxInvalidPasswordAttempts="5"
           minRequiredPasswordLength="6"
           minRequiredNonalphanumericCharacters="0"
           passwordAttemptWindow="10"
           applicationName="MyApp1" />
    </providers>
  </membership>
    <machineKey
                       decryption="AES"
                       decryptionKey="0CA3EFAF0F7A5E7A62681C0BF656EE0ECE31ACEE3E1023BA3FAD20E
A5F199DE8"
                      validation="SHA1"
                      validationKey="448396CF27E32841EB374CF1D787713ABF42A2049DE62168764FF0DCE537184F0
                      535D5D9AD66DEDC97DC1ABFF7FA540B4DFD82E5BB196B95D15FF81F75AD5328">
</machineKey>

Add attribute [Authorize]


public class HomeController : Controller
{
       [Authorize]
        public ActionResult Index()
        {

To recognize Cassandra membership provider in Web.config add keys like this:


<appSettings>
       <add key="enableSimpleMembership" value="false" />
     <add key="autoFormsAuthentication" value="false" />

Add Cassandra Providers configuration to Web.config like this:


<appSettings>

  <add key="enableSimpleMembership" value="false" />

  <add key="autoFormsAuthentication" value="false" />



  <!-- A static key is used instead of machiekey to encrypt passwords since machinekey changes when in development -->

<add key="StaticKey" value="7862520A3D5D9B045B4C408034564A794DB25CA89DE62168764FF0DCE537184F0535D
                                 5D9AD66DEDC97DC1ABFF7FA540B4DFD82E5BB196B95D15FF81F75AD5328" />

  <!-- This is the e-mail address that will be the sender on all the e-mails sent by this application -->

  <add key="fromEmail" value="cassandraprovider@gmail.com" />

  <!-- By default a administrator user is made and this is its sign in name. Only low letters-->

  <add key="AdminName" value="admin" />

  <!-- By default a administrator user is made and this is its e-mail -->

  <add key="AdminEmail" value="ad@example.com" />

  <!-- By default a administrator user is made and this is its password -->

  <add key="AdminPassword" value="cassandra2014" />

  <!-- By default a administrator user is made and this is its password retrieval question -->

  <add key="AdminQuestion" value="Color of my first car?" />

  <!-- By default a administrator user is made and this is its password retrieval answer -->

  <add key="AdminAnswer" value="Kiwi" />

  <!-- By default a role is created to hold administrators and this is its name. Admin is added to this by default. -->

  <add key="AdminRoleName" value="Admins" />

  <!-- The name of your application -->

  <add key="ApplicationName" value="MyApp1" />

  <!-- Connection string to your Cassandra database -->

  <add key="ConnectionString" value="localhost" />

  <!-- Time window describe what mean that user is online -->

  <add key="UserIsOnlineTimeWindow" value="900" />

  <!-- Cassandra parameter. Set Cassandra replication factor. -->

  <add key="ReplicationFactor" value="1" />

  <!-- Time window describe what mean that profile is active -->

  <add key="UserIsActiveTimeWindow"  value="9000" />

In  AccountController.cs  comment [InitializeSimpleMembership]  like this:


   [Authorize]
// [InitializeSimpleMembership]
public class AccountController : Controller

Override  Login  method in  AccountController.cs  like this:


// POST: /Account/Login
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Login(LoginModel model, string returnUrl)
        {
            if (ModelState.IsValid && Membership.ValidateUser(model.UserName, model.Password))
            {
                return RedirectToLocal(returnUrl);
            }

            // If we got this far, something failed, redisplay form
            ModelState.AddModelError("", "The user name or password provided is incorrect.");
            return View(model);
                   }

Override  Register  method in  AccountController.cs  like this:


// POST: /Account/Register
 [HttpPost]
 [AllowAnonymous]
 [ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
      if (ModelState.IsValid)
      {
                  // Attempt to register the user
                  try
                  {
                                  Membership.CreateUser(model.UserName, model.Password, "email");
                                  return RedirectToAction("Index", "Home");
                  }
                  catch (MembershipCreateUserException e)
                  {
                                  ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
                  }
      }
      // If we got this far, something failed, redisplay form
      return View(model);
}
Cassandra Provider requirements email to create user. You can add meaningless string or textbox to get email.

Override    LogOff  method in  AccountController  like this:


// POST: /Account/LogOff
 [HttpPost]
 [ValidateAntiForgeryToken]
public ActionResult LogOff()
{
                FormsAuthentication.SignOut();
                return RedirectToAction("Index", "Home");
}

     Role Provider

In Web.config add Role provider:


<!-- Custom role provider -->
<roleManager enabled="true"
    defaultProvider="CassandraRoleProvider">
                <providers>
                                      <clear />
                                      <add name="CassandraRoleProvider"
                                      type="Cognitum.CassandraProviders.Cassandra.CassandraRoleProvider"
                                      applicationName="MyApp1" />
                                      </providers>
 </roleManager>

In HomeController add role restrictions:


public class HomeController : Controller
{
             [Authorize(Roles="Admins")]
             public ActionResult Index()
                {  
Only admins can view Index page.

Add admin account (optional):

  • Add to your application class InitApplication.
  • Add reference System.Data.Services.Client to your project.
  • Fill class:

public class InitApplication
{
    public InitApplication()
    {
        CreateAdminUserIfNotExists();
        CreateAdminsRoleIfNotExists();
    }

    private void CreateAdminsRoleIfNotExists()
    {
        var adminsRoleName = ConfigurationManager.AppSettings["AdminRoleName"];
        if (AdminsRoleExists(adminsRoleName)) return;

        Roles.CreateRole(adminsRoleName);
        Roles.AddUsersToRole(new string[]
{ ConfigurationManager.AppSettings["AdminName"] },adminsRoleName);
}

    private void CreateAdminUserIfNotExists()
    {
        var adminName = ConfigurationManager.AppSettings["AdminName"];

        if (AdminExists(adminName)) return;

        MembershipCreateStatus status;
        var user = Membership.CreateUser(
                adminName,
                ConfigurationManager.AppSettings["AdminPassword"],
                ConfigurationManager.AppSettings["AdminEmail"],
                ConfigurationManager.AppSettings["AdminQuestion"],
                ConfigurationManager.AppSettings["AdminAnswer"],
                true, null, out status);

        if (status != MembershipCreateStatus.Success || user == null) return;

        user.IsApproved = true;
        Membership.UpdateUser(user);
    }

    private bool AdminExists(string adminName)
    {
        try
        {
            if (Membership.GetUser(adminName) == null) return false;
        }
        catch (DataServiceQueryException)
        {
            return false;
        }

        return true;
    }

    private bool AdminsRoleExists(string adminsRoleName)
    {
        try
        {
            return Roles.RoleExists(adminsRoleName);
        }
        catch (DataServiceQueryException)
        {
            return false;
        }
    }
}
Add all using:

 using System.Configuration;
 using System.Data.Services.Client;
using System.Web.Security;
  • In Global.asax in Application_Start on the end add line new InitApplication (); to create admin account and role. Admin account have username: admin and password: cassandra2014 (defined in Web.config).

In  AccountController  override  Login  method like this:


// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
             if (ModelState.IsValid)
             {
                         if (ModelState.IsValid && Membership.ValidateUser(model.UserName, model.Password))
{
             FormsAuthentication.SetAuthCookie(model.UserName, false);
             return RedirectToLocal(returnUrl ?? Url.Action("Index", "Admin"));
 }
                         else
                         {
                                 // If we got this far, something failed, redisplay form
                                 ModelState.AddModelError("", "The user name or password provided is incorrect.");
                                 return View(model);
                         }
             }
             else
             {
             return View();
             }
}
Run application, login as admin and go to Home page, next logout, register new user, login and try go to Home page.

     Profile Provider

Add Profile Provider:


<profile enabled="true"
defaultProvider="CassandraProfileProvider">
  <providers>
    <clear />
    <add name="CassandraProfileProvider"
         type="Cognitum.CassandraProviders.Cassandra.CassandraProfileProvider"
         applicationName="MyApp1" />
  </providers>
  <properties>
    <clear />
    <add name="Gender"
type="System.Int32" defaultValue="-1" />
    <add name="Birthdate"
type="System.DateTime" defaultValue="-1" />
    <add name="PortraitBlobAddressUri"
type="System.String" defaultValue="" />
  </properties>
</profile>
Profile include gender, birthday and portrait blob address Uri. There are defines default values too.

     Session-State Store Provider

Add Session-State Store Provider:


<sessionState
timeout="60"
cookieName="MyApp1"
mode="Custom"
customProvider="CassandraSessionProvider">
           <providers>
                      <clear />
           <add
name="CassandraSessionProvider"
type="Cognitum.CassandraProviders.Cassandra.CassandraSessionStateProvider" />
           </providers>
</sessionState>

4.      Differences with MSDN providers

Profile providers have methods to ask about inactive profiles. One of parameter of these methods is data called userInactiveSinceDate. My solution doesn't use that parameter. This data is once precise in Web.config file as a duration. So every query with different userInactiveSinceDate parameter returns the same results.

5.      Problems

Implementation of this providers using non-relational database is problematic. Problems are:
  • Query about online user.
  • Query about active profiles.
  • Query about inactive profiles.
  • Query about matches email by part of email.
  • Query about matches username for part of username.
Solution of these problems do not implement full functionality. The first, what Cassandra database modeler do, is collect all queries. And next make model, which is the best for these queries. In this case, we have all possible queries, so it is very hard (or it is not possible) to make very efficient database model.

6.      References




Cognitum provides consulting and trainings in our areas of expertise:
  • Ontology engineering and semantic knowledge management (introductory and expert)
  • Semantic Web Technology (introductory and expert)
  • Big Data challenges (introductory and expert for Apache Cassandra, Windows Azure, Hadoop, HDInsight, Solr)
As an official partner of DataStax in Poland we offer: 
  • Big Data and Apache Cassandra™ trainings.
  • Certified courses for developers and database administrators of Apache Cassandra™.
We offer variety of training formats: certified courses, onsite and offsite trainings, workshops and online and onsite consulting.

1 comment:

  1. nice post on role membership in Cassandra, thanks for the post very useful like to see more.

    ReplyDelete