MVC 4, Web API, OData, Entity Framework, Kendo UI, Binding a Form to Datasource (CRUD) with MVVM

This will be part five of a six part series of blog posts.

  1. Modern Web Application Layered High Level Architecture with SPA, MVC, Web API, EF, Kendo UI, OData
  2. Generically Implementing the Unit of Work & Repository Pattern with Entity Framework in MVC & Simplifying Entity Graphs
  3. MVC 4, Kendo UI, SPA with Layout, View, Router & MVVM
  4. MVC 4, Web API, OData, EF, Kendo UI, Grid, Datasource (CRUD) with MVVM
  5. MVC 4, Web API, OData, EF, Kendo UI, Binding a Form to Datasource (CRUD) with MVVM
  6. Upgrading to Async with Entity Framework, MVC, OData AsyncEntitySetController, Kendo UI, Glimpse & Generic Unit of Work Repository Framework v2.0

Update: 09/09/2013 – Sample application and source code has been uploaded to CodePlex: https://genericunitofworkandrepositories.codeplex.com, updated Visual 2013, Twitter Bootstrap, MVC 5, EF6, Kendo UI Bootstrap theme, project redeployed to Windows Azure Website.

Update: 06/20/2013 – Bug fix: productEdit View intermittently failing to update. Enhancement: Added state management for Grid, after productEdit View updates (syncs), will auto navigate back to Grid and re-select the last selected row. Updated blog, sample app download, and live demo.

Just a quick recap on the last post, we wired up the Kendo UI Grid, DataSource with MVVM with all the traditional CRUD functionality. In this blog we’ll cover editing with a form in a Kendo UI View that is remotely loaded into our SPA that is bound the Kendo UI Datasource using MVVM. The View will be loaded in from with a click of a button on the row your are trying to edit from the Kendo UI Grid.

This will be part three of a five part series of blog posts.

  1. Generically Implementing the Unit of Work & Repository Pattern with Entity Framework in MVC & Simplifying Entity Graphs
  2. MVC 4, Kendo UI, SPA with Layout, View, Router & MVVM
  3. MVC 4, Web API, OData, EF, Kendo UI, Grid, Datasource (CRUD) with MVVM
  4. MVC 4, Web API, OData, EF, Kendo UI, Binding a Form to Datasource (CRUD) with MVVM
  5. Upgrading to Async with Entity Framework, MVC, OData AsyncEntitySetController, Kendo UI, Glimpse & Generic Unit of Work Repository Framework v2.0

Taking a look at a high level architecture of this three part series blog: Modern Web Application Layered High Level Architecture with SPA, MVC, Web API, EF, Kendo UI.

Live demo: http://longle.azurewebsites.net, courtesy of Windows Azure free 10 Website’s

Spa.Controllers.ProductController.cs


    public class ProductController : EntitySetController<Product, int>
    {
        private readonly IUnitOfWork _unitOfWork;

        public ProductController(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        public override IQueryable<Product> Get()
        {
            return _unitOfWork.Repository<Product>().Query().Get();
        }

        protected override Product GetEntityByKey(int key)
        {
            return _unitOfWork.Repository<Product>().FindById(key);
        }

        protected override Product UpdateEntity(int key, Product update)
        {
            update.State = ObjectState.Modified;
            _unitOfWork.Repository<Product>().Update(update);
            _unitOfWork.Save();
            return update;
        }
        
        public override void Delete([FromODataUri] int key)
        {
            _unitOfWork.Repository<Product>().Delete(key);
            _unitOfWork.Save();
        }
        
        protected override void Dispose(bool disposing)
        {
            _unitOfWork.Dispose();
            base.Dispose(disposing);
        }
    }

Our Web Api OData ProductsController, pretty much the same Controller used in the previous blog to hydrate our Grid along with the other CRUD actions. This Controller will also share the same duties as it did before, for the Grid. Here it handle hydrating the Form, perform updates and deletes.

Adding a custom command Edit button to the Grid (line 32)

Spa/Content/Views/products.html


<script type="text/x-kendo-template" id="products">
    <section class="content-wrapper main-content clear-fix">    
        <h3>Technlogy Stack</h3>
        <ol class="round">
            <li class="one">
                <h5>.NET</h5>
                ASP.NET MVC 4, Web API, OData, Entity Framework        
            </li>
            <li class="two">
                <h5>Kendo UI Web Framework</h5>
                MVVM, SPA, Grid, DataSource
            </li>
            <li class="three">
                <h5>Patterns</h5>
                Unit of Work, Repository, MVVM
            </li>
        </ol>
        <h3>View Products</h3><br/>
            <div class="k-content" style="width:100%">    
            <div id="productsForm">
            <div id="productGrid" 
                data-role="grid"
                data-sortable="true"
                data-pageable="true"
                data-filterable="true"
                data-bind="source: dataSource, events:{dataBound: dataBound, change: onChange}"
                data-editable = "inline"
                data-selectable="true" 
                data-columns='[
                    { field: "ProductID", title: "Id", width: "50px" }, 
                    { field: "ProductName", title: "Name", width: "300px" }, 
                    { field: "UnitPrice", title: "Price", format: "{0:c}", width: "100px" },                    
                    { field: "Discontinued", width: "150px" }, 
                    { command : [ "edit", "destroy", { text: "Edit Details", click: editProduct } ], title: "Action",  } ]'>
            </div>
            </div>
            </div>
    </section>    
</script>

<script>
    function editProduct(e) {
        e.preventDefault();
        var tr = $(e.currentTarget).closest("tr");
        var dataItem = $("#productGrid").data("kendoGrid").dataItem(tr);
        window.location.href = '#/productEdit/' + dataItem.ProductID;
    }

    var lastSelectedProductId;
                
    var crudServiceBaseUrl = "/odata/Product";
    var productsModel = kendo.observable({
        dataSource: dataSource = new kendo.data.DataSource({
            type: "odata",
            transport: {
                read: {
                    url: crudServiceBaseUrl,
                    dataType: "json"
                },
                update: {
                    url: function (data) {
                        return crudServiceBaseUrl + "(" + data.ProductID + ")";
                    },
                    dataType: "json"
                },
                destroy: {
                    url: function (data) {
                        return crudServiceBaseUrl + "(" + data.ProductID + ")";
                    },
                    dataType: "json"
                }
            },
            batch: false,
            serverPaging: true,
            serverSorting: true,
            serverFiltering: true,
            pageSize: 10,
            schema: {
                data: function (data) {
                    return data.value;
                },
                total: function (data) {
                    return data["odata.count"];
                },
                errors: function (data) {
                },
                model: {
                    id: "ProductID",
                    fields: {
                        ProductID: { type: "number", editable: false, nullable: true },
                        ProductName: { type: "string", validation: { required: true } },
                        UnitPrice: { type: "number", validation: { required: true, min: 1 } },
                        Discontinued: { type: "boolean" },
                        UnitsInStock: { type: "number", validation: { min: 0, required: true } }
                    }
                }
            },
            error: function (e) {
                var message = e.xhr.responseJSON["odata.error"].message.value;
                var innerMessage = e.xhr.responseJSON["odata.error"].innererror.message;
                alert(message + "\n\n" + innerMessage);
            }
        }),
        dataBound: function (arg) {
            if (lastSelectedProductId == null) return; // check if there was a row that was selected
            var view = this.dataSource.view(); // get all the rows
            for (var i = 0; i < view.length; i++) { // iterate through rows
                if (view[i].ProductID == lastSelectedProductId) { // find row with the lastSelectedProductd
                    var grid = arg.sender; // get the grid
                    grid.select(grid.table.find("tr[data-uid='" + view[i].uid + "']")); // set the selected row
                    break;
                }
            }
        },
        onChange: function (arg) {
            var grid = arg.sender;
            var dataItem = grid.dataItem(grid.select());
            lastSelectedProductId = dataItem.ProductID;
        }
    });

    $(document).bind("viewSwtichedEvent", function (e, args) { // subscribe to the viewSwitchedEvent
        if (args.name == "products") { // check if this view was switched too
            if (args.isRemotelyLoaded) { // check if this view was remotely loaded from server
                kendo.bind($("#productsForm"), productsModel); // bind the view to the model
            } else {// view already been loaded in cache
                productsModel.dataSource.fetch(function() {}); // refresh grid
            }
        }
    });

</script>

Additions to Client-Side

editProduct


function editProduct(e) {
    e.preventDefault();
    var tr = $(e.currentTarget).closest("tr");
    var dataItem = $("#productGrid").data("kendoGrid").dataItem(tr);
    window.location.href = '#/productEdit/' + dataItem.ProductID;
}

The editProduct method will handle extracting the ProductID of the row we clicked “Edit Details” and navigating to the ProductEdit View.

Additions to the Observable Model

dataBound


dataBound: function (arg) {
    if (lastSelectedProductId == null) return; // check if there was a row that was selected
    var view = this.dataSource.view(); // get all the rows
    for (var i = 0; i < view.length; i++) { // iterate through rows
        if (view[i].ProductID == lastSelectedProductId) { // find row with the lastSelectedProductd
            var grid = arg.sender; // get the grid
            grid.select(grid.table.find("tr[data-uid='" + view[i].uid + "']")); // set the selected row
            break;
        }
    }

The dataBound (delgate) event handler will be responsible for re-selecting the last selected row in the Grid before we navigated away from the Grid to the productEdit View.

onChange


onChange: function (arg) {
    var grid = arg.sender;
    var dataItem = grid.dataItem(grid.select());
    lastSelectedProductId = dataItem.ProductID;
}

The onChange (delegate) event handler will be responsible for saving off the last selected rows ProductID so that if we navigate to the ProductEdit View and back we can maintain the last selected row state.

6-19-2013 6-27-27 PM

Creating the ProductEdit View

Spa/Content/Views/productEdit.html
(styles have been omitted, for clarity)


<!-- styles remove for clarity -->

<script type="text/x-kendo-template" id="productEdit">
    <section class="content-wrapper main-content clear-fix">                
        <div class="k-block" style="width:600px; margin-top:35px">            
            <div class="k-block k-info-colored">
                <strong>Note: </strong>Please fill out all of the fields in this form.
            </div>
            <div id="product-edit-form">
                <dl>
                    <dt>
                        <label for="firstName">Product Name:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="productName" type="text" data-bind="value: ProductName" />
                            <a href="#" data-field="productName" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="lastName">English Name:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="englishName" type="text" data-bind="value: EnglishName" />
                            <a href="#" data-field="englishName" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="quanityPerUnit">Quanity Per Unit:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="quanityPerUnit" type="text" data-bind="value: QuantityPerUnit" />
                            <a href="#" data-field="quanityPerUnit" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="unitPrice">Unit Price:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="unitPrice" type="text" data-bind="value: UnitPrice" />
                            <a href="#" data-field="unitPrice" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="unitPrice">Unit In Stock:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="unitsInStock" type="text" data-bind="value: UnitsInStock" />
                            <a href="#" data-field="unitsInStock" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="unitsOnOrder">Unit On Order:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="unitsOnOrder" type="text" data-bind="value: UnitsOnOrder" />
                            <a href="#" data-field="unitsOnOrder" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="reorderLevel">Reorder Level:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="reorderLevel" type="text" data-bind="value: ReorderLevel" />
                            <a href="#" data-field="reorderLevel" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="discontinued">Discontinued:</label></dt>
                    <dd>
                        <select id="discontinued" data-role="dropdownlist">
                            <option value="1">Yes</option>
                            <option value="2">No</option>
                        </select>
                    </dd>
                    <dt>
                        <label for="Recieved">Recieved:</label></dt>
                    <dd>
                        <input data-role="datepicker" id="recieved">
                    </dd>
                </dl>
                <a class="k-button" data-bind="click: saveProduct"><span span class="k-icon k-i-tick"></span> Submit</a>
                <a class="k-button" data-bind="click: cancel"><span span class="k-icon k-i-tick"></span> Cancel</a>
            </div>
        </div>
    </section>
</script>

<script>
    var getProductId = function () { // parse for ProductId from url
        var array = window.location.href.split('/');
        var productId = array[array.length - 1];
        return productId;
    };
    
    var crudServiceBaseUrl = "/odata/Product";
    
    $(document).bind("viewSwtichedEvent", function (e, args) { // subscribe to viewSwitchedEvent
        if (args.name == "productEdit") { // check if this view was switched to
            var productModel = kendo.data.Model.define({ // we want to refresh this view anytime its switched to
                id: "ProductID",
                fields: {
                    ProductID: { type: "number", editable: false, nullable: true },
                    ProductName: { type: "string", validation: { required: true } },
                    EnglishName: { type: "string", validation: { required: true } },
                    UnitPrice: { type: "number", validation: { required: true, min: 1 } },
                    Discontinued: { type: "boolean" },
                    UnitsInStock: { type: "number", validation: { min: 0, required: true } }
                },
                saveProduct: function (e) {
                    e.preventDefault();
                    dataSource.sync();
                    window.location.href = '/index.html#/products';
                },
                cancel: function (e) {
                    e.preventDefault();
                    window.location.href = '/index.html#/products';
                }
            });

            var dataSource = new kendo.data.DataSource({
                type: "odata",
                transport: {
                    read: {
                        url: function (data) {
                            return crudServiceBaseUrl + "(" + getProductId() + ")";
                        },
                        dataType: "json"
                    },
                    update: {
                        url: function (data) {
                            delete data.guid;
                            delete data["odata.metadata"];
                            return crudServiceBaseUrl + "(" + getProductId() + ")";
                        },
                        contentType: "application/json",
                        type: "PUT",
                        dataType: "json"
                    },
                    create: {
                        url: crudServiceBaseUrl,
                        dataType: "json"
                    },
                    destroy: {
                        url: function (data) {
                            return crudServiceBaseUrl + "(" + getProductId() + ")";
                        },
                        dataType: "json"
                    },
                    parameterMap: function (data, operation) {
                        if (operation == "update") {
                            delete data.guid;
                            delete data["odata.metadata"];
                            data.UnitPrice = data.UnitPrice.toString();
                        }
                        return JSON.stringify(data);
                    }
                },
                sync: function (e) {
                    window.location.href = '/index.html#/products';
                },
                batch: false,
                schema: {
                    type: "json",
                    data: function (data) {
                        delete data["odata.metadata"];
                        return data;
                    },
                    total: function (data) {
                        return 1;
                    },
                    model: productModel
                }
            });
            dataSource.fetch(function() {
                if (dataSource.view().length > 0) {
                    kendo.bind($("#product-edit-form"), dataSource.at(0));
                }
            });
        }
    });

</script>

The ProductEdit View will be bound to the Kendo Observable Model that the Datasource will return. It’s called Observable Model because the there is two-binding between the Form and the Model, meaning when a change happens in the Form, it is automatically synced with the Model, which is bound to the Datasource.

6-19-2013 6-28-33 PM

You can essentially setup auto-sync on the Datasource so that when there are changes, it will automatically sync back to our OData Web API ProductsController. However for purposes of this post we will stick to a manual sync when we are ready to send our updates to our Controller.

product-edit-form (DIV)


            <div id="product-edit-form">
                <dl>
                    <dt>
                        <label for="firstName">Product Name:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="productName" type="text" data-bind="value: ProductName" />
                            <a href="#" data-field="productName" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="lastName">English Name:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="englishName" type="text" data-bind="value: EnglishName" />
                            <a href="#" data-field="englishName" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="quanityPerUnit">Quanity Per Unit:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="quanityPerUnit" type="text" data-bind="value: QuantityPerUnit" />
                            <a href="#" data-field="quanityPerUnit" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="unitPrice">Unit Price:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="unitPrice" type="text" data-bind="value: UnitPrice" />
                            <a href="#" data-field="unitPrice" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="unitPrice">Unit In Stock:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="unitsInStock" type="text" data-bind="value: UnitsInStock" />
                            <a href="#" data-field="unitsInStock" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="unitsOnOrder">Unit On Order:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="unitsOnOrder" type="text" data-bind="value: UnitsOnOrder" />
                            <a href="#" data-field="unitsOnOrder" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="reorderLevel">Reorder Level:</label></dt>
                    <dd>
                        <span class="k-textbox k-space-right">
                            <input id="reorderLevel" type="text" data-bind="value: ReorderLevel" />
                            <a href="#" data-field="reorderLevel" data-bind="click: clear" class="k-icon k-i-close">&nbsp;</a>
                        </span>
                    </dd>
                    <dt>
                        <label for="discontinued">Discontinued:</label></dt>
                    <dd>
                        <select id="discontinued" data-role="dropdownlist">
                            <option value="1">Yes</option>
                            <option value="2">No</option>
                        </select>
                    </dd>
                    <dt>
                        <label for="Recieved">Recieved:</label></dt>
                    <dd>
                        <input data-role="datepicker" id="recieved">
                    </dd>
                </dl>
                <a class="k-button" data-bind="click: saveProduct"><span span class="k-icon k-i-tick"></span> Submit</a>
                <a class="k-button" data-bind="click: cancel"><span span class="k-icon k-i-tick"></span> Cancel</a>
            </div>

Notice how everything that needs to bound is using the attributes prefixed with “data-“. This is what the Kendo Web MVVM Framework will scan for when when binding a View with a Model, long story short, this is how you specify the binding mapping options for the following:

  • Widget Type (e.g. Grid, TreeView, Calendar, DropDownList, etc.)
  • Widget Properties (Attributes)
  • Binding Type (e.g. value, click, text, etc.)
  • Binding Property from Model (e.g. firstName, lastName, productDatasource, etc.)
  • Binding Methods from Model (e.g. openWindow, cancel, sendEmail, etc.)

Client Side & Product Datasource


<script>
    var getProductId = function () { // parse for ProductId from url
        var array = window.location.href.split('/');
        var productId = array[array.length - 1];
        return productId;
    };
    
    var crudServiceBaseUrl = "/odata/Product";
    
    $(document).bind("viewSwtichedEvent", function (e, args) { // subscribe to viewSwitchedEvent
        if (args.name == "productEdit") { // check if this view was switched to
            var productModel = kendo.data.Model.define({ // we want to refresh this view anytime its switched to
                id: "ProductID",
                fields: {
                    ProductID: { type: "number", editable: false, nullable: true },
                    ProductName: { type: "string", validation: { required: true } },
                    EnglishName: { type: "string", validation: { required: true } },
                    UnitPrice: { type: "number", validation: { required: true, min: 1 } },
                    Discontinued: { type: "boolean" },
                    UnitsInStock: { type: "number", validation: { min: 0, required: true } }
                },
                saveProduct: function (e) {
                    e.preventDefault();
                    dataSource.sync();
                    window.location.href = '/index.html#/products';
                },
                cancel: function (e) {
                    e.preventDefault();
                    window.location.href = '/index.html#/products';
                }
            });

            var dataSource = new kendo.data.DataSource({
                type: "odata",
                transport: {
                    read: {
                        url: function (data) {
                            return crudServiceBaseUrl + "(" + getProductId() + ")";
                        },
                        dataType: "json"
                    },
                    update: {
                        url: function (data) {
                            delete data.guid;
                            delete data["odata.metadata"];
                            return crudServiceBaseUrl + "(" + getProductId() + ")";
                        },
                        contentType: "application/json",
                        type: "PUT",
                        dataType: "json"
                    },
                    create: {
                        url: crudServiceBaseUrl,
                        dataType: "json"
                    },
                    destroy: {
                        url: function (data) {
                            return crudServiceBaseUrl + "(" + getProductId() + ")";
                        },
                        dataType: "json"
                    },
                    parameterMap: function (data, operation) {
                        if (operation == "update") {
                            delete data.guid;
                            delete data["odata.metadata"];
                            data.UnitPrice = data.UnitPrice.toString();
                        }
                        return JSON.stringify(data);
                    }
                },
                sync: function (e) {
                    window.location.href = '/index.html#/products';
                },
                batch: false,
                schema: {
                    type: "json",
                    data: function (data) {
                        delete data["odata.metadata"];
                        return data;
                    },
                    total: function (data) {
                        return 1;
                    },
                    model: productModel
                }
            });
            dataSource.fetch(function() {
                if (dataSource.view().length > 0) {
                    kendo.bind($("#product-edit-form"), dataSource.at(0));
                }
            });
        }
    });

</script>

The Product Datasource is responsible for loading the Product details and providing a Observable Model the Form can bind to. It will handle all the rest of the CRUD activities such as updating and deleting the Product. All of the CRUD activities handled by the Datasource will happen over REST using the OData protocol asynchronously.

Client Side Code

Parsing the ProductId From the URL


    var getProductId = function () { // parse for ProductId from url
        var array = window.location.href.split('/');
        var productId = array[array.length - 1];
        return productId;
    };

This code pretty much speaks for itself, we are simply parsing the Url to get the ProductId of the Product we are loading and binding to the View.

6-19-2013 6-30-21 PM

productModel (Observable Model)


            var productModel = kendo.data.Model.define({ // we want to refresh this view anytime its switched to
                id: "ProductID",
                fields: {
                    ProductID: { type: "number", editable: false, nullable: true },
                    ProductName: { type: "string", validation: { required: true } },
                    EnglishName: { type: "string", validation: { required: true } },
                    UnitPrice: { type: "number", validation: { required: true, min: 1 } },
                    Discontinued: { type: "boolean" },
                    UnitsInStock: { type: "number", validation: { min: 0, required: true } }
                },
                saveProduct: function (e) {
                    e.preventDefault();
                    dataSource.sync();
                    window.location.href = '/index.html#/products';
                },
                cancel: function (e) {
                    e.preventDefault();
                    window.location.href = '/index.html#/products';
                }
            });

This is how we set up our Observable Product Model that will be returned from the Datasource and bound to the View. We can see here we define the primary key field (property), fields, and methods that are View buttons will bind to. When the saveProduct method is invoked, we will perform a sync, meaning all changes in the dataSource will be sent back to the server side for processing when this is invoked. Because our Model is an Observable Model, and there is two-way binding between the Model and the Datasource (as mentioned earlier), the Datasource is keeping track and knows of all the changes that are happening.

Notice how the cancel method is a redirect with the hash (#) in it, so when the redirect happens the Kendo Router will process this and have our SPA load in the Products View which is the Grid with the Product listing.

Decomposing the Datasource Configuration

paramaterMap


                        parameterMap: function (data, operation) {
                            if (operation == "update") {
                                delete data.guid;
                                delete data["odata.metadata"];
                                data.UnitPrice = data.UnitPrice.toString();
                            }
                            return JSON.stringify(data);
                        }


The parameterMap purpose is so that we can intercept and perform any pre-processing on the payload before it is sent to our Controller.

We are deleting all the properties that are not needed by our Controller, more importantly, we are doing this so that we don’t have any extra properties that are not on our Product Model or Entity, so that the MVC ModelBinder will recognize our payload and bind it to the Product parameter on our UpdateEntity(int key, Product update) method on our ProductController.

We are also converting the UnitPrice to a string before we sending back to the server side, because the UnitPrice type is decimal, and currently when using Web Api and OData, the MVC out of the box ModelBinder is not smart enough (yet) to convert a number to decimal in the ModelBinding process. Ironically, it is smart enough to convert to decimal if we send it as a string, so that’s what we’ll send of for now.

sync


sync: function (e) {
    window.location.href = '/index.html#/products';
},

The sync event is raised after the changes have been saved on the server side, once this is complete we simply navigate back to the Products Grid.

schema


schema: {
    type: "json",
    data: function (data) {
        delete data["odata.metadata"];
        return data;
    }

Here we are simply transforming the payload before binding it to the Form, we are removing the data.odata.metadata property since it’s really not needed and unpacking the data.

total


total: function (data) {
    return 1;
}

The total defined method here is simply returning the count of how many records where returned from the server, we are always returning 1 here, since this is a form bound to a single Product at all times. You can add some null checking here to return 0 or 1.

dataSource.fetch(callback)


            dataSource.fetch(function() {
                if (dataSource.view().length > 0) {
                    kendo.bind($("#product-edit-form"), dataSource.at(0));
                }
            });

This will invoke the Datasource to make a call to our Controller Get() method, and load the Product, notice how we are passing in a callback so that when the loading is complete (because it’s happening asynchronously) we are then setting to the variable productEditModel because this is the convention we need to follow mentioned in the previous posts (e.g. view, viewModel, view.html). Because we are following these conventions, our implantation in the Index.html view will work off of these conventions and bind the View to the correct Model for us.

There you have it, MVC 4, Web API, OData, EF, Kendo UI, Binding a Form to Datasource (CRUD) with MVVM – Part 3.

Live demo: http://longle.azurewebsites.net

Happy Coding…! :)

Download sample application: https://genericunitofworkandrepositories.codeplex.com

Building an Extensible Fluent Validation Framework using Generic Funcs and Wiring it up to MVC 4 with ModelValidatorProvider and ModelValidator

Live demo http://longle.azurewebsites.net/customer, courtesy of Windows Azure free 10 Websites, will keep this out there for as long as it’s free :)

We will start off in a pre-baked solution from my last post http://blog.longle.net/2013/05/17/generically-implementing-the-unit-of-work-repository-pattern-with-entity-framework-in-mvc-simplifying-entity-graphs-part-2-mvc-4-di-ioc-with-mef-attributeless-conventions/ using Unity 3.0 as our DI framework of choice.

Before we starts let’s define some of key architecture & design principles we are attempting to achieve here.

  • Validation Framework can run in any .NET technology space e.g. WPF, MVC, ASP.NET WebForms, ASP.NET MVC, SilverLight, WF, Windows Services, WCF, etc..
  • Validation Framework is extensible, and easy to do so.
  • Validation Framework will support reusable validations so that we can reuse them across the enterprise.
  • Validation Framework will support ad-hoc validations, meaning validations, that potentially will only be used once, that are not common validations, and very specific to a given entity with a unique use case.
  • Validation Framework can easily be plugged in e.g such as the MVC 4 run-time, not requiring developer’s to do anything different than they are today to validate their models.

We have a lot to cover so let’s dive right into the Validation project and its’ code!

Validation.Validator.cs


namespace Validation
{
    public class Validator<TModel> : IValidator
    {
        private readonly List<ValidationResult> _validationResults;
        private readonly List<Validation<TModel>> _validations;

        public Validator()
        {
            _validations = new List<Validation<TModel>>();
            _validationResults = new List<ValidationResult>();
        }

        public List<ValidationResult> Validate(object model)
        {
            foreach (var validation in _validations)
            {
                validation.OnValidating();
                var validater = validation.GetValidater();

                if (!validater((TModel) model))
                    _validationResults.Add(validation.GetValidationResult());
            }

            return _validationResults;
        }

        public Validation<TModel> AddValidation(Validation<TModel> validationRule)
        {
            _validations.Add(validationRule);
            return validationRule;
        }
    }
}

This is probably one of the most important class of our Validation Framework, it primary responsibility are:

  • Iterating through all of our validations for a given entity
  • Storing the result for each of the validations

Validation.Validation.cs


    public class Validation<TModel>
    {
        private string _errorMessage;
        private string _propertyName;
        private Func<TModel, bool> _validater;
        private Expression<Func<TModel, object>> _property;

        public Expression<Func<TModel, object>> GetProperty()
        {
            return _property;
        }

        public Validation<TModel> SetProperty(Expression<Func<TModel, object>> property)
        {
            _property = property;
            return this;
        }

        public string GetPropertyName()
        {
            var memberExpression = GetProperty().Body as MemberExpression ?? ((UnaryExpression) GetProperty().Body).Operand as MemberExpression;

            if (memberExpression == null)
            {
                _propertyName = default(string);
                return _propertyName;
            }

            _propertyName = memberExpression.Member.Name;

            return _propertyName;
        }

        public Validation<TModel> SetErrorMessage(string errorMessage)
        {
            _errorMessage = errorMessage;
            return this;
        }

        public Validation<TModel> SetValidater(Func<TModel, bool> validater)
        {
            _validater = validater;
            return this;
        }

        public Func<TModel, bool> GetValidater()
        {
            return _validater;
        }

        public string GetErrorMessage()
        {
            return _errorMessage;
        }

        public ValidationResult GetValidationResult()
        {
            return new ValidationResult {ErrorMessage = GetErrorMessage(), PropertyName = GetPropertyName()};
        }

        public virtual void OnValidating()
        {
            if (string.IsNullOrEmpty(_errorMessage))
                _errorMessage = GetPropertyName() + " is not valid";
        }}

The Validation class is primarily responsible for:

  • Keeping track which property of the entity it’s its validation is for.
  • Setting the appropriate validation result and message.

Now really this is really all you need to start validating your entities/models. Although, there are some other classes that are in the Validation project, they are really not needed to start validating. They are there to help us later wire up our Validation Framework to MVC 4. With that being said let’s wire up our first validation, all you need to do is implement the the Validator class.

I’ve added a UnitTest project with two test class, one is simply a dummy model with fictitious property names, the property names are named after the type of validation we are performing on the property so that we can easily understand what type of validation is happening on each of them. The second class MyValidator is where are validations actually reside for validating MyModel.cs.

Notice all the validations are stored in the MyValidator.cs class and that our MyModel.cs knows nothing about any validation business. More importantly our validation is completely decoupled from our entities, giving us nice separation of concerns.

Validation.Tests.MyModel.cs

Note: Again, The property names here are named so that we can easily see what types of validations are happening for each of them. Obviously in the real world they would be named FirstName, LastName, Age, versus Compare1, Compare2, Regex, etc. :p


namespace Validation.Test
{
    public class MyModel
    {
        public string Compare1 { get; set; }
        public string Compare2 { get; set; }
        public string CreditCard { get; set; }
        public string Id { get; set; }
        public string Length { get; set; }
        public string Range { get; set; }
        public string Regex { get; set; }
        public string Required { get; set; }
        public string IpAddress { get; set; }
        public string Email { get; set; }
    }
}

Validation.Tests.MyValidator.cs
A simple test/sample validator implementation so that we can code up a quick unit test.


namespace Validation.Test
{
    public class MyValidator : Validator<MyModel>
    {
        public MyValidator()
        {
            this.ValidateCompare(m => m.Compare1, m => m.Compare2)
                .WithDataType(ValidationDataType.Integer)
                .WithOperator(ValidationOperator.GreaterThan);

            this.ValidateCreditCard(m => m.CreditCard);

            this.ValidateId(m => m.Id);

            this.ValidateLength(m => m.Length)
                .WithMax(5)
                .WithMin(1);

            this.ValidateRange(m => m.Range)
                .WithDataType(ValidationDataType.Integer)
                .WithMin(1)
                .WithMax(5);

            this.ValidateRegex(m => m.Regex)
                .SetPattern(@"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*")
                .SetErrorMessage("Email must be in valid format");

            this.ValidateRequired(m => m.Required);

            this.ValidateIpAddress(m => m.IpAddress);

            this.ValidateEmail(m => m.Email);

            this.AddValidation()
                .SetProperty(m => m.Email)
                .SetValidater(model => !string.IsNullOrEmpty(model.Email))
                .SetErrorMessage("Email is required");
        }
    }
}

Here we have some simple validation implemented and wired up for our MyModel.cs entity. Here we are using some of the out of the box validations I’ve coded up, and some ad-hoc validations we are able to add to our Validator that the Validation Framework provides e.g. validating and Id, length, range, using Regex, required, IP address, email, etc.. Obviously you can easily add your own reusable validation, and again add ad-hoc (lines 34-37) validations that will probably only be specific to a given entity.

Now let’s test our validation by coding up a quick unit test.

ValidationTests.UnitTest1.cs


namespace Validation.Test
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            var myModel = new MyModel();
            var myValidator = new MyValidator();
            var validationResults = myValidator.Validate(myModel);
            
            Assert.AreEqual(validationResults.Count, 4);
            Assert.AreEqual(validationResults[0].PropertyName, "Compare1" );
            Assert.AreEqual(validationResults[1].PropertyName, "Id");
            Assert.AreEqual(validationResults[2].PropertyName, "Required");
            Assert.AreEqual(validationResults[3].PropertyName, "Email");
        }
    }
}

If we run our unit test, we can see that MyModel.cs was nicely validated…!

6-3-2013 2-41-04 PM

Now, this is great, but how do we seamlessly wire this up to MVC 4..?! Good question, let’s get started. To wire up our new Validation Framework with the MVC 4 run-time there are a couple of things we need to do.

First is to implement the ModelValidatorProvider (http://msdn.microsoft.com/en-us/library/system.web.mvc.modelvalidatorprovider(v=vs.98).aspx), this will provide the MVC 4 runtime the necessary implementation to get a set of validations for a model.

Validation.CustomModelValidatorProvider.cs


namespace Validation
{
    public class CustomModelValidatorProvider : ModelValidatorProvider
    {
        public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
        {
            var isPropertyValidation = metadata.ContainerType != null && !String.IsNullOrEmpty(metadata.PropertyName);

            if (isPropertyValidation) yield break;

            var validatorFactory = new ValidatorFactory();
            var validator = validatorFactory.GetValidator(metadata.ModelType, metadata.Model);

            if (validator != null)
                yield return new CustomModelValidator(metadata, context, validator);
        }
    }
}

When implementing the ModeValidatorProvider we see that we do need a factory of some sort to new up an instance of the right Validator to validate our model. We accomplish this with the ValidatorFactory. This class is responsible for discovering the ValidatorAttribute that the MVC model should be decorated with has the Validator type to be activated to validate the model.

Validation.ValidatorFactory.cs


namespace Validation
{
    public class ValidatorFactory
    {
        public IValidator GetValidator(Type type, object model)
        {
            var validatorAttributes = type.GetCustomAttributes(typeof (ValidatorAttribute), true);

            if (validatorAttributes.Length > 0)
            {
                var validatorAttribute = (ValidatorAttribute) validatorAttributes[0];
                return Activator.CreateInstance(validatorAttribute.Validator) as IValidator;
            }   
            return null;
        }
    }
}

The ValidatorAttribute is pretty straight forward, it’s an attribute that accepts a type, which is the Validator type we need to activate to validate the model.

Validation.ValidatorAttribute.cs


namespace Validation
{
    [AttributeUsage(AttributeTargets.Class)]
    public class ValidatorAttribute : Attribute
    {
        private readonly Type _validator;

        public ValidatorAttribute(Type validator)
        {
            _validator = validator;
        }


        public Type Validator
        {
            get { return _validator; }
        }
    }
}

Let’s take a look at our Customer model that is decorated with the ValidatorAttribute so that our ValidatorFactory knows which Validator to activate for it, in our case CustomerValidator.cs.

Data.Models.Customer.cs


namespace Data.Models
{
    [Validator(typeof (CustomerValidator))]
    public class Customer : IObjectState
    {
        public Customer()
        {
            Orders = new List<Order>();
            CustomerDemographics = new List<CustomerDemographic>();
        }

        public string CustomerID { get; set; }
        public string CompanyName { get; set; }
        public string ContactName { get; set; }
        public string ContactTitle { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public string Region { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
        public string Phone { get; set; }
        public string Fax { get; set; }
        public virtual ICollection<Order> Orders { get; set; }
        public virtual ICollection<CustomerDemographic> CustomerDemographics { get; set; }
        public ObjectState State { get; set; }
    }
}

Second, we need to implement the ModelValidator (http://msdn.microsoft.com/en-us/library/system.web.mvc.modelvalidator(v=vs.108).aspx), this will provide the MVC 4 runtime to call into our Validation Framework and execute our Validator.Validate(object model) method and return a set of ValidationResults. Once our ValidationResult payload is returned we will then need to map it back to MVC’s ModelValidationResult so that it can display our validation messages correctly.

Validation.CustomModelValidator.cs


namespace Validation
{
    public class CustomModelValidator : ModelValidator
    {
        private readonly IValidator _validator;

        public CustomModelValidator(ModelMetadata metadata, ControllerContext controllerContext, IValidator validator)
            : base(metadata, controllerContext)
        {
            _validator = validator;
        }

        public override IEnumerable<ModelValidationResult> Validate(object container)
        {
            var validationResults = _validator.Validate(Metadata.Model);

            return validationResults
                .Select(validationResult => 
                    new ModelValidationResult
                        {
                            MemberName = validationResult.PropertyName, 
                            Message = validationResult.ErrorMessage
                        });
        }
    }
}

Finally we need to register our Validation.CustomModelValidatorProvider.cs implementation with the MVC runtime.

Web.Global.asax.cs


namespace Web
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            Bootstrapper.Initialise();

            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();

            // Add our custom ModelValiidatorProvider for MVC runtime
            ModelValidatorProviders.Providers.Add(new CustomModelValidatorProvider());
        }
    }
}

Now let’s start off by validating one of our existing entities Entites.Customer.cs. You can really place your validation objects anywhere you’d like, for simplicity sake I’ll go ahead and place them in the same project as our Entities under a folder named Validations.

Entities.Validators.CustomerValidator.cs


namespace Entities.Validations
{
    internal class CustomerValidator : Validator<Customer>
    {
        public CustomerValidator()
        {
            this.ValidateId(m => m.CustomerID);

            this.ValidateLength(m => m.CompanyName)
                .WithMax(5)
                .WithMin(1);

            this.ValidateRequired(m => m.ContactName);

            this.AddValidation()
                .SetProperty(m => m.City)
                .SetValidater(model => !string.IsNullOrEmpty(model.City))
                .SetErrorMessage("Customer city is required");
        }
    }
}

Now let’s edit our existing CustomController and add two more actions for editing our Customer entity.

Web.Controllers.CustomerController.cs


namespace Web.Controllers
{
    public class CustomerController : Controller
    {
        private readonly IUnitOfWork _unitOfWork;

        public CustomerController(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        public ActionResult Index(int? page)
        {
            var pageNumber = page ?? 1;
            const int pageSize = 20;

            int totalCustomerCount;

            var customers =
                _unitOfWork.Repository<Customer>()
                    .Query()
                    .Include(i => i.Orders)
                    .OrderBy(q => q
                        .OrderBy(c => c.ContactName)
                        .ThenBy(c => c.CompanyName))
                    .Filter(q => q.ContactName != null)
                    .GetPage(pageNumber, pageSize, out totalCustomerCount);

            ViewBag.Customers = new StaticPagedList<Customer>(
                customers, pageNumber, pageSize, totalCustomerCount);

            return View();
        }

        [HttpGet]
        public ActionResult Edit(string id)
        {
            var customer = _unitOfWork.Repository<Customer>().FindById(id);
            return View(customer);
        }

        [HttpPost]
        public ActionResult Edit(Customer customer)
        {
            if (ModelState.IsValid)
                RedirectToAction("Edit");
            
            _unitOfWork.Repository<Customer>().Update(customer);
            _unitOfWork.Save();

            return View(customer);
        }
    }
}

Notice how we are calling the MVC ModelState.IsValid, and when we debugging this, we see that the MVC run-time will invoke our custom Validation Framework.

6-3-2013 3-30-50 PM

Our error message from Entities.Validation.CustomerValidator.cs.

6-3-2013 3-31-57 PM

All of the out of the box Validators that are included in the example download, follow the described pattern listed below, this is also how you would extend or add your own reusable validations to the framework.

  1. Extend the Validator, by writing an Extension method
  2. Instantiating a fluent helper class for the validation
  3. Setting the property to be validated
  4. Setting the validation logic
  5. Adding the the validation to stack of validations to the Validator instance

Let’s take a quick look at one of the out of the box validations e.g. ValidateLength.

Validation.Validators.ValidateLengthExtension.cs


namespace Validation.Validators
{
    public static class ValidateLengthExtension
    {
        public static ValidateLengthFluentHelper<TModel> ValidateLength<TModel>(this Validator<TModel> validator, Expression<Func<TModel, object>> property)
        {
            var fluentHelper = new ValidateLengthFluentHelper<TModel>();

            fluentHelper.SetProperty(property);
            fluentHelper.SetValidater(model =>
                {
                    var value = property.GetPropertyValue(model) as string;

                    return string.IsNullOrEmpty(value) || value.Length >= fluentHelper.GetMin() && value.Length <= fluentHelper.GetMax();
                });

            validator.AddValidation(fluentHelper);

            return fluentHelper;
        }
    }
}

To provide a nice and easy to use fluent API experience, let’s take a look at the fluent helper class for this validation.

Validation.Validators.ValidateLengthFluentHelper.cs


namespace Validation.Validators
{
    public class ValidateLengthFluentHelper<TModel> : Validation<TModel>
    {
        private int _max;
        private int _min;

        public override void OnValidating()
        {
            if (String.IsNullOrEmpty(GetErrorMessage()))
                SetErrorMessage(GetPropertyName() + " must be between " + _min + " and " + _max + " characters long.");
        }

        public int GetMin()
        {
            return _min;
        }

        public int GetMax()
        {
            return _max;
        }

        public new ValidateLengthFluentHelper<TModel> SetProperty(Expression<Func<TModel, object>> propertySelector)
        {
            base.SetProperty(propertySelector);
            return this;
        }

        public ValidateLengthFluentHelper<TModel> WithMin(int min)
        {
            _min = min;
            return this;
        }

        public ValidateLengthFluentHelper<TModel> WithMax(int max)
        {
            _max = max;
            return this;
        }
    }
}

To use this is fairly straight forward and simple..!

ValidationTest.MyValidator.cs


            this.ValidateLength(m => m.Length)
                .WithMax(5)
                .WithMin(1);


Support for Ad-Hoc Validations with Generic Funcs using AddValidation() Fluent Method

Finally, let’s quickly go over adding ad-hoc validations by adding in-line Lambda’s or Generic Funcs, all you hvae to do is call AddValidation() and using the fluent API, and make sure your generic func accepts a TModel (could be of any object type) and returns a Boolean. In the sample code below we are doing a simple ad-hoc validation for the property Email, validating if there’s a value or not and returning an validation message.

ValidationTest.MyValidator.cs


            this.AddValidation()
                .SetProperty(m => m.Email)
                .SetValidater(model => !string.IsNullOrEmpty(model.Email))
                .SetErrorMessage("Email is required");


There you have it, a Validation Framework registered and wired up to the MVC 4 run-time.

Happy Coding..! :)

Download sample application.

https://skydrive.live.com/redir?resid=949A1C97C2A17906!5153&authkey=!AKbcAGKuETlcm6M

Adding Dependency Injection to your ASP.NET MVC Twilio app using MEF (Managed Extensibility Framework)

Adding Dependency Injection to your ASP.NET MVC Twilio app using MEF (Managed Extensibility Framework)

http://www.twilio.com/blog/2012/11/adding-dependency-injection-to-your-asp-net-mvc-twilio-app-using-mef.html

Thanks for the Reblog Twilio!

IoC, Dependency Injection, Manage Extensiblity Framework (MEF), ServiceLocator in .NET 4, MVC 4, WebApi, IDependencyResolver with Closed Types e.g. Twilio Types

Being accustomed to writing Mvc apps with IoC, specifically with Ninject and Managed Extensibility Framework (Mef), I wanted to see how we could inject most our Twilio instances (types TwilioResponse and the TwilioRestClient) when using them in my Mvc 4 WebApi Controller Actions.

To “Mefify” your Mvc 4 .NET 4.0 app, download the Mvc4.Mef project and reference this in your web app.

Let’s quickly review some of important classes in this project (Mvc.Mef) that do the heavy lifting to wire up Mef in your Mvc app.

  1. MefMvcConfig.cs

    • Creating our CompositionContainer, the container that holds all our exports and imports.
    • AggregateCatalog, which is a collection of catalogs, in our case composed of a ComposableCatalog (we pass in a TypeCatalog later from our our Mvc app, which inherits the Composable Catalog) and DirectoryCatalog which are of the possible classes that are decorated with [Export] and [Import] attributes from the current executing assembly as well as another other assemblies that maybe in the bin from our other projects.

    Note: The RegisterMef method will be invoked from your Application_Start(), Global.asax.cs class to wire integrate and wire up Mef into your Mvc app.

    
        public static class MefMvcConfig
        {
            public static void RegisterMef(TypeCatalog typeCatalog)
            {
                var compositionContainer = ConfigureContainer(typeCatalog);
                ServiceLocator
                    .SetLocatorProvider(() => new MefServiceLocator(compositionContainer));
    
                ControllerBuilder
                    .Current.SetControllerFactory(
                    new MefMvcControllerFactory(compositionContainer));
    
                GlobalConfiguration
                    .Configuration
                    .DependencyResolver = 
                    new MefMvcDependencyResolver(compositionContainer);
            }
    
            private static CompositionContainer ConfigureContainer(
                ComposablePartCatalog composablePartCatalog)
            {
                var path = HostingEnvironment.MapPath("~/bin");
                if (path == null) throw new Exception("Unable to find the path");
    
                var aggregateCatalog = new AggregateCatalog(new DirectoryCatalog(path));
    
                if (composablePartCatalog != null)
                    aggregateCatalog.Catalogs.Add(composablePartCatalog);
    
                return new CompositionContainer(
                    aggregateCatalog,
                    new MefNameValueCollectionExportProvider(
                        ConfigurationManager.AppSettings));
            }
        }
    
  2. MefMvcControllerFactory.cs, which inherits the Overriding and setting the default Mvc ControllerFactory with our own that we use our DefaultControllerFactory, the MefMvcControllerFactory is used to scan our CompositionContainer when attempting to resolve Controller types.
    
        public class MefMvcControllerFactory : DefaultControllerFactory
        {
            private readonly CompositionContainer _compositionContainer;
    
            public MefMvcControllerFactory(CompositionContainer compositionContainer)
            {
                _compositionContainer = compositionContainer;
            }
    
            protected override IController GetControllerInstance(
                RequestContext requestContext, Type controllerType)
            {
                var export = _compositionContainer
                    .GetExports(controllerType, null, null).SingleOrDefault();
    
                IController result;
    
                if (null != export)
                    result = export.Value as IController;
                else
                {
                    result = base.GetControllerInstance(requestContext, controllerType);
                    _compositionContainer.ComposeParts(result);
                }
    
                return result;
            }
    
    
    
  3. MefMvcDependencyResolver.cs, which is used, when we override and the default Mvc DependencyResolver, so that when there are dependencies in our WebApi Controllers, the Mvc runtime will use our own MefMvcDependencyResolver to resolve those dependencies
    
        public class MefMvcDependencyResolver : IDependencyResolver
        {
            private readonly CompositionContainer _compositionContainer;
    
            public MefMvcDependencyResolver(CompositionContainer compositionContainer)
            {
                _compositionContainer = compositionContainer;
            }
    
            #region IDependencyResolver Members
    
            public IDependencyScope BeginScope()
            {
                return this;
            }
    
            public object GetService(Type type)
            {
                var export = _compositionContainer
                    .GetExports(type, null, null).SingleOrDefault();
    
                return null != export ? export.Value : null;
            }
    
            public IEnumerable<object> GetServices(Type type)
            {
                var exports = _compositionContainer.GetExports(type, null, null);
                var exportList = new List<object>();
                if (exports.Any()) exportList.AddRange(exports.Select(export => export.Value));
                return exportList;
            }
    
            public void Dispose()
            {
            }
    
            #endregion
        }
    
    
    
  4. MefNameValueCollectionExportProvider.cs, which enables us to load in to our CompositionContainer our Web.config AppSettings key and values in the case we want to resolve them with injection e.g. Twilio AccountSid and AuthoToken from our Web.config AppSettings.
    
        public class MefNameValueCollectionExportProvider : ExportProvider
        {
            private readonly List<Export> _exports;
    
            public MefNameValueCollectionExportProvider(NameValueCollection settings)
            {
                _exports = new List<Export>();
    
                foreach (string key in settings)
                {
                    var metadata = new Dictionary<string, object> {
                        {
                            CompositionConstants
                            .ExportTypeIdentityMetadataName, typeof (string)
                            .FullName
                        }};
    
                    var exportDefinition = new ExportDefinition(key, metadata);
    
                    _exports.Add(new Export(exportDefinition, () => settings[key]));
                }
            }
    
            protected override IEnumerable<Export> GetExportsCore(
                ImportDefinition importDefinition, AtomicComposition atomicComposition)
            {
                return _exports
                    .Where(x => importDefinition.IsConstraintSatisfiedBy(x.Definition));
            }
        }
    
    
  5. MefAdapter.cs, which is based from Mark Seemaan’s blog on Resolving Close Types with Mef, this class will be used so that we can add close classes/objects, which we cannot annotate with the [Export] and/or [Import] attributes, in this example I will demonstrate this with Twilio’s objects.

    
        public class MefAdapter<T> where T : new()
        {
            private readonly T _typeToExport;
    
            public MefAdapter()
            {
                _typeToExport = new T();
            }
    
            [Export]
            public virtual T TypeToExport
            {
                get { return _typeToExport; }
            }
        }
    
        public class MefAdapter<T1, T2, TResult>
        {
            private static readonly Func<T1, T2, TResult> 
                CreateExport = Create<T1, T2, TResult>();
    
            private readonly TResult _typeToExport;
    
            [ImportingConstructor]
            public MefAdapter(T1 arg1, T2 arg2)
            {
                _typeToExport = CreateExport(arg1, arg2);
            }
    
            [Export]
            public virtual TResult TypeToExport
            {
                get { return _typeToExport; }
            }
    
            internal static Func<T1, T2, TResult> Create<T1, T2, TResult>()
            {
                var constructorArgExpression1 = Expression.Parameter(typeof (T1), "arg1");
                var constructorArgExpression2 = Expression.Parameter(typeof (T2), "arg2");
    
                var constructorInfo = typeof (TResult).GetConstructor(new[]
                        {
                            typeof (T1),
                            typeof (T2)
                        });
    
                var constructorExpression = Expression
                    .New(constructorInfo, constructorArgExpression1, constructorArgExpression2);
    
                return Expression.Lambda<Func<T1, T2, TResult>>(
                    constructorExpression, 
                    constructorArgExpression1, 
                    constructorArgExpression2)
                    .Compile();
            }
        }
    

Now switching to your Mvc 4 app, here are the simple steps to wire this up, first few steps is to simply NuGet CommonServiceLocator, using this we obviously will also be practicing the ServiceLocator Pattern.

Now, download the Mvc4.Mef.Framework project, and reference it in your MVC 4 application and add one line to your Global.asax.cs file to get this all setup. Since we are here let’s go ahead and add some UriPathExtensionMapping configurations so that we can serve up some real Twilio request later on to demonstrate Dependency Injection with the TwilioRequest object from their Api, these UriPathExtensionMappings are not required for Mef’ing your Mvc app.

Note: notice that we are passing in a TypeCatalog when we invoke the RegisterMef method, this is so that we can pass in closed classes (types) that we would like to inject or resolve with Dependency Injection using Mef in this case the TwilioResponse object from their Api.

Global.asax.cs


    public class MvcApplication : HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            MefMvcConfig.RegisterMef(new TypeCatalog(typeof(MefAdapter<TwilioResponse>)));

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            GlobalConfiguration
                .Configuration.Formatters
                .XmlFormatter
                .AddUriPathExtensionMapping("xml", "text/xml");

            GlobalConfiguration
                .Configuration
                .Formatters
                .XmlFormatter
                .AddUriPathExtensionMapping("json", "application/json");
        }
    }

Now if we run our app you should see that the request from the HomeController is now getting resolved through our MefMvcControllerFactory from our CompositionContainer. In this screenshot, I’ve pinned the watch to source (Visual Studio 2012) to illustrate that the Controller type that is being succesfully resolved with DependencyInjection is indeed the HomeController.

In order for Mef to serve up and resolve these Controllers we have to decorate our controllers with the [Export] attribute e.g. HomeController.

Note: The [PartCreationPolicy(CreationPolicy.NonShared)], means that whenever this instance is requested, Mef will new up (instantiate) a new instance every time, the opposite would be CreationPolicy.Shared, which will new one up the first time and keep it alive, and serve this same instance, every time it is requested (which is the behavior we don’t want for Mvc Controllers).


    [Export]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Message = 
                "Modify this template to jump-start your ASP.NET MVC application.";

            return View();
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your app description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
    }
}

Now for the fun part, this Mvc 4 project is from one of my previous blogs Multi-Step (Two-Factor) ASP.NET MVC 4 Registration with SMS using Twilio Cloud Communication and SimpleMembershipProvider for Increased User Validity, where we were using TwilioRestClient from their Api to send SMS messages for Two-Factor registration. Let’s see how we can inject this now with Mef in our Register Action.

Before:


        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                try
                {
                    var smsVerificationCode =
                        GenerateSimpleSmsVerificationCode();

                    WebSecurity.CreateUserAndAccount(
                        model.UserName,
                        model.Password,
                        new
                            {
                                model.Mobile,
                                IsSmsVerified = false,
                                SmsVerificationCode = smsVerificationCode
                            },
                        true);

                    var twilioRestClient = new TwilioRestClient(
                        ConfigurationManager.AppSettings.Get("Twilio:AccoundSid"),
                        ConfigurationManager.AppSettings.Get("Twilio:AuthToken"));

                    twilioRestClient.SendSmsMessage(
                        "+19722001298",
                        model.Mobile,
                        string.Format(
                            "Your ASP.NET MVC 4 with Twilio " +
                            "registration verification code is: {0}",
                            smsVerificationCode)
                        );

                    Session["registrationModel"] = model;

                    return RedirectToAction("SmsVerification", "Account", model);
                }
                catch (MembershipCreateUserException e)
                {
                    ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

After:


        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                try
                {
                    var smsVerificationCode =
                        GenerateSimpleSmsVerificationCode();

                    WebSecurity.CreateUserAndAccount(
                        model.UserName,
                        model.Password,
                        new
                            {
                                model.Mobile,
                                IsSmsVerified = false,
                                SmsVerificationCode = smsVerificationCode
                            },
                        true);

                    var twilioRestClient = ServiceLocator.Current.GetInstance<TwilioRestClient>();

                    twilioRestClient.SendSmsMessage(
                        "+19722001298",
                        model.Mobile,
                        string.Format(
                            "Your ASP.NET MVC 4 with Twilio " +
                            "registration verification code is: {0}",
                            smsVerificationCode)
                        );

                    Session["registrationModel"] = model;

                    return RedirectToAction("SmsVerification", "Account", model);
                }
                catch (MembershipCreateUserException e)
                {
                    ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

Notice in line 25, we are now using ServiceLocator to resolve the dependency in our Register action for the TwilioRestClient. Here Mef will take care of activating and setting up our TwilioRestClient instance for us.

Ideally we would want to wrap (Composition Pattern) the TwilioRestClient, implementing our own interface and injecting the interface with our wrapped TWilioRestClient. If time permits, I’ll cover this in another post, for now we’ll just have fun with injecting closed classes with Mef.

Let’s see the TwilioRestClient injection with ServiceLocator in action by running the app and registering.

Register View

Debugging in our Register Action from the AccountController, notice how our TwilioRestClient is successfully being activated, injected and resolved with the ServiceLocator, which really is just scanning our CompositionContainer we had setup earlier, which was made possible with our TwilioRestClientMefAdapter.

Great! We received a test SMS text message with our injected TwilioRestClient.

How did this happen? Will we had to create a TwilioRestClientMefAdapter (this is a variation of the Adapter Pattern) class, this was needed because the TwilioRestClient does not have parameterless constructor, and those parameters were our Twilio AccountSid and AuthToken which were in our Web.config.


    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class TwilioRestClientMefAdapter
    {
        private readonly TwilioRestClient _twilioRestClient;

        [ImportingConstructor]
        public TwilioRestClientMefAdapter(
            [Import("Twilio:AccoundSid")] string accountSid,
            [Import("Twilio:AuthToken")] string authToken)
        {
            _twilioRestClient = new TwilioRestClient(accountSid, authToken);
        }

        [Export]
        public TwilioRestClient TwilioRestClient
        {
            get { return _twilioRestClient; }
        }
    }

Let’s debug the activation process, just to understand what’s happening here.

Now, when we inject our TwilioRestClient with


var twilioRestClient = ServiceLocator.Current.GetInstance<TwilioRestClient>();

what’s happening is that, what’s marked for Export is the property named TwilioRestClient. In order for Mef to export that property it must instantiate the class that it lives in which in our case is the TwilioRestClientMefAdapter class. The constructor marked with [ImportingConstructor] is what Mef will scan for, to activate this class. In the constructor is where we are also injecting our AccountSid and AuthToken from our web.conf appSettings that was loaded earlier into our CompositionContainer using our MefNameValueCollectionExportProvider.cs class, where we passed in ConfigurationManager.AppSettings to our MefMvcConfig.ConfigureContainer method. Fortunately for us, the ConfigurationManager.AppSettings happens to be a NameValueCollection.


        private static CompositionContainer ConfigureContainer(
            ComposablePartCatalog composablePartCatalog)
        {
            var path = HostingEnvironment.MapPath("~/bin");
            if (path == null) throw new Exception("Unable to find the path");

            var aggregateCatalog = new AggregateCatalog(new DirectoryCatalog(path));

            if (composablePartCatalog != null)
                aggregateCatalog.Catalogs.Add(composablePartCatalog);

            return new CompositionContainer(
                aggregateCatalog,
                new MefNameValueCollectionExportProvider(
                    ConfigurationManager.AppSettings));
        }


Now, one more injection test with the TwilioResponse object, which is probably used most when developing with Twilio Cloud. We will test our WeatherController, which is pretty much a voice caller, calling in, being prompt to enter in their zip code and our our application telling the user what the weather is like for their zip. For this test we will need the Curl command line utility since it’s a WebApi method that will be invoked via a POST.

Let’s run, our app and test the pseudo WeatherController which we use an injected instance of the TwilioResponse. For this test, we will use the [Import] attribute to inject and activate our TwilioResponse vs. using the ServiceLocator. There’s no wrong or right way to get our instances activated, this is just preference.

Now lets run this command with Curl.

curl http://localhost:64190/api/weather/gatherzipcode.xml -X POST -d “fro
m=123″

With breakpoints setup, we can see that our TwilioResponse is being successfully activated and injected into our WeatherController using the [Import] attribute!

Our WebApi returns the required TwiML response.


<Response><Gather action="http://myapp.com/api/Weather/RetrieveWeather.xml" finishOnKey="#"><Say voice="woman">Please en
ter the zip code of the area you would like the weather in.</Say></Gather><Gather action="http://myapp.com/api/Weather/R
etrieveWeather.xml" finishOnKey="#"><Say voice="woman">Please enter the zip code of the area you would like the weather
in.</Say></Gather></Response>

How did our TwilioResponse object get resolved, activated and injected into our WeatherController? Well when we registered the Mef framework in our Global.asax.cs with:


MefMvcConfig.RegisterMef(new TypeCatalog(typeof(MefAdapter<TwilioResponse>)));

We passed in a TypeCatalog, and we initialized the TypeCatalog, passing in a list of types, in our case we are passing in our MefAdapter, that is used for the TwilioResponse type. This is because the TwilioResponse object is a closed object that we have no control over, and cannot decorate the class with any [Export] attributes.

Now let’s take a quick look at the internals of the MefAdapter, to understand how this is happening, it’s behavior is pretty similar to our TwilioRestClientAdapter we implemented earlier.


    public class MefAdapter<T> where T : new()
    {
        private readonly T _typeToExport;

        public MefAdapter()
        {
            _typeToExport = new T();
        }

        [Export]
        public virtual T TypeToExport
        {
            get { return _typeToExport; }
        }
    }

When we pass in the MefAdapter to our TypeCatalog, and we request an instance of T, Mef will new up our MefAdapter, by default use the parameterless constructor, which will new up an instance of T (which is TwilioResponse in our case), and now the TwilioResponse is initialized and passed back to our WeatherController.

Well, there we have it, integrating Mef into our .NET 4 Mvc 4 project, and injecting closed objects which we are unable to attribute with [Export] and/or [Import] attributes with our Mef adapters.

Happy Coding…! :)

Download sample solution: https://skydrive.live.com/redir?resid=949A1C97C2A17906!2391

Multi-Step (Two-Factor) ASP.NET MVC 4 Registration with SMS using Twilio Cloud Communication and SimpleMembershipProvider for Increased User Validity

Some sites such as live.com, gmail.com will require a multi-step registration and/or forgot password workflows to validate you say you are. Having an opportunity working with the Twilio Cloud Communication Platform, exposed how easily this can be done with their Api’s.

So for this post, I wanted to illustrate the steps in getting your MVC 4 application wired up with multi-step registration process with SMS code verification leveraging Twilio. We will start from my last blog post with Seed Users and Roles with MVC 4, SimpleMembershipProvider, SimpleRoleProvider, EntityFramework 5 CodeFirst, and Custom User Properties.

Since we already gathered the user’s mobile number during registration, let’s go ahead and add a property/field “IsSmsVerified” and run EntityFramework’s migration command update-database -verbose (so we can see what commands are being issued to our database for the migration.

NuGet and install the Twilio.Mvc package.

Update our UserProfile entity with IsSmsVerified and SmsVerificationCode properties.


    [Table("UserProfile")]
    public class UserProfile
    {
        public UserProfile()
        {
            IsSmsVerified = false;
        }

        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string Mobile { get; set; }
        [DefaultValue(false)]
        public bool IsSmsVerified { get; set; }
        public string SmsVerificationCode { get; set; }
    }

Update our Seed method so that we are not inserting nulls for the provisioned users.


#region

using System.Data.Entity.Migrations;
using System.Linq;
using System.Web.Security;
using MVC4SimpleMembershipCodeFirstSeedingEF5.Models;
using WebMatrix.WebData;

#endregion

namespace MVC4SimpleMembershipCodeFirstSeedingEF5.Migrations
{
    internal sealed class Configuration : DbMigrationsConfiguration<UsersContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }

        protected override void Seed(UsersContext context)
        {
            WebSecurity.InitializeDatabaseConnection(
                "DefaultConnection",
                "UserProfile",
                "UserId",
                "UserName", autoCreateTables: true);

            if (!Roles.RoleExists("Administrator"))
                Roles.CreateRole("Administrator");

            if (!WebSecurity.UserExists("lelong37"))
                WebSecurity.CreateUserAndAccount(
                    "lelong37",
                    "password",
                    new {Mobile = "+19725000374", IsSmsVerified = false});

            if (!Roles.GetRolesForUser("lelong37").Contains("Administrator"))
                Roles.AddUsersToRoles(new[] {"lelong37"}, new[] {"Administrator"});
        }
    }
}

Run: update-database -verbose from the Package Manager Console

Now the fun begins, let’s update our AccountController.

  • Update the Register(RegisterModel model) Action and introduce the second step registration process of entering an SMS verfication code that we send the user using Twilio’s REST Api Client.

    Note: We are just scratching the tip of the ice berg in terms of what the Twilio Cloud Communication offers, you can visit their docs site for more info.

  • Add SmsVerification() Action, so that the user can enter the SMS verification code.
  • Add SmsVerication(SmsVerificationModel smsVerificationModel) Action, so that we can validate the user, the user’s mobile number, and SMS verification code.
  • Add GenerateSimpleSmsVerificationCode() method, a simple static helper method to generate a six character SMS verification code.

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                try
                {
                    var smsVerificationCode =
                        GenerateSimpleSmsVerificationCode();

                    WebSecurity.CreateUserAndAccount(
                        model.UserName,
                        model.Password,
                        new
                            {
                                model.Mobile,
                                IsSmsVerified = false,
                                SmsVerificationCode = smsVerificationCode
                            },
                        false);

                    var twilioRestClient = new TwilioRestClient(
                        ConfigurationManager.AppSettings.Get("Twilio:AccoundSid"),
                        ConfigurationManager.AppSettings.Get("Twilio:AuthToken"));

                    twilioRestClient.SendSmsMessage(
                        "+19722001298",
                        model.Mobile,
                        string.Format(
                            "Your ASP.NET MVC 4 with Twilio " +
                            "registration verification code is: {0}",
                            smsVerificationCode)
                        );

                    Session["registrationModel"] = model;

                    return RedirectToAction("SmsVerification", "Account");
                }
                catch (MembershipCreateUserException e)
                {
                    ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

        [AllowAnonymous]
        public ActionResult SmsVerification()
        {
            return View(new SmsVerificationModel
                {
                    Username =
                        ((RegisterModel) Session["registrationModel"])
                            .UserName
                });
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult SmsVerification(SmsVerificationModel smsVerificationModel)
        {
            if (ModelState.IsValid)
            {
                var userContext = new UsersContext();

                var userProfile = userContext.UserProfiles
                    .Single(u => u.UserName == smsVerificationModel.Username);

                var registerModel = ((RegisterModel) Session["registrationModel"]);

                if (userProfile.SmsVerificationCode == smsVerificationModel.SmsVerificationCode)
                {
                    WebSecurity.Login(userProfile.UserName, registerModel.Password);
                    return RedirectToAction("Index", "Home");
                }
            }

            ModelState.AddModelError("", "The SMS verfication code was incorrect.");
            return RedirectToAction("SmsVerification", "Account");
        }

        private static string GenerateSimpleSmsVerificationCode()
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            var random = new Random();
            return new string(
                Enumerable.Repeat(chars, 6)
                    .Select(s => s[random.Next(s.Length)])
                    .ToArray());
        }

We could combine the two actions SmsVerication() and SmsVerication(SmsVerificationModel smsVerificationModel) into one, by checking the request verb for GET or Post, however for separation of concerns we will keep them “nice” and “separate”.

Let’s add some AppSettings entries to store our Twilio Rest Api credentials.


  <appSettings>
    <add key="webpages:Version" value="2.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="PreserveLoginUrl" value="true" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="Twilio:AccoundSid" value="youtwilioaccountid" />
    <add key="Twilio:AuthToken" value="yourtwilioauthtoken" />
  </appSettings>

Note: Your Twilio credentials for using their REST Api can be found on your dashboard after registering.

Create a SmsVerification ViewModel.


using System.ComponentModel.DataAnnotations;

using System.ComponentModel.DataAnnotations;

namespace MVC4SimpleMembershipCodeFirstSeedingEF5.Models
{
    public class SmsVerificationModel
    {
        [Display(Name = "Username")]
        public string Username { get; set; }

        [Required]
        [Display(Name = "SMS Verification Code")]
        public string SmsVerificationCode { get; set; }
    }
}

Let’s create the SmsVerification View where a user can input the SMS verification code that we sent to the user bound to the ViewModel we just created.

@model MVC4SimpleMembershipCodeFirstSeedingEF5.Models.SmsVerificationModel
@{
    //ViewBag.Title = "SMS Verification with MVC 4 & Twilio";
    ViewBag.Title = "SmsVerification";
}

<hgroup class="title">
    <h1>@ViewBag.Title.<br/></h1>
    <h3>Please enter your SMS verification code to complete registration.</h3>
</hgroup>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary()

    @Html.HiddenFor(m => m.Username)

    <fieldset>
        <legend>SMS Verifcation Form</legend>
        <ol>
            <li>
                @Html.LabelFor(m => m.Username)
                @Html.DisplayTextFor(m => m.Username) 
                <br/><br/>
            </li>
            <li>
                @Html.LabelFor(m => m.SmsVerificationCode)
                @Html.TextBoxFor(m => m.SmsVerificationCode)
            </li>
        </ol>
        <input type="submit" value="SmsVerification" />
    </fieldset>
}

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Step 1 of the registration process, run the application and register.

For a quick sanity check let’s just make sure our SimpleMembershipProvider is persisting the extra properties we added earlier e.g. SmsVerificationCode, IsSmsVerified.


SELECT TOP 1000 [UserId]
      ,[UserName]
      ,[Mobile]
      ,[IsSmsVerified]
      ,[SmsVerificationCode]
  FROM [aspnet-MVC4SimpleMembershipCodeFirstSeedingEF5].[dbo].[UserProfile]

Good, we can see here that Mobile, IsSmsVerified and SmsVerificationCode is being saved when we invoked the WebSecurity.CreateUserAndAccount method earlier from our Registration Action.


                    WebSecurity.CreateUserAndAccount(
                        model.UserName,
                        model.Password,
                        new
                            {
                                model.Mobile,
                                IsSmsVerified = false,
                                SmsVerificationCode = smsVerificationCode
                            },
                        false);

Step 2, SMS notification to the user’s mobile number was received with the SMS verification code.

Step 3 of the registration process, input the SMS verification code in the SMSVerfication View.

You have now successfully completed the 3 step registration process and have been automatically logged into the site!

Now there are obviously TODO’s here, you can create an new authorize Attribute to verify that the IsSmsVerified property for the user is not false, clean up how we are storing the RegisterModel in session, additional bullet proofing the app in terms of security gaps, etc.. However the emphasis of this blog was multi-step registration to for increased validity of the user.

Last but not least, you can use the a similar implementation for things like forgot password or any other type of workflow that needs that extra degree of validation.

Happy Coding…! :)

Download sample application: https://skydrive.live.com/redir?resid=949A1C97C2A17906!2383