AngularJS & Kendo UI using Angular Kendo with ASP.NET MVC 5, Web API & OData

Update: 9/12/2014 – Codeplex source code download URL update.

Update: 5/1/2014 – Customer View & customerController.js has been updated to toggle styles by manipulating the DOM through the ViewModel with ng-show to adhere and stay consistent with AngularJS best practices.

Update: 5/14/2014 – Added section “Animating our View Swapping”, to elaborate on adding animation to the “ng-view” swapping process.

Source code: http://goo.gl/f1esAf
Live demo: http://longle.azurewebsites.net

With AngularJS and ASP.NET MVC, we now have an MVC pattern and architecture for both on the client and server. What do we do and/or how do we approach this? Well one can argue we don’t use any of the MVC architecture on the server and build out a full-fledged AngularJS front-end application and only use make async calls to Web API for all things that absolutely need to be on the server e.g. CRUD, workflows, business logic, etc.

Now, there’s absolutely nothing wrong with this approach, and for the most part a lot of heavy front end SPA’s are built this way in ASP.NET MVC. However, with all the .NET, ASP.NET and AngularJS goodness, why not leverage the best of both worlds? Again, there’s absolutely nothing wrong with building a pure AngularJS application and only using Web API, but for this blog post we’ll go over patterns of using the best of both worlds along with integrating AngularJS with Kendo UI.

Note: for those that prefer to build a pure AngularJS front-end and only leveraging Web API on the backend (which many devs prefer), simply ignore the MVC Razor sections. If this is the case you can also opt to render raw *.html vs. *.cshtml views as well.

Let’s take a look at our Project structure for all things client-side.

2014-05-01_1-21-35

Staying true to the AngularJS seed (sample) application from the AngularJS team, we’ll also create an “app” folder under “scripts”, staying true to ASP.NET MVC, where JS scripts are supposed to reside.

Northwind.Web/Scripts/app/app.js


var northwindApp = angular.module('northwindApp', ['kendo.directives', 'ngRoute', 'ngAnimate'])
    .config(function ($routeProvider)
    {
        $routeProvider
            .when('/home',
            {
                templateUrl: '/home/home',
                controller: 'homeController'
            })
            .when('/customer',
            {
                templateUrl: '/customer',
                controller: 'customerController'
            })
            .when('/customer/edit/:id',
            {
                templateUrl: '/customer/edit',
                controller: 'customerEditController'
            })
            .when('/help',
            {
                templateUrl: '/help'
            })
            .otherwise(
            {
                redirectTo: '/home'
            });
    });

This is where our AngularJS application bootstrapping process takes place, here we’ve declared our new app “northwindApp” and we register all our routes (URL’s) for our SPA application. Note, if you’re not building a SPA, you can still leverage all the AngularJS goodness and architecture it brings (which btw, is huge), you can just omit the registering any of the routes in this step. Notice, the parameters we are passing into the module method: “northwindApp” is the name of our app, after that it’s an array of all the AngularJS modules we want injected into our application:

  • “kendo.directives” are needed so that we have full integration with AngularJS and Kendo UI, this will make more sense when we cover how AngularJS will compile Kendo UI widgets using directives.
  • “ngRoute” is needed so that we can configure our SPA routes, if your familiar with ASP.NET MVC routes, pretty much the same principles here, only difference here is routing of your application will take place on the client vs. the server. Here we are configuring each out with the “when” method by simply passing in the path in the URL and within the “when” we configure what the server URL path is to remotely load the View and which Controller to use. The “otherwise” method is simply a default route that will be used if none of the other routes are matched from what’s in the URL. You could route the default “otherwise” route to go to a custom 404 page, for our purposes we’ll just route the user to the default home page.
  • “ngAnimate” is injected here so that we can add some animation when AngularJS swaps out our Views in our SPA.

Northwind.Web/Scripts/app/controllers/homeController.js


'use strict';

northwindApp.controller('homeController',
    function ($scope) {
        $scope.title = "ASP.NET";
    });

AngularJS has the notion of scopes with in application, there is one $rootScope and multiple children $scopes (so to speak), in your application. The best .NET analogy of a scope is something like a context, so $rootScope would map to an ApplicationContext (global with Singleton like behaivor) and $scope would map to something like a (if we had one) ControlllerContext. $scope is more of a context that you use to as an adhesive to bind you View to your Controller. So from homeController.js you see that we’ve defined a property named “title” with the value “ASP.NET”, let’s take a look at our partial Home.cshtml mark-up.

Northwind.Web/Views/Home/Home.cshtml


<div class="jumbotron">
   <h1>{{title}</h1>
   <p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS, and JavaScript.</p>
   <p><a href="http://asp.net" class="btn btn-primary btn-lg">Learn more &raquo;</a></p>
</div>
<div class="row">
   <div class="col-md-4">
       <h2>Getting started</h2>
       <p>
           ASP.NET Web API is a framework that makes it easy to build HTTP services that reach a broad range of clients, including browsers and mobile devices. ASP.NET Web API is an ideal platform for building RESTful applications on the .NET Framework.
       </p>
       <p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301870">Learn more &raquo;</a></p>
   </div>
   <div class="col-md-4">
       <h2>Get more libraries</h2>
       <p>NuGet is a free Visual Studio extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects.</p>
       <p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301871">Learn more &raquo;</a></p>
   </div>
   <div class="col-md-4">
       <h2>Web Hosting</h2>
       <p>You can easily find a web hosting company that offers the right mix of features and price for your applications.</p>
       <p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301872">Learn more &raquo;</a></p>
   </div>
</div>

Notice the {{title}} on line 2, between the h1 tags, when the View (Home.cshtml) loads, AngularJS will replace the h1 html with the $scope.title value: “ASP.NET”.

http://longle.azurewebsites.net

2014-05-01_0-04-13

Now that we have a primer to AngularJS’s MVVM pattern, let’s take a look at some bit more complex, the customerController.js which will display a Kendo UI Grid with the customizations which include a toolbar with buttons all wired up with Angular Kendo directives and AngularJS MVVM.

Northwind.Web/Scripts/app/controllers/customerController.js


'use strict';

northwindApp.controller('customerController',
    function ($scope, $rootScope, $location, customerDataSource)
    {
        customerDataSource.filter({}); // reset filter on dataSource everytime view is loaded

        var onClick = function (event, delegate)
        {
            var grid = event.grid;
            var selectedRow = grid.select();
            var dataItem = grid.dataItem(selectedRow);

            if (selectedRow.length &amp;gt; 0)
                delegate(grid, selectedRow, dataItem);
            else
                alert(&amp;quot;Please select a row.&amp;quot;);
        };

        $scope.isToolbarVisible = false;

        var toggleToolbar = function() {
            $scope.isToolbarVisible = !$scope.isToolbarVisible;
        }

        $scope.toolbarTemplate = kendo.template($(&amp;quot;#toolbar&amp;quot;).html());

        $scope.save = function (e)
        {
            onClick(e, function (grid)
            {
                grid.saveRow();
                toggleToolbar();
            });
        };

        $scope.cancel = function (e)
        {
            onClick(e, function (grid)
            {
                grid.cancelRow();
                toggleToolbar();
            });
        },

        $scope.details = function (e)
        {
            onClick(e, function (grid, row, dataItem)
            {
                $location.url('/customer/edit/' + dataItem.CustomerID);
            });
        },

        $scope.edit = function (e)
        {
            onClick(e, function (grid, row)
            {
                grid.editRow(row);
                toggleToolbar();
            });
        },

        $scope.destroy = function (e)
        {
            onClick(e, function (grid, row, dataItem)
            {
                grid.dataSource.remove(dataItem);
                grid.dataSource.sync();
            });
        },

        $scope.onChange = function (e)
        {
            var grid = e.sender;

            $rootScope.lastSelectedDataItem = grid.dataItem(grid.select());
        },

        $scope.dataSource = customerDataSource;

        $scope.onDataBound = function (e)
        {
            // check if there was a row that was selected
            if ($rootScope.lastSelectedDataItem == null)
                return;

            var view = this.dataSource.view(); // get all the rows

            for (var i = 0; i &amp;lt; view.length; i++)
            {
                // iterate through rows
                if (view[i].CustomerID == $rootScope.lastSelectedDataItem.CustomerID)
                {
                    // find row with the lastSelectedProductd
                    var grid = e.sender; // get the grid

                    grid.select(grid.table.find(&amp;quot;tr[data-uid='&amp;quot; + view[i].uid + &amp;quot;']&amp;quot;)); // set the selected row
                    break;
                }
            }
        };
    });

We declare a Controller by using the “controller” method, the first parameter is the name of our Controller, the second parameter takes a function who’s parameters are all the modules we want our Controller to be injected with. Our Controller is being injected with the following.

  • $scope, which we already know about
  • $rootScope, so that we can store some things to help us manage our View state, in this case we will store what was the last selected row on our Grid so that when the user navigates back to this view we can re-select the same row and highlight it.
  • $location, so that we can navigate from this View to other Views e.g. customer edit view which will have a form so we can make changes to a Customer.
  • customerDataSource, which is a reusable AngularJS service which returns a Kendo UI DataSource.

Notice how we are leveraging MVVM to manipulate the DOM to toggle the tool bar above the grid with the “ng-show” directive vs. manipulating the DOM with jQuery, this is key with AngularJS design principles.

As for the rest of the code in CustomerController.js it’s fairly straight forward, for me details on what each of these methods responsibilities are please have a quick read up here: http://blog.longle.net/2013/06/18/mvc-4-web-api-odata-entity-framework-kendo-ui-grid-datasource-with-mvvm.

Northwind.Web/Scripts/app/services/customerDataSource.js


'use strict';

northwindApp.factory('customerDataSource',
    function (customerModel)
    {
        var crudServiceBaseUrl = "/odata/Customer";

        return new kendo.data.DataSource({
            type: "odata",
            transport: {
                read: {
                    async: false,
                    url: crudServiceBaseUrl,
                    dataType: "json"
                },
                update: {
                    url: function (data)
                    {
                        return crudServiceBaseUrl + "(" + data.CustomerID + ")";
                    },
                    type: "put",
                    dataType: "json"
                },
                destroy: {
                    url: function (data)
                    {
                        return crudServiceBaseUrl + "(" + data.CustomerID + ")";
                    },
                    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"]; },
                model: customerModel
            },
            error: function (e)
            {
                alert(e.xhr.responseText);
            }
        });
    });

We create the customerDataSource as a reusable AngularJS service using the “factory” method. The first parameter is the name of our service and the second parameter is method who’s parameters indicates that we are injected with customModel.

Northwind.Web/Scripts/app/services/customerModel.js


'use strict';

northwindApp.factory('customerModel', function ()
{
    return kendo.data.Model.define({
        id: "CustomerID",
        fields: {
            CustomerID: { type: "string", editable: false, nullable: false },
            CompanyName: { title: "Company", type: "string" },
            ContactName: { title: "Contact", type: "string" },
            ContactTitle: { title: "Title", type: "string" },
            Address: { type: "string" },
            City: { type: "string" },
            PostalCode: { type: "string" },
            Country: { type: "string" },
            Phone: { type: "string" },
            Fax: { type: "string" },
            State: { type: "string" }
        }
    });
});

Again, we create a reusable AngularJS service which returns a Kendo UI Customer model. This reusable Cusomer model will be used for our Grid and Controllers.

Northwind.Web/Customer/Index.cshtml


@{
   ViewBag.Title = "Index";
}

<div>
   <h2 ng-cloak>{{title}}</h2>
   <div>
       <div class="demo-section">
           <div class="k-content" style="width: 100%">
               <div kendo-grid="grid"
                    k-sortable="true"
                    k-pageable="true"
                    k-filterable="true"
                    k-editable="'inline'"
                    k-selectable="true"
                    k-toolbar='[ { template: toolbarTemplate } ]'
                    k-columns='[
                       { field: "CustomerID", title: "ID", width: "75px" },
                       { field: "CompanyName", title: "Company"},
                       { field: "ContactName", title: "Contact" },
                       { field: "ContactTitle", title: "Title" },
                       { field: "Address" },
                       { field: "City" },
                       { field: "PostalCode" },
                       { field: "Country" },
                       { field: "Phone" },
                       { field: "Fax" }]'
                    k-data-source="dataSource"
                    k-on-data-bound="onDataBound(kendoEvent)"
                    k-on-change="onChange(kendoEvent)">
               </div>
               <style scoped>
                   .toolbar { padding: 15px; float: right; }
               </style>
           </div>
       </div>

       <script type="text/x-kendo-template" id="toolbar">
           <div>
               <div class="toolbar">
                   <button kendo-button ng-click="edit(this)"><span class="k-icon k-i-tick"></span>Edit</button>
                   <button kendo-button ng-click="destroy(this)"><span class="k-icon k-i-tick"></span>Delete</button>
                   <button kendo-button ng-click="details(this)"><span class="k-icon k-i-tick"></span>Edit Details</button>
               </div>
               <div class="toolbar" style="display:none">
                   <button kendo-button ng-click="save(this)"><span class="k-icon k-i-tick"></span>Save</button>
                   <button kendo-button ng-click="cancel(this)"><span class="k-icon k-i-tick"></span>Cancel</button>
               </div>
           </div>
       </script>
   </div>
</div>

Here we can see that we that the declarative syntax for Kendo UI widgets have changed a bit, and at a first glance it seems that it’s dramatic change, but as they say, there’s a method to the madness. If you understand the convention here, it’s pretty simple, take look at the cheat sheet we have below for our view.

Customer.cshtml Cheat Sheet

Before (with Kendo UI) After (with Angular Kendo)
data-role=”grid” kendo-grid
data-sortable k-sortable
data-bind=”source: dataSource” k-data-source=”dataSource”
data-bind=”events: { change: onChange }” k-on-change=”onChange(kendoEvent)”

For declarative widgets, we simply prefix with “kendo-“, quick note, where the name has camel casing, you would simply separate it with dashes e.g. DropDownList -> k-drop-down-list.

  • Options for a widget are simply prefixed with a “k-“.
  • For all things that were bound with “data-bind”, simply become first class citizen attributes again following the dash-separted convention.
  • Same for events, however the only other item, is that you have to explicitly pass in a “kendoEvent” vs. before you didn’t.

If we run our application, we can see that everything renders and it’s business as usual.

http://longle.azurewebsites.net/#/customer

2014-05-01_0-44-42

Northwind.Web/Scripts/app/controllers/customerEditController.js


'use strict';

northwindApp.controller('customerEditController',
    function ($scope, $routeParams, $location, customerDataSource)
    {
        var customerId = $routeParams.id;

        customerDataSource.filter({ field: "CustomerID", operator: "eq", value: customerId });
        $scope.customer = customerDataSource.at(0);

        $scope.save = function ()
        {
            customerDataSource.view()[0].dirty = true;
            customerDataSource.sync();
            $location.url('/customer');
        };

        $scope.cancel = function ()
        {
            $location.url('/customer');
        }
    });

For the customerEditController.js, we are being injected with $scope, $routeParams, $location and customerDataSource, notice the reusability of our customerDataSource. We use the customerDataSource to hydrate our grid with a list of customers on our Northwind.Web.Views/Conrollers/Index.cshml view as well as our Northwind.Web/Views/Customers/Edit.cshtml view with a single customer.

On the Sever-Side of Things

In the beginning of the post, we mentioned leveraging the best of both worlds, we’ve seen how we done this on the client side of things with all the goodness of AngularJS and Kendo UI. AngularJS brings a nice fully fledged framework which forces us to using nice well defined patterns (MVC’ish & MVVM), which has a natural side effect of having an elegant architecture on the client side. So how do we make the most of ASP.NET MVC on the server side? Simple, we follow all the best practices as we’ve always been doing in the past.

Taking full advantage of MVC’s bundling and Minification

Northwind.Web/App_Start/BundleConfig.cs


using System.Web.Optimization;

namespace Northwind.Web
{
    public class BundleConfig
    {
        // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862
        public static void RegisterBundles(BundleCollection bundles)
        {
            //Scripts
            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-{version}.js",
                        "~/Scripts/jquery-migrate-{version}.js"));

            // Use the development version of Modernizr to develop with and learn from. Then, when you're ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
            bundles.Add(new ScriptBundle("~/bundles/modernizr").Include("~/Scripts/modernizr-*"));

            bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
                      "~/Scripts/bootstrap.js",
                      "~/Scripts/respond.js"));

            bundles.Add(new ScriptBundle("~/bundles/angular").Include(
                      "~/Scripts/angular.js",
                      "~/Scripts/angular-route.js",
                      "~/Scripts/angular-animate.js"));

            bundles.Add(new ScriptBundle("~/bundles/kendo").Include(
                "~/Scripts/kendo/2014.1.318/kendo.web.min.js",
                "~/Scripts/angular-kendo.js"));

            bundles.Add(new ScriptBundle("~/bundles/app").Include(
                      "~/Scripts/app/app.js",
                      "~/Scripts/app/services/customerModel.js",
                      "~/Scripts/app/services/customerDataSource.js",
                      "~/Scripts/app/controllers/homeController.js",
                      "~/Scripts/app/controllers/customerController.js",
                      "~/Scripts/app/controllers/customerEditController.js"));

            //Styles
            bundles.Add(new StyleBundle("~/Content/css").Include(
                      "~/Content/bootstrap.css",
                      "~/Content/site.css"));

            bundles.Add(new StyleBundle("~/Content/kendo").Include(
                      "~/Content/kendo/2014.1.318/kendo.common.min.css",
                      "~/Content/kendo/2014.1.318/kendo.bootstrap.min.css"));

            // Set EnableOptimizations to false for debugging. For more information, visit http://go.microsoft.com/fwlink/?LinkId=301862
            BundleTable.EnableOptimizations = false;
        }
    }
}


ASP.NET’s Layout.cshtml have always been great to work with, so we stick with it.


<!DOCTYPE html>
<html lang="en" ng-app="northwindApp">
<head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width" />
   <meta name="description" content="SPA Application" />

   <title>@ViewBag.Title</title>

   @Styles.Render("~/Content/css")
   @Styles.Render("~/Content/kendo")
   @RenderSection("Styles", required: false)
   @Scripts.Render("~/bundles/modernizr")
</head>
<body>
   <div class="navbar navbar-inverse navbar-fixed-top">
       <div class="container">
           <div class="navbar-header">
               <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                   <span class="icon-bar"></span>
                   <span class="icon-bar"></span>
                   <span class="icon-bar"></span>
               </button>
               <a href="#/home" class="navbar-brand">http://blog.longle.net</a>
           </div>
           <div class="navbar-collapse collapse">
               <ul class="nav navbar-nav">
                   <li><a href="#/home">Home</a></li>
                   <li><a href="#/customer">Customer</a></li>
                   <li><a href="#/help">API</a></li>
               </ul>
           </div>
       </div>
   </div>
   <div class="container body-content">
       @RenderBody()
       <footer>
           <p>Copyright &copy; @DateTime.Now.Year - SPA Application</p>
       </footer>
   </div>

   @Scripts.Render("~/bundles/jquery")
   @Scripts.Render("~/bundles/angular")
   @Scripts.Render("~/bundles/app")
   @Scripts.Render("~/bundles/kendo")
   @Scripts.Render("~/bundles/bootstrap")

   @RenderSection("Scripts", required: false)
</body>
</html>

Notice we are taking advantage of all of our bundling and minification.

By default, ASP.NET MVC loads Index.cshtml e.g. http://localhost:2569/Home, again, same here, we use Index.cshtml as our landing page as usual.

Northwind.Web/Vviews/Home/Index.cshml


@{
   ViewBag.Title = "SPA";
   ViewBag.Description = "SPA App";
}
<div ng-view></div>

Notice how we have very little mark-up here and this View is simply for us to server up all the HTML in our Northwind.Web/Views/Shared/_Layout.cshml, then the @RenderBody() will render Northwind.Web/Views/Home/Index.cshml which will have the “div” element where AngularJS will load our AngularJS Views into on the client side.

Northwind.Web/Controllers/CustomerController.cs


using System.Web.Mvc;

namespace Northwind.Web.Controllers
{
    public class CustomerController : Controller
    {
        // GET: Customer
        public ActionResult Index()
        {
            return PartialView();
        }

        public ActionResult Edit()
        {
            return PartialView();
        }
    }
}

In CustomerController.cs, we are returning PartialView’s vs. View’s, well because that’s what they really are now “PartialViews”, there simply chunks of HTML that AngularJS will remotely load into our div> element when swapping views.

Northwind.Web.Api.CustomerController.cs


public class CustomerController : ODataController
{
private readonly ICustomerService _customerService;
private readonly IUnitOfWorkAsync _unitOfWorkAsync;

public CustomerController(
    IUnitOfWorkAsync unitOfWorkAsync,
    ICustomerService customerService)
{
    _unitOfWorkAsync = unitOfWorkAsync;
    _customerService = customerService;
}

// GET: odata/Customers
[HttpGet]
[Queryable]
public IQueryable<Customer> GetCustomer()
{
    return _customerService.ODataQueryable();
}

// GET: odata/Customers(5)
[Queryable]
public SingleResult<Customer> GetCustomer([FromODataUri] string key)
{
    return SingleResult.Create(_customerService.ODataQueryable().Where(t => t.CustomerID == key));
}

// PUT: odata/Customers(5)
public async Task<IHttpActionResult> Put(string key, Customer customer)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    if (key != customer.CustomerID)
    {
        return BadRequest();
    }

    customer.ObjectState = ObjectState.Modified;
    _customerService.Update(customer);

    try
    {
        await _unitOfWorkAsync.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!CustomerExists(key))
        {
            return NotFound();
        }
        throw;
    }

    return Updated(customer);
}

// POST: odata/Customers
public async Task<IHttpActionResult> Post(Customer customer)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    customer.ObjectState = ObjectState.Added;
    _customerService.Insert(customer);

    try
    {
        await _unitOfWorkAsync.SaveChangesAsync();
    }
    catch (DbUpdateException)
    {
        if (CustomerExists(customer.CustomerID))
        {
            return Conflict();
        }
        throw;
    }

    return Created(customer);
}

//// PATCH: odata/Customers(5)
[AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> Patch([FromODataUri] string key, Delta<Customer> patch)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    Customer customer = await _customerService.FindAsync(key);

    if (customer == null)
    {
        return NotFound();
    }

    patch.Patch(customer);
    customer.ObjectState = ObjectState.Modified;

    try
    {
        await _unitOfWorkAsync.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!CustomerExists(key))
        {
            return NotFound();
        }
        throw;
    }

    return Updated(customer);
}

// DELETE: odata/Customers(5)
public async Task<IHttpActionResult> Delete(string key)
{
    Customer customer = await _customerService.FindAsync(key);

    if (customer == null)
    {
        return NotFound();
    }

    customer.ObjectState = ObjectState.Deleted;

    _customerService.Delete(customer);
    await _unitOfWorkAsync.SaveChangesAsync();

    return StatusCode(HttpStatusCode.NoContent);
}

// GET: odata/Customers(5)/CustomerDemographics
[Queryable]
public IQueryable<CustomerDemographic> GetCustomerDemographics([FromODataUri] string key)
{
    return
        _customerService.ODataQueryable()
            .Where(m => m.CustomerID == key)
            .SelectMany(m => m.CustomerDemographics);
}

// GET: odata/Customers(5)/Orders
[Queryable]
public IQueryable<Order> GetOrders([FromODataUri] string key)
{
    return _customerService.ODataQueryable().Where(m => m.CustomerID == key).SelectMany(m => m.Orders);
}

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        _unitOfWorkAsync.Dispose();
    }
    base.Dispose(disposing);
}

private bool CustomerExists(string key)
{
    return _customerService.Query(e => e.CustomerID == key).Select().Any();
}
}

This is fairly straight forward, and we used the Visual Studio 2013 Update 2 RC out of the box Controller scaffolding to generate most of this code. We then replaced the code that was directly using Entity Framework to use a Repository and Service pattern, for more information on the pattern used please have a quick read here: https://genericunitofworkandrepositories.codeplex.com.

Visual Studio 2013 with Update 2 RC Web API OData Scaffolding Template

2014-05-01_1-10-42

Animating our View Swapping

To polish our UX (user experience) a bit, were going to add some animation to how AngularJS swaps view in our “ng-view” div.

Northwind.Web/Content/Site.css


body {
    padding-top: 50px;
    padding-bottom: 20px;
}

/* Set padding to keep content from hitting the edges */
.body-content {
    padding-left: 15px;
    padding-right: 15px;
    position: relative;
}

/* Set width on the form input elements since they're 100% wide by default */
input,
select,
textarea {
    max-width: 280px;
}

[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
    display: none !important;
}

.ng-enter, .ng-leave {
    -webkit-transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) .5s;
    transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) .5s;
    display: block;
    width: 1170px;
    padding-left: 15px;
    padding-right: 15px;
    position: absolute;
    left: 0;
    right: 0;
}

.ng-enter {
    left: 100%;
}

    .ng-enter.ng-enter-active {
        left: 0;
    }

.ng-leave.ng-leave-active {
    left: -200%;
}

We’ve added some rudimentary animation with .ng-enter and .ng-leave, both which the cubic-brezier, to slide in our Views. This implementation was originally based off AngularJS original animation documentation for “ng-view”, please have a quick read up here: https://docs.angularjs.org/api/ngRoute/directive/ngView for more details.

There you have it AngularJS, Kendo UI with Angular Kendo and ASP.NET MVC 5, all harmoniously with the best of both worlds.

Happy Coding…! :)

Source code: http://goo.gl/f1esAf
Live demo: http://longle.azurewebsites.net

Stay tuned for integrating this with RequireJS for large scale apps!

Harness the Power of ASP.NET MVC, Web Api, OData, Kendo UI & RequireJS to Build an Easy & Maintainable SPA (for the .NET Developer) – Published

Apologize for the delay, the original article: “Harness the Power of ASP.NET MVC, Web Api, OData, Kendo UI & RequireJS to Build an Easy & Maintainable SPA (for the .NET Developer)” has now been published as “A .NET Developer Primer to Single-Page Applications (SPA)“.

Download: http://lelong37.files.wordpress.com/2014/03/msdn-mag-1403.pdf
Online: http://msdn.microsoft.com/en-us/magazine/dn605877.aspx
Source code: https://easyspa.codeplex.com
Live Demo: http://easyspa.azurewebsites.net/home/spa#/customer/index

3-4-2014 1-36-58 AM

Unit of Work & Repository Framework with ASP.NET MVC 5, Entity Framework 6 & Unity 3 (Quick-Start Video)

Update: 08/12/2014 – Please refer to https://genericunitofworkandrepositories.codeplex.com/documentation for latest documentation. There have been breaking changes released since this recording was published.

Update: 02/24/2014 – v3.2 released, improved API and reusable queries with the Object Query Pattern. Breaking change: Framework now ships returning all things TEntity or IEnumberable for compartmentalization, you will need to change the Repository.cs (see below, what methods signatures to change) if IQueryable is preferred over IEnumerable, IEnumerable is preferred as a best practice (http://genericunitofworkandrepositories.codeplex.com/documentation).

Update: 01/06/2014 – When viewing please configure your YouTube
player to 1080p for clear viewing of the coded demos.

https://genericunitofworkandrepositories.codeplex.com/documentation

Hope everyone had a wonderful New Years holiday, with the new year starting wanted to fulfill a high request, which was a quick start video on the Unit of Work and Repository Frameworks in ASP.NET MVC with Unity for IoC and DI.

This video will touch on the following topics:

  • Entity Framework Power Tools
  • Uow & Repo Framework
  • Generating EF Mappings and POCO’s
  • Upgrading the stack to EF 6.2
  • Basic Customer CRUD Use Case with MVC Scafolding Template w/ Async
  • Refactoring the CustomerController to use UoW & Repo Framework w/ Async
  • Why the ICustomerService Approach?
  • Why Commit Unit of Work Outside the Service vs. versa
  • Quick Examples with Eager Loading, Filter, and Sorting

Sample application download: https://skydrive.live.com/redir?resid=949A1C97C2A17906%216769
(Please enable Nuget Packages Restore at the Solution Level)

Questions and/or comments please tweet @LeLong37.

Note:

1. Although the quick start video, takes the approach of committing the unit of work outside the ICustomerService exampled, whether you choose to do this inside your services or manage this outside, there is no right or wrong, this is totally team preference.

2. Also all use cases demonstrated in the CustomerController could have been satisfied by using the UnitOfWork out of the box without the need to implement the ICustomerService, however as mentioned, this is the preferred best practice.

Using ICustomerService in CustomerController


[HttpPost]
[ValidateAntiForgeryToken]
public async Task&lt;ActionResult&gt; Edit([Bind(Include = &quot;CustomerID,CompanyName,ContactName,ContactTitle,Address,City,Region,PostalCode,Country,Phone,Fax&quot;)] Customer customer)
{
    if (ModelState.IsValid)
    {
        customer.ObjectState = ObjectState.Modified;
        _customerService.Update(customer);
        await _unitOfWork.SaveAsync();
        return RedirectToAction(&quot;Index&quot;);
    }
    return View(customer);
}

Without ICustomerService and using IUnitOfWork out of the box in the CustomerController


[HttpPost]
[ValidateAntiForgeryToken]
public async Task&lt;ActionResult&gt; Edit([Bind(Include = &quot;CustomerID,CompanyName,ContactName,ContactTitle,Address,City,Region,PostalCode,Country,Phone,Fax&quot;)] Customer customer)
{
    if (ModelState.IsValid)
    {
        customer.ObjectState = ObjectState.Modified;
        _unitOfWork.Repository&lt;Customer&gt;().Update(customer);
        await _unitOfWork.SaveAsync();
        return RedirectToAction(&quot;Index&quot;);
    }
    return View(customer);
}

In this case it is preferred that the IRepository return IEnumerable vs. IQueryable.

HAVING THE REPOSITORY RETURN ALL THINGS IEnumerable or IList is a best practice and preferred approach. Down the line, if you were to ever switch out your back-end, you won’t be bound to the requirements that the back-end implements IQueryable e.g. moving from EF to pure REST services.

Question? Why does the Repository in the framework return IQueryable?

Answer: Because most devs have been spoiled with ORM’s (e.g. nHibernate.Linq, Linq to Sql, Entity Framework) returning IQueryable and I received to many requests and complaints when the Repository layer in the Framework was returning IEnumerable or IList. Another caveat is that you can’t definitively tell what SQL queries are happening in your application by looking at the Repository layer, because you haven’t truly compartmentalized them in this layer; developers are more than able to modify the query plan before the query is actually executed.

For teams that prefer all things returned as IEnumerable from Repository layer, this would require three lines of code to be changed in the framework, here’s how:

  1. Repository.IRepositoryQuery.cs

    
    IEnumerable&lt;TEntity&gt; Get();
    
    
  2. Repository.RepositoryQuery.cs

    
            public IEnumerable&lt;TEntity&gt; Get()
            {
                return _repository.Get(_filter, _orderByQuerable, _includeProperties, _page, _pageSize);
            }
    
    
  3. Repository.Repository.cs

    
            internal IEnumerable&lt;TEntity&gt; Get(
                Expression&lt;Func&lt;TEntity, bool&gt;&gt; filter = null,
                Func&lt;IQueryable&lt;TEntity&gt;, IOrderedQueryable&lt;TEntity&gt;&gt; orderBy = null,
                List&lt;Expression&lt;Func&lt;TEntity, object&gt;&gt;&gt; includeProperties = null,
                int? page = null,
                int? pageSize = null)
            {
                IQueryable&lt;TEntity&gt; query = _dbSet;
    
                if (includeProperties != null)
                    includeProperties.ForEach(i =&gt; query = query.Include(i));
    
                if (filter != null)
                    query = query.Where(filter);
    
                if (orderBy != null)
                    query = orderBy(query);
    
                if (page != null &amp;&amp; pageSize != null)
                    query = query
                        .Skip((page.Value - 1) * pageSize.Value)
                        .Take(pageSize.Value);
    
                return query;
            }
    
    
    

Additional references: http://blog.longle.net/2013/05/11/genericizing-the-unit-of-work-pattern-repository-pattern-with-entity-framework-in-mvc/

Sample application download: https://skydrive.live.com/redir?resid=949A1C97C2A17906%216769

CodePlex download for the framework: https://genericunitofworkandrepositories.codeplex.com/

Happy coding…! :)

Upgrading to Async with Entity Framework, Web Api, OData AsyncEntitySetController, Kendo UI, Glimpse & Generic Unit of Work Repository Framework v2.0

Update [11/18/2013]: Added mocked DbContext and DbSet and example Unit Tests using the mocks, download v2.1 https://genericunitofworkandrepositories.codeplex.com.

Thanks to everyone for allowing us to give back to the .NET community, we released Generic Unit of Work and Repository Framework v1.0 for four weeks and received 655 downloads and 4121 views. This post will also serve as the documentation for release v2.0. Thanks to Ivan (@ifarkas) for helping out on the Async development, Ken for debugging the Unit of Work life cycle management for use in web applications with DI & IoC (specifically with Entlib Unity v3.0) and scaling the framework to handle Bounded DbContexts, and to the Glimpse Team (@nickswan ) for helping out on getting Glimpse MVC4 working with MVC5, and providing guidance on how to leverage Glimpse EF6 to view SQL queries from EF.

This will be part six 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

We’ll continue on from the most recent post in this series, you can do a quick review of it here http://blog.longle.net/2013/06/19/mvc-4-web-api-odata-ef-kendo-ui-binding-a-form-to-datasource-crud-with-mvvm-part. Now let’s get right into it, by first taking a look at what was all involved on the server side.

First off let’s take a quick look and the changes we made to our DbContextBase to support Async.

Repository.DbContextBase.cs

Before


    public class DbContextBase : DbContext, IDbContext
    {
        private readonly Guid _instanceId;

        public DbContextBase(string nameOrConnectionString) : base(nameOrConnectionString)
        {
            _instanceId = Guid.NewGuid();
        }

        public Guid InstanceId
        {
            get { return _instanceId; }
        }

        public void ApplyStateChanges()
        {
            foreach (var dbEntityEntry in ChangeTracker.Entries())
            {
                var entityState = dbEntityEntry.Entity as IObjectState;
                if (entityState == null)
                    throw new InvalidCastException(&quot;All entites must implement the IObjectState interface, &quot; +
                                                   &quot;this interface must be implemented so each entites state can explicitely determined when updating graphs.&quot;);

                dbEntityEntry.State = StateHelper.ConvertState(entityState.State);
            }
        }

        public new IDbSet&lt;T&gt; Set&lt;T&gt;() where T : class
        {
            return base.Set&lt;T&gt;();
        }

        protected override void OnModelCreating(DbModelBuilder builder)
        {
            builder.Conventions.Remove&lt;PluralizingTableNameConvention&gt;();
            base.OnModelCreating(builder);
        }

        public override int SaveChanges()
        {
            ApplyStateChanges();
            return base.SaveChanges();
        }
    }

After:


    public class DbContextBase : DbContext, IDbContext
    {
        private readonly Guid _instanceId;

        public DbContextBase(string nameOrConnectionString) : base(nameOrConnectionString)
        {
            _instanceId = Guid.NewGuid();
        }

        public Guid InstanceId
        {
            get { return _instanceId; }
        }

        public void ApplyStateChanges()
        {
            foreach (DbEntityEntry dbEntityEntry in ChangeTracker.Entries())
            {
                var entityState = dbEntityEntry.Entity as IObjectState;
                if (entityState == null)
                    throw new InvalidCastException(&quot;All entites must implement the IObjectState interface, &quot; +
                                                   &quot;this interface must be implemented so each entites state can explicitely determined when updating graphs.&quot;);

                dbEntityEntry.State = StateHelper.ConvertState(entityState.State);
            }
        }

        public new IDbSet&lt;T&gt; Set&lt;T&gt;() where T : class
        {
            return base.Set&lt;T&gt;();
        }

        public override int SaveChanges()
        {
            ApplyStateChanges();
            return base.SaveChanges();
        }

        public override Task&lt;int&gt; SaveChangesAsync()
        {
            ApplyStateChanges();
            return base.SaveChangesAsync();
        }

        public override Task&lt;int&gt; SaveChangesAsync(CancellationToken cancellationToken)
        {
            ApplyStateChanges();
            return base.SaveChangesAsync(cancellationToken);
        }

        protected override void OnModelCreating(DbModelBuilder builder)
        {
            builder.Conventions.Remove&lt;PluralizingTableNameConvention&gt;();
            base.OnModelCreating(builder);
        }
    }

All that was needed here was to expose all the DbContext Async save operations so that we could use with our IUnitOfWork implementation, and also not forgetting to invoke our ApplyStateChanges so that we are managing the different states each entity could have when dealing with graphs.

Next up, are the enhancements made to our Repository.cs, so that our generic repositories can leverage the Async goodness as well.

Repostiory.Repository.cs

Before:


 public class Repository&lt;TEntity&gt; : IRepository&lt;TEntity&gt; where TEntity : class
    {
        private readonly Guid _instanceId;
        internal IDbContext Context;
        internal IDbSet&lt;TEntity&gt; DbSet;

        public Repository(IDbContext context)
        {
            Context = context;
            DbSet = context.Set&lt;TEntity&gt;();
            _instanceId = Guid.NewGuid();
        }

        public Guid InstanceId
        {
            get { return _instanceId; }
        }

        public virtual TEntity FindById(object id)
        {
            return DbSet.Find(id);
        }

        public virtual void InsertGraph(TEntity entity)
        {
            DbSet.Add(entity);
        }

        public virtual void Update(TEntity entity)
        {
            DbSet.Attach(entity);
        }

        public virtual void Delete(object id)
        {
            var entity = DbSet.Find(id);
            ((IObjectState) entity).State = ObjectState.Deleted;
            Delete(entity);
        }

        public virtual void Delete(TEntity entity)
        {
            DbSet.Attach(entity);
            DbSet.Remove(entity);
        }

        public virtual void Insert(TEntity entity)
        {
            DbSet.Attach(entity);
        }

        public virtual IRepositoryQuery&lt;TEntity&gt; Query()
        {
            var repositoryGetFluentHelper = new RepositoryQuery&lt;TEntity&gt;(this);
            return repositoryGetFluentHelper;
        }

        internal IQueryable&lt;TEntity&gt; Get(
            Expression&lt;Func&lt;TEntity, bool&gt;&gt; filter = null,
            Func&lt;IQueryable&lt;TEntity&gt;, IOrderedQueryable&lt;TEntity&gt;&gt; orderBy = null,
            List&lt;Expression&lt;Func&lt;TEntity, object&gt;&gt;&gt; includeProperties = null,
            int? page = null,
            int? pageSize = null)
        {
            IQueryable&lt;TEntity&gt; query = DbSet;

            if (includeProperties != null)
                includeProperties.ForEach(i =&gt; query = query.Include(i));

            if (filter != null)
                query = query.Where(filter);

            if (orderBy != null)
                query = orderBy(query);

            if (page != null &amp;&amp; pageSize != null)
                query = query
                    .Skip((page.Value - 1)*pageSize.Value)
                    .Take(pageSize.Value);

            var results = query;

            return results;
        }
    }

After:


    public class Repository&lt;TEntity&gt; : IRepository&lt;TEntity&gt; where TEntity : class
    {
        private readonly Guid _instanceId;
        private readonly DbSet&lt;TEntity&gt; _dbSet;

        public Repository(IDbContext context)
        {
            _dbSet = context.Set&lt;TEntity&gt;();
            _instanceId = Guid.NewGuid();
        }

        public Guid InstanceId
        {
            get { return _instanceId; }
        }

        public virtual TEntity Find(params object[] keyValues)
        {
            return _dbSet.Find(keyValues);
        }

        public virtual async Task&lt;TEntity&gt; FindAsync(params object[] keyValues)
        {
            return await _dbSet.FindAsync(keyValues);
        }

        public virtual async Task&lt;TEntity&gt; FindAsync(CancellationToken cancellationToken, params object[] keyValues)
        {
            return await _dbSet.FindAsync(cancellationToken, keyValues);
        }

        public virtual IQueryable&lt;TEntity&gt; SqlQuery(string query, params object[] parameters)
        {
            return _dbSet.SqlQuery(query, parameters).AsQueryable();
        }

        public virtual void InsertGraph(TEntity entity)
        {
            _dbSet.Add(entity);
        }

        public virtual void Update(TEntity entity)
        {
            _dbSet.Attach(entity);
            ((IObjectState)entity).State = ObjectState.Modified;
        }

        public virtual void Delete(object id)
        {
            var entity = _dbSet.Find(id);
            Delete(entity);
        }

        public virtual void Delete(TEntity entity)
        {
            _dbSet.Attach(entity);
            ((IObjectState)entity).State = ObjectState.Deleted;
            _dbSet.Remove(entity);
        }

        public virtual void Insert(TEntity entity)
        {
            _dbSet.Attach(entity);
            ((IObjectState)entity).State = ObjectState.Added;
        }

        public virtual IRepositoryQuery&lt;TEntity&gt; Query()
        {
            var repositoryGetFluentHelper = new RepositoryQuery&lt;TEntity&gt;(this);
            return repositoryGetFluentHelper;
        }

        internal IQueryable&lt;TEntity&gt; Get(
            Expression&lt;Func&lt;TEntity, bool&gt;&gt; filter = null,
            Func&lt;IQueryable&lt;TEntity&gt;, IOrderedQueryable&lt;TEntity&gt;&gt; orderBy = null,
            List&lt;Expression&lt;Func&lt;TEntity, object&gt;&gt;&gt; includeProperties = null,
            int? page = null,
            int? pageSize = null)
        {
            IQueryable&lt;TEntity&gt; query = _dbSet;

            if (includeProperties != null)
            {
                includeProperties.ForEach(i =&gt; query = query.Include(i));
            }

            if (filter != null)
            {
                query = query.Where(filter);
            }

            if (orderBy != null)
            {
                query = orderBy(query);
            }

            if (page != null &amp;&amp; pageSize != null)
            {
                query = query
                    .Skip((page.Value - 1)*pageSize.Value)
                    .Take(pageSize.Value);
            }
            return query;
        }

        internal async Task&lt;IEnumerable&lt;TEntity&gt;&gt; GetAsync(
                    Expression&lt;Func&lt;TEntity, bool&gt;&gt; filter = null,
                    Func&lt;IQueryable&lt;TEntity&gt;, IOrderedQueryable&lt;TEntity&gt;&gt; orderBy = null,
                    List&lt;Expression&lt;Func&lt;TEntity, object&gt;&gt;&gt; includeProperties = null,
                    int? page = null,
                    int? pageSize = null)
        {
            return Get(filter, orderBy, includeProperties, page, pageSize).AsEnumerable();
        }
    }

Here we’ve exposed the FindAsync methods from DbSet, so our Repositories can make use of them, and we’ve also wrapped implemented an Async implementation of our Get() method so that we can use it in our new Web Api ProductController.cs later.

Important note: here is that although our method is named GetAsync, it is not truly performing an Async interaction, this is due to the fact that if we were to use ToListAsync(), we would already executed the the query prior to OData applying it’s criteria to the execution plan e.g. if the OData query was requesting 10 records for page 2 of a grid from a Products table that had 1000 rows in it, ToListAsync() would have actually pulled a 1000 records from SQL to the web server and at that time do a skip 10 and take 20 from the collection of Products with 1000 objects. What we want is for this to happen on the SQL Server, meaning, SQL query the Products table, skip the first 10, and take next 10 records and only send those 10 records over to the web server, which will eventually surface into the Grid in the user’s browsers. Hence we are favoring payload size (true SQL Server side paging) going over the wire, vs. a true Async call to SQL.

Northwind.Web.Areas.Spa.Api.ProductController.cs

Before:


    public class ProductController : EntitySetController&lt;Product, int&gt;
    {
        private readonly IUnitOfWork _unitOfWork;

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

        public override IQueryable&lt;Product&gt; Get()
        {
            return _unitOfWork.Repository&lt;Product&gt;().Query().Get();
        }

        protected override Product GetEntityByKey(int key)
        {
            return _unitOfWork.Repository&lt;Product&gt;().FindById(key);
        }

        protected override Product UpdateEntity(int key, Product update)
        {
            update.State = ObjectState.Modified;
            _unitOfWork.Repository&lt;Product&gt;().Update(update);
            _unitOfWork.Save();

            return update;
        }

        public override void Delete([FromODataUri] int key)
        {
            _unitOfWork.Repository&lt;Product&gt;().Delete(key);
            _unitOfWork.Save();
        }

        protected override void Dispose(bool disposing)
        {
            _unitOfWork.Dispose();
            base.Dispose(disposing);
        }
    }
 

After:

Note: Don’t be overwhelmed by how much more code there is in the “After” for our new ProductController that now inherits AsyncEntitySetController. I’ll explain later, what all the other Actions are there for. For now, please keep in mind there are only a few of these Actions that are actually required for the use case on the live demo site. The only Actions (methods) that are needed for our use case are as follows:

  • Task<IEnumerable> Get()
  • Task Get([FromODataUri] int key)
  • Task UpdateEntityAsync(int key, Product update)
  • Task Delete([FromODataUri] int key)

[ODataNullValue]
public class ProductController : AsyncEntitySetController&lt;Product, int&gt;
{
    private readonly IUnitOfWork _unitOfWork;

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

    protected override void Dispose(bool disposing)
    {
        _unitOfWork.Dispose();
        base.Dispose(disposing);
    }

    protected override int GetKey(Product entity)
    {
        return entity.ProductID;
    }

[Queryable]
public override async Task&lt;IEnumerable&lt;Product&gt;&gt; Get()
{
return await _unitOfWork.Repository&lt;Product&gt;().Query().GetAsync();
}

    [Queryable]
    public override async Task&lt;HttpResponseMessage&gt; Get([FromODataUri] int key)
    {
        var query = _unitOfWork.Repository&lt;Product&gt;().Query().Filter(x =&gt; x.ProductID == key).Get();
        return Request.CreateResponse(HttpStatusCode.OK, query);
    }

    ///// &lt;summary&gt;
    ///// Retrieve an entity by key from the entity set.
    ///// &lt;/summary&gt;
    ///// &lt;param name=&quot;key&quot;&gt;The entity key of the entity to retrieve.&lt;/param&gt;
    ///// &lt;returns&gt;A Task that contains the retrieved entity when it completes, or null if an entity with the specified entity key cannot be found in the entity set.&lt;/returns&gt;
    [Queryable]
    protected override async Task&lt;Product&gt; GetEntityByKeyAsync(int key)
    {
        return await _unitOfWork.Repository&lt;Product&gt;().FindAsync(key);
    }

    protected override async Task&lt;Product&gt; CreateEntityAsync(Product entity)
    {
        if (entity == null)
            throw new HttpResponseException(HttpStatusCode.BadRequest);
            
        _unitOfWork.Repository&lt;Product&gt;().Insert(entity);
        await _unitOfWork.SaveAsync();
        return entity;
    }

    protected override async Task&lt;Product&gt; UpdateEntityAsync(int key, Product update)
    {
        if (update == null)
            throw new HttpResponseException(HttpStatusCode.BadRequest);
            
        if (key != update.ProductID)
            throw new HttpResponseException(Request.CreateODataErrorResponse(HttpStatusCode.BadRequest, new ODataError { Message = &quot;The supplied key and the Product being updated do not match.&quot; }));

        try
        {
            update.State = ObjectState.Modified;
            _unitOfWork.Repository&lt;Product&gt;().Update(update);
            var x = await _unitOfWork.SaveAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            throw new HttpResponseException(HttpStatusCode.BadRequest);
        }
        return update;
    }

    // PATCH &lt;controller&gt;(key)
    /// &lt;summary&gt;
    /// Apply a partial update to an existing entity in the entity set.
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;key&quot;&gt;The entity key of the entity to update.&lt;/param&gt;
    /// &lt;param name=&quot;patch&quot;&gt;The patch representing the partial update.&lt;/param&gt;
    /// &lt;returns&gt;A Task that contains the updated entity when it completes.&lt;/returns&gt;
    protected override async Task&lt;Product&gt; PatchEntityAsync(int key, Delta&lt;Product&gt; patch)
    {
        if (patch == null)
            throw new HttpResponseException(HttpStatusCode.BadRequest);

        if (key != patch.GetEntity().ProductID)
            throw Request.EntityNotFound();

        var entity = await _unitOfWork.Repository&lt;Product&gt;().FindAsync(key);

        if (entity == null)
            throw Request.EntityNotFound();

        try
        {
            patch.Patch(entity);
            await _unitOfWork.SaveAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            throw new HttpResponseException(HttpStatusCode.Conflict);
        }
        return entity;
    }

    public override async Task Delete([FromODataUri] int key)
    {
        var entity = await _unitOfWork.Repository&lt;Product&gt;().FindAsync(key);

        if (entity == null)
            throw Request.EntityNotFound();

        _unitOfWork.Repository&lt;Product&gt;().Delete(entity);

        try
        {
            await _unitOfWork.SaveAsync();
        }
        catch (Exception e)
        { 
            throw new HttpResponseException(
                new HttpResponseMessage(HttpStatusCode.Conflict)
                {
                    StatusCode = HttpStatusCode.Conflict, 
                    Content = new StringContent(e.Message), 
                    ReasonPhrase = e.InnerException.InnerException.Message
                });
        }
    }

    #region Links
    // Create a relation from Product to Category or Supplier, by creating a $link entity.
    // POST &lt;controller&gt;(key)/$links/Category
    // POST &lt;controller&gt;(key)/$links/Supplier
    /// &lt;summary&gt;
    /// Handle POST and PUT requests that attempt to create a link between two entities.
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;key&quot;&gt;The key of the entity with the navigation property.&lt;/param&gt;
    /// &lt;param name=&quot;navigationProperty&quot;&gt;The name of the navigation property.&lt;/param&gt;
    /// &lt;param name=&quot;link&quot;&gt;The URI of the entity to link.&lt;/param&gt;
    /// &lt;returns&gt;A Task that completes when the link has been successfully created.&lt;/returns&gt;
    [AcceptVerbs(&quot;POST&quot;, &quot;PUT&quot;)]
    public override async Task CreateLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
    {
        var entity = await _unitOfWork.Repository&lt;Product&gt;().FindAsync(key);

        if (entity == null)
            throw Request.EntityNotFound();
            
        switch (navigationProperty)
        {
            case &quot;Category&quot;:
                var categoryKey = Request.GetKeyValue&lt;int&gt;(link);
                var category = await _unitOfWork.Repository&lt;Category&gt;().FindAsync(categoryKey);

                if (category == null)
                    throw Request.EntityNotFound();
                    
                    entity.Category = category;
                break;

            case &quot;Supplier&quot;:
                var supplierKey = Request.GetKeyValue&lt;int&gt;(link);
                var supplier = await _unitOfWork.Repository&lt;Supplier&gt;().FindAsync(supplierKey);

                if (supplier == null)
                    throw Request.EntityNotFound();
                    
                    entity.Supplier = supplier;
                break;

            default:
                await base.CreateLink(key, navigationProperty, link);
                break;
        }
        await _unitOfWork.SaveAsync();
    }

    // Remove a relation, by deleting a $link entity
    // DELETE &lt;controller&gt;(key)/$links/Category
    // DELETE &lt;controller&gt;(key)/$links/Supplier
    /// &lt;summary&gt;
    /// Handle DELETE requests that attempt to break a relationship between two entities.
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;key&quot;&gt;The key of the entity with the navigation property.&lt;/param&gt;
    /// &lt;param name=&quot;relatedKey&quot;&gt;The key of the related entity.&lt;/param&gt;
    /// &lt;param name=&quot;navigationProperty&quot;&gt;The name of the navigation property.&lt;/param&gt;
    /// &lt;returns&gt;Task.&lt;/returns&gt;
    public override async Task DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty)
    {
        var entity = await _unitOfWork.Repository&lt;Product&gt;().FindAsync(key);

        if (entity == null)
            throw Request.EntityNotFound();

        switch (navigationProperty)
        {
            case &quot;Category&quot;:
                entity.Category = null;
                break;

            case &quot;Supplier&quot;:
                entity.Supplier = null;
                break;

            default:
                await base.DeleteLink(key, relatedKey, navigationProperty);
                break;
        }

        await _unitOfWork.SaveAsync();
    }

    // Remove a relation, by deleting a $link entity
    // DELETE &lt;controller&gt;(key)/$links/Category
    // DELETE &lt;controller&gt;(key)/$links/Supplier
    /// &lt;summary&gt;
    /// Handle DELETE requests that attempt to break a relationship between two entities.
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;key&quot;&gt;The key of the entity with the navigation property.&lt;/param&gt;
    /// &lt;param name=&quot;navigationProperty&quot;&gt;The name of the navigation property.&lt;/param&gt;
    /// &lt;param name=&quot;link&quot;&gt;The URI of the entity to remove from the navigation property.&lt;/param&gt;
    /// &lt;returns&gt;Task.&lt;/returns&gt;
    public override async Task DeleteLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
    {
        var entity = await _unitOfWork.Repository&lt;Product&gt;().FindAsync(key);

        if (entity == null)
            throw Request.EntityNotFound();

        switch (navigationProperty)
        {
            case &quot;Category&quot;:
                entity.Category = null;
                break;

            case &quot;Supplier&quot;:
                entity.Supplier = null;
                break;

            default:
                await base.DeleteLink(key, navigationProperty, link);
                break;
        }

        await _unitOfWork.SaveAsync();
    }
    #endregion Links

    public override async Task&lt;HttpResponseMessage&gt; HandleUnmappedRequest(ODataPath odataPath)
    {
        //TODO: add logic and proper return values
        return Request.CreateResponse(HttpStatusCode.NoContent, odataPath);
    }

    #region Navigation Properties
    public async Task&lt;Category&gt; GetCategory(int key)
    {
        var entity = await _unitOfWork.Repository&lt;Product&gt;().FindAsync(key);

        if (entity == null)
            throw Request.EntityNotFound();
            
        return entity.Category;
    }

    public async Task&lt;Supplier&gt; GetSupplier(int key)
    {
        var entity = await _unitOfWork.Repository&lt;Product&gt;().FindAsync(key);

        if (entity == null)
            throw Request.EntityNotFound();
            
        return entity.Supplier;
    }
    #endregion Navigation Properties
}

Quickly looking at this, one can realize there is a lot more code than our pre-Async implementation. Well don’t be alarmed, there’s a lot of code here that wasn’t required to support our use case in the live demo (http://longle.azurewebsites.net), however we wanted to take the extra step so that we can really grasp on how to work with entity graphs with OData by leveraging the ?$expand query string parameter.

We’ll leave all the other that Actions that aren’t actually required for our use case on the live demo SPA as is, so we can see how to deep load your entity graph with OData and Web Api. We’ve included some pre-baked clickable OData URL’s (queries) on the View so that you can actually click and see the response payload in your browser (you’ll have to use Chrome or Firefox, IE has some catching up to do here).

*Click on image
10-9-2013 8-51-43 PM

Now let’s do a deep dive on the our Async Get() Action in our Controller.


[Queryable]
 
public override async Task&lt;IEnumerable&lt;Product&gt;&gt; Get()
{
    return await _unitOfWork.Repository&lt;Product&gt;().Query().GetAsync();
}
 

My initial thought when seeing this this Action (signature) is that it’s not IQueryable?! Which means that the SQL plan from EF has already been executed before OData has an opportunity to apply it’s criteria to the query plan! Well that’s not the case, we outfitted the Project with Glimpse and Glimpse EF6 to actually see what SQL queries were being sent over the wire.

So let’s take a look at the loading up our Kendo UI Grid with the awesomeness of Glimpse running. Since our View is built with Kendo UI, and we know it’s invoking Ajax calls to request data, we’ll click on the Ajax panel on the Glimpse HUD.

*Click on image
10-9-2013 7-30-56 PM

Now with the HUD automatically switching to standard view we can see all the Ajax requests that our View made, we are interested in the OData request that was made to hydrate our Kendo Grid.

*Click on image
10-9-2013 8-04-32 PM

After clicking on Inspect for the Ajax OData request, we see that menu buttons buttons that have tracing data for that request start to actual blink…! One of them being SQL, so let’s click on it.

*Click on image
10-9-2013 8-32-10 PM

Ladies and gentlemen, I kid you not, behold this is the actual SQL query that was from our Unit Of Work -> Repostiory -> Entity Framework 6 -> T-SQL, that was actually sent to SQL Server (actually in our case SQL Server CE, so that the live demo can be complete free with Azure Website without the need to pay for SQL Azure). BTW, we just scratching the surface of what Glimpse can do, the list is pretty much endless e.g. displays MVC Routes, Actions, Tracing, Environment Variables, MVC Views, and performance metrics for pretty much all of them, etc.

Now back to the topic at hand, we can definitively see that although our Action and our Repository are returning IEnumerable:

Get Action the Kendo UI Datasource is calling, which returns IEnumerable.


[Queryable] 
 
public override async Task&lt;IEnumerable&lt;Product&gt;&gt; Get()
{
    return await _unitOfWork.Repository&lt;Product&gt;().Query().GetAsync();
}
 

Repository method the Action is calling, which also returns IEnumerable.


        internal async Task&lt;IEnumerable&lt;TEntity&gt;&gt; GetAsync(
                    Expression&lt;Func&lt;TEntity, bool&gt;&gt; filter = null,
                    Func&lt;IQueryable&lt;TEntity&gt;, IOrderedQueryable&lt;TEntity&gt;&gt; orderBy = null,
                    List&lt;Expression&lt;Func&lt;TEntity, object&gt;&gt;&gt; includeProperties = null,
                    int? page = null,
                    int? pageSize = null)
        {
            return Get(filter, orderBy, includeProperties, page, pageSize).AsEnumerable();
        }
 

The query plan is still valid, meaning it’s selecting only the rows (10 records to be exact) that the Grid is requesting for page one (1) of the Grid. So how is this happening? Well we’ve decorated our action with the [Queryable] attribute, so OData and Web Api is able to perform it’s magic together during run-time in the ASP.NET HTTP pipeline.

T-SQL that’s being sent over the wire, courtesy of Glimpse EF6

 
 
SELECT TOP (10 /* @p__linq__0 */) 
    [Extent1].[Product ID] AS [Product ID], 
    [Extent1].[Product Name] AS [Product Name], 
    [Extent1].[Supplier ID] AS [Supplier ID], 
    [Extent1].[Category ID] AS [Category ID], 
    [Extent1].[Quantity Per Unit] AS [Quantity Per Unit], 
    [Extent1].[Unit Price] AS [Unit Price], 
    [Extent1].[Units In Stock] AS [Units In Stock], 
    [Extent1].[Units On Order] AS [Units On Order], 
    [Extent1].[Reorder Level] AS [Reorder Level], 
    [Extent1].[Discontinued] AS [Discontinued]
    FROM [Products] AS [Extent1]
    ORDER BY [Extent1].[Product ID] ASC
 

Now, let’s cover at a high-level on all the Actions that aren’t required for our live demo use case, which are mostly to support Navigation Properties e.g. Product.Supplier, Product.Category, etc.

The $expand query string parameter allows us to hydrate complex navigation property types. For example in our case when we query for a Product, and Product has a property of Category and we the Category to be hydrated with its data we would leverage the $expand querystring parameter to do this, click this Url : http://longle.azurewebsites.net/odata/Product/?$inlinecount=allpages&$orderby=ProductName&$skip=1&$top=2&$expand=Category&$select=ProductID,ProductName,Category/CategoryID,Category/CategoryName to see the $expand in action.

T-SQL that’s being sent over the wire, again, courtesy of Glimpse EF6

 
 
SELECT TOP (2 /* @p__linq__1 */) 
    [top].[Product ID] AS [Product ID], 
    [top].[C1] AS [C1], 
    [top].[C2] AS [C2], 
    [top].[Product Name] AS [Product Name], 
    [top].[C3] AS [C3], 
    [top].[C4] AS [C4], 
    [top].[C5] AS [C5], 
    [top].[C6] AS [C6], 
    [top].[Category Name] AS [Category Name], 
    [top].[C7] AS [C7], 
    [top].[Category ID] AS [Category ID], 
    [top].[C8] AS [C8]
    FROM ( SELECT [Project1].[Product ID] AS [Product ID], [Project1].[Product Name] AS [Product Name], [Project1].[Category ID] AS [Category ID], [Project1].[Category Name] AS [Category Name], [Project1].[C1] AS [C1], [Project1].[C2] AS [C2], [Project1].[C3] AS [C3], [Project1].[C4] AS [C4], [Project1].[C5] AS [C5], [Project1].[C6] AS [C6], [Project1].[C7] AS [C7], [Project1].[C8] AS [C8]
        FROM ( SELECT 
            [Extent1].[Product ID] AS [Product ID], 
            [Extent1].[Product Name] AS [Product Name], 
            [Extent1].[Category ID] AS [Category ID], 
            [Extent2].[Category Name] AS [Category Name], 
            N'ace5ad31-e3e9-4cde-9bb8-d75fced846fa' AS [C1], 
            N'ProductName' AS [C2], 
            N'ProductID' AS [C3], 
            N'Category' AS [C4], 
            N'ace5ad31-e3e9-4cde-9bb8-d75fced846fa' AS [C5], 
            N'CategoryName' AS [C6], 
            N'CategoryID' AS [C7], 
            CASE WHEN ([Extent1].[Category ID] IS NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C8]
            FROM  [Products] AS [Extent1]
            LEFT OUTER JOIN [Categories] AS [Extent2] ON [Extent1].[Category ID] = [Extent2].[Category ID]
        )  AS [Project1]
        ORDER BY [Project1].[Product Name] ASC, [Project1].[Product ID] ASC
        OFFSET 1 /* @p__linq__0 */ ROWS 
    )  AS [top]
 

Product results with Categories hydrated

 
  
{
  &quot;odata.metadata&quot;:&quot;http://longle.azurewebsites.net/odata/$metadata#Product&amp;$select=ProductID,ProductName,Category/CategoryID,Category/CategoryName&quot;,&quot;odata.count&quot;:&quot;77&quot;,&quot;value&quot;:[
    {
      &quot;Category&quot;:{
        &quot;CategoryID&quot;:2,&quot;CategoryName&quot;:&quot;Condiments&quot;
      },&quot;ProductID&quot;:3,&quot;ProductName&quot;:&quot;Aniseed Syrup&quot;
    },{
      &quot;Category&quot;:{
        &quot;CategoryID&quot;:8,&quot;CategoryName&quot;:&quot;Seafood&quot;
      },&quot;ProductID&quot;:40,&quot;ProductName&quot;:&quot;Boston Crab Meat&quot;
    }
  ]
} 
 

We can really see the power of Web Api and OData now, we’re actually able to query for Products (skip the first and take the next two) and request that Category be hydrated but specifically only the CategoryId and Name and none of the other fields.

Sample Application Client Side (Kendo UI) Tweaks

We’ve polished the UI/UX a bit, relocated Edit, Edit Details, and Delete buttons out of the rows into the Grid Toolbar (header) to make better use of the Grid real estate, using Kendo’s Template Framework, which illustrates how flexible Kendo UI can be. The app has been upgraded to, Twitter Bootstrap as by leveraging the new out of the box MVC Project Templates in Visual Studio 2013 (Preview) and changing the Kendo UI theme to Bootstrap to match.

All Kendo Views which are remotely loaded on demand into the SPA are now actually MVC Razor Views, the Kendo Router remotely loads views by traditional MVC routes e.g.
{controller}/{action}/{id} vs. what was in the previous post (http://blog.longle.net/2013/06/17/mvc-4-kendo-ui-spa-with-layout-router-mvvm/) which was just serving up raw *.html pages. This has been a request for devs that are making the transition from server side MVC development into the SPA realm, and had .NET libraries they still wanted to make use of and leverage in their their Controllers, and Razor Views for SPA’s. Obviously, all Views and ViewModel binding on the client-side are done with with Kendo’s MVVM Framework.

Northwind.Web/Areas/Spa/Content/Views/products.html

Before (non Razor, just plain *.html pages were used for SPA):

 
@{
    ViewBag.Title = &quot;Products&quot;;
    Layout = &quot;&quot;;
}
&lt;div class=&quot;row&quot;&gt;
    &lt;div class=&quot;span5&quot;&gt;
        &lt;h2&gt;Technlogy Stack&lt;/h2&gt;
        &lt;h3&gt;&lt;a href=&quot;http://blog.longle.net&quot;&gt;blog.longle.net&lt;/a&gt;&lt;/h3&gt;
        &lt;p&gt;ASP.NET MVC 4, Web API, OData, Entity Framework 6 CTP, EntityFramework CE 6 RC1, Visual Studio 2013 Preview, Sql Server CE, Twitter Bootstrap, Kendo UI Web, Azure Website PaaS (&lt;a href=&quot;http://www.windowsazure.com/en-us/develop/net/aspnet/&quot; target=&quot;blank&quot;&gt;free!&lt;/a&gt;)&lt;/p&gt;
        &lt;br /&gt;
        &lt;p&gt;&lt;a class=&quot;btn&quot; href=&quot;http://go.microsoft.com/fwlink/?LinkId=301865&quot;&gt;Learn more &amp;raquo;&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;br /&gt;&lt;br /&gt;
&lt;div class=&quot;k-content&quot; style=&quot;width: 100%&quot;&gt;
    &lt;div id=&quot;view&quot;&gt;
        &lt;div id=&quot;productGrid&quot;
             data-role=&quot;grid&quot;
             data-sortable=&quot;true&quot;
             data-pageable=&quot;true&quot;
             data-filterable=&quot;true&quot;
             data-bind=&quot;source: dataSource, events: { dataBound: dataBound, change: onChange }&quot;
             data-editable=&quot;inline&quot;
             data-selectable=&quot;true&quot;
             data-toolbar='[ { template: $(&quot;#toolbar&quot;).html() } ]'
             data-columns='[
                    { field: &quot;ProductID&quot;, title: &quot;ID&quot;, width: &quot;50px&quot; },
                    { field: &quot;ProductName&quot;, title: &quot;Name&quot;},
                    { field: &quot;QuantityPerUnit&quot;, title: &quot;Quantity&quot;, width: &quot;200px&quot; },
                    { field: &quot;UnitsInStock&quot;, title: &quot;Stock&quot;, width: &quot;90px&quot; },
                    { field: &quot;UnitPrice&quot;, title: &quot;Price&quot;, format: &quot;{0:c}&quot;, width: &quot;100px&quot; },
                    { field: &quot;Discontinued&quot;, width: &quot;150px&quot; } ]'&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;h3&gt;Use Chrome or Firefox and click on OData (queries) Urls below for example results.&lt;/h3&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;a href=&quot;/odata/$metadata&quot;&gt;/odata/$metadata&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/odata/Product&quot;&gt;/odata/Product&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/odata/Product/?$select=ProductID,ProductName&quot;&gt;/odata/Product/?$select=ProductID,ProductName&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/odata/Product/?$orderby=ProductName&amp;$skip=1&amp;$top=2&quot;&gt;/odata/Product/?$orderby=ProductName&amp;$skip=1&amp;$top=2&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/odata/Product/?$orderby=ProductName&amp;$skip=1&amp;$top=2&quot;&gt;/odata/Product/?$orderby=ProductName&amp;$skip=1&amp;$top=2&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/odata/Product/?$inlinecount=allpages&amp;$filter=UnitPrice ge 20&quot;&gt;/odata/Product/?$inlinecount=allpages&amp;$filter=UnitPrice ge 20&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/odata/Product/?$expand=Category&quot;&gt;/odata/Product/?$expand=Category&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/odata/Product/?$expand=Category&amp;$select=ProductID,ProductName,Category/CategoryID,Category/CategoryName&quot;&gt;/odata/Product/?$expand=Category&amp;$select=ProductID,ProductName,Category/CategoryID,Category/CategoryName&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/odata/Product/?$inlinecount=allpages&amp;$orderby=ProductName&amp;$skip=1&amp;$top=2&amp;$expand=Category&amp;$select=ProductID,ProductName,Category/CategoryID,Category/CategoryName&quot;&gt;/odata/Product/?$inlinecount=allpages&amp;$orderby=ProductName&amp;$skip=1&amp;$top=2&amp;$expand=Category&amp;$select=ProductID,ProductName,Category/CategoryID,Category/CategoryName&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;

&lt;script type=&quot;text/x-kendo-template&quot; id=&quot;toolbar&quot;&gt;
    &lt;div class=&quot;toolbar&quot;&gt;
        &lt;a class=&quot;k-button&quot; onclick=&quot;edit(event);&quot;&gt;&lt;span class=&quot;k-icon k-i-tick&quot;&gt;&lt;/span&gt;Edit&lt;/a&gt;
        &lt;a class=&quot;k-button&quot; onclick=&quot;destroy(event);&quot;&gt;&lt;span class=&quot;k-icon k-i-tick&quot;&gt;&lt;/span&gt;Delete&lt;/a&gt;
        &lt;a class=&quot;k-button&quot; onclick=&quot;details(event);&quot;&gt;&lt;span class=&quot;k-icon k-i-tick&quot;&gt;&lt;/span&gt;Edit Details&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;toolbar&quot; style=&quot;display:none&quot;&gt;
        &lt;a class=&quot;k-button&quot; onclick=&quot;save(event);&quot;&gt;&lt;span class=&quot;k-icon k-i-tick&quot;&gt;&lt;/span&gt;Save&lt;/a&gt;
        &lt;a class=&quot;k-button&quot; onclick=&quot;cancel(event);&quot;&gt;&lt;span class=&quot;k-icon k-i-tick&quot;&gt;&lt;/span&gt;Cancel&lt;/a&gt;
    &lt;/div&gt;
&lt;/script&gt;

&lt;script&gt;
    var lastSelectedDataItem;

    var save = function (event) {
        onClick(event, function (grid) {
            grid.saveRow();
            $(&quot;.toolbar&quot;).toggle();
        });
    };

    var cancel = function (event) {
        onClick(event, function (grid) {
            grid.cancelRow();
            $(&quot;.toolbar&quot;).toggle();
        });
    };

    var details = function (event) {
        onClick(event, function (grid, row, dataItem) {
            window.location.href = '#/edit/' + dataItem.ProductID;
        });
    };

    var edit = function (event) {
        onClick(event, function (grid, row) {
            grid.editRow(row);
            $(&quot;.toolbar&quot;).toggle();
        });
    };

    var destroy = function (event) {
        onClick(event, function (grid, row, dataItem) {
            grid.dataSource.remove(dataItem);
            grid.dataSource.sync();
        });
    };

    var onClick = function (event, delegate) {
        event.preventDefault();
        var grid = $(&quot;#productGrid&quot;).data(&quot;kendoGrid&quot;);
        var selectedRow = grid.select();
        var dataItem = grid.dataItem(selectedRow);
        if (selectedRow.length &gt; 0)
            delegate(grid, selectedRow, dataItem);
        else
            alert(&quot;Please select a row.&quot;);
    };

    var Product = kendo.data.Model.define({
        id: &quot;ProductID&quot;,
        fields: {
            ProductID: { type: &quot;number&quot;, editable: false, nullable: true },
            ProductName: { type: &quot;string&quot;, validation: { required: true } },
            QuantityPerUnit: { type: &quot;string&quot;, validation: { required: true } },
            UnitsInStock: { type: &quot;number&quot;, validation: { required: true } },
            UnitPrice: { type: &quot;number&quot;, validation: { required: true, min: 1 } },
            Discontinued: { type: &quot;boolean&quot; }
        }
    });

    var baseUrl = &quot;/odata/Product&quot;;

    var dataSource = new kendo.data.DataSource({
        type: &quot;odata&quot;,
        transport: {
            read: {
                url: baseUrl,
                dataType: &quot;json&quot;
            },
            update: {
                url: function (data) {
                    return baseUrl + &quot;(&quot; + data.ProductID + &quot;)&quot;;
                },
                dataType: &quot;json&quot;
            },
            destroy: {
                url: function (data) {
                    return baseUrl + &quot;(&quot; + data.ProductID + &quot;)&quot;;
                },
                dataType: &quot;json&quot;
            }
        },
        batch: false,
        serverPaging: true,
        serverSorting: true,
        serverFiltering: true,
        pageSize: 10,
        schema: {
            data: function (data) {
                return data.value;
            },
            total: function (data) {
                return data[&quot;odata.count&quot;];
            },
            errors: function (e) {
                return e.errors;
            },
            model: Product
        },
        error: function (e) {
            var responseJson = e.xhr.responseJSON;
            if (responseJson != undefined) {
                if (responseJson[&quot;odata.error&quot;] != undefined) {
                    var error = responseJson[&quot;odata.error&quot;];
                    var message = error.message.value + '\n\n' + error.innererror.message;
                    alert(message);
                }
            } else {
                alert(e.xhr.status + &quot;\n\n&quot; + e.xhr.responseText + &quot;\n\n&quot; + e.xhr.statusText);
            }
            this.read();
        }
    });

    var viewModel = kendo.observable({
        dataSource: dataSource,
        dataBound: function (arg) {
            if (lastSelectedDataItem == 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 &lt; view.length; i++) { // iterate through rows
                if (view[i].ProductID == lastSelectedDataItem.ProductID) { // find row with the lastSelectedProductd
                    var grid = arg.sender; // get the grid
                    grid.select(grid.table.find(&quot;tr[data-uid='&quot; + view[i].uid + &quot;']&quot;)); // set the selected row
                    break;
                }
            }
        },
        onChange: function (arg) {
            var grid = arg.sender;
            lastSelectedDataItem = grid.dataItem(grid.select());
        },
    });

    $(document).bind(&quot;viewSwtichedEvent&quot;, function (e, args) { // subscribe to the viewSwitchedEvent
        if (args.name == &quot;list&quot;) { // check if this view was switched too
            if (args.isRemotelyLoaded) { // check if this view was loaded for the first time (remotely from server)
                kendo.bind($(&quot;#view&quot;), viewModel); // bind the view to the model
            } else {// view already been loaded in cache
                viewModel.dataSource.read(); // refresh grid
            }
        }
    });

&lt;/script&gt;
&lt;style scoped&gt;
    #productGrid .k-toolbar {
        padding: .7em;
    }

    .toolbar {
        float: right;
    }
&lt;/style&gt;
 

10-9-2013 7-11-39 PM

Happy Coding…! :)

Live Demo: http://longle.azurewebsites.net
Download: https://genericunitofworkandrepositories.codeplex.com/

Bounded DbContext with Generic Unit of Work, Generic Repositories, Entity Framework 6 & EntLib Unity 3.0 in MVC 4

Update: 08/12/2013 – Changed InjectionConstructor parameter to: ResolvedParameter<IDbContext>(), to trigger compilation of the container when setting up the DbBounded Context and UnitOfWork(s) registrations.

Update: 08/08/2013 – Added PerRequestLifetimeManager() to the IUnitOfWork Unity Registration (binding) in UnityConfig.cs, so that the life-cycle of the UnitOfWork(s) instances being injected have singleton behavior within the scope of an Http request.

Update: 08/07/2013 – Ken from Microsoft has been kind enough to reach out and inform those of us that are using EF4 or EF5, that there maybe some potential collision issues, if there are entities with overlapping names, even if they live in different assemblies, please read below for a potential solution for this. If this does not apply to your use case or scenario, please continue on to the blog post after the block-quote.

At the risk of spamming your blog in comments I am turning to email. This is Ken the poster on your blog. J Your BoundedContext implementation has another interesting usage to easily support multiple DbContexts. Something that isn’t always that easy to do with a Repo + UoW frameworks. However, with EF5 and probably EF4 your readers will run into a bug if they have entities with overlapping names – EVEN IF they are separated by namespaces or live in different assemblies. For instance say you have two databases that both have a Logging table.

ExceptionMessage: The mapping of CLR type to EDM type is ambiguous because multiple CLR types match the EDM type ‘MyType’. Previously found CLR type ‘Namespace1.MyTable’, newly found CLR type ‘Namespace2.MyTable’. The mapping of CLR type to EDM type is ambiguous because multiple CLR types match the EDM type ‘ReferenceTable’. Previously found CLR type ‘Namespace1.ReferenceTable’, newly found CLR type ‘Namespace2.ReferenceTable’.”

The issue occurs with at EF5 unsure about EF4 but I suspect so. Read more here: http://entityframework.codeplex.com/workitem/911

The issue is resolved in EF6 beta1 from my testing.

Codewise this would be setup as follows

 

UnityConfig.cs
 
container.RegisterType(&quot;DbContext1&quot;);
container.RegisterType(&quot;DbContext2&quot;);
container.RegisterType(
    &quot;DbContext1UnitOfWork&quot;, new InjectionConstructor(container.Resolve(&quot;DbContext1&quot;)));
container.RegisterType(
    &quot;DbContext2UnitOfWork&quot;, new InjectionConstructor(container.Resolve(&quot;DbContext2&quot;)));
 
An Api Controller
 
public class SomethingFromDbContext1Controller : ApiController
    {
        private readonly IUnitOfWork _uow;
 
        public GenericRaptorTicketController(
            [Dependency(&quot;DbContext1UnitOfWork &quot;)] IUnitOfWork uow)
        {
            _uow = uow;
        }

Now all of the above logic in the controller goes to Database1 using the types specified by namespace (dealing with overlapping table names that resulted in POCO classes that had the same name, different namespace). Easily I could add a second, third, fourth controller and specify DbContext2UnitOfWork and point to a second database. Cool stuff. Your approach is creative and I am sharing it with my peers and customers.

Now if only I have find a T4 template to bend to my will to shape the Data Mappings and Entities. Simon Huge’s Reverse POCO template comes close with a few modifications. J

-Ken

So there was an interesting question that was raised over the weekend from Tim, on could we take our generic Unit of Work and Repositories and implement the Bounded DbContext Pattern or philosophy if you will from DDD (Domain Driven Design) concepts. There are a few reasons to go with this Pattern e.g organization, manageability, decoupling, performance (in some cases), maintainability, etc.

My favorite reason is when working with large databases and having functionality in your application that is only working with specific domain areas, why load up a DbContext that has the overhead of your entire entity graph when your only working with a specific few? For example, you may have a database that has close to 100 tables (e.g. AdventureWorks), however if a user is only managing Products on a screen, why load up a DbContext that has the overhead of the entire entity graph. Figuring out where to decouple and decompose your domain model, to implement the Bounded DbContext Pattern can span a wide array of reasons, those reasons could span from business to technical reasons, usually both.

As an example, the AdventureWorks database is already separated into domain SQL Schemas, each of the tables shown here are prefixed with the SQL Schema. This is somewhat of an example of which entities would be in a Bounded DbContext, a Bounded DbContext could be created for each of the SQL Schema’s, and each of the Bounded DbContext’s would have the tables as DbSet’s in them. Again, separating your domain into areas really depends on your use cases both business and technical, this is just an example of a starting point.

7-31-2013 2-52-45 PM

Example: Potential Bounded DbContext’s in AdventureWorks based on SQL schemas defined.

  • HumanDbcontext
  • PersonDbcontext
  • ProductionDbcontext
  • PurchasingDbcontext
  • SalesDbcontext

Anyhow, back to the topic at hand, with some minor changes, here’s how we can accomplish Bounded DbContext with our UnitOfWork and Generic Repositories, we’ll start off from our last post: Generically Implementing the Unit of Work & Repository Pattern with Entity Framework in MVC & Simplifying Entity Graphs. We are using the Northwind database as an example since this was used in the previous post, however with a database schema of this size, it’s probably not the ideal candidate for Bounded DbContext, you would probably implement this pattern on a database that had a much larger schema. But for the objective of this blog, Northwind will do. :)

Note: although we are using EF6 (alpha) in this example, we aren’t using any of EF6′s new features, however, it was a bit of a wiggle to get everything working. If you are attempting to get MVC, EF6 & SQL Sever CE 4.0 working, than this post and download maybe of use.

Data.NorthwindContext.cs – Before


    public class NorthwindContext : DbContext, IDbContext
    {
        static NorthwindContext()
        {
            Database.SetInitializer&lt;NorthwindContext&gt;(null);
        }

        public NorthwindContext()
            : base(&quot;Name=NorthwindContext&quot;)
        {
            Configuration.LazyLoadingEnabled = false;
        }

        public new IDbSet&lt;T&gt; Set&lt;T&gt;() where T : class
        {
            return base.Set&lt;T&gt;();
        }

        public override int SaveChanges()
        {
            this.ApplyStateChanges();
            return base.SaveChanges();
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new CategoryMap());
            modelBuilder.Configurations.Add(new CustomerDemographicMap());
            modelBuilder.Configurations.Add(new CustomerMap());
            modelBuilder.Configurations.Add(new EmployeeMap());
            modelBuilder.Configurations.Add(new Order_DetailMap());
            modelBuilder.Configurations.Add(new OrderMap());
            modelBuilder.Configurations.Add(new ProductMap());
            modelBuilder.Configurations.Add(new RegionMap());
            modelBuilder.Configurations.Add(new ShipperMap());
            modelBuilder.Configurations.Add(new SupplierMap());
            modelBuilder.Configurations.Add(new TerritoryMap());
            modelBuilder.Configurations.Add(new InvoiceMap());
        }
    }

After

Data.DbContextBase.cs

We’ll go ahead abstract out our DbContext into a base class, since we’ll have multiple Bounded DbContexts.


    public abstract DbContextBase : DbContext, IDbContext
    {
        public DbContextBase(string nameOrConnectionString) : 
            base(nameOrConnectionString)
        {
            Configuration.LazyLoadingEnabled = false;
        }

        public new IDbSet&lt;T&gt; Set&lt;T&gt;() where T : class
        {
            return base.Set&lt;T&gt;();
        }

        public override int SaveChanges()
        {
            this.ApplyStateChanges();
            return base.SaveChanges();
        }
    }

Data.NorthwindCustomerDataContext.cs
*Customer Bounded Context


    public class NorthwindCustomerContext : DbContextBase
    {
        static NorthwindCustomerContext()
        {
            Database.SetInitializer&lt;NorthwindCustomerContext&gt;(null);
        }

        public NorthwindCustomerContext()
            : base(&quot;Name=NorthwindContext&quot;)
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new CustomerDemographicMap());
            modelBuilder.Configurations.Add(new CustomerMap());
        }
    }

Data.NorthwindDataContext – Everything else, Bounded Context :p


    public class NorthwindContext : DbContextBase
    {
        static NorthwindContext()
        {
            Database.SetInitializer&lt;NorthwindCustomerContext&gt;(null);
        }

        public NorthwindContext()
            : base(&quot;Name=NorthwindContext&quot;)
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new CategoryMap());
            modelBuilder.Configurations.Add(new EmployeeMap());
            modelBuilder.Configurations.Add(new Order_DetailMap());
            modelBuilder.Configurations.Add(new OrderMap());
            modelBuilder.Configurations.Add(new ProductMap());
            modelBuilder.Configurations.Add(new RegionMap());
            modelBuilder.Configurations.Add(new ShipperMap());
            modelBuilder.Configurations.Add(new SupplierMap());
            modelBuilder.Configurations.Add(new TerritoryMap());
            modelBuilder.Configurations.Add(new InvoiceMap());
        }
    }

We’ll need the following EntLib Unity v3.0 NuGet Packages.

  • Unity v3.0
  • Unity bootstrapper for ASP.NET MVC v3.0
  • Unity bootstrapper for ASP.NET MVC Web API v3.0

7-30-2013 9-29-07 AM

Spa.App_Start.UnityConfig.csUnity Bindings Before


container.RegisterType&lt;IDbContext, NorthwindContext&gt;();
container.RegisterType&lt;IUnitOfWork, UnitOfWork&gt;();
 

Spa.App_Start.UnityConfig.cs – Unity Bindings After (with Registration Names)


        public static void RegisterTypes(IUnityContainer container)
        {
            container.RegisterType&lt;IDbContext, NorthwindContext&gt;(new PerRequestLifetimeManager(), &quot;NorthwindContext&quot;);
            container.RegisterType&lt;IDbContext, NorthwindCustomerContext&gt;(new PerRequestLifetimeManager(), &quot;NorthwindCustomerContext&quot;);
            
            container.RegisterType&lt;IUnitOfWork, UnitOfWork&gt;(
                &quot;NorthwindUnitOfWork&quot;, new InjectionConstructor(new ResolvedParameter&lt;IDbContext&gt;(&quot;NorthwindContext&quot;)));
            
            container.RegisterType&lt;IUnitOfWork, UnitOfWork&gt;(
                &quot;NorthwindCustomerUnitOfWork&quot;, new InjectionConstructor(new ResolvedParameter&lt;IDbContext&gt;(&quot;NorthwindCustomerContext&quot;)));
        }
 

When working with ASP.NET (web apps) remember to make sure you are making good use of the UnityPerRequestHttpModule (line 12, below) in your UnityWebActivator. This will default the lifetime of your instances to lifetime of the current HttpRequest. You can configure registrations and pass in a other specific lifetime manager’s for other registration configurations who’s life-cycle does not need to bound to the HttpRequest.

Spa.App_Start.UnityWebActivator.cs


    public static class UnityWebActivator
    {
        public static void Start() 
        {
            var container = UnityConfig.GetConfiguredContainer();

            FilterProviders.Providers.Remove(FilterProviders.Providers.OfType&lt;FilterAttributeFilterProvider&gt;().First());
            FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));

            DependencyResolver.SetResolver(new UnityDependencyResolver(container));

             DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
        }
    }
  

Now we could just instantiate and pass in the appropriate Bounded DbContext implementations into the UnitOfWork registrations, however we would defeat one of the fundamental reasons of DI & IoC to begin with e.g. when we write our unit test later, we aren’t going to be able to switch out DbContext with a mocked one, easily. We could even do this registration in the web.config to give us more flexibility in terms of swapping the implementations of our DbContext’s however for the purposes of this post, we’ll continue on pro-grammatically.

Spa.Controllers.CustomerController – Before

Well now, that we have Bounded DbContext and UnitOfworks, how do we get them? We have two options, first options which is leveraging DI & IoC with Unity 3.0, and the obvious method of instantiating them manually. We’ll demonstrate the first option below, in our CustomerController.


    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&lt;Customer&gt;()
                    .Query()
                    .OrderBy(q =&gt; q
                        .OrderBy(c =&gt; c.ContactName)
                        .ThenBy(c =&gt; c.CompanyName))
                    .Filter(q =&gt; q.ContactName != null)
                    .GetPage(pageNumber, pageSize, out totalCustomerCount);

            ViewBag.Customers = new StaticPagedList&lt;Customer&gt;(
                customers, pageNumber, pageSize, totalCustomerCount);

            return View();
        }

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

        [HttpPost]
        public ActionResult Edit(Customer customer)
        {
            if (ModelState.IsValid)
                RedirectToAction(&quot;Edit&quot;);

            customer.State = ObjectState.Modified;
            _unitOfWork.Repository&lt;Customer&gt;().Update(customer);
            _unitOfWork.Save();

            return View(customer);
        }
    }

Spa.CustomerController – After

We can get them by passing the registration name of Unity binding we setup earlier.

Option A:


    public class CustomerController : Controller
    {
        private readonly IUnitOfWork _customerUnitOfWork;
        private readonly IUnitOfWork _northwindUnitOfWork;

        public CustomerController(IUnityContainer container)
        {
            _northwindUnitOfWork = container.Resolve&lt;IUnitOfWork&gt;(&quot;NorthwindUnitOfWork&quot;);;
            _customerUnitOfWork = container.Resolve&lt;IUnitOfWork&gt;(&quot;NorthwindCustomerUnitOfWork&quot;);
        }

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

            int totalCustomerCount;

            var customers =
                _customerUnitOfWork.Repository&lt;Customer&gt;()
                    .Query()
                    .OrderBy(q =&gt; q
                        .OrderBy(c =&gt; c.ContactName)
                        .ThenBy(c =&gt; c.CompanyName))
                    .Filter(q =&gt; q.ContactName != null)
                    .GetPage(pageNumber, pageSize, out totalCustomerCount);

            ViewBag.Customers = new StaticPagedList&lt;Customer&gt;(
                customers, pageNumber, pageSize, totalCustomerCount);

            return View();
        }

        [HttpGet]
        public ActionResult Edit(string id)
        {
            var customer = _customerUnitOfWork.Repository&lt;Customer&gt;().FindById(id);
            return View(customer);
        }

        [HttpPost]
        public ActionResult Edit(Customer customer)
        {
            if (ModelState.IsValid)
                RedirectToAction(&quot;Edit&quot;);

            customer.State = ObjectState.Modified;
            _customerUnitOfWork.Repository&lt;Customer&gt;().Update(customer);
            _customerUnitOfWork.Save();

            return View(customer);
        }
    }

Option B:


    public class CustomerController : Controller
    {
        private readonly IUnitOfWork _customerUnitOfWork;
        private readonly IUnitOfWork _northwindUnitOfWork;

        public CustomerController(
            [Dependency(&quot;NorthwindUnitOfWork&quot;)] IUnitOfWork northwindUnitOfWork,
            [Dependency(&quot;NorthwindCustomerUnitOfWork&quot;)] IUnitOfWork customerUnitOfWork)
        {
            _northwindUnitOfWork = northwindUnitOfWork;
            _customerUnitOfWork = customerUnitOfWork;
        }

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

            int totalCustomerCount;

            var customers =
                _customerUnitOfWork.Repository&lt;Customer&gt;()
                    .Query()
                    .OrderBy(q =&gt; q
                        .OrderBy(c =&gt; c.ContactName)
                        .ThenBy(c =&gt; c.CompanyName))
                    .Filter(q =&gt; q.ContactName != null)
                    .GetPage(pageNumber, pageSize, out totalCustomerCount);

            ViewBag.Customers = new StaticPagedList&lt;Customer&gt;(
                customers, pageNumber, pageSize, totalCustomerCount);

            return View();
        }

        [HttpGet]
        public ActionResult Edit(string id)
        {
            var customer = _customerUnitOfWork.Repository&lt;Customer&gt;().FindById(id);
            return View(customer);
        }

        [HttpPost]
        public ActionResult Edit(Customer customer)
        {
            if (ModelState.IsValid)
                RedirectToAction(&quot;Edit&quot;);

            customer.State = ObjectState.Modified;
            _customerUnitOfWork.Repository&lt;Customer&gt;().Update(customer);
            _customerUnitOfWork.Save();

            return View(customer);
        }
    }

Note: Probably a good idea, specially in this case to go ahead and create an Enum or a class with constants instead of passing in hand coded strings as the registration name.

I prefer Option B, personally I don’t like the fact that you injecting anything with the entire Container, I rather have it when something is requesting to be injected, that the requester is specific in what it requesting for. Anyhow, I’ve seen this debate go both ways, moving on…

The alternative for those of us that are not using any IoC & DI

You should be using some form of DI & IoC with the N-number of frameworks out there, however if your not, obviously you an instantiate your Bounded UnitOfwork and DbContext directly.

Spa.CustomerController – without IoC and/or DI


    public class CustomerController : Controller
    {
        private readonly IUnitOfWork _customerUnitOfWork;
        private readonly IUnitOfWork _northwindUnitOfWork;

        public CustomerController(IUnityContainer container)
        {
            _northwindUnitOfWork = new UnitOfWork(new NorthwindContext());
            _customerUnitOfWork = new UnitOfWork(new NorthwindCustomerContext());
        }

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

            int totalCustomerCount;

            var customers =
                _customerUnitOfWork.Repository&lt;Customer&gt;()
                    .Query()
                    .OrderBy(q =&gt; q
                        .OrderBy(c =&gt; c.ContactName)
                        .ThenBy(c =&gt; c.CompanyName))
                    .Filter(q =&gt; q.ContactName != null)
                    .GetPage(pageNumber, pageSize, out totalCustomerCount);

            ViewBag.Customers = new StaticPagedList&lt;Customer&gt;(
                customers, pageNumber, pageSize, totalCustomerCount);

            return View();
        }

        [HttpGet]
        public ActionResult Edit(string id)
        {
            var customer = _customerUnitOfWork.Repository&lt;Customer&gt;().FindById(id);
            return View(customer);
        }

        [HttpPost]
        public ActionResult Edit(Customer customer)
        {
            if (ModelState.IsValid)
                RedirectToAction(&quot;Edit&quot;);

            customer.State = ObjectState.Modified;
            _customerUnitOfWork.Repository&lt;Customer&gt;().Update(customer);
            _customerUnitOfWork.Save();

            return View(customer);
        }
    }
 

Now, let’s run the application.

http://localhost:29622/Customer

7-30-2013 12-35-41 AM

There you have it, Happy Coding…! :)

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

Note: Please “Enable NuGet Package Restore” on the VS Solution.