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.
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"
"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
Int64 Id {
get
;
set
; }
DateTime AddedDate {
DateTime ModifiedDate {
string
IPAddress {
The Author and Book entities have one to many relationship, as shown below.
Now, we create an Author entity, which is inherited from BaseEntity class. The code snippet, mentioned below is for the Author entity.
System.Collections.Generic;
Author:BaseEntity
FirstName {
LastName {
Email {
virtual
ICollection<Book> Books {
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).
Microsoft.EntityFrameworkCore.Metadata.Builders;
AuthorMap
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.
Book : BaseEntity
Int64 AuthorId {
Name {
ISBN {
Publisher {
Author Author {
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).
BookMap
BookMap(EntityTypeBuilder<Book> entityBuilder)
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.
Microsoft.EntityFrameworkCore;
ApplicationContext:DbContext
ApplicationContext(DbContextOptions<ApplicationContext> options) :
base
(options)
protected
override
void
OnModelCreating(ModelBuilder modelBuilder)
.OnModelCreating(modelBuilder);
new
AuthorMap(modelBuilder.Entity<Author>());
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.
interface
IRepository<T> where T : BaseEntity
IEnumerable<T> GetAll();
T Get(
long
id);
Insert(T entity);
Update(T entity);
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.
System.Linq;
Repository<T> : IRepository<T> where T : BaseEntity
private
readonly
ApplicationContext context;
DbSet<T> entities;
errorMessage =
.Empty;
Repository(ApplicationContext context)
this
.context = context;
entities = context.Set<T>();
IEnumerable<T> GetAll()
return
entities.AsEnumerable();
id)
entities.SingleOrDefault(s => s.Id == id);
Insert(T entity)
if
(entity ==
null
)
throw
ArgumentNullException(
"entity"
);
entities.Add(entity);
context.SaveChanges();
Update(T entity)
Delete(T entity)
entities.Remove(entity);
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.
ConfigureServices(IServiceCollection services)
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(Configuration.GetConnectionString(
"DefaultConnection"
)));
services.AddScoped(
typeof
(IRepository<>),
(Repository<>));
Here, the DefaultConnection is connection string which defined in appsettings.json file as per following code snippet.
"ConnectionStrings"
"Data Source=DESKTOP-RG33QHE;Initial Catalog=GRepoDb;User ID=sa; Password=****"
"ApplicationInsights"
"InstrumentationKey"
""
"Logging"
"IncludeScopes"
false
"LogLevel"
"Default"
"Debug"
"System"
"Information"
"Microsoft"
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.
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.
GR.Data;
GR.Web.Models;
Microsoft.AspNetCore.Mvc;
GR.Web.Controllers
AuthorController : Controller
IRepository<Author> repoAuthor;
IRepository<Book> repoBook;
AuthorController(IRepository<Author> repoAuthor, IRepository<Book> repoBook)
.repoAuthor = repoAuthor;
.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.
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.
GR.Web.Models
AuthorListingViewModel
Id {
int
TotalBooks {
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]
IActionResult Index()
List<AuthorListingViewModel> model =
List<AuthorListingViewModel>();
repoAuthor.GetAll().ToList().ForEach(a =>
AuthorListingViewModel author =
Id = a.Id,
Name = $
"{a.FirstName} {a.LastName}"
Email = a.Email
};
author.TotalBooks = repoBook.GetAll().Count(x => x.AuthorId == a.Id);
model.Add(author);
});
View(
"Index"
, model);
Now, we create an index view, as per the code snippet, mentioned below under the Author folder of views.
@model IEnumerable<
>
@using GR.Web.Models
@using GR.Web.Code
<
div
=
"top-buffer"
></
"panel panel-primary"
"panel-heading panel-head"
>Authors</
"panel-body"
"btn-group"
a
id
"createEditAuthorModal"
data-toggle
"modal"
asp-action
"AddAuthor"
data-target
"#modal-action-author"
"btn btn-primary"
i
"glyphicon glyphicon-plus"
> Add Author
</
table
"table table-bordered table-striped table-condensed"
thead
tr
th
>Name</
>Email</
>Total Books</
>Action</
tbody
@foreach (var item in Model)
td
>@Html.DisplayFor(modelItem => item.Name)</
>@Html.DisplayFor(modelItem => item.Email)</
>@Html.DisplayFor(modelItem => item.TotalBooks)</
"editAuthorModal"
"EditAuthor"
asp-route-id
"@item.Id"
"btn btn-info"
"glyphicon glyphicon-pencil"
> Edit
"addBookModal"
"AddBook"
"btn btn-success"
"glyphicon glyphicon-book"
> Book
@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"
It shows all forms in bootstrap model popup so create the author - index.js file as per following code snippet.
(
function
($) {
Author() {
var
$
initilizeModel() {
$(
).on(
'loaded.bs.modal'
(e) {
}).on(
'hidden.bs.modal'
).removeData(
'bs.modal'
.init =
() {
initilizeModel();
self =
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.
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.
System.ComponentModel.DataAnnotations;
AuthorBookViewModel
[Display(Name=
"First Name"
)]
[Display(Name =
"Last Name"
"Book Name"
BookName {
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.
PartialViewResult AddAuthor()
AuthorBookViewModel model =
AuthorBookViewModel();
PartialView(
"_AddAuthor"
[HttpPost]
ActionResult AddAuthor(AuthorBookViewModel model)
Author author =
Author
FirstName = model.FirstName,
LastName = model.LastName,
Email = model.Email,
AddedDate = DateTime.UtcNow,
IPAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString(),
ModifiedDate = DateTime.UtcNow,
Books =
List<Book>
Book
Name = model.BookName,
ISBN= model.ISBN,
Publisher = model.Publisher,
ModifiedDate = DateTime.UtcNow
repoAuthor.Insert(author);
RedirectToAction(
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
form
role
"form"
@await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = "Add Author" })
"modal-body form-horizontal"
"row"
"col-lg-6"
"form-group"
label
asp-for
"FirstName"
"col-lg-3 col-sm-3 control-label"
input
"form-control"
/>
"LastName"
"Email"
"BookName"
"ISBN"
"Publisher"
@await Html.PartialAsync("_ModalFooter", new ModalFooter { })
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.
To pass the data from UI to controller to edit an author, define view model named AuthorViewModel, as per the code snippet, mentioned below.
AuthorViewModel
IActionResult EditAuthor(
AuthorViewModel model =
AuthorViewModel();
Author author = repoAuthor.Get(id);
(author !=
model.FirstName = author.FirstName;
model.LastName = author.LastName;
model.Email = author.Email;
"_EditAuthor"
id, AuthorViewModel model)
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);
GET request for the EditAuthor action method returns _EditAuthor partial view, where code snippet is following under the Author folder of views.
@model AuthorViewModel
@await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = "Edit Author" })
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
To pass the data from UI to controller to add a book, define view model named BookViewModel, as per the code snippet, mentioned below.
BookViewModel
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.
PartialViewResult AddBook(
BookViewModel model =
BookViewModel();
"_AddBook"
IActionResult AddBook(
id, BookViewModel model)
Book book =
AuthorId = id,
ISBN = model.ISBN,
repoBook.Insert(book);
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
@await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = "Add Book" })
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
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.
Microsoft.AspNetCore.Mvc.Rendering;
Microsoft.AspNetCore.Http;
BookController : Controller
BookController(IRepository<Author> repoAuthor, IRepository<Book> 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.
BookListingViewModel
AuthorName {
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.
List<BookListingViewModel> model =
List<BookListingViewModel>();
repoBook.GetAll().ToList().ForEach(b =>
BookListingViewModel book =
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);
Now, we create an index view, as per the code snippet, mentioned below under the Book folder of Views.
>Books</
>Author Name</
>ISBN</
>Publisher</
>@Html.DisplayFor(modelItem => item.BookName)</
>@Html.DisplayFor(modelItem => item.AuthorName)</
>@Html.DisplayFor(modelItem => item.ISBN)</
>@Html.DisplayFor(modelItem => item.Publisher)</
"editBookModal"
"EditBook"
"#modal-action-book"
"deleteBookModal"
"DeleteBook"
"btn btn-danger"
"glyphicon glyphicon-trash"
> Delete
@Html.Partial("_Modal", new BootstrapModel { ID = "modal-action-book", AreaLabeledId = "modal-action-book-label", Size = ModalSize.Medium })
"~/js/book-index.js"
It shows all forms in bootstrap model popup, so create the book-index.js file, as per the code snippet, mentioned below.
Book() {
Book();
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
To pass the data from UI to controller to edit a book, define view model named EditBookViewModel, as shown below.
EditBookViewModel
List<SelectListItem> Authors {
; } =
List<SelectListItem>();
"Author"
AuthorId {
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.
PartialViewResult EditBook(
EditBookViewModel model =
EditBookViewModel();
model.Authors = repoAuthor.GetAll().Select(a =>
SelectListItem
Text = $
Value = a.Id.ToString()
}).ToList();
Book book = repoBook.Get(id);
(book !=
model.BookName = book.Name;
model.ISBN = book.ISBN;
model.Publisher = book.Publisher;
model.AuthorId = book.AuthorId;
"_EditBook"
,model);
ActionResult EditBook(
id, EditBookViewModel model)
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);
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
@await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = "Edit Book" })
"AuthorId"
select
asp-items
"@Model.Authors"
option
>Please select</
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
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.
PartialViewResult DeleteBook(
"_DeleteBook"
,book?.Name);
ActionResult DeleteBook(
id, FormCollection form)
repoBook.Delete(book);
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
@Html.Partial("_ModalHeader", new ModalHeader { Heading = "Delete Book" })
Are you want to delete @Model?
@Html.Partial("_ModalFooter", new ModalFooter { SubmitButtonText = "Delete" })
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
You can download complete source code from MSDN Sample, using the links, mentioned below.
Its recommended to read more articles related to ASP.NET Core.
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.