Repository Pattern In ASP.NET Core

Introduction

This article introduces how to implement repository pattern in the ASP.NET Core, using Entity Framework Core. The repository pattern implements in a separate class library project. We will use the "Code First" development approach and create a database from model using migration. We can view this article’s sample on TechNet Gallery. We will create a single entity Student to perform the CRUD operations.

The repository pattern is intended to create an abstraction layer between the data access layer and the business logic layer of an application. It is a data access pattern that prompts a more loosely coupled approach to data access. We create the data access logic in a separate class, or set of classes, called a repository with the responsibility of persisting the application's business model.

Figure 1: Repository Pattern

As per figure 1, the repository mediates between the data source layer and the business layers of the application. It queries the data source for the data, maps the data from the data source to a business entity, and persists changes in the business entity to the data source. A repository separates the business logic from the interactions with the underlying data source. The repository pattern has some advantages which are as follows.

  1. As we can access data source from many locations, so we can apply centrally managed, caching, consistent access rules and logic
  2. As business logic and database access logic are separate, so both can be tested separately.
  3. It provides the code's maintainability and readability by separating business logic from the data or service access logic.

Implement Repository Pattern

First, we create two projects - one is an ASP.NET Core Web Application and another is class library project which are StudentApplication and SA.Data respectively in the solution. The class library (SA.Data) project has data access logic with repository, entities, and context so we install Entity Framework Core in this.

There is an unsupported issue of EF Core 1.0.0-preview2-final with "NETStandard.Library": "1.6.0". So, we have changed the target framework to netstandard1.6 > netcoreapp1.0. We modify the project.json file of SA.Data project to implement Entity Framework Core in this class library project. So the following code snippet for the project.json file after modification.

{
  "dependencies": {
    "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
  },
  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  },
  "tools": {
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
  },
  "version": "1.0.0-*"
}

We are working with Entity Framework Code First approach so the project SA.Data contains entities that are needed in the application's database. In this SA.Data project, we create two entities, one is the BaseEntity class that has common properties that will be inherited by each entity and another is Student. Let's see each entity. The following is a code snippet for the BaseEntity class.

using System;

namespace SA.Data
{
    public class BaseEntity
    {
        public Int64 Id { get; set; }
        public DateTime AddedDate { get; set; }
        public DateTime ModifiedDate { get; set; }
        public string IPAddress { get; set; }
    }
}

Now, we create a Student entity which inherits from the BaseEntity class. The following is a code snippet for the Student entity.

namespace SA.Data
{
    public class Student : BaseEntity
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string EnrollmentNo { get; set; }
    }
}

Now, we define the configuration for the Student entity that will be used when the database table will be created by the entity. The following is a code snippet for the Student mapping entity (StudentMap.cs).

using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace SA.Data
{
    public class StudentMap
    {
        public StudentMap(EntityTypeBuilder<Student> entityBuilder)
        {
            entityBuilder.HasKey(t => t.Id);
            entityBuilder.Property(t => t.FirstName).IsRequired();
            entityBuilder.Property(t => t.LastName).IsRequired();
            entityBuilder.Property(t => t.Email).IsRequired();
            entityBuilder.Property(t => t.EnrollmentNo).IsRequired();
        }
    }
}

The SA.Data project also contains DataContext. The ADO.NET Entity Framework Code First data access approach needs to create a data access context class that inherits from the DbContext class, so we create a context class ApplicationContext (ApplicationContext.cs) class. In this class, we override the OnModelCreating() method. This method is called when the model for a context class (ApplicationContext) has been initialized, but before the model has been locked down and used to initialize the context such that the model can be further configured before it is locked down. The following is the code snippet for the context class.

using Microsoft.EntityFrameworkCore;

namespace SA.Data
{
    public class ApplicationContext : DbContext
    {
        public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            new StudentMap(modelBuilder.Entity<Student>());
        }
    }
}

The DbContext must have an instance of DbContextOptions in order to execute. We will use dependency injection so we pass options via constructor dependency injection.

ASP.NET Core is designed from the ground up to support and leverage dependency injection. So, we create repository interface for student entity so that we can develop loosely coupled applications. The following code snippet for the IStudentRepository interface.

using System.Collections.Generic;  
  
namespace SA.Data  
{  
    public interface IStudentRepository   
    {  
        void SaveStudent(Student student);  
        IEnumerable<Student> GetAllStudents();  
        Student GetStudent(long id);  
        void DeleteStudent(long id);  
        void UpdateStudent(Student student);  
    }  
}  

Now, let's create a repository class to perform CRUD operations on the Student entity which implements IStudentRepository. This repository contains a parameterized constructor with a parameter as Context so when we create an instance of the repository we pass a context so that the entity has the same context. The following is a code snippet for the StudentRepository class under SA.Data project.

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;

namespace SA.Data
{
    public class StudentRepository : IStudentRepository
    {
        private readonly ApplicationContext context;
        private readonly DbSet<Student> studentEntity;

        public StudentRepository(ApplicationContext context)
        {
            this.context = context;
            studentEntity = context.Set<Student>();
        }

        public void SaveStudent(Student student)
        {
            context.Entry(student).State = EntityState.Added;
            context.SaveChanges();
        }

        public IEnumerable<Student> GetAllStudents()
        {
            return studentEntity.AsEnumerable();
        }

        public Student GetStudent(long id)
        {
            return studentEntity.SingleOrDefault(s => s.Id == id);
        }

        public void DeleteStudent(long id)
        {
            Student student = GetStudent(id);
            studentEntity.Remove(student);
            context.SaveChanges();
        }

        public void UpdateStudent(Student student)
        {
            context.Entry(student).State = EntityState.Modified;
            context.SaveChanges();
        }
    }
}

We developed entity and context to create database but we will come to back on this after creating the web application project.

A Web Application Using the Repository Pattern

Now, we create a MVC application (StudentApplication). This is our third project of the application, this project contains user interface for a Student entity's CRUD operations and the controller to do these operations.

As the concept of dependency injection is central to the ASP.NET Core application, so we register both context and repository to the dependency injection during the application start up. So, we register these as a service in the ConfigureServices method in the StartUp class.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddDbContext<ApplicationContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    services.AddTransient<IStudentRepository, StudentRepository>();
}

Here, the DefaultConnection is connection string which defined in appsettings.json file as per following code snippet.

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=DESKTOP-RG33QHE;Initial Catalog=RepoTestDb;User ID=sa; Password=****"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

Now, we have configured settings to create database so we have time to create a database using migration. We must choose the SA.Data project in the package manager console during the performance of the following steps.

  1. Tools –> NuGet Package Manager –> Package Manager Console.
  2. Run PM> Add-Migration MyFirstMigration to scaffold a migration to create the initial set of tables for our model. If we receive an error stating the term ‘add-migration’ is not recognized as the name of a cmdlet, then close and reopen Visual Studio.
  3. Run PM> Update-Database to apply the new migration to the database. Because our database doesn’t exist yet, it will be created for us before the migration is applied.

Create Application User Interface

Now, we proceed to the controller. Create a StudentController under the Controllers folder of the application.

This controller has all ActionResult methods for each user interface of a CRUD operation. We first create a IStudentRepository interface instance then we inject it in the controller's constructor to get its object. The following is a code snippet for the StudentController.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SA.Data;
using StudentApplication.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace StudentApplication.Controllers
{
    public class StudentController : Controller
    {
        private readonly IStudentRepository studentRepository;

        public StudentController(IStudentRepository studentRepository)
        {
            this.studentRepository = studentRepository;
        }

        [HttpGet]
        public IActionResult Index()
        {
            IEnumerable<StudentViewModel> model = studentRepository.GetAllStudents().Select(s => new StudentViewModel
            {
                Id = s.Id,
                Name = $"{s.FirstName} {s.LastName}",
                EnrollmentNo = s.EnrollmentNo,
                Email = s.Email
            });
            return View("Index", model);
        }

        [HttpGet]
        public IActionResult AddEditStudent(long? id)
        {
            StudentViewModel model = new StudentViewModel();
            if (id.HasValue)
            {
                Student student = studentRepository.GetStudent(id.Value);
                if (student != null)
                {
                    model.Id = student.Id;
                    model.FirstName = student.FirstName;
                    model.LastName = student.LastName;
                    model.EnrollmentNo = student.EnrollmentNo;
                    model.Email = student.Email;
                }
            }
            return PartialView("~/Views/Student/_AddEditStudent.cshtml", model);
        }

        [HttpPost]
        public ActionResult AddEditStudent(long? id, StudentViewModel model)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    bool isNew = !id.HasValue;
                    Student student = isNew ? new Student
                    {
                        AddedDate = DateTime.UtcNow
                    } : studentRepository.GetStudent(id.Value);

                    student.FirstName = model.FirstName;
                    student.LastName = model.LastName;
                    student.EnrollmentNo = model.EnrollmentNo;
                    student.Email = model.Email;
                    student.IPAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString();
                    student.ModifiedDate = DateTime.UtcNow;

                    if (isNew)
                    {
                        studentRepository.SaveStudent(student);
                    }
                    else
                    {
                        studentRepository.UpdateStudent(student);
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return RedirectToAction("Index");
        }

        [HttpGet]
        public IActionResult DeleteStudent(long id)
        {
            Student student = studentRepository.GetStudent(id);
            StudentViewModel model = new StudentViewModel
            {
                Name = $"{student.FirstName} {student.LastName}"
            };
            return PartialView("~/Views/Student/_DeleteStudent.cshtml", model);
        }

        [HttpPost]
        public IActionResult DeleteStudent(long id, FormCollection form)
        {
            studentRepository.DeleteStudent(id);
            return RedirectToAction("Index");
        }
    }
}

We can notice that the Controller takes the IStudentRepository as a constructor parameter. The ASP.NET dependency injection will take care of passing an instance of IStudentRepository into student controller. The controller is developed to handle CURD operation requests for the Student entity. Now, let's develop the user interface for the CRUD operations. We develop it for the views for adding and editing a student, a student listing, student deletion. Let's see each one by one.

Student List View

This is the first view when the application is accessed or the entry point of the application is executed. It shows the student listing as in Figure 2. We display student data in tabular format and on this view we create links to add a new student, edit a student and delete a student. This view is an index view and the following is a code snippet for index.cshtml under the Student folder of Views.

@model IEnumerable<StudentViewModel>
@using StudentApplication.Models
@using StudentApplication.Code

<div class="top-buffer"></div>
<div class="panel panel-primary">
    <div class="panel-heading panel-head">Students</div>
    <div class="panel-body">
        <div class="btn-group">
            <a id="createEditStudentModal" data-toggle="modal" asp-action="AddEditStudent" data-target="#modal-action-student" class="btn btn-primary">
                <i class="glyphicon glyphicon-plus"></i> Add Student
            </a>
        </div>
        <div class="top-buffer"></div>
        <table class="table table-bordered table-striped table-condensed">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Enrollment No</th>
                    <th>Action</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var item in Model)
                {
                    <tr>
                        <td>@Html.DisplayFor(modelItem => item.Name)</td>
                        <td>@Html.DisplayFor(modelItem => item.Email)</td>
                        <td>@Html.DisplayFor(modelItem => item.EnrollmentNo)</td>
                        <td>
                            <a id="editStudentModal" data-toggle="modal" asp-action="AddEditStudent" asp-route-id="@item.Id" data-target="#modal-action-student"
                               class="btn btn-info">
                                <i class="glyphicon glyphicon-pencil"></i> Edit
                            </a>
                            <a id="deleteStudentModal" data-toggle="modal" asp-action="DeleteStudent" asp-route-id="@item.Id" data-target="#modal-action-student" class="btn btn-danger">
                                <i class="glyphicon glyphicon-trash"></i> Delete
                            </a>
                        </td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
</div>

@Html.Partial("_Modal", new BootstrapModel { ID = "modal-action-student", AreaLabeledId = "modal-action-student-label", Size = ModalSize.Medium })

@section scripts
{
    <script src="~/js/student-index.js" asp-append-version="true"></script>
}

When we run the application and call the index() action with a HttpGet request, then we get all the students listed in the UI as in Figure 2. This UI has options for CRUD operations.

Figure 2: Student Listing UI

Create / Edit Student View

We create a common view to create and edit a student so we create a single student view model. The following code snippet for StudentViewModel.cs.

using System.ComponentModel.DataAnnotations;

namespace StudentApplication.Models
{
    public class StudentViewModel
    {
        public long Id { get; set; }

        [Display(Name = "First Name")]
        public string FirstName { get; set; }

        [Display(Name = "Last Name")]
        public string LastName { get; set; }

        public string Name { get; set; }

        public string Email { get; set; }

        [Display(Name = "Enrollment No")]
        public string EnrollmentNo { get; set; }
    }
}

We show form in bootstrap modal popup and submit using ajax post; that’s why we create a javascript file which contains method for removing loaded data.

(function ($) {
    function Student() {
        var $this = this;

        function initializeModel() {
            $("#modal-action-student")
                .on('loaded.bs.modal', function (e) {
                    // Add logic for when the modal is loaded
                })
                .on('hidden.bs.modal', function (e) {
                    $(this).removeData('bs.modal');
                });
        }

        $this.init = function () {
            initializeModel();
        }
    }

    $(function () {
        var self = new Student();
        self.init();
    })
}(jQuery))

Now, define a create/edit student partial view. The following is the code snippet for _AddEditStudent.cshtml.

@model StudentViewModel
@using StudentApplication.Models

<form asp-action="AddEditStudent" role="form">
    @await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = $"{(@Model.Id == 0 ? "Add" : "Edit")} Student" })
    
    <div class="modal-body form-horizontal">
        <div class="form-group">
            <label asp-for="FirstName" class="col-lg-3 col-sm-3 control-label"></label>
            <div class="col-lg-6">
                <input asp-for="FirstName" class="form-control" />
            </div>
        </div>
        <div class="form-group">
            <label asp-for="LastName" class="col-lg-3 col-sm-3 control-label"></label>
            <div class="col-lg-6">
                <input asp-for="LastName" class="form-control" />
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Email" class="col-lg-3 col-sm-3 control-label"></label>
            <div class="col-lg-6">
                <input asp-for="Email" class="form-control" />
            </div>
        </div>
        <div class="form-group">
            <label asp-for="EnrollmentNo" class="col-lg-3 col-sm-3 control-label"></label>
            <div class="col-lg-6">
                <input asp-for="EnrollmentNo" class="form-control" />
            </div>
        </div>
    </div>

    @await Html.PartialAsync("_ModalFooter", new ModalFooter { })
</form>

Now, run the application and click on Edit button of listing which calls AddEditStudent action method, then we get the UI as in Figure 3 to edit a student.

Figure 3: Edit Student UI

Delete A Student

To delete a student, we follow the process of clicking on the Delete button that exists in the Student listing data then a modal popup shows to ask “Are you sure you want to delete xxx?” after clicking on the Delete button that exists in the popup view such as in Figure 4 then it makes a HttpPost request that calls the DeleteStudent() action method and deletes the student. The following is a code snippet for _DeleteStudent.cshtml.

@model StudentViewModel
@using StudentApplication.Models

@using (Html.BeginForm())
{
    @Html.Partial("_ModalHeader", new ModalHeader { Heading = "Delete Student" })

    <div class="modal-body form-horizontal">
        Are you sure you want to delete @Model.Name?
    </div>
    
    @Html.Partial("_ModalFooter", new ModalFooter { SubmitButtonText = "Delete" })
}

Now, run the application and click on Delete button of listing which call DeleteStudent action method, then we get the UI as in Figure 4 to delete a student.

Figure 4: Delete Student UI

Download

You can download complete source code from TechNet Gallery using following links.

  1. Repository Pattern in ASP.NET Core
  2. CRUD Operations in ASP.NET Core and Entity Framework Core
  3. Rating Star Application in ASP.NET Core

See Also

I would recommend more articles which describes the ASP.NET Core application development with the Entity Framework Core.

  1. Overview Of ASP.NET Core
  2. CRUD Operations in ASP.NET Core using Entity Framework Core Code First
  3. Repository Pattern in ASP.NET Core

Conclusion

This article introduced the repository pattern in the ASP.NET Core, using Entity Framework Core with "code first" development approach. We used bootstrap CSS and JavaScript for the user interface design in this application. What do you think about this article?