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("All entites must implement the IObjectState interface, " +
                                                   "this interface must be implemented so each entites state can explicitely determined when updating graphs.");

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

        public new IDbSet<T> Set<T>() where T : class
        {
            return base.Set<T>();
        }

        protected override void OnModelCreating(DbModelBuilder builder)
        {
            builder.Conventions.Remove<PluralizingTableNameConvention>();
            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("All entites must implement the IObjectState interface, " +
                                                   "this interface must be implemented so each entites state can explicitely determined when updating graphs.");

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

        public new IDbSet<T> Set<T>() where T : class
        {
            return base.Set<T>();
        }

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

        public override Task<int> SaveChangesAsync()
        {
            ApplyStateChanges();
            return base.SaveChangesAsync();
        }

        public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
        {
            ApplyStateChanges();
            return base.SaveChangesAsync(cancellationToken);
        }

        protected override void OnModelCreating(DbModelBuilder builder)
        {
            builder.Conventions.Remove<PluralizingTableNameConvention>();
            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<TEntity> : IRepository<TEntity> where TEntity : class
    {
        private readonly Guid _instanceId;
        internal IDbContext Context;
        internal IDbSet<TEntity> DbSet;

        public Repository(IDbContext context)
        {
            Context = context;
            DbSet = context.Set<TEntity>();
            _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<TEntity> Query()
        {
            var repositoryGetFluentHelper = new RepositoryQuery<TEntity>(this);
            return repositoryGetFluentHelper;
        }

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

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

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

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

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

            var results = query;

            return results;
        }
    }

After:


    public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        private readonly Guid _instanceId;
        private readonly DbSet<TEntity> _dbSet;

        public Repository(IDbContext context)
        {
            _dbSet = context.Set<TEntity>();
            _instanceId = Guid.NewGuid();
        }

        public Guid InstanceId
        {
            get { return _instanceId; }
        }

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

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

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

        public virtual IQueryable<TEntity> 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<TEntity> Query()
        {
            var repositoryGetFluentHelper = new RepositoryQuery<TEntity>(this);
            return repositoryGetFluentHelper;
        }

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

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

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

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

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

        internal async Task<IEnumerable<TEntity>> GetAsync(
                    Expression<Func<TEntity, bool>> filter = null,
                    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
                    List<Expression<Func<TEntity, object>>> 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<Product, int>
    {
        private readonly IUnitOfWork _unitOfWork;

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

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

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

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

            return update;
        }

        public override void Delete([FromODataUri] int key)
        {
            _unitOfWork.Repository<Product>().Delete(key);
            _unitOfWork.Save();
        }

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

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<Product, int>
{
    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<IEnumerable<Product>> Get()
{
return await _unitOfWork.Repository<Product>().Query().GetAsync();
}

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

    ///// <summary>
    ///// Retrieve an entity by key from the entity set.
    ///// </summary>
    ///// <param name="key">The entity key of the entity to retrieve.</param>
    ///// <returns>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.</returns>
    [Queryable]
    protected override async Task<Product> GetEntityByKeyAsync(int key)
    {
        return await _unitOfWork.Repository<Product>().FindAsync(key);
    }

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

    protected override async Task<Product> 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 = "The supplied key and the Product being updated do not match." }));

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

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

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

        var entity = await _unitOfWork.Repository<Product>().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<Product>().FindAsync(key);

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

        _unitOfWork.Repository<Product>().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 <controller>(key)/$links/Category
    // POST <controller>(key)/$links/Supplier
    /// <summary>
    /// Handle POST and PUT requests that attempt to create a link between two entities.
    /// </summary>
    /// <param name="key">The key of the entity with the navigation property.</param>
    /// <param name="navigationProperty">The name of the navigation property.</param>
    /// <param name="link">The URI of the entity to link.</param>
    /// <returns>A Task that completes when the link has been successfully created.</returns>
    [AcceptVerbs("POST", "PUT")]
    public override async Task CreateLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
    {
        var entity = await _unitOfWork.Repository<Product>().FindAsync(key);

        if (entity == null)
            throw Request.EntityNotFound();
            
        switch (navigationProperty)
        {
            case "Category":
                var categoryKey = Request.GetKeyValue<int>(link);
                var category = await _unitOfWork.Repository<Category>().FindAsync(categoryKey);

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

            case "Supplier":
                var supplierKey = Request.GetKeyValue<int>(link);
                var supplier = await _unitOfWork.Repository<Supplier>().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 <controller>(key)/$links/Category
    // DELETE <controller>(key)/$links/Supplier
    /// <summary>
    /// Handle DELETE requests that attempt to break a relationship between two entities.
    /// </summary>
    /// <param name="key">The key of the entity with the navigation property.</param>
    /// <param name="relatedKey">The key of the related entity.</param>
    /// <param name="navigationProperty">The name of the navigation property.</param>
    /// <returns>Task.</returns>
    public override async Task DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty)
    {
        var entity = await _unitOfWork.Repository<Product>().FindAsync(key);

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

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

            case "Supplier":
                entity.Supplier = null;
                break;

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

        await _unitOfWork.SaveAsync();
    }

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

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

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

            case "Supplier":
                entity.Supplier = null;
                break;

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

        await _unitOfWork.SaveAsync();
    }
    #endregion Links

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

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

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

    public async Task<Supplier> GetSupplier(int key)
    {
        var entity = await _unitOfWork.Repository<Product>().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<IEnumerable<Product>> Get()
{
    return await _unitOfWork.Repository<Product>().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<IEnumerable<Product>> Get()
{
    return await _unitOfWork.Repository<Product>().Query().GetAsync();
}
 

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


        internal async Task<IEnumerable<TEntity>> GetAsync(
                    Expression<Func<TEntity, bool>> filter = null,
                    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
                    List<Expression<Func<TEntity, object>>> 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

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

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 = "Products";
    Layout = "";
}
<div class="row">
    <div class="span5">
        <h2>Technlogy Stack</h2>
        <h3><a href="http://blog.longle.net">blog.longle.net</a></h3>
        <p>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 (<a href="http://www.windowsazure.com/en-us/develop/net/aspnet/" target="blank">free!</a>)</p>
        <br />
        <p><a class="btn" href="http://go.microsoft.com/fwlink/?LinkId=301865">Learn more &raquo;</a></p>
    </div>
</div>

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

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

<script>
    var lastSelectedDataItem;

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

    var cancel = function (event) {
        onClick(event, function (grid) {
            grid.cancelRow();
            $(".toolbar").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);
            $(".toolbar").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 = $("#productGrid").data("kendoGrid");
        var selectedRow = grid.select();
        var dataItem = grid.dataItem(selectedRow);
        if (selectedRow.length > 0)
            delegate(grid, selectedRow, dataItem);
        else
            alert("Please select a row.");
    };

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

    var baseUrl = "/odata/Product";

    var dataSource = new kendo.data.DataSource({
        type: "odata",
        transport: {
            read: {
                url: baseUrl,
                dataType: "json"
            },
            update: {
                url: function (data) {
                    return baseUrl + "(" + data.ProductID + ")";
                },
                dataType: "json"
            },
            destroy: {
                url: function (data) {
                    return baseUrl + "(" + data.ProductID + ")";
                },
                dataType: "json"
            }
        },
        batch: false,
        serverPaging: true,
        serverSorting: true,
        serverFiltering: true,
        pageSize: 10,
        schema: {
            data: function (data) {
                return data.value;
            },
            total: function (data) {
                return data["odata.count"];
            },
            errors: function (e) {
                return e.errors;
            },
            model: Product
        },
        error: function (e) {
            var responseJson = e.xhr.responseJSON;
            if (responseJson != undefined) {
                if (responseJson["odata.error"] != undefined) {
                    var error = responseJson["odata.error"];
                    var message = error.message.value + '\n\n' + error.innererror.message;
                    alert(message);
                }
            } else {
                alert(e.xhr.status + "\n\n" + e.xhr.responseText + "\n\n" + 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 < 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("tr[data-uid='" + view[i].uid + "']")); // set the selected row
                    break;
                }
            }
        },
        onChange: function (arg) {
            var grid = arg.sender;
            lastSelectedDataItem = grid.dataItem(grid.select());
        },
    });

    $(document).bind("viewSwtichedEvent", function (e, args) { // subscribe to the viewSwitchedEvent
        if (args.name == "list") { // 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($("#view"), viewModel); // bind the view to the model
            } else {// view already been loaded in cache
                viewModel.dataSource.read(); // refresh grid
            }
        }
    });

</script>
<style scoped>
    #productGrid .k-toolbar {
        padding: .7em;
    }

    .toolbar {
        float: right;
    }
</style>
 

10-9-2013 7-11-39 PM

Happy Coding…! :)

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

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

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

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

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

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

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

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

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

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

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

Spa.Controllers.ProductController.cs


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

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

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

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

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

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

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

Spa/Content/Views/products.html


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

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

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

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

</script>

Additions to Client-Side

editProduct


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

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

Additions to the Observable Model

dataBound


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

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

onChange


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

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

6-19-2013 6-27-27 PM

Creating the ProductEdit View

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


<!-- styles remove for clarity -->

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

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

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

</script>

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

6-19-2013 6-28-33 PM

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

product-edit-form (DIV)


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

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

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

Client Side & Product Datasource


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

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

</script>

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

Client Side Code

Parsing the ProductId From the URL


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

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

6-19-2013 6-30-21 PM

productModel (Observable Model)


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

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

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

Decomposing the Datasource Configuration

paramaterMap


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


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

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

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

sync


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

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

schema


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

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

total


total: function (data) {
    return 1;
}

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

dataSource.fetch(callback)


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

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

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

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

Happy Coding…! :)

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

MVC 4, Web API, OData, Entity Framework, Kendo UI, Grid, Datasource (CRUD) with MVVM

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

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

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

Update: 06/18/2013 – Added CRUD actions to Kendo UI Grid & Datasource (read, update, delete) updated sample download and live demo.

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

Let’s start off where we left off from my previous blog MVC 4, Kendo UI, SPA with Layout, View, Router & MVVM – Part 1. In this post, we’ll cover how to wire up a Kendo UI Grid and Datasource in our SPA with MVVM using OData.

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

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

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

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

Let’s get Web API setup and configured with OData, you will need the Nuget package Microsoft ASP.NET Web API OData.

6-18-2013 1-32-31 AM

We will use a SQL Server Compact (4.0) Northwind database for this example, you can easily use a full SQL Server Database if you’d like with libraries in this project. I’ve tested them both and they work fine. Both the Northwind SQL Compact and SQL Server database are included in the sample download application that will be available for download in Part 3 of this series. For all of those who are wondering, why did I use (embedded) SQL Server Compact for as web app..?! Well, because I can host the SQL Server Compact database in my Windows Azure Website for free..!

You will need the NuGet package EntityFramework.SqlServerCompact and Microsoft.SqlServerCompact packages. You can read up on some more in-depth details on how to setup ASP.NET MVC with SQL Server Compact Databases here.

6-18-2013 1-38-54 AM

We need to create a ProductController so that we can provide data to our View, this controller will inherit the EntitySetController which inherits the ApiController that we all know so well, so we can serve up our data using OData.

Spa.Controllers.ProductController.cs


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

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

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

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

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

We need to setup OData with the MVC runtime as well as setup the OData endpoint. Not relevant to this post, however, notice that our OData Products Web Api Controller has a dependency for IUnitOfWork and that it is being injected (DI) with an instance of it’s concrete implementation UnitOfWork, courtesy of Unity 3.0. Please read up on post: Generically Implementing the Unit of Work & Repository Pattern with Entity Framework in MVC & Simplifying Entity Graphs, if any clarity is needed for the generic Unit Of Work and Repository pattern used in the ProductController seen here.

Spa.WebApiConfig



    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
            var entitySetConfiguration = modelBuilder.EntitySet<Product>("Product");
            entitySetConfiguration.EntityType.Ignore(t => t.Order_Details);
            entitySetConfiguration.EntityType.Ignore(t => t.Category);
            entitySetConfiguration.EntityType.Ignore(t => t.Supplier);

            var model = modelBuilder.GetEdmModel();
            config.Routes.MapODataRoute("ODataRoute", "odata", model);

            config.EnableQuerySupport();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new {id = RouteParameter.Optional}
                );
        }
    }

For interest of time we’ll go ahead an ignore all the relational mappings that our Product entity has.

Now let’s test our ProductsController with Fiddler and makes sure everything we’re able to query our Get method on our ProductController using OData queries.

6-18-2013 12-11-03 AM

The raw HTTP response message should look similar to the following:

http://localhost:29622/odata


HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/atomsvc+xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcVXNlcnNcbGxlXERvd25sb2Fkc1xUZW1wMlxTb2x1dGlvblxTcGFcb2RhdGE=?=
X-Powered-By: ASP.NET
Date: Tue, 18 Jun 2013 05:09:41 GMT
Content-Length: 363

<?xml version="1.0" encoding="utf-8"?>
<service xml:base="http://localhost:29622/odata/" xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom">
  <workspace>
    <atom:title type="text">Default</atom:title>
    <collection href="Product">
      <atom:title type="text">Product</atom:title>
    </collection>
  </workspace>
</service>

The response body contains the OData service document in JSON format. The service document contains an array of JSON objects that represent the entity sets. In this case, there is a single entity set, “Product”. To query this set, send a GET request to http://localhost:port/odata/Products. The
response should be similar to the following:

http://localhost:29622/odata/Product


HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; odata=minimalmetadata; streaming=true; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcVXNlcnNcbGxlXERvd25sb2Fkc1xUZW1wMlxTb2x1dGlvblxTcGFcb2RhdGFcUHJvZHVjdA==?=
X-Powered-By: ASP.NET
Date: Tue, 18 Jun 2013 05:18:27 GMT
Content-Length: 21696

{
  "odata.metadata":"http://localhost:29622/odata/$metadata#Product","value":[
    {
      "ProductID":1,"ProductName":"Chai","EnglishName":"Dharamsala Tea","SupplierID":1,"CategoryID":1,"QuantityPerUnit":"10 boxes x 20 bags","UnitPrice":"20","UnitsInStock":39,"UnitsOnOrder":10,"ReorderLevel":10,"Discontinued":false,"State":"Unchanged"
    },{
      "ProductID":2,"ProductName":"Chang","EnglishName":"Tibetan Barley Beer","SupplierID":1,"CategoryID":1,"QuantityPerUnit":"24 - 12 oz bottles","UnitPrice":"19","UnitsInStock":17,"UnitsOnOrder":40,"ReorderLevel":25,"Discontinued":false,"State":"Unchanged"
    },{
      "ProductID":3,"ProductName":"Aniseed Syrup","EnglishName":"Licorice Syrup","SupplierID":1,"CategoryID":2,"QuantityPerUnit":"12 - 550 ml bottles","UnitPrice":"10","UnitsInStock":13,"UnitsOnOrder":71,"ReorderLevel":25,"Discontinued":false,"State":"Unchanged"
    },{
      "ProductID":4,"ProductName":"Chef Anton's Cajun Seasoning","EnglishName":"Chef Anton's Cajun Seasoning","SupplierID":2,"CategoryID":2,"QuantityPerUnit":"48 - 6 oz jars","UnitPrice":"22","UnitsInStock":53,"UnitsOnOrder":0,"ReorderLevel":0,"Discontinued":false,"State":"Unchanged"
    },{
      "ProductID":5,"ProductName":"Chef Anton's Gumbo Mix","EnglishName":"Chef Anton's Gumbo Mix","SupplierID":2,"CategoryID":2,"QuantityPerUnit":"36 boxes","UnitPrice":"21.35","UnitsInStock":0,"UnitsOnOrder":0,"ReorderLevel":0,"Discontinued":true,"State":"Unchanged"
    },{
      "ProductID":6,"ProductName":"Grandma's Boysenberry Spread","EnglishName":"Grandma's Boysenberry Spread","SupplierID":3,"CategoryID":2,"QuantityPerUnit":"12 - 8 oz jars","UnitPrice":"25","UnitsInStock":120,"UnitsOnOrder":0,"ReorderLevel":25,"Discontinued":false,"State":"Unchanged"
    },{
      "ProductID":7,"ProductName":"Uncle Bob's Organic Dried Pears","EnglishName":"Uncle Bob's Organic Dried Pears","SupplierID":3,"CategoryID":7,"QuantityPerUnit":"12 - 1 lb pkgs.","UnitPrice":"30","UnitsInStock":15,"UnitsOnOrder":0,"ReorderLevel":10,"Discontinued":false,"State":"Unchanged"
    },{
      "ProductID":8,"ProductName":"Northwoods Cranberry Sauce","EnglishName":"Northwoods Cranberry Sauce","SupplierID":3,"CategoryID":2,"QuantityPerUnit":"12 - 12 oz jars","UnitPrice":"40","UnitsInStock":6,"UnitsOnOrder":0,"ReorderLevel":0,"Discontinued":false,"State":"Unchanged"
    }
  ]
}

Great, we have data with OData :P

One of the important take aways here, is by implementing OData with a data provider (e.g. Entity Framework) that supports IQueryable all of our REST HTTP GET queries options (e.g. skip, take, sort, filter, equals, etc.) are automatically translated for to us to Entity Framework, so that we don’t have to wire up any of this.

Let’s launch Fiddler again and see this in action and do a OData HTTP GET request querying for product that has the name equal to “Chai”.

http://localhost:29622/odata/Product?%24inlinecount=allpages&%24top=10&%24filter=ProductName+eq+’chai&#8217;

6-18-2013 1-26-12 PM

Raw Request of OData Query


GET http://localhost:29622/odata/Product?%24inlinecount=allpages&%24top=10&%24filter=ProductName+eq+'chai' HTTP/1.1
X-Requested-With: XMLHttpRequest
Accept: application/json, text/javascript, */*; q=0.01
Referer: http://localhost:29622/index.html#/products
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: localhost:29622
DNT: 1
Connection: Keep-Alive

Raw Request of OData Response


HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcVXNlcnNcbGxlXERvd25sb2Fkc1xUZW1wMlxTb2x1dGlvblxTcGFcb2RhdGFcUHJvZHVjdA==?=
X-Powered-By: ASP.NET
Date: Tue, 18 Jun 2013 18:22:02 GMT
Content-Length: 374

{
  "odata.metadata":"http://localhost:29622/odata/$metadata#Product","odata.count":"1","value":[
    {
      "ProductID":1,"ProductName":"Chai","EnglishName":"Dharamsala Tea","SupplierID":1,"CategoryID":1,"QuantityPerUnit":"10 boxes x 20 bags","UnitPrice":"20","UnitsInStock":39,"UnitsOnOrder":10,"ReorderLevel":10,"Discontinued":false,"State":"Unchanged"
    }
  ]
}

Again, querying is navtively supported out of the box with OData and a data provider that supports IQueryable, however if we re-visit our ProductController, we didn’t have to write up any code for this..!

Spa.Controllers.ProductController


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

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

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

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

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

Now we create a View for the Products listing using Kendo UI Grid, Datasource and use MVVM as the adhesive to bind it all together.

Spa/Content/Views/products.html


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

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

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

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

</script>

We wrap our html content in the script tags with type=”text/x-kendo-template”, this is so we can leverage all the goodness that Kendo UI Web Templates bring to the table.

productGrid (Spa/Content/Views/products.html)


            <div id="productGrid" 
                data-role="grid"
                data-sortable="true"
                data-pageable="true"
                data-filterable="true"
                data-bind="source: dataSource, events:{dataBound: dataBound, change: onChange}"
                data-editable = "inline"
                data-selectable="true" 
                data-columns='[
                    { field: "ProductID", title: "Id", width: "50px" }, 
                    { field: "ProductName", title: "Name", width: "300px" }, 
                    { field: "UnitPrice", title: "Price", format: "{0:c}", width: "100px" },                    
                    { field: "Discontinued", width: "150px" }, 
                    { command : [ "edit", "destroy", { text: "Edit Details", click: editProduct } ], title: "Action",  } ]'>
            </div>

Model & Datasource (Spa/Content/Views/products.html)


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

Notice how there is very little, if any, of our own code here. All we’ve done, is simply configured (filling in the blanks) the Kendo Datasource.

The important items to note here is that by default our OData enabled ProductController returns the count as data.odata.count and our data set in data.value, with this being the case we will need to help the Datasource by unpacking this and returning it to the Datasource. You can see how this is done in the schema data and total functions defined above.

We also define the model in the Datasource, the model inherits Kendo’s Observable object (class), meaning their is two-way binding with the Grid and Datasource, so anytime something happens on the Grid updates are automatically sent to the Datasource, then sent to our ProductsController.

The productGrid is declaratively attributed using the data- attributes, this is what Kendo UI MVVM uses for binding the View to the Model.

Subscribing to the viewSwitchedEvent Event


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

Here we are simply subscribing to viewSwitchedEvent that is published from the host page (Spa\index.html) of our SPA. This event is published (raised) everytime is View switching is complete. Here we check that the View that was switched in place was indeed the products View and that it was the first time is was loaded remotely from the server, if so, we bind the View to the Model. We do this only on the first time it loads from the server because there is really no need to do this more than once.

Now let’s load up the application and see our Products listing.

6-18-2013 3-01-38 PM

Testing Filtering and Sorting

6-18-2013 3-01-27 PM

Testing Inline Grid Editing

6-18-2013 6-31-01 PM

Testing Inline Grid Deleting

6-18-2013 6-57-55 PM

6-18-2013 6-55-22 PM

We see this error:

An error has occurred.

The primary key value cannot be deleted because references to this key still exist. [ Foreign key constraint name = Order Details_FK00 ]

Which is expected since the Order Details still has foreign keys to the Product table.

There you have it MVC 4, Web API, OData, Kendo UI, Grid, Datasource with MVVM. To spice things up, I changed the Kendo UI CSS to the Metro UI theme.

For live demo: http://longle.azurewebsites.net

Stay tuned for Part 3…

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

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

Happy Coding…! :)

Implementing DI & IoC Pattern in MVC 4 with MEF 2 Attributeless using Conventions with RegistrationBuilder in .NET 4.5

  • Update, downloadable sample using MEF with System.ComponentModel.Composition.Web.Mvc Preview 5 – 05/31/2013
  • Update, downloadable sample using Unity 3.0 for MVC 4 – 06/03/2013

From my previous article Generically Implementing the Unit of Work & Repository Pattern with Entity Framework in MVC & Simplifying Entity Graphs – Part 1, we covered how to properly setup your project and implement the Unit Of Work and Repository patterns. In this article, we’ll take our solution the next logical progression, which is getting DI (Dependency Injection) & IoC (Inverse of Control) patterns implemented.

DI & IoC will bring many advantages to our solution. a few of them are as follows.

  1. Programming against abstractions and away from our concrete implementations.
  2. Complementing the previous item, allowing us replace or select concrete implementations during run-time.
  3. Giving us an easy way to mock-up certain implementations for quick unit tests.

Now there are many other debatable benefits for DI & IoC, and I’ll let you make you form your own opinions on this topic, however for the purposes of this post, let’s jump into implementation. We will start off, where we left off in our solution, from our previous post.

First we will need to go ahead and get MEF (Managed Extensibility Framework) wired up, we can get a jump start to this by using our MVC MEF library from my blog post found here.

Note: There are two methods MEF will register exports and imports, one by attributes, which most of know about, the second, is by conventions. I’ve searched high and low on how to get MEF working with MVC using MEF Registrations and have absolutely zero luck, so with that being said, more reason to setup our solution using MEF Registrations (convention based) vs. the Attribute approach

With the Mv4.Mef project added and referenced in our Web project, let’s go ahead and wire up the some code in our Web startup. Now, instead of polluting our Global.asax.cs Application_Start() method with all our MEF Registrations and Conventions, let’s follow the MVC pattern, by adding a MefConfig.cs class under the App_Start folder with the rest of the application start-up code resides.

5-17-2013 7-37-59 PM

Web.App_Start.MefConfig.cs


    public static class MefConfig
    {
        public static void RegisterMef()
        {
            // Set up all the Mef conventions for our web assembly
            var registrationBuilder = new RegistrationBuilder();

            registrationBuilder.ForTypesDerivedFrom<Controller>()
                .SetCreationPolicy(CreationPolicy.NonShared).Export();

            registrationBuilder.ForTypesDerivedFrom<ApiController>()
                .SetCreationPolicy(CreationPolicy.NonShared).Export();

            registrationBuilder
                .ForTypesMatching(t =>
                    t.FullName.StartsWith(
                        Assembly
                            .GetExecutingAssembly()
                            .GetName().Name + ".Parts."))
                .SetCreationPolicy(CreationPolicy.NonShared)
                .ExportInterfaces(x => x.IsPublic);

            var aggregateCatalog = new AggregateCatalog();

            aggregateCatalog.Catalogs.Add(
                new AssemblyCatalog(Assembly.GetExecutingAssembly(), registrationBuilder));

            // Set up all the Mef conventions for our repository assembly
            registrationBuilder = new RegistrationBuilder();

            registrationBuilder.ForTypesDerivedFrom<IUnitOfWork>().Export<IUnitOfWork>();

            aggregateCatalog.Catalogs.Add(
                new AssemblyCatalog(typeof(IUnitOfWork).Assembly, registrationBuilder));

            // Set up all the Mef conventions for our data assembly
            registrationBuilder = new RegistrationBuilder();

            registrationBuilder.ForTypesDerivedFrom<IDbContext>().Export<IDbContext>();

            aggregateCatalog.Catalogs.Add(
                new AssemblyCatalog(typeof(IDbContext).Assembly, registrationBuilder));

            // Add all our catalogs with Mef conventions to our container
            MefMvcConfig.RegisterMef(new CompositionContainer(aggregateCatalog));
        }
    }

Now let’s invoke our MEF configuration from the Global.asax.cs Application_Start() method.

Web.Global.asax.cs


namespace Web
{
    public class MvcApplication : HttpApplication
    {
        protected void Application_Start()
        {
            MefConfig.RegisterMef();
            AreaRegistration.RegisterAllAreas();
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();
        }
    }
}

So we everything from line 5 to 51 is simply setting up all the import and export mappings from all of our projects or assemblies, I’ll go over some of the conventions we are setting up with the MEF’s RegistrationBuilder.


            registrationBuilder.ForTypesDerivedFrom<Controller>()
                .SetCreationPolicy(CreationPolicy.NonShared).Export();

Her we are saying go ahead and export everything that inherits or is derived from Controller, which means go ahead and add all of our controllers to the Composition Container.


            registrationBuilder.ForTypesDerivedFrom<ApiController>()
                .SetCreationPolicy(CreationPolicy.NonShared).Export();

Here (same principles as the previous item) we are saying go ahead and add all of our Web Api Controllers to the container.


            registrationBuilder
                .ForTypesMatching(t =>
                    t.FullName.StartsWith(
                        Assembly
                            .GetExecutingAssembly()
                            .GetName().Name + ".Parts."))
                .SetCreationPolicy(CreationPolicy.NonShared)
                .ExportInterfaces(x => x.IsPublic);

In this block of code, we are saying go ahead and export everything that implements a public interface as the interface it’s implementing that in the Parts folder in our web project as exampled here http://msdn.microsoft.com/en-us/library/hh708870.aspx.


            registrationBuilder
                .ForTypesDerivedFrom<IUnitOfWork>().Export<IUnitOfWork>();

This is pretty straight forward export anything that is derived from IUnitOfWork as IUnitOfWork. Great, hopefully this sheds some light on how we can setup some conventions for export and import mapping for our Composition Container.

Now let’s revisit to our CustomerController we were working on previously.

Before:


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

            var unitOfWork = new UnitOfWork();

            int totalCustomerCount;

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

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

            unitOfWork.Save();

            return View();
        }
    }

Now, lets inject all our dependencies that the CustomerController has using one of MEF’s supported Import methods, Constructor Injection.

After:


    public class CustomerController : Controller
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly ICustomerService _customerService;

        public CustomerController(
            IUnitOfWork unitOfWork, 
            ICustomerService customerService)
        {
            _unitOfWork = unitOfWork;
            _customerService = customerService;
        }

        public IUnitOfWork UnitOfWork { get; set; }

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

            int totalCustomerCount;

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

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

            return View();
        }
    }

Let’s debug and make sure that the IUnitOfWork is getting injected with Constructor Injection from MEF by putting int two breakpoints, first one in the MefMvcControllerFactory so we can take a peek at the Catalog in our Composition Container and the second one in the CustomerController itself.

5-17-2013 5-33-56 PM

Now here we see that all of our previously wired up MEF conventions are valid, we see all of our Controllers, Services, UnitOfWork and NorthwindContext in our CompositionContainer, great! Now for a sanity check, let’s take a look at our CustomerController to ensure that we are actually getting injected now that we validated our container.

5-17-2013 5-43-22 PM

Now let’s just take a quick look at our UnitOfWork and CustomerService objects and notice how there are not attributes decorated anywhere and that they are indeed being added to our CompositionContainer by the conventions we setup earlier with the RegistrationBuilder.

Repostiory.UnitOfWork


namespace Repository
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly IDbContext _context;

        private bool _disposed;
        private Hashtable _repositories;

        public UnitOfWork(IDbContext context)
        {
            _context = context;
            InstanceId = Guid.NewGuid();
        }

        public Guid InstanceId { get; set; }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Save()
        {
            _context.SaveChanges();
        }

        public virtual void Dispose(bool disposing)
        {
            if (!_disposed)
                if (disposing)
                    _context.Dispose();

            _disposed = true;
        }

        public IRepository<T> Repository<T>() where T : class
        {
            if (_repositories == null)
                _repositories = new Hashtable();

            var type = typeof (T).Name;

            if (!_repositories.ContainsKey(type))
            {
                var repositoryType = typeof (Repository<>);

                var repositoryInstance = 
                    Activator.CreateInstance(repositoryType
                            .MakeGenericType(typeof (T)), _context);
                
                _repositories.Add(type, repositoryInstance);
            }

            return (IRepository<T>) _repositories[type];
        }
    }
}

Web.Parts.CustomerService


namespace Web.Parts
{
    public class CustomerService : ICustomerService
    {
        private readonly IUnitOfWork _unitOfWork;

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

        public void UpdateWithAddressValidation(Customer customer)
        {
                // Example stubbed method, for same UnitOfWork 
                //instance injetction test with page request scoped
        }
    }
}

Awesome, our CustomerController is being handled and instantiated by MEF, therefore it is also handling all the Dependency Injection in the CustomerController e.g. IUnitOfWork and the ICustomerService.

5-17-2013 5-43-22 PM

Now, one very important note, notice that I’ve added a property the UnitOfWork named InstanceId of type Guid. I’ve deliberately drilled down the UnitOfWork.InstanceId in both the UnitOfWork and CustomerService objects in the debug mode screenshot, so that we can see that they are both indeed the same instance. This is very important when using MEF with MVC, that by default, the Scope of the items life cycle are per page request, and will be disposed of after the request has completed. For scenarios where we deliberately want an instance to live for the entire life cycle of the application we can set CreationPolicy for that export to be shared.

Happy Coding..! :)

Download sample applications:

Self managed life-cycle using HTTP scoped IUnitOfWork, IDbContext using HttpContext.Current.Items with MEF

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

HTTP scoped using System.ComponentModel.Composition.Web.Mvc Preview 5, which I have upgraded to target .NET 4.5 using MEF

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

Same solution from post, using the new Unity 3 for MVC 4 instead of MEF
Used the NuGet command install-package Unity.Mvc4 from the Package Manager Console for this one.

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

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

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

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

Thanks for the Reblog Twilio!

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

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

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

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

  1. MefMvcConfig.cs

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

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

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

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

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

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

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

Global.asax.cs


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

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

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

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

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

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

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

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


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

            return View();
        }

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

            return View();
        }

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

            return View();
        }
    }
}

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

Before:


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

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

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

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

                    Session["registrationModel"] = model;

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

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

After:


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

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

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

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

                    Session["registrationModel"] = model;

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

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

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

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

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

Register View

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

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

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


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

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

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

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

Now, when we inject our TwilioRestClient with


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

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


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

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

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

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


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

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

Now lets run this command with Curl.

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

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

Our WebApi returns the required TwiML response.


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

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


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

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

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


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

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

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

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

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

Happy Coding…! :)

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

Building a Composite MVC3 Application with Ninject

Update: 05/04/2012 – Preferred alternative approach without using IoC for Plugins (http://blog.longle.net/2012/03/29/building-a-composite-mvc3-application-with-pluggable-areas/).

So there’s great support in Prism to building composite applications with Silverlight with the notion of Prism modules. They have a nice discovery approach for dynamically discovering modules when loading XAPs and assemblies during runtime for all the different modules your Silverlight app may need. You can load them during start-up or on demand so that you don’t have to download the entire application at one time.

When building an enterprise MVC application, I wanted to borrow some of the ideas and architecture of the Prism extensible plug and play concepts to provide the ability to build modules (areas) of your MVC application outside of your core MVC application.

For example if you had a MVC application and it’s primary role is an E-Commerce site (core site), however you have this new requirement to build a integration point to show customers all their tracking statuses for their orders. Now you have to ramp up a development team as quick as possible so that they can build out this feature, however you now find yourself giving them a full blown training and overview of your core MVC application, full source control of your entire VS Solution set which could have up to 20-30 projects in it.

Now when devs from the team compile it’s taking forever…! Here we can easily see that our MVC app’s footprint is getting to large and somewhat difficult to manage. So here is now the need to break it down into blocks that can be injected into the app during runtime so that development for these different pieces, blocks, or modules can happen in parallel with your core MVC application (loose coupling, modularity and extensibility).

My preference here is to actually use Mef since I have experience with it and the great support for MVC3 and Mef now. However, I’ve been given the opportunity to be engaged on a project that uses Ninject. With this being the situation at hand I wanted to explore and see if we could borrow the some concepts from Prism, and incorporate them using what was out of the box with Ninject. Some of these concepts include separation of concerns and loosely coupled components (modules) that can evolve independently but can be easily and seamlessly integrated into the overall application, which is also known as a composite application.

With a little bit of Googling I stumbled upon this article http://www.thegecko.org/index.php/2010/06/pluggable-mvc-2-0-using-mef-and-strongly-typed-views/ which was pretty much what we were looking for however it just needed to be ported from MVC2 to MVC3, use Ninject instead of Mef and just a tad bit of polishing.

So after a little bit of poking around with Ninject, I quickly found that Ninject also has a notion of Modules, NinjectModules, or classes that implement the INinjectModule interface. You can ask the Ninject Kenerl (container) to scan an assembly and it will scan for all any classes that implement INinjectModule, this can be done directly by implementing the actual interface or inheriting their out of the box NinjectModule abstract class.

So rather than starting from ground zero here, I’m going to continue with the project from my last post http://blog.longle.net/2012/02/15/wrapping-the-ninject-kernel-with-servicelocator/.

Quick breakdown on the VS Solution structure:

  • MvcApplication

    This is our main MVC application which will host all the Plugins

  • MvcApplication.Plugin.Framework

    This is a C# class library where all the plugin infrastructure classes will reside

  • MvcApplicationA.Plugin

    Example PluginA

  • MvcApplicationA.Plugin

    Example PluginB

  • MvcApplicationA.Plugin

    Example PluginB

Let’s take a quick look at some of the classes in the MvcApplication.PluginFramework VS Project.

  • MyController.cs (implements IMyController)

    All controllers from plugins will need to inherit MyController, this provides some meta-data so that we can infer the correct type of a plugin Controller from our IoC that was requested.

  • MyPlugin.cs

    There will be one class in each plugin that must inherit MyPlugin, this class provides meta-data about the plugin, e.g. PluginName, AssemblyName, etc.. It also gives us the overidable Load operation where we can setup our Ninject bindings e.g. Controller and plugin bindings.

  • MyControllerFactory.cs

    We inherit the DefaultControllerFactory and override the GetControllerType method so that when a user is routed to a controller that is in a plugin, we can help the MVC runtime derive what it’s type is by scanning our IoC for registered instances for that a given Controller and return it’s type.

    
        public class MyControllerFactory : DefaultControllerFactory
        {
            protected override Type GetControllerType(RequestContext requestContext, 
                string controllerName)
            {
                var controllerType = base.GetControllerType(requestContext, controllerName);
    
                if (controllerType == null)
                {
                    var controller = ServiceLocator.Current.GetAllInstances<IController>().ToList()
                    .OfType<IMyController>()
                    .SingleOrDefault(c => c.ControllerName == controllerName);
    
                    if (controller != null)
                    {
                        return controller.GetType();
                    }
                }
    
                return controllerType;
            }
        }
    
    
  • MyRazorViewEngine (MyViewEngine.cs)

    Custom RazorViewEngine so that we can properly return Views *.cshtml (Razor) that have been copied to our Plugins directory in our main app (MvcApplication/Plugins).

  • MyWebFormEngine (MyViewEngine.cs)

    Custom WebFormViewEngine so that we can properly return MVC *.aspx (non-Razor) Views that have been copied to our Plugins directory in our main app (MvcApplication/Plugins).

  • MyPluginBootstrapper.cs and yes, I borrowed the name from Prism :p

    • Scans the “MvcApplication/Plugin” directory and loads all of our plugins
    • Register’s any custom routes from our plugins
    • Register’s our MyControllerFactory with the MVC runtime
    • Register’s our MyWebFormViewEngine with the MVC runtime
    • Register’s our MyRazorViewEngine with the MVC runtime
    
        public class MyPluginBootstrapper : NinjectModule
        {
            private const string _pluginPath = "Plugins";
            private readonly string _fullPluginPath;
            private const string _defaultMaster = "Site";
            private const string _defaultRazorMaster = "_Layout";
    
            public MyPluginBootstrapper()
            {
                _fullPluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _pluginPath);
            }
    
            public override void Load()
            {
                var assemblies = new List<Assembly>();
    
                // Discover  Modules in plugin directory (e.g. site/plugins)
                foreach (var file in Directory.EnumerateFiles(_fullPluginPath, "*.Plugin.dll"))
                    assemblies.Add(Assembly.LoadFile(file));
    
                Kernel.Load(assemblies);
    
                //  plugins discovery
                var plugins = new List<IMyPlugin>();
                foreach (IMyPlugin plugin in ServiceLocator.Current.GetAllInstances<IMyPlugin>())
                {
                    plugins.Add(plugin);
                    plugin.RegisterRoutes(RouteTable.Routes);
                }
    
                // Register ControllerFactory with site
                IControllerFactory myControllerFactory = new MyControllerFactory();
                ControllerBuilder.Current.SetControllerFactory(myControllerFactory);
    
                // Setup ViewEngines
                var myWebFormViewEngine = 
                    new MyWebFormViewEngine(_pluginPath, plugins, _defaultMaster);
    
                var myRazorViewEngine = 
                    new MyRazorViewEngine(_pluginPath, plugins, _defaultRazorMaster);
    
                // Register ViewEngines with site
                ViewEngines.Engines.Clear();
                ViewEngines.Engines.Add(myWebFormViewEngine);
                ViewEngines.Engines.Add(myRazorViewEngine);            
            }
        }
    
    

So what’s all required to setup an MVC plugin now?

  1. Create a regular MVC3 app project to be the plugin
  2. Assembly name must match this pattern *.Plugin.dll, yes we are using convention for this.
  3. Must have one class that inherits MyPlugin.cs

    
        public class PluginA : MyPlugin
        {
            public override void Load()
            {
                Bind<IMyPlugin>().To<PluginA>();
                Bind<IController>().To<PluginAController>()
                    .Named(GetControllerName<PluginAController>());
            }
        }
    
    
  4. Setup binding for the plugin e.g. IMyPlugin -> PluginA

    
        public class PluginA : MyPlugin
        {
            public override void Load()
            {
                Bind<IMyPlugin>().To<PluginA>();
                Bind<IController>().To<PluginAController>()
                    .Named(GetControllerName<PluginAController>());
            }
        }
    
    
  5. Setup bindings for any Controllers for the plugin e.g. IController -> PluginAController
    
        public class PluginA : MyPlugin
        {
            public override void Load()
            {
                Bind<IMyPlugin>().To<PluginA>();
                Bind<IController>().To<PluginAController>()
                    .Named(GetControllerName<PluginAController>());
            }
        }
    
    
  6. All plugin Controllers must inherit MyController.cs
    
        public class PluginAController : MyController
        {
            public ActionResult Index()
            {
                return View();
            }
        }
    
    

Wrapping up, we have addressed the following concerns:

  • Loose coupling
  • Separation of concerns
  • Application modularity
  • Building our application from partitioned components
  • IoC & Dependency Injection
  • ServiceLocation with ServiceLocator and/or Ninject’s IKernel
  • Composite pattern

As requirements changed and the project matures, it will be helpful that we can change parts of the application without having these changes cascade throughout the system. Modularizing an application allow you to build application components separately (and loosely coupled) and to change whole parts of your application without affecting the rest of your code base.

Happy coding…! :)