Nov 30, 2014

N-Layered App with Entity Framework, Autofac, ASP.NET MVC and Unit Testing

In my recent post, I explained how to implement a decoupled, unit-testable, N tier architecture based on Generic Repository Pattern with Entity Framework, IoC Container and Dependency Injection in ASP.NET MVC, then I got feedback against the repository/Unit of Work pattern. On googling, I found some more posts:

Say No to the Repository Pattern in your DAL

Repositories On Top UnitOfWork Are Not a Good Idea

Why Entity Framework renders the Repository pattern obsolete?

Entity Framework already implements a repository pattern. Implementing another layer on top of this is not only redundant, but makes maintenance harder. You might want to mock your Entity Framework context rather than using the repository pattern. This post explains how you can implement N Layered app without repository/unit of work pattern on top of EF.

Environment:

1. .NET Framework 4.5

2. ASP.NET MVC 5.1

3. Entity Framework 6.1.1 Code First

4. Sql Server LocalDB v11

5. Autofac 3.4

6. Moq

7. Visual Studio 2012

Setting up the Solution:

1. Open Visual Studio, Create new "ASP.NET MVC 5 Empty Project" Say "SampleArch".

2. In the solution, Add following new "Class Library" Projects

SampleArch.Model

SampleArch.Service

Remove Class1.cs which is created by default.

3. Add project > Test > "Unit Test project" with following name

SampleArch.Test

Remove UnitTest1.cs which is created by default.

The Model:

First install EntityFramework, run the following command in the Package Manager Console

Install-Package EntityFramework

In "SampleArch.Model" project, Add following base classes

IEntity.cs:


  public interface IEntity<T> 
   {
       T Id { get; set; }
   }

Entity.cs:


 public abstract class BaseEntity { 
    
    }
    public abstract class Entity<T> : BaseEntity, IEntity<T> 
    {
        public virtual T Id { get; set; }
    }

IAuditableEntity.cs:


  public interface IAuditableEntity 
    {
        DateTime CreatedDate { get; set; }
     
        string CreatedBy { get; set; }

        DateTime UpdatedDate { get; set; }
             
        string UpdatedBy { get; set; }
    }

AuditableEntity.cs:


  public abstract class AuditableEntity<T> : Entity<T>, IAuditableEntity    
    {
        [ScaffoldColumn(false)]
        public DateTime CreatedDate { get; set; }

      
        [MaxLength(256)]
        [ScaffoldColumn(false)]
        public string CreatedBy { get; set; }

        [ScaffoldColumn(false)]
        public DateTime UpdatedDate { get; set; }

        [MaxLength(256)]
        [ScaffoldColumn(false)]
        public string UpdatedBy { get; set; }
    }

ScaffoldColumn(false) is used So that ASP.NET MVC Scaffolding will NOT generate controls for this in Views. We will handle these properties in context SaveChanges method.

Let us add Person and Country Models:


 [Table("Person")]
    public class Person : AuditableEntity<long>
    {       

        [Required]
        [MaxLength(50)]
        public string Name { get; set; }

        [Required]
        [MaxLength(20)]
        public string Phone { get; set; }

        [Required]
        [MaxLength(100)]
        public string Address { get; set; }

        [Required]
        [MaxLength(50)]
        public string State { get; set; }

        [Display(Name="Country")]
        public int CountryId { get; set;  }

        [ForeignKey("CountryId")]
        public virtual Country Country { get; set; }

    }
	
	 public class Country : Entity<int>
    {      

        [Required]
        [MaxLength(100)]
        [Display(Name="Country Name")]
        public string Name { get; set; }

        public virtual IEnumerable<Person> Persons { get; set; }
    }

Adding DB Context:


 public interface IContext
    {
        IDbSet<Person> Persons { get; set; }
        IDbSet<Country> Countries { get; set; }
       
        DbSet<TEntity> Set<TEntity>() where TEntity : class;
        DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;

        int SaveChanges();
    }


    public class SampleArchContext : DbContext, IContext
    {

        public SampleArchContext()
            : base("Name=SampleArchContext")
        {
            //this.Configuration.LazyLoadingEnabled = false; 
        }

        public IDbSet<Person> Persons { get; set; }
        public IDbSet<Country> Countries { get; set; }
        
		protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();  
            base.OnModelCreating(modelBuilder);
        }

        public override int SaveChanges()
        {
            var modifiedEntries = ChangeTracker.Entries()
                .Where(x => x.Entity is IAuditableEntity
                    && (x.State == System.Data.Entity.EntityState.Added || x.State == System.Data.Entity.EntityState.Modified));

            foreach (var entry in modifiedEntries)
            {
                IAuditableEntity entity = entry.Entity as IAuditableEntity;
                if (entity != null)
                {
                    string identityName = Thread.CurrentPrincipal.Identity.Name;
                    DateTime now = DateTime.UtcNow;

                    if (entry.State == System.Data.Entity.EntityState.Added)
                    {
                        entity.CreatedBy = identityName;
                        entity.CreatedDate = now;
                    }
                    else {
                        base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
                        base.Entry(entity).Property(x => x.CreatedDate).IsModified = false;                   
                    }

                    entity.UpdatedBy = identityName;
                    entity.UpdatedDate = now;
                }
            }

            return base.SaveChanges();
        }       
    }

Now in web.config of asp.net MVC "SampleArch" project, add connectionstring named "SampleArchContext".


<connectionStrings>
  <add name="SampleArchContext"
    connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=SampleArch;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\SampleArch.mdf"
    providerName="System.Data.SqlClient"/>
</connectionStrings>

Service Layer:

In SampleArch.Service project, add references of Model project, add following base services class:

IService.cs:


  public interface IService
    {
    }

IEntityService.cs:


  public interface IEntityService<T> : IService
     where T : BaseEntity
    {
        void Create(T entity);
        void Delete(T entity);
        IEnumerable<T> GetAll();      
        void Update(T entity);
    }

EntityService.cs:


 public abstract class EntityService<T> : IEntityService<T> where T : BaseEntity
    {
        protected IContext _context;
        protected IDbSet<T> _dbset;

        public EntityService(IContext context)
        {
            _context = context;
            _dbset = _context.Set<T>();
        }     


        public virtual void Create(T entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }

            _dbset.Add(entity);
           _context.SaveChanges(); 
        }


        public virtual void Update(T entity)
        {
            if (entity == null) throw new ArgumentNullException("entity");
            _context.Entry(entity).State = System.Data.Entity.EntityState.Modified;
            _context.SaveChanges(); 
        }

        public virtual void Delete(T entity)
        {
            if (entity == null) throw new ArgumentNullException("entity");         
              _dbset.Remove(entity);
              _context.SaveChanges(); 
        }

        public virtual IEnumerable<T> GetAll()
        {         
             return _dbset.AsEnumerable<T>();
        }
    }

This is common implementation for the services. Let us create Service classes for Country and Person.

ICountryService.cs:


   public interface ICountryService : IEntityService<Country>
    {
        Country GetById(int Id);
    }

CountryService.cs:


public class CountryService : EntityService<Country>, ICountryService
    {
        IContext _context;
        
        public CountryService(IContext context)
            : base(context)
        {
            _context = context;
            _dbset = _context.Set<Country>();
        }

        public Country GetById(int Id) {
            return _dbset.FirstOrDefault(x=>x.Id == Id);
        }
    }

IPersonService.cs:


  public interface IPersonService : IEntityService<Person>
    {
        Person GetById(long Id);
    }

PersonService.cs:


 public class PersonService : EntityService<Person>, IPersonService
    {
        IContext _context;
        public PersonService(IContext context)
            : base(context)
        {
            _context = context;
            _dbset = _context.Set<Person>();
        }

        public override IEnumerable<Person> GetAll()
        {
            return _context.Persons.Include(x => x.Country).ToList(); 
        }

        public Person GetById(long Id) {
            return _dbset.FirstOrDefault(x => x.Id == Id);
        }       
        
    }

Setup AutoFac:

In MVC project, To install Autofac ASP.NET MVC 5 Integration, run the following command in the Package Manager Console

Install-Package Autofac.Mvc5

It will install Autofac also.

As Autofac is setup, we can start to register our classes. Add a Modules folder and add following files:

ServiceModule.cs:


  public class ServiceModule : Autofac.Module
    {

        protected override void Load(ContainerBuilder builder)
        {

            builder.RegisterAssemblyTypes(Assembly.Load("SampleArch.Service"))

                      .Where(t => t.Name.EndsWith("Service"))

                      .AsImplementedInterfaces()

                      .InstancePerLifetimeScope();

        }

    }

It will register every class the ends with "Service" in Autofac.

EFModule.cs:



        protected override void Load(ContainerBuilder builder)
        {         
            builder.RegisterType(typeof(SampleArchContext)).As(typeof(IContext)).InstancePerLifetimeScope();          
        }

In Global.asax, add following in Application_Start method:


            //Autofac Configuration
            var builder = new Autofac.ContainerBuilder();

            builder.RegisterControllers(typeof(MvcApplication).Assembly).PropertiesAutowired();

          
            builder.RegisterModule(new ServiceModule());
            builder.RegisterModule(new EFModule());

            var container = builder.Build();

            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

Add following namespaces also:


using Autofac.Integration.Mvc;
using Autofac;

Country CRUD Implementation:

In MVC project, Add reference of Model and Service Projects.

Add new controller with "MVC 5 Controller with Views using Entity Framework" option,

Enter name "CountryController", Select Model : "Country", DataContext: SampleArchContext, Set "Generate Views" true, Click Add.

You will get action and views implemented using EF. Now let us modify code to use service layer as below:


  public class CountryController : Controller
    {
        //initialize service object
        ICountryService _CountryService;

        public CountryController(ICountryService CountryService)
        {
            _CountryService = CountryService;
        }

        //
        // GET: /Country/
        public ActionResult Index()
        {
            return View(_CountryService.GetAll());
        }

        //
        // GET: /Country/Details/5
        public ActionResult Details(int id)
        {
            return View();
        }

        //
        // GET: /Country/Create
        public ActionResult Create()
        {
            return View();
        }

        //
        // POST: /Country/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Country country)
        {

            // TODO: Add insert logic here
            if (ModelState.IsValid)
            {
                _CountryService.Create(country);
                return RedirectToAction("Index");
            }
            return View(country);

        }

        //
        // GET: /Country/Edit/5
        public ActionResult Edit(int id)
        {            
            Country country = _CountryService.GetById(id);
            if (country == null)
            {
                return HttpNotFound();
            }
            return View(country);
        }

        //
        // POST: /Country/Edit/5
        [HttpPost]
        public ActionResult Edit(Country country)
        {

            if (ModelState.IsValid)
            {
                _CountryService.Update(country);
                return RedirectToAction("Index");
            }
            return View(country);

        }

        //
        // GET: /Country/Delete/5
        public ActionResult Delete(int id)
        {
            Country country = _CountryService.GetById(id);
            if (country == null)
            {
                return HttpNotFound();
            }
            return View(country);
        }

        //
        // POST: /Country/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult Delete(int id, FormCollection data)
        {
            Country country = _CountryService.GetById(id);
            _CountryService.Delete(country);
            return RedirectToAction("Index");
        }
    }

Here you can see we are using ICountryService as constructor argument means CountryController will receive service layer as a constructor injection. Constructor Dependency injection makes the controller class very testable.

Now lets see one more CRUD implementation example which includes relationship.

Person CRUD Implementation:

Similar to CountryController, Add PersonController. You will get Country dropdown-list implemented with EF.

If you are getting following error while creating controller:

Multiple object sets per type are not supported. The object sets 'Persons' and 'People' can both contain instances of type 'Person'.

Temporary rename "Persons" to "People" in SampleArchContext, create the controller then revert it back.

Let us modify code to use service layer as below:


 public class PersonController : Controller
    {
        IPersonService _PersonService;
        ICountryService _CountryService;
        public PersonController(IPersonService PersonService, ICountryService CountryService)
        {
            _PersonService = PersonService;
            _CountryService = CountryService;
        }

        // GET: /Person/
        public ActionResult Index()
        {
            return View(_PersonService.GetAll());
        }

        // GET: /Person/Details/5
        public ActionResult Details(long? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Person person = _PersonService.GetById(id.Value);
            if (person == null)
            {
                return HttpNotFound();
            }
            return View(person);
        }

        // GET: /Person/Create
        public ActionResult Create()
        {
            ViewBag.CountryId = new SelectList(_CountryService.GetAll(), "Id", "Name");
            return View();
        }

        // POST: /Person/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "Id,Name,Phone,Address,State,CountryId")] Person person)
        {
            if (ModelState.IsValid)
            {
                _PersonService.Create(person);
                return RedirectToAction("Index");
            }

            ViewBag.CountryId = new SelectList(_CountryService.GetAll(), "Id", "Name", person.CountryId);
            return View(person);
        }

        // GET: /Person/Edit/5
        public ActionResult Edit(long? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Person person = _PersonService.GetById(id.Value);
            if (person == null)
            {
                return HttpNotFound();
            }
            ViewBag.CountryId = new SelectList(_CountryService.GetAll(), "Id", "Name", person.CountryId);
            return View(person);
        }

        // POST: /Person/Edit/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind(Include = "Id,Name,Phone,Address,State,CountryId")] Person person)
        {
            if (ModelState.IsValid)
            {
                _PersonService.Update(person);
                return RedirectToAction("Index");
            }
            ViewBag.CountryId = new SelectList(_CountryService.GetAll(), "Id", "Name", person.CountryId);
            return View(person);
        }

        // GET: /Person/Delete/5
        public ActionResult Delete(long? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Person person = _PersonService.GetById(id.Value);
            if (person == null)
            {
                return HttpNotFound();
            }
            return View(person);
        }

        // POST: /Person/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(long id)
        {
            Person person = _PersonService.GetById(id);
            _PersonService.Delete(person);
            return RedirectToAction("Index");
        }       
    }

Here constructor takes ICountryService and IPersonService objects. CountryService is used for Country dropdownlist and PersonService is for CRUD operations.

No need to change generated Views.

crud

Unit Testing:

In SampleArch.Test project, install Moq by running the following command in the Package Manager Console

Install-Package Moq

Add reference of all other projects in it.

Controller Testing:

There is no change in controller testing as mentioned in "Generic Repository and Unit of Work Pattern, Entity Framework, Unit Testing, Autofac IoC Container and ASP.NET MVC [Part 3]" post.

Service Testing:

For simplicity, We are going to test CountryService GetAll and Create methods by using mockable context.


   [TestClass]
    public class CountryServiceTest
    {
        private ICountryService _service;
        Mock<IContext> _mockContext;
        Mock<DbSet<Country>> _mockSet;
        IQueryable<Country> listCountry;

        [TestInitialize]
        public void Initialize()
        {
            listCountry = new List<Country>() {
             new Country() { Id = 1, Name = "US" },
             new Country() { Id = 2, Name = "India" },
             new Country() { Id = 3, Name = "Russia" }
            }.AsQueryable();

            _mockSet = new Mock<DbSet<Country>>();
            _mockSet.As<IQueryable<Country>>().Setup(m => m.Provider).Returns(listCountry.Provider);
            _mockSet.As<IQueryable<Country>>().Setup(m => m.Expression).Returns(listCountry.Expression);
            _mockSet.As<IQueryable<Country>>().Setup(m => m.ElementType).Returns(listCountry.ElementType);
            _mockSet.As<IQueryable<Country>>().Setup(m => m.GetEnumerator()).Returns(listCountry.GetEnumerator());

            _mockContext = new Mock<IContext>();
            _mockContext.Setup(c => c.Set<Country>()).Returns(_mockSet.Object);
            _mockContext.Setup(c => c.Countries).Returns(_mockSet.Object);

            _service = new CountryService(_mockContext.Object);

        }

        [TestMethod]
        public void Country_Get_All()
        {


            //Act
            List<Country> results = _service.GetAll().ToList() as List<Country>;

            //Assert
            Assert.IsNotNull(results);
            Assert.AreEqual(3, results.Count);
        }


        [TestMethod]
        public void Can_Add_Country()
        {
            //Arrange
            int Id = 1;
            Country country = new Country() { Name = "UK" };

            _mockSet.Setup(m => m.Add(country)).Returns((Country e) =>
            {
                e.Id = Id;
                return e;
            });


            //Act
            _service.Create(country);

            //Assert
            Assert.AreEqual(Id, country.Id);
			_mockContext.Verify(m => m.SaveChanges(), Times.Once()); 
        }
    }

The above test uses Moq to create a context. It then creates a DbSet and wires it up to be returned from the context’s Countries property. Next, the context is used to create a new CountryService which is then used to add a new Country – using the Create method. Finally, the test verifies that the service added a new Country and called SaveChanges on the context.

Source Code:

Conclusion:

We have replaced the Repository/Unit of Work pattern on top of Entity Framework to avoid redundancy. It makes coding simpler and maintenance easier. We have implemented simple CRUD operations and unit testing.

Enjoy Architecture !!