Improving MVC 3 applications

In this recipe, we will work through a full MVC 3 application, using patterns and the Entity Framework to give a solid basis for any enterprise application.

Getting ready

We will be using the NuGet Package Manager to install the Entity Framework 4.1 assemblies.

The package installer can be found at http://nuget.codeplex.com/.

We will also be using a database for connecting to the data, and updating.

Open the Improving MVC 3 Applications solution in the included source code examples.

How to do it...

  1. We start off with a couple of tests that will make sure that our controller and our repository work the way that we intend. We need to add a new C# test class named ControllerTests with the following code:
    using System.Collections.Generic;
    using System.Web.Mvc;
    using BusinessLogic;
    using BusinessLogic.Interfaces;
    using DataAccess.Queries;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Rhino.Mocks;
    using UI.Controllers;
    
    namespace Test
    {
      [TestClass]
      public class ControllerTests
      {
        [TestMethod]
        public void ShouldQueryRepository()
        {
          //Arrange
          var repo = MockRepository.GenerateStrictMock<IRepository>();
          repo
            .Stub(x => x.Find(Arg<BlogByTitleQuery>.Is.Anything))
            .Return(new Blog()
          {
            Title = "Test",
            ShortDescription = "This is a test description",
            Posts = new List<Post>()
            {
              new Post()
              {
                Title = "Test Post 1",
                Content = "Test Content 1"
              },
              new Post()
              {
                Title = "Test Post 1",
                Content = "Test Content 1"
              }
            }
          });
          var controller = new HomeController(repo);
    
          //Act
          var result = controller.Index();
    
          //Assert
          Assert.IsInstanceOfType(result, typeof(ViewResult));
          var viewResult = result as ViewResult;
          Assert.IsNotNull(viewResult.Model);
          Assert.IsInstanceOfType(viewResult.Model, typeof(Blog));
          Assert.AreEqual("Test",((Blog)viewResult.Model).Title);
        }
      }
    }
  2. We then want to add a new C# test class named RepositoryTests with the following code, which will define our problem scope:
    using System.Collections.Generic;
    using System.Linq;
    using BusinessLogic;
    using BusinessLogic.Interfaces;
    using DataAccess;
    using DataAccess.Queries;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Rhino.Mocks;
    
    namespace Test
    {
      [TestClass]
      public class RepositoryTests
      {
        [TestMethod]
        public void ShouldQueryBlogs()
        {
          //Arrange
          var context = MockRepository.GenerateStrictMock<IDbContext>();
          context
            .Stub(x => x.AsQueryable<Blog>())
            .Return(new List<Blog>
          {
            new Blog()
            {
              Title = "Test",
              ShortDescription = "This is a test description",
              Posts = new List<Post>()
              {
                new Post()
                {
                  Title = "Test Post 1",
                  Content = "Test Content 1"
                },
                new Post()
                {
                  Title = "Test Post 1",
                  Content = "Test Content 1"
                }
              }
            },
            new Blog(){Title = "Not Test"}
          }.AsQueryable());
          var repository = new BlogRepository(context);
    
          //Act
          var blog = repository.Find(new BlogByTitleQuery("Test"));
    
          //Assert
          Assert.IsNotNull(blog);
          Assert.IsNotNull(blog.Posts);
          Assert.AreEqual(2, blog.Posts.Count());
        }
      }
    }
  3. We then will add our Blog and Post objects as new C# classes to the BusinessLogic project, and the audit objects that will back it with the following code:
    using System;
    using System.Collections;
    using System.Collections.Generic;
    
    namespace BusinessLogic
    {
      public class Blog : AuditableEntity
      {
        public Blog()
        {
          Posts = new List<Post>();
        }
        public string ShortDescription { get; set; }
        public string Title { get; set; }
        public double Rating { get; set; }
    
        public ICollection<Post> Posts { get; set; }
      }
    
      public class AuditableEntity : Entity
      {
        public DateTime Created { get; set; }
        public string CreatedBy { get; set; }
        public DateTime Modified { get; set; }
        public string ModifiedBy { get; set; }
      }
    
      public class Entity
      {
        public int Id { get; set; }
      }
    
      public class Post : Entity
      {
        public string Title { get; set; }
        public string Content { get; set; }
      }
    }
  4. We will then add our mapping for Blog and Post to the DataAccess project as new C# classes with the following code:
    using System.ComponentModel.DataAnnotations;
    using System.Data.Entity.ModelConfiguration;
    using BusinessLogic;
    
    namespace DataAccess.Mappings
    {
      public class BlogMapping : EntityTypeConfiguration<Blog>
      {
        public BlogMapping()
        {
          this.ToTable("Blogs");
          this.HasKey(x => x.Id);
    
          this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("BlogId");
    
          this.Property(x => x.Title).IsRequired().HasMaxLength(250);
    
          this.Property(x => x.Created).HasColumnName("CreationDate").IsRequired();
    
          this.Property(x => x.ShortDescription).HasColumnType("Text").IsMaxLength().IsOptional().HasColumnName("Description");
    
          this.HasMany(x => x.Posts).WithRequired();
        }
    
      }
    
      public class PostMapping : EntityTypeConfiguration<Post>
      {
        public PostMapping()
        {
          this.ToTable("Posts");
          this.HasKey(x => x.Id);
    
          this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("PostId");
    
          this.Property(x => x.Title).IsRequired().HasMaxLength(250);
          this.Property(x => x.Content).IsRequired().HasMaxLength(5000);
    
        }
      }
    }
  5. We now want to add a new interface to the BusinessLogic project named IDbContext with the following code:
    using System;
    using System.Linq;
    
    namespace BusinessLogic.Interfaces
    {
      public interface IDbContext : IDisposable
      {
        IQueryable<T> AsQueryable<T>() where T : class;
        T Add<T>(T item) where T : class;
        T Remove<T>(T item) where T : class;
        T Update<T>(T item) where T : class;
        T Attach<T>(T item) where T : class;
        T Detach<T>(T item) where T : class;
        int SaveChanges();
      }
    }
  6. We then want to modify the BlogContext to use the mappings, and implement the IDbContext interface with the following code:
    public class BlogContext : BaseDbContext, IDbContext
      {
        static BlogContext()
        {
          System.Data.Entity.Database.SetInitializer(new Initializer());
        }
    
        public BlogContext(string connection) : base(connection) { }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
          modelBuilder.Configurations.Add(new BlogMapping());
          modelBuilder.Configurations.Add(new PostMapping());
          base.OnModelCreating(modelBuilder);
        }
      }
    
      public class BaseDbContext : DbContext
      {
        protected BaseDbContext(string connection) : base(connection)
        {
    
        }
    
        public override int SaveChanges()
        {
          var auditableCreates =this.ChangeTracker.Entries().Where(x => x.State == EntityState.Added && x.Entity is AuditableEntity);
    
          foreach (var item in auditableCreates.Select(auditableCreate => auditableCreate.Entity as AuditableEntity))
          {
            item.Created = item.Modified = DateTime.Now;
            item.CreatedBy = item.ModifiedBy = "UserName";
          }
    
          var auditableModifies = this.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified && x.Entity is AuditableEntity);
    
          foreach (var item in auditableModifies.Select(auditableModify => auditableModify.Entity as AuditableEntity))
          {
            item.Modified = DateTime.Now;
            item.ModifiedBy = "UserName";
          }
          return base.SaveChanges();
        }
    
        public IQueryable<T> AsQueryable<T>() where T : class
        {
          return this.Set<T>();
        }
    
        public T Add<T>(T item) where T : class
        {
          this.Set<T>().Add(item);
          return item;
        }
    
        public T Remove<T>(T item) where T : class
        {
          this.Set<T>().Remove(item);
          return item;
        }
    
        public T Update<T>(T item) where T : class
        {
          var entry = this.Entry(item);
    
          if (entry != null)
          {
            entry.CurrentValues.SetValues(item);
          }
    
          else
          {
            this.Attach(item);
          }
    
          return item;
        }
    
        public T Attach<T>(T item) where T : class
        {
          this.Set<T>().Attach(item);
          return item;
        }
    
        public T Detach<T>(T item) where T : class
        {
          this.Entry(item).State = EntityState.Detached;
          return item;
        }
      }
    }
  7. We now want to create the interfaces for the query specifications and the related objects, with the following interfaces:
    using System.Collections.Generic;
    
    namespace BusinessLogic.Interfaces
    {
      public interface IQueryObject
      {
        int Execute(IDbContext context);
      }
    
      public interface ICommandObject
      {
        void Execute(IDbContext context);
      }
    
      public interface IScalarObject<out T>
      {
        T Execute(IDbContext context);
      }
    
      public interface IQueryObject<out T>
      {
        IEnumerable<T> Execute(IDbContext context);
      }
    }
  8. Now, we want to create the classes that implement these interfaces in the BusinessLogic project with the following code:
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using BusinessLogic.Interfaces;
    
    namespace BusinessLogic.Domain
    {
      public class QueryObject : IQueryObject
      {
        public Func<IDbContext, int> ContextQuery { get; set; }
    
        protected void CheckContextAndQuery(IDbContext context)
        {
          if (context == null) throw new ArgumentNullException("context");
    
          if (this.ContextQuery == null) throw new InvalidOperationException("Null Query cannot be executed.");
    
        }
    
        #region IQueryObject<T> Members
    
        public virtual int Execute(IDbContext context)
        {
          CheckContextAndQuery(context);
         return this.ContextQuery(context);
        }
    
        #endregion
      }
    
      public abstract class QueryObjectBase<T> : IQueryObject<T>
      {
        //if this func returns IQueryable then we can add //functionaltly, such as Where, OrderBy, Take, and so on, to //the QueryOjbect and inject that into the expression before //is it is executed
        protected Func<IDbContext, IQueryable<T>> ContextQuery { get; set; }
        protected IDbContext Context { get; set; }
    
        protected void CheckContextAndQuery()
        {
          if (Context == null) throw new InvalidOperationException("Context cannot be null.");
    
          if (this.ContextQuery == null) throw new InvalidOperationException("Null Query cannot be executed.");
        }
    
        protected virtual IQueryable<T> ExtendQuery()
        {
          return this.ContextQuery(Context);
        }
        #region IQueryObject<T> Members
    
        public virtual IEnumerable<T> Execute(IDbContext context)
        {
          Context = context;
          CheckContextAndQuery();
          var query = this.ExtendQuery();
          return query.AsEnumerable() ?? Enumerable.Empty<T>();
        }
    
        #endregion
      }
    
      public class QueryObject<T> : QueryObjectBase<T>
      {
        protected override IQueryable<T> ExtendQuery()
        {
          var source = base.ExtendQuery();
          source = this.AppendExpressions(source);
          return source;
        }
    
        public IQueryObject<T> Take(int count)
        {
          var generics = new Type[] { typeof(T) };
          var parameters = new Expression[] { Expression.Constant(count) };
    
          this.AddMethodExpression("Take", generics, parameters);
          return this;
        }
    
        public IQueryObject<T> Skip(int count)
        {
          var generics = new Type[] { typeof(T) };
          var parameters = new Expression[] { Expression.Constant(count) };
    
          this.AddMethodExpression("Skip", generics, parameters);
          return this;
        }
    
        #region Helper methods
        static ReadOnlyCollection<MethodInfo> QueryableMethods;
        static QueryObject()
        {
          QueryableMethods = new ReadOnlyCollection<MethodInfo>(typeof(System.Linq.Queryable)
            .GetMethods(BindingFlags.Public | BindingFlags.Static).ToList());
        }
    
        List<Tuple<MethodInfo, Expression[]>> _expressionList = new List<Tuple<MethodInfo, Expression[]>>();
    
        private void AddMethodExpression(string methodName, Type[] generics, Expression[] parameters)
        {
          MethodInfo orderMethodInfo = QueryableMethods.Where(m => m.Name == methodName && m.GetParameters().Length == parameters.Length + 1).First();
    
          orderMethodInfo = orderMethodInfo.MakeGenericMethod(generics);
    
          _expressionList.Add(new Tuple<MethodInfo, Expression[]>(orderMethodInfo, parameters));
        }
    
        private IQueryable<T> AppendExpressions(IQueryable<T> query)
        {
          var source = query;
          foreach (var exp in _expressionList)
          {
            var newParams = exp.Item2.ToList();
            newParams.Insert(0, source.Expression);
            source = source.Provider.CreateQuery<T>(Expression.Call(null, exp.Item1, newParams));
          }
          return source;
        }
        #endregion
    
      }
    
      public class ScalarObject<T> : IScalarObject<T>
      {
        public Func<IDbContext, T> ContextQuery { get; set; }
    
        protected void CheckContextAndQuery(IDbContext context)
        {
          if (context == null) throw new ArgumentNullException("context");
    
          if (this.ContextQuery == null) throw new InvalidOperationException("Null Query cannot be executed.");
        }
    
        #region IQueryObject<T> Members
    
        public virtual T Execute(IDbContext context)
        {
          CheckContextAndQuery(context);
          return this.ContextQuery(context);
        }
    
        #endregion
      }
    }
  9. We now want to modify the IRepository interface to accept the query objects on Find with the following code:
    using System.Collections.Generic;
    
    namespace BusinessLogic.Interfaces
    {
      public interface IRepository
      {
        T Find<T>(IScalarObject<T> query);
        IEnumerable<T> Find<T>(IQueryObject<T> query);
        T Add<T>(T item) where T: class;
        T Remove<T>(T item) where T : class;
        T Update<T>(T item) where T : class;
        T Attach<T>(T item) where T : class;
        T Detach<T>(T item) where T : class;
         void SaveChanges();
      }
    }
  10. We then modify the BlogRepository to implement this interface with the following code:
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using BusinessLogic;
    using BusinessLogic.Interfaces;
    
    namespace DataAccess
    {
      public class BlogRepository : IRepository, IDisposable
      {
        private readonly IDbContext _context;
    
        public BlogRepository(IDbContext context)
        {
          _context = context;
        }
    
        public T Detach<T>(T item) where T : class
        {
          return _context.Detach(item);
        }
    
        public void SaveChanges()
        {
          _context.SaveChanges();
        }
    
        public void Dispose()
        {
    
         }
    
        public T Find<T>(IScalarObject<T> query)
        {
          return query.Execute(_context);
        }
    
        public IEnumerable<T> Find<T>(IQueryObject<T> query)
        {
          return query.Execute(_context);
        }
        public T Add<T>(T entity) where T : class
        {
          return _context.Add(entity);
        }
    
        public T Remove<T>(T entity) where T : class
        {
          return _context.Remove(entity);
        }
    
        public T Update<T>(T item) where T : class
        {
          throw new NotImplementedException();
        }
    
        public T Attach<T>(T item) where T : class
        {
          throw new NotImplementedException();
        }
      }
    }
  11. The last step is to modify the HomeController to supply the correct data to the view with the following code:
    using System.Web.Mvc;
    using BusinessLogic.Interfaces;
    using DataAccess.Queries;
    
    namespace UI.Controllers
    {
      public class HomeController : Controller
      {
        private readonly IRepository _repository;
    
        public HomeController(IRepository repo)
        {
          _repository = repo;
        }
    
        public ActionResult Index()
        {
          var blog = _repository.Find(new BlogByTitleQuery("Test"));
          return View(blog);
        }
        public ActionResult About()
        {
          return View();
        }
      }
    }
  12. All of our tests pass.

How it works...

We start with a set of tests, one to define the behavior we want from our controller, and the other to define the behavior we want from the repository. The controller should send a query to the repository but have no control over how the predefined query is executed, and the repository should execute the query with no knowledge of what is in it. This gives us clear lines of separation, and allows us to plug in new queries at will.

We then need to define our object graph, in this case, a Blog with many Post objects. These are mapped conventionally to focus our attention on the problem domain.

We then add our interface to the DbContext that will allow interaction with the database in an abstract fashion. We implement this interface on the DbContext, and abstract the re-usable chunks into a base repository that will allow us to reuse this behavior at will.

We then define the IQueryObject , ICommandObject , IScalarObject , IQueryObject<out T> interfaces to our Query objects. This will give us a contract definition that can be implemented for new queries. The objects that implement this will store the query and a function on IDbContext to give us a deferred execution and compose-ability. These interfaces will also give some extension points, through the Extend query, which will allow us to implement behaviors such as paging or ordering.

We define the repository interface to accept the query objects as parameters for the Find methods. The implementation of these Find methods invoke the execute method of the query, and pass in the DbContext. This gives the repository a layer of separation from the implementation of queries, and keeps the repository interface simple and incredibly powerful.

There's more...

This leverages the specification pattern, expression trees, and strategy pattern that are well worth understanding and we are using them extensively.

Specification pattern

The specification pattern is a pattern that allows the business rules, or, in our case, queries to be chained together and reused by simply adhering to a standard contract. We specified that all Query objects had to have an execute method that is overrideable and is a ContextQuery. This allows us to chain them together and define ever more complex query paths without sacrificing the elegance and simplicity of the solution.

Expression trees

Expression trees are a data representation of code, and are traversable as such. They allow us to build large and complex code chunks, store them until needed, and supply the parameters required for them to be compiled and executed. This gives us a fairly flexible framework for defining queries and database interactions without needing to focus on when the call will be made.

Strategy pattern

The Strategy pattern simply allows the functionality being executed to vary without the code exercising it being changed. This is to say that we pass in new derived types with new ContextQueries, without the code that uses it ever having to know that is is dealing with a different object. No if statements, no switch statements, just exercising the behavior we have pre-loaded.

See also

Chapter 5, Improving Entity Framework with Query Libraries:

  • Increasing Performance with Code Access
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset