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 !!

16 comments

  1. Not a good architecture. All three projects reference EntityFramework. So what’s the point of all this then? Sorry, I’ll have to move on.

    1. I have the same interrogation. most solution include entityframework everywhere. Is it required to do that? its more an EF question, not about the article.

  2. Its good up until the tests which I fear are pointless. Mocks are great for mocking infrastructure dependencies in business logic tests i.e. mock these data services to generate a known dataset to test outcomes against, but here with those mocks what are you really testing? Imo these should be physical integration tests using LocalDB to test real expected behaviour with underlying database engine. By mocking iqueryable these tests generate little value.

  3. Where do I add a call to a stored procedure with .SqlQuery? I attempted within my individual entity’s service classes but _context doesn’t have .Database and _dbset doesn’t have .SqlQuery??

  4. I see that all 3 projects reference Entity Framework which is a little confusing to me. Isn’t the idea of having a service layer between your front end and your data project to have separation of concerns? If you wanted to switch out EF for something like Nhibernate how would you do that with Entity Framework being used within all the projects?

    This isn’t really just an issue with your example but I am curious myself of how I would keep my models within one class library, my EF (infrastructure) in another, a service layer to abstract out the models and the infrastructure libraries from my front end, and one or more front end projects that would only access the service layer, not requiring references to EF or Nhibernate but simply passing DTOs to my service layer which would deal with the business logic and mapping the data to domain models where the infrastructure layer could handle database communications. Unless I am wrong, wouldn’t only the infrastructure layer need to reference EF or any other ORM at that point?

  5. Thanks for a fantastic article :)

    Why do you use the _context.Entry(entity).State = System.Data.Entity.EntityState.Modified;?

    And is the solution a good one for a large web solution?

    Regards
    Jakob

  6. This is a great approach. Could you please show us how to get the current user’s id using AutoFac in the ApplicationDbContext class SaveChanges method? Thank you!

    1. async methods, notracking method, other, in others links, assume create a custom IDbSet and implements other methods, notracking, async, etc

  7. Another great complete working example. Excellent post, thanks.

    I’m not sure on one thing, the two service classes, CountryService.cs and PersonService.cs look a lot like repositories, what makes them different? Why are they called services? Or could they be called CountryRepository.cs / PersonRepository.cs instead?

Leave a Reply

Your email address will not be published. Required fields are marked *