Using the Repository Pattern in an ASP.Net MVC 4.0 application

In this post I will demonstrate with a hands-on demo the importance of using patterns in an ASP.Net MVC application. I will use the Repository Pattern to create an additional abstract layer between my domain classes and the data layer.

For a more formal definition, I will use the definition that Martin Fowler has given to his book "Patterns of Enterprise Application Architecture".

"Repository Pattern mediates between the domain and the data mapping layers using a collection-like interface for accessing domain objects".

Basically what that means is that it is a layer to separate our application from the data storage technology. It is a pattern for data access.

I will explain more about this pattern as I build the application.

First I will build a small ASP.Net MVC 4.0 EF Code first application without using the repository pattern and then I will rewrite my code leveraging that pattern.

People who follow this blog have seen me in the past building an application first and then changing it a bit to demonstrate the post's main idea.

I will keep things simple. I will create a simple POCO class, Footballer, and then build an ASP.Net MVC application where one can create,edit and delete Footballer entities.

Also I will show you how to architecture properly your ASP.Net MVC 4.0 EF Code first application.

We should not have our domain classes, data access classes and views,controllers in the same project.

We should have them in 3 different projects.

That way our code is more extensible, maintainable and easy to reuse in other non web applications that might need to consume or use EF Code first data access layer.We have a clear separation of concerns.

 

I have installed VS 2012 Ultimate edition in my Windows 8 machine. Υou can use Visual Studio Express 2012 for Web. You can install Visual Studio Express 2012 for Web if you download Web Platform Installer.You can download this tool from this link.

1)  I am launching VS 2012 and I will Visual C# as the programming language. I will also select ASP.NET MVC 4 Web Application from the available templates. Choose C# as the development language and Internet Application. I will name my application MvcRepository. This will be the startup project.

2) Now that we have our MVC project structure we will create another project in our solution. This will be a Windows Library project. I will name it MvcRepository.DomainClasses. In this project I will add only the definitions of the domain class Footballer. I delete the class1.cs class file.

I create a new class, Footballer.cs. The code follows

   public class Footballer
    {
        public int FootballerID { getset; }
        public string FirstName { getset; }
        public string LastName { getset; }
        public double Weight { getset; }
        public double Height { getset; }
 
 
    }
My class is simple class and knows nothing about the Entity Framework.

3) I will add another project in this solution. This will be a Windows Library project. I will name it MvcRepository.DomainAccess.I delete the class1.cs class file.

In this class I will add classes that act at the data access layer that will interact with the ASP.Net MVC application and the SQL Server database that will be created.

I will add a reference to the MvcRepository.DomainClasses project as well.

Then we need to install Entity Framework 5.0. We will do that through Nuget packages. Make sure you do that.


Then we need to create a context class that inherits from DbContext.Add a new class to project. Name it TheFootballerDBContext.Now that we have the entity class created, we must let the model know.I will have to use the DbSet<T> property.The code for this class follows

 

 public class TheFootballerDBContext : DbContext
    {
 
 public DbSet<Footballer> Footballers { getset; }
 
    }

    Do not forget to add  (using System.Data.Entity;) in the beginning of the class file

 

4) We must take care of the connection string. It is very easy to create one in the web.config.It does not matter that we do not have a database yet.When we run the DbContext and query against it , it will use a connection string in the web.config and will create the database based on the classes.I will use the LocalDb.

In my case the connection string inside the web.config, looks like this

      <add name="TheFootballerDBContext"
 connectionString="Data Source=(LocalDb)\v11.0;
 AttachDbFilename=|DataDirectory|\NewDBFootballers.mdf;
 Integrated Security=True"
 providerName="System.Data.SqlClient" />

 

5) Now we need to access our model from a controller.This is going to be a simple class that retrieves the footballers data.

I will add a reference to the MvcRepository.DomainClasses project.

Right-click the Controllers folder and create a new FootballerController controller. Have a look at the picture below to set the appropriate settings and then click Add.

Have a look at the picture below


 

 Visual Studio 2012 will create the following


A FootballerController.cs file in the project's Controllers folder.
A Footballer folder in the project's Views folder.
Create.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml, and Index.cshtml in the new Views\Footballer folder.

Then we need to make a small change to the _Layout.cshtml file. We will add this line of code to add another item in the menu so we can navigate to the Footballers page.

 

    <li>@Html.ActionLink("Footballers""Index""Footballer")</li>

 

6)  Build and run your application.Navigate to the localhost/youport/footballer

You have a UI ready for you to add,edit,delete footballers. I will add 2-3 entries.

7) Now we are ready to change our code to incorporate the Repository pattern.

I am going to show you the contents of the FootballerController.cs class, so you can see changes we will make to the controller later on, when we incorporate the Repository pattern.

 

    public class FootballerController : Controller
    {
        private TheFootballerDBContext db = new TheFootballerDBContext();
 
        //
        // GET: /Footballer/
 
        public ActionResult Index()
        {
 
        
            return View(db.Footballers.ToList());
        }
 
        //
        // GET: /Footballer/Details/5
 
        public ActionResult Details(int id = 0)
        {
            Footballer footballer = db.Footballers.Find(id);
            if (footballer == null)
            {
                return HttpNotFound();
            }
            return View(footballer);
        }
 
        //
        // GET: /Footballer/Create
 
        public ActionResult Create()
        {
            return View();
        }
 
        //
        // POST: /Footballer/Create
 
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Footballer footballer)
        {
            if (ModelState.IsValid)
            {
                db.Footballers.Add(footballer);
                db.SaveChanges();
                return RedirectToAction("Index");
            }
 
            return View(footballer);
        }
 
        //
        // GET: /Footballer/Edit/5
 
        public ActionResult Edit(int id = 0)
        {
            Footballer footballer = db.Footballers.Find(id);
            if (footballer == null)
            {
                return HttpNotFound();
            }
            return View(footballer);
        }
 
        //
        // POST: /Footballer/Edit/5
 
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(Footballer footballer)
        {
            if (ModelState.IsValid)
            {
                db.Entry(footballer).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(footballer);
        }
 
        //
        // GET: /Footballer/Delete/5
 
        public ActionResult Delete(int id = 0)
        {
            Footballer footballer = db.Footballers.Find(id);
            if (footballer == null)
            {
                return HttpNotFound();
            }
            return View(footballer);
        }
 
        //
        // POST: /Footballer/Delete/5
 
        [HttpPostActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            Footballer footballer = db.Footballers.Find(id);
            db.Footballers.Remove(footballer);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
 
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
 public class FootballerController : Controller
    {
        private TheFootballerDBContext db = new TheFootballerDBContext();

I want to remind you that in an MVC application the View is my user interface and the Controller interacts with the view.

Ιn the line above I can see that I create an instance of the TheFootballerDBContext class that inherits from the DbContext class. That means that I work directly with EF.

I do not want that. I do not want to work with EF in all the layers of my application.

Have a look in some action methods in the FootballerController.cs class

        public ActionResult Index()
        {
            return View(db.Footballers.ToList());
        }
 
        //
        // GET: /Footballer/Details/5
 
        public ActionResult Details(int id = 0)
        {
            Footballer footballer = db.Footballers.Find(id);
            if (footballer == null)
            {
                return HttpNotFound();
            }
            return View(footballer);
        }

You can see that there are queries that talk directly to the data access layer.Not just the queries in those two methods but in all methods in the controller. That is not the ideal option.We need a new abstract layer between the Controller class and the DbContext class.

 

We will do that by adding another class.In that way our code facilitates unit testing and test driven development.

8) Now we will add another class file to the MvcRepository.DomainAccess project. I will name it IFootballerRepository.cs. The code follows

 

    public interface IFootballerRepository : IDisposable
    {
        IEnumerable<Footballer> GetFootballers();
        Footballer GetFootballerByID(int FootballerID);
        void InsertFootballer(Footballer footballer);
        void DeleteFootballer(int FootballerID);
        void UpdateFootballer(Footballer footballer);
        void Save();
    }

In this class we declare CRUD methods.We also have two read methods. The first one returns all Footballer entities, and the second one finds a single Footballer entity by ID.

Now we will add another class file to the MvcRepository.DomainAccess project. I will name it FootballerRepository.cs. It will implement the IFootballerRepository interface. Interfaces cannot be instantiated and the methods they declare cannot be implemented. You can inherit only from one base class but from multiple interfaces.

The code for the FootballerRepository.cs follows

    public class FootballerRepository : IFootballerRepositoryIDisposable
    {
        private TheFootballerDBContext context;
 
        public FootballerRepository(TheFootballerDBContext context)
        {
            this.context = context;
        }
 
        public IEnumerable<Footballer> GetFootballers()
        {
            return context.Footballers.ToList();
        }
 
        public Footballer GetFootballerByID(int id)
        {
            return context.Footballers.Find(id);
        }
 
        public void InsertFootballer(Footballer footballer)
        {
            context.Footballers.Add(footballer);
        }
 
        public void DeleteFootballer(int FootballerID)
        {
            Footballer footballer = context.Footballers.Find(FootballerID);
            context.Footballers.Remove(footballer);
        }
 
        public void UpdateFootballer(Footballer footballer)
        {
            context.Entry(footballer).State = EntityState.Modified;
        }
 
        public void Save()
        {
            context.SaveChanges();
        }
 
        private bool disposed = false;
 
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
        }
 
        public void Dispose()
        {
            Dispose(true);
       
        }
    }

The database context is defined in a class variable, and the constructor expects the calling object to pass in an instance of the context.

The implementation of the various methods is straightforward

       public void InsertFootballer(Footballer footballer)
        {
            context.Footballers.Add(footballer);
        }

In the simple method above, a footballer entity is passed from the calling application (controller) to the method and this is added to the collection of footballers.

Please run your application and place breakpoints so you can see what is going on.

We also implement the SaveChanges method so all the data that lives in the memory is saved back to the datastore in a transactional way.

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

We also implement the IDisposable and dispose the database context.

Now I will show you the complete code (with the changes made) to the FootballerController.cs class. I have used italics to highlight the changes.

 

    public class FootballerController : Controller
    {
        private IFootballerRepository footballerRepository = null;
 
    public FootballerController()
      
    {
this.footballerRepository=new FootballerRepository(new TheFootballerDBContext());
    }
 
    public FootballerController(IFootballerRepository footballerRepository)
    {
        this.footballerRepository = footballerRepository;
    }
 
        //
        // GET: /Footballer/
 
        public ActionResult Index()
        {
IEnumerable<Footballer> footballers=footballerRepository.GetFootballers();
            return View(footballers);
        }
 
        //
        // GET: /Footballer/Details/5
 
        public ViewResult Details(int id = 0)
        {
Footballer footballer = footballerRepository.GetFootballerByID(id);
            return View(footballer);
        }
 
        //
        // GET: /Footballer/Create
 
        public ActionResult Create()
        {
            return View();
        }
 
        //
        // POST: /Footballer/Create
 
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Footballer footballer)
        {
            if (ModelState.IsValid)
            {
        footballerRepository.InsertFootballer(footballer);
        footballerRepository.Save();
        return RedirectToAction("Index");
            }
 
            return View(footballer);
        }
 
        //
        // GET: /Footballer/Edit/5
 
        public ActionResult Edit(int id = 0)
        {
 Footballer footballer = footballerRepository.GetFootballerByID(id);
            if (footballer == null)
            {
                return HttpNotFound();
            }
            return View(footballer);
        }
 
        //
        // POST: /Footballer/Edit/5
 
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(Footballer footballer)
        {
            if (ModelState.IsValid)
            {
     footballerRepository.UpdateFootballer(footballer);
     footballerRepository.Save();
     return RedirectToAction("Index");
            }
            return View(footballer);
        }
 
        //
        // GET: /Footballer/Delete/5
 
        public ActionResult Delete(int id = 0)
        {
  Footballer footballer = footballerRepository.GetFootballerByID(id);
 
            return View(footballer);
        }
 
        //
        // POST: /Footballer/Delete/5
 
        [HttpPost,ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
   Footballer footballer = footballerRepository.GetFootballerByID(id);
  footballerRepository.DeleteFootballer(id);
  footballerRepository.Save();
  return RedirectToAction("Index");
        }
 
        protected override void Dispose(bool disposing)
        {
            footballerRepository.Dispose();
            base.Dispose(disposing);
        }
    }

The controller declares a class variable for an object that implements the IFootballerRepository interface.

The default constructor creates a new context instance, and the other constructor allows the caller to pass in a context instance.

All the methods in the controller are very easy to understand.

Have a look at the method below.

Instead of working with the DbContext (EF) directly we now make calls to the Repository methods (footballerRepository.ΙnsertFootballer(footballer)) that are part of our data access layer (MvcRepository.DomainAccess) that talk with the TheFootballerDBContext that inherits from the DbContext = EF. But this time we have the controller talking to the repository that talks to the data access layer that talks to the database.

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Footballer footballer)
        {
            if (ModelState.IsValid)
            {
   footballerRepository.InsertFootballer(footballer);
   footballerRepository.Save();
   return RedirectToAction("Index");
            }
 
            return View(footballer);
        }

 

Build and run your application. If you have understood and followed everything your ASP.Net MVC will work just fine. Try to add some more footballer entries.

 

Hope it helps!!!

1 Comment

Comments have been disabled for this content.