Share via


ASP.NET Core: Generic Repository Pattern

Introduction

This article introduces how to implement generic repository pattern in ASP.NET Core, using Entity Framework Core. The generic repository pattern implements in a separate class library project. It uses the "Code First" development approach and creates a database from a model, using migration. This article demonstrates a sample Application, which has one to many relationship in ASP.NET Core with Entity Framework Core. The Application source code is available for download on MSDN sample.

The repository pattern is intended to create an Abstraction layer between the Data Access layer and Business Logic layer of an Application. It is a data access pattern that prompts a more loosely coupled approach to data access. We create a generic repository, which 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.

Implement Generic Repository Pattern

To implement generic repository pattern, create two projects - one is an ASP.NET Core Web Application and another is a class library project, which are GR.Web and GR.Data respectively in the solution. The class library is named as SA.Data project, which has data access logic with generic repository, entities and context, so we install Entity Framework Core in this project.

There is an unsupported issue of EF Core 1.0.0-preview2-final with "NETStandard.Library": "1.6.0". Thus, we have changed the target framework to netstandard1.6 > netcoreapp1.0. We modify the project.json file of GR.Data project to implement Entity Framework Core in this class library project. Thus, the code snippet, mentioned below 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-*"      
      }  

This Application uses the Entity Framework Code First approach, so the project GR.Data contains entities that are required in the Application's database. The GR.Data project holds three entities, one is the BaseEntity class that has common properties that will be inherited by each entity. The code snippet, mentioned below is for the BaseEntity class.



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

The Author and Book entities have one to many relationship, as shown below.

Figure 1: Author-Book Relationship

Now, we create an Author entity, which is inherited from BaseEntity class. The code snippet, mentioned below is for the Author entity.

using System.Collections.Generic;
 
namespace GR.Data
{
    public class  Author:BaseEntity
    {
        public string  FirstName { get; set; }
        public string  LastName { get; set; }
        public string  Email { get; set; }
        public virtual  ICollection<Book> Books { get; set; }
    }
}

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



      using Microsoft.EntityFrameworkCore.Metadata.Builders;
   
      namespace GR.Data
      {  
                    public class  AuthorMap  
                    {      
                        public AuthorMap(EntityTypeBuilder<Author> entityBuilder)  
                        {      
                            entityBuilder.HasKey(t => t.Id);      
                            entityBuilder.Property(t => t.FirstName).IsRequired();      
                            entityBuilder.Property(t => t.LastName).IsRequired();      
                            entityBuilder.Property(t => t.Email).IsRequired();                 
                        }      
                    }      
      }  

Now, we create a Book entity, which inherits from the BaseEntity class. The code snippet, mentioned below is for the Book entity.



      using System;
   
      namespace GR.Data
      {  
                    public class  Book : BaseEntity  
                    {      
                        public Int64 AuthorId { get; set; }  
                        public string  Name { get; set; }  
                        public string  ISBN { get; set; }  
                        public string  Publisher { get; set; }  
                        public virtual  Author Author { get; set; }  
                    }      
      }  

Now, we define the configuration for the Book entity that will be used when the database table will be created by the entity. The code snippet is mentioned below for the Book mapping entity (BookMap.cs).



      using Microsoft.EntityFrameworkCore.Metadata.Builders;
   
      namespace GR.Data
      {  
                    public class  BookMap  
                    {      
                        public BookMap(EntityTypeBuilder<Book> entityBuilder)  
                        {      
                            entityBuilder.HasKey(t => t.Id);      
                            entityBuilder.Property(t => t.Name).IsRequired();      
                            entityBuilder.Property(t => t.ISBN).IsRequired();      
                            entityBuilder.Property(t => t.Publisher).IsRequired();      
                            entityBuilder.HasOne(e => e.Author).WithMany(e => e.Books).HasForeignKey(e => e.AuthorId);                  
                        }      
                    }      
      }  

The GR.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 GR.Data
      {  
                    public class  ApplicationContext:DbContext  
                    {      
                        public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)  
                        {      
                        }      
                        protected override  void OnModelCreating(ModelBuilder modelBuilder)  
                        {      
                            base      .OnModelCreating(modelBuilder);      
                            new AuthorMap(modelBuilder.Entity<Author>());  
                            new BookMap(modelBuilder.Entity<Book>());  
                        }      
                    }      
      }  

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 to support and leverage dependency injection. Thus, we create generic repository interface for the entity operations, so that we can develop loosely coupled Applications. The code snippet, mentioned below is for the IRepository interface.



      using System.Collections.Generic;
   
      namespace GR.Data
      {  
                    public interface  IRepository<T> where T : BaseEntity  
                    {      
                        IEnumerable<T> GetAll();      
                        T Get(      long id);  
                        void Insert(T entity);          
                        void Update(T entity);  
                        void Delete(T entity);  
                    }      
      }  

Now, let's create a repository class to perform CRUD operations on the entity, which implements IRepository. 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 code snippet is mentioned below for the Repository class under GR.Data project.

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
 
namespace GR.Data
{
    public class  Repository<T> : IRepository<T> where T : BaseEntity
    {
        private readonly  ApplicationContext context;
        private DbSet<T> entities;
        string errorMessage = string.Empty;
 
        public Repository(ApplicationContext context)
        {
            this.context = context;
            entities = context.Set<T>();
        }
        public IEnumerable<T> GetAll()
        {
            return entities.AsEnumerable();
        }
 
        public T Get(long id)
        {
            return entities.SingleOrDefault(s => s.Id == id);
        }
        public void  Insert(T entity)
        {
            if (entity == null)
            {
                throw new  ArgumentNullException("entity");
            }
            entities.Add(entity);
            context.SaveChanges();
        }
 
        public void  Update(T entity)
        {
            if (entity == null)
            {
                throw new  ArgumentNullException("entity");
            }
            context.SaveChanges();
        }
 
        public void  Delete(T entity)
        {
            if (entity == null)
            {
                throw new  ArgumentNullException("entity");
            }
            entities.Remove(entity);
            context.SaveChanges();
        }
    }
}
We developed entity and context which are required to create a database but we will come back to this after creating the Web Application project.

A Web Application Using the Generic Repository Pattern

Now, we create a MVC Application (GR.Web). This is our second project of the Application. This project contains user interface for both author and book entities database operations and the controller to do these operations.

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



      public void  ConfigureServices(IServiceCollection services)
                      {      
                          // Add framework services.      
                          services.AddApplicationInsightsTelemetry(Configuration);      
   
                          services.AddMvc();      
                          services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(Configuration.GetConnectionString(      "DefaultConnection"      )));                  
                          services.AddScoped(      typeof      (IRepository<>),       typeof      (Repository<>));      
                      }      

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=GRepoDb;User ID=sa; Password="      
                  },      
                  "ApplicationInsights"      : {      
                    "InstrumentationKey"      :       ""      
                  },      
                  "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 GR.Data project in the Package Manager console during the performance of the steps, mentioned below.

  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. We create two controllers, where one is AuthorController and another is BookController under the Controllers folder of the Application.

These controllers have all ActionResult methods for each user interface of an operation. We create an IRepository interface instance, then we inject it in the controller's constructor to get its object. The following is a partial code snippet for the AuthorController in which repository is injected, using constructor dependency injection.



      using GR.Data;
      using GR.Web.Models;
      using Microsoft.AspNetCore.Mvc;
      using System;
      using System.Collections.Generic;
      using System.Linq;
              
      namespace GR.Web.Controllers
      {  
                    public class  AuthorController : Controller  
                    {      
                        private IRepository<Author> repoAuthor;  
                        private IRepository<Book> repoBook;  
                        public AuthorController(IRepository<Author> repoAuthor, IRepository<Book> repoBook)  
                        {      
                            this      .repoAuthor = repoAuthor;      
                            this      .repoBook = repoBook;      
                        }      
                    }      
      }  

We can notice that Controller takes the IRepository as a constructor parameter. ASP.NET dependency injection will take care of passing an instance of IRepository into Author controller. The controller is developed to handle operations requests for both Author and Book entities. Now, let's develop the user interface for the Author Listing, Add Author with Book, Edit Author and Add Book. Let's see each one by one.

Author List View

This is the first view when the Application is accessed or the entry point of the application is executed. It shows the author listing as in Figure 2. The author data is displayed in a tabular format and on this view, it has linked to add a new author with his/her book, edit an author and add a book.

To pass data from controller to view, create named AuthorListingViewModel view model, as per code snippet, mentioned below.



      namespace GR.Web.Models
      {  
                    public class  AuthorListingViewModel  
                    {      
                        public long  Id { get; set; }  
                        public string  Name { get; set; }  
                        public string  Email { get; set; }  
                        public int  TotalBooks { get; set; }  
                    }      
      }  

Now, we create action method, which returns an index view with the data. The code snippet of Index action method in AuthorController is mentioned below.



      [HttpGet]  
                        public IActionResult Index()  
                        {      
                            List<AuthorListingViewModel> model =       new  List<AuthorListingViewModel>();  
                            repoAuthor.GetAll().ToList().ForEach(a =>      
                            {      
                                AuthorListingViewModel author =       new  AuthorListingViewModel  
                                {      
                                    Id = a.Id,      
                                    Name = $      "{a.FirstName} {a.LastName}"      ,      
                                    Email = a.Email      
                                };      
                                author.TotalBooks = repoBook.GetAll().Count(x => x.AuthorId == a.Id);      
                                model.Add(author);      
                            });      
                            return View("Index", model);  
                        }      

Now, we create an index view, as per the code snippet, mentioned below under the Author folder of views.



      @model IEnumerable<    AuthorListingViewModel    >  
      @using GR.Web.Models  
      @using GR.Web.Code  
              
      <    div class="top-buffer"></div>
      <    div class="panel panel-primary">
                    <      div class="panel-heading panel-head">Authors</div>  
                    <      div class="panel-body">  
                        <      div class="btn-group">  
                            <      a id="createEditAuthorModal" data-toggle="modal" asp-action="AddAuthor" data-target="#modal-action-author" class="btn btn-primary">  
                                <      i class="glyphicon glyphicon-plus"></i>  Add Author  
                            </      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      >Total Books</      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.TotalBooks)</      td      >      
                                        <      td      >      
                                            <      a id="editAuthorModal" data-toggle="modal" asp-action="EditAuthor" asp-route-id="@item.Id" data-target="#modal-action-author"  
                                               class      =      "btn btn-info"      >      
                                                <      i class="glyphicon glyphicon-pencil"></i>  Edit  
                                            </      a      >      
                                            <      a id="addBookModal" data-toggle="modal" asp-action="AddBook" asp-route-id="@item.Id" data-target="#modal-action-author" class="btn btn-success">  
                                                <      i class="glyphicon glyphicon-book"></i>  Book  
                                            </      a      >      
                                        </      td      >      
                                    </      tr      >      
                                }      
                            </      tbody      >      
                        </      table      >      
                    </      div      >      
      </    div    >  
              
      @Html.Partial("_Modal", new BootstrapModel { ID = "modal-action-author", AreaLabeledId = "modal-action-author-label", Size = ModalSize.Large })  
              
      @section scripts  
      {     
                    <      script src="~/js/author-index.js" asp-append-version="true"></script>  
      }  

It shows all forms in bootstrap model popup so create the author - index.js file as per following code snippet.



      (    function ($) {
                    function Author() {  
                        var $this = this;  
              
                        function initilizeModel() {  
                            $(      "#modal-action-author"      ).on(      'loaded.bs.modal'      ,       function  (e) {  
              
                            }).on(      'hidden.bs.modal'      ,       function  (e) {  
                                $(      this      ).removeData(      'bs.modal'      );      
                            });      
                        }      
                        $      this      .init =       function  () {  
                            initilizeModel();      
                        }      
                    }      
                    $(      function () {  
                        var self = new Author();  
                        self.init();      
                    })      
      }(jQuery))  

When we run the Application and call the index() action method from AuthorController with a HttpGet request, we get all the authors listed in the UI, as shown in Figure 2.

Figure2: Author Listing

Add Author

To pass the data from UI to controller to add an author with his/her book, define view model named AuthorBookViewModel, as per the code snippet, mentioned below.



      using System.ComponentModel.DataAnnotations;
              
      namespace GR.Web.Models
      {  
                    public class  AuthorBookViewModel  
                    {      
                        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  Email { get; set; }  
                        [Display(Name =       "Book Name"      )]      
                        public string  BookName { get; set; }  
                        public string  ISBN { get; set; }  
                        public string  Publisher { get; set; }  
                    }      
      }  

The AuthorController has an action method named AddAuthor, which returns view to add an author. The code snippet is mentioned below is for same action method for both GET and Post requests.



      [HttpGet]  
                      public PartialViewResult AddAuthor()  
                      {      
                          AuthorBookViewModel model =       new  AuthorBookViewModel();  
                          return PartialView("_AddAuthor", model);  
                      }      
              
                      [HttpPost]      
                      public ActionResult AddAuthor(AuthorBookViewModel model)  
                      {      
                          Author author =       new  Author  
                          {      
                              FirstName = model.FirstName,      
                              LastName = model.LastName,      
                              Email = model.Email,      
                              AddedDate = DateTime.UtcNow,      
                              IPAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString(),      
                              ModifiedDate = DateTime.UtcNow,      
                              Books =       new  List<Book>  
                              {      
                                  new Book  
                                  {      
                                      Name = model.BookName,      
                                      ISBN= model.ISBN,      
                                      Publisher = model.Publisher,      
                                      IPAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString(),      
                                      AddedDate = DateTime.UtcNow,      
                                      ModifiedDate = DateTime.UtcNow      
                                  }      
                              }      
                          };      
                          repoAuthor.Insert(author);      
                          return RedirectToAction("Index");  
                      }      

The GET request for the AddAuthor action method returns _AddAuthor partial view, which code snippet is following under the Author folder of views.



      @model AuthorBookViewModel  
      @using GR.Web.Models  
              
      <    form asp-action="AddAuthor" role="form">
                    @await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = "Add Author" })      
                    <      div class="modal-body form-horizontal">  
                        <      div class="row">  
                            <      div class="col-lg-6">  
                                <      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      >      
                            <      div class="col-lg-6">  
                                <      div class="form-group">  
                                    <      label asp-for="BookName" class="col-lg-3 col-sm-3 control-label"></label>  
                                    <      div class="col-lg-6">  
                                        <      input asp-for="BookName" class="form-control" />  
                                    </      div      >      
                                </      div      >      
                                <      div class="form-group">  
                                    <      label asp-for="ISBN" class="col-lg-3 col-sm-3 control-label"></label>  
                                    <      div class="col-lg-6">  
                                        <      input asp-for="ISBN" class="form-control" />  
                                    </      div      >      
                                </      div      >      
                                <      div class="form-group">  
                                    <      label asp-for="Publisher" class="col-lg-3 col-sm-3 control-label"></label>  
                                    <      div class="col-lg-6">  
                                        <      input asp-for="Publisher" class="form-control" />  
                                    </      div      >      
                                </      div      >      
                            </      div      >      
                        </      div      >      
                    </      div      >      
                    @await Html.PartialAsync("_ModalFooter", new ModalFooter { })      
      </    form    >  

When the Application runs and clicks on the Add Author button, it makes a GET request for the AddAuthor() action, add an author screen, as shown in Figure 3.

Figure 3: Add Author and Book Screen

Edit Author

To pass the data from UI to controller to edit an author, define view model named AuthorViewModel, as per the code snippet, mentioned below.

using System.ComponentModel.DataAnnotations;
  
namespace GR.Web.Models
{
    public class  AuthorViewModel
    {
        [Display(Name = "First Name")]
        public string  FirstName { get; set; }
        [Display(Name = "Last Name")]
        public string  LastName { get; set; }
        public string  Email { get; set; }
    }
}
The AuthorController has an action method named EditAuthor, which returns view to edit an author. The code snippet is mentioned below for same action method for both GET and Post requests.



      [HttpGet]  
                        public IActionResult EditAuthor(long id)  
                        {      
                            AuthorViewModel model =       new  AuthorViewModel();  
                            Author author = repoAuthor.Get(id);      
                            if (author != null)  
                            {      
                                model.FirstName = author.FirstName;      
                                model.LastName = author.LastName;      
                                model.Email = author.Email;      
                            }      
                            return PartialView("_EditAuthor", model);  
                        }      
                        [HttpPost]      
                        public IActionResult EditAuthor(long id, AuthorViewModel model)  
                        {      
                            Author author = repoAuthor.Get(id);      
                            if (author != null)  
                            {      
                                author.FirstName = model.FirstName;      
                                author.LastName = model.LastName;      
                                author.Email = model.Email;      
                                author.IPAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString();      
                                author.ModifiedDate = DateTime.UtcNow;      
                                repoAuthor.Update(author);      
                            }      
                            return RedirectToAction("Index");  
                        }      

GET request for the EditAuthor action method returns _EditAuthor partial view, where code snippet is following under the Author folder of views.



      @model AuthorViewModel  
      @using GR.Web.Models  
              
      <    form asp-action="EditAuthor" role="form">
                    @await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = "Edit Author" })      
                    <      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      >      
                    @await Html.PartialAsync("_ModalFooter", new ModalFooter { })      
      </    form    >  

When an Application runs and clicks on the Edit button in the Author listing, it makes a GET request for the EditAuthor() action, then the edit author screen is shown in Figure 4.

Figure 4: Edit Author

Add Book

To pass the data from UI to controller to add a book, define view model named BookViewModel, as per the code snippet, mentioned below.

using System.ComponentModel.DataAnnotations;
 
namespace GR.Web.Models
{
    public class  BookViewModel
    {
        [Display(Name ="Book Name")]
        public string  BookName { get; set; }
        public string  ISBN { get; set; }
        public string  Publisher { get; set; }
    }
}

The AuthorController has an action method named AddBook, which returns a view to add a book. The code snippet is mentioned below for same action method for both GET and Post requests.

[HttpGet]
        public PartialViewResult AddBook(long id)
        {
            BookViewModel model = new  BookViewModel();
            return PartialView("_AddBook", model);
        }
        [HttpPost]
        public IActionResult AddBook(long id, BookViewModel model)
        {
            Book book = new  Book
            {
                AuthorId = id,
                Name = model.BookName,
                ISBN = model.ISBN,
                Publisher = model.Publisher,
                IPAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString(),
                AddedDate = DateTime.UtcNow,
                ModifiedDate = DateTime.UtcNow
            };
            repoBook.Insert(book);
            return RedirectToAction("Index");
        }

GET request for the AddBook action method returns _AddBook partial view, where the code snippet is following under the Author folder of the views.

@model BookViewModel
@using GR.Web.Models
 
<form asp-action="AddBook" role="form">
    @await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = "Add Book" })
    <div class="modal-body form-horizontal">
             <div class="form-group">
                    <label asp-for="BookName" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="BookName" class="form-control" />
                    </div>
                </div>
                <div class="form-group">
                    <label asp-for="ISBN" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="ISBN" class="form-control" />
                    </div>
                </div>
                <div class="form-group">
                    <label asp-for="Publisher" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="Publisher" class="form-control" />
                    </div>
                </div>
            </div>    
     
    @await Html.PartialAsync("_ModalFooter", new ModalFooter { })
</form>

When an Application runs and clicks on the Book button in the Author listing, it makes a GET request for the AddBook() action, then the add book screen is shows below in Figure 5.

Figure 5: Add Book

Book Listing

These operations are about the author controller. As another controller BookController has some more operations such as Book listing, Edit Book and Delete book. The BookController’s constructor injects repository for both Author and Book entities. The code snippet is mentioned below for the same in the BookController.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using GR.Data;
using GR.Web.Models;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Http;
 
namespace GR.Web.Controllers
{
    public class  BookController : Controller
    {
        private IRepository<Author> repoAuthor;
        private IRepository<Book> repoBook;
        public BookController(IRepository<Author> repoAuthor, IRepository<Book> repoBook)
        {
            this.repoAuthor = repoAuthor;
            this.repoBook = repoBook;
        }
    }
}

Now, click on top menu of the book. It shows the book listing, as shown in Figure 6. The book data is displayed in a tabular format and on this view. These book listing has options to edit a book and delete a book. To pass the data from controller to view, create named BookListingViewModel view model, as shown below.

namespace GR.Web.Models
{
    public class  BookListingViewModel
    {
        public long  Id { get; set; }
        public string  BookName { get; set; }
        public string  AuthorName { get; set; }
        public string  ISBN { get; set; }
        public string  Publisher { get; set; }
    }
}

Now, we create an action method, which returns an index view with the data. The code snippet, mentioned below is of Index action method in BookController.

public IActionResult Index()
       {
           List<BookListingViewModel> model = new  List<BookListingViewModel>();
           repoBook.GetAll().ToList().ForEach(b =>
           {
               BookListingViewModel book = new  BookListingViewModel
               {
                   Id = b.Id,
                   BookName = b.Name,
                   Publisher = b.Publisher,
                   ISBN=b.ISBN
               };
               Author author = repoAuthor.Get(b.AuthorId);
               book.AuthorName = $"{author.FirstName} {author.LastName}";
               model.Add(book);
           });
           return View("Index", model);
       }

Now, we create an index view, as per the code snippet, mentioned below under the Book folder of Views.

@model IEnumerable<BookListingViewModel>
@using GR.Web.Models
@using GR.Web.Code
 
<div class="top-buffer"></div>
<div class="panel panel-primary">
    <div class="panel-heading panel-head">Books</div>
    <div class="panel-body">       
        <div class="top-buffer"></div>
        <table class="table table-bordered table-striped table-condensed">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Author Name</th>
                    <th>ISBN</th>
                    <th>Publisher</th>
                    <th>Action</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var item in Model)
                {
                    <tr>
                        <td>@Html.DisplayFor(modelItem => item.BookName)</td>
                        <td>@Html.DisplayFor(modelItem => item.AuthorName)</td>
                        <td>@Html.DisplayFor(modelItem => item.ISBN)</td>
                        <td>@Html.DisplayFor(modelItem => item.Publisher)</td>
                        <td>
                            <a id="editBookModal" data-toggle="modal" asp-action="EditBook" asp-route-id="@item.Id" data-target="#modal-action-book"
                               class="btn btn-info">
                                <i class="glyphicon glyphicon-pencil"></i>  Edit
                            </a> 
                            <a id="deleteBookModal" data-toggle="modal" asp-action="DeleteBook" asp-route-id="@item.Id" data-target="#modal-action-book"
                               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-book", AreaLabeledId = "modal-action-book-label", Size = ModalSize.Medium })
 
@section scripts
{    
    <script src="~/js/book-index.js" asp-append-version="true"></script>
}

It shows all forms in bootstrap model popup, so create the book-index.js file, as per the code snippet, mentioned below.

(function ($) {
    function Book() {
        var $this = this;
 
        function initilizeModel() {
            $("#modal-action-book").on('loaded.bs.modal', function  (e) {
 
            }).on('hidden.bs.modal', function  (e) {
                $(this).removeData('bs.modal');
            });
        }
        $this.init = function  () {
            initilizeModel();
        }
    }
    $(function () {
        var self = new Book();
        self.init();
    })
}(jQuery))

When we run the Application and click on top menu Book, which calls index() action method with a HttpGet request from BookController, then we get all the book listed in the UI, as shown below.

Figure 6: Book Listing

Edit Book

To pass the data from UI to controller to edit a book, define view model named EditBookViewModel, as shown below.

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
 
namespace GR.Web.Models
{
    public class  EditBookViewModel
    {
        [Display(Name="Book Name")]
        public string  BookName { get; set; }
        public string  ISBN { get; set; }
        public string  Publisher { get; set; }
        public List<SelectListItem> Authors { get; set; } = new  List<SelectListItem>();
        [Display(Name = "Author")]
        public long  AuthorId { get; set; }
    }
}

The BookController has an action method named EditBook, which returns view for editing a book. The code snippet is mentioned below for same action method for both GET and Post requests.

public PartialViewResult EditBook(long id)
        {
            EditBookViewModel model = new  EditBookViewModel();
            model.Authors = repoAuthor.GetAll().Select(a => new  SelectListItem
            {
                Text = $"{a.FirstName} {a.LastName}",
                Value = a.Id.ToString()
            }).ToList();
            Book book = repoBook.Get(id);
            if(book != null)
            {
                model.BookName = book.Name;
                model.ISBN = book.ISBN;
                model.Publisher = book.Publisher;
                model.AuthorId = book.AuthorId;
            }
            return PartialView("_EditBook",model);
        }
        [HttpPost]
        public ActionResult EditBook(long id, EditBookViewModel model)
        {
            Book book = repoBook.Get(id);
            if (book != null)
            {
                book.Name = model.BookName;
                book.ISBN = model.ISBN;
                book.Publisher = model.Publisher;
                book.AuthorId = model.AuthorId;
                book.IPAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString();
                book.ModifiedDate = DateTime.UtcNow;
                repoBook.Update(book);
            }
            return RedirectToAction("Index");
        }

The GET request for the EditBook action method returns _EditBook partial view, where code snippet is mentioned below under the Book folder of views.

@model EditBookViewModel
@using GR.Web.Models
 
<form asp-action="EditBook" role="form">
    @await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = "Edit Book" })
    <div class="modal-body form-horizontal">
        <div class="form-group">
            <label asp-for="BookName" class="col-lg-3 col-sm-3 control-label"></label>
            <div class="col-lg-6">
                <input asp-for="BookName" class="form-control" />
            </div>
        </div>
        <div class="form-group">
            <label asp-for="ISBN" class="col-lg-3 col-sm-3 control-label"></label>
            <div class="col-lg-6">
                <input asp-for="ISBN" class="form-control" />
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Publisher" class="col-lg-3 col-sm-3 control-label"></label>
            <div class="col-lg-6">
                <input asp-for="Publisher" class="form-control" />
            </div>
        </div>
        <div class="form-group">
            <label asp-for="AuthorId" class="col-lg-3 col-sm-3 control-label"></label>
            <div class="col-lg-6">
                <select asp-for="AuthorId" asp-items="@Model.Authors" class="form-control">
                    <option>Please select</option>
                </select>                
            </div>
            </div>
        </div>
 
    @await Html.PartialAsync("_ModalFooter", new ModalFooter { })
</form>

When an Application runs and clicks the Edit button in the listed books, it makes a GET request for the EditBook() action, then the edit book screen is shown below.

Figure 7: Edit Book

Delete Book

The BookController has an action method named DeleteBook, which returns view to delete a book. The code snippet is mentioned below for same action method for both GET and Post requests.

[HttpGet]
   public PartialViewResult DeleteBook(long id)
   {
       Book book = repoBook.Get(id);            
       return PartialView("_DeleteBook",book?.Name);
   }
   [HttpPost]
   public ActionResult DeleteBook(long id, FormCollection form)
   {
       Book book = repoBook.Get(id);
       if(book != null)
       {
           repoBook.Delete(book);
       }            
       return RedirectToAction("Index");
   }

The GET request for the DeleteBook action method returns _DeleteBook partial view, where code snippet is mentioned below under the Book folder of views.

@model string
@using GR.Web.Models
 
<form asp-action="DeleteBook" role="form">
    @Html.Partial("_ModalHeader", new ModalHeader { Heading = "Delete Book" })
 
    <div class="modal-body form-horizontal">
        Are you want to delete @Model?
    </div>
    @Html.Partial("_ModalFooter", new ModalFooter { SubmitButtonText = "Delete" })
</form>

When an Application runs and you click on the Delete button in the listed books, it makes a GET request for the DeleteBook() action, then the delete book screen is shown below.

Figure 8: Delete Book

Download

You can download complete source code from MSDN Sample, using the links, mentioned below.

  1. Rating Star Application in ASP.NET Core
  2. CRUD Operations in ASP.NET Core and Entity Framework Core
  3. Repository Pattern In ASP.NET Core
  4. Generic Repository Pattern in ASP.NET Core
  5. Onion Architecture In ASP.NET Core MVC
  6. ASP.NET Core MVC: Authentication and Role Based Authorisation with Identity
  7. ASP.NET Core MVC: Authentication and Claim Based authorization with Identity

See Also

Its recommended to read more articles related to ASP.NET Core.

  1. ASP.NET Core: Overview
  2. ASP.NET Core With Visual Studio 2017 RC
  3. ASP.NET Core Entity Framework Core Code First: CRUD Operations
  4. Repository Pattern In ASP.NET Core
  5. Onion Architecture In ASP.NET Core MVC
  6. ASP.NET Core MVC: Authentication And Role Based Authorization With ASP.NET Core Identity
  7. ASP.NET Core MVC: Authentication And Claim Based Authorisation With ASP.NET Identity Core
  8. ASP.NET Core : Overview Of Dependency Injection
  9. ASP.NET Core: In-Memory Caching

Conclusion

This article introduced the generic 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.