Building ASP.NET Core Web Application With Dependency Injection Passing Objects Between Layers

Introduction

In this article, I will talk about how to build ASP.Net Core Web Application with Dependency Injection passing objects between Layers.

This application is a large scale, enterprise-level application, but there are only several steps necessary to create an application.

ASP.net core uses MVC (model-view-controller) design pattern so that we do not have to worry about the issue of the “separation of concern” anymore.

Since .Net core has built-in Dependency Injection, which solves tight coupling issues, the architecture of this application includes Repository and Service layers. Each layer interacts with interfaces.

This database is from AdventureWorks2019 in SQL Server with Entity Framework Core.

Although the frontend is ASP.net Razor with jQuery Ajax in this example, with this architecture it is easy to change to Restful Web API with Angular, Knockout, React, or any other frontend combinations.

Project

This project uses Visual Studio Community 2019 and SQL Server with Entity Framework Core.

The Database is AdventureWorks2019 from Microsoft using the following tables: Department, EmployeeDepartmentHistory, Person, EmailAddress.

It is based on Asp.Net Core MVC (model view controller) and click events are handled by jQuery ajax to call Action Methods in the Controller.

Starting with the Department List, a user chooses data from the list, a new Partial View shows up each time. The final result is the image below.

Building ASP.Net Core Web Application with Dependency Injection passing objects between Layers

There are several steps to create this application,

  1. Layers
  2. Database and Entity Framework 
  3. Models and DbContext for EF 
  4. Connecting to SQL server and Register Services 
  5. Repository Layer
  6. Service Layer
  7. Controller 
  8. Views
  9. jQuery

(1) Layers

This application is based on MVC (Model-View-Controller), but it has additional layers, Repository layer and Service layer.

In general, the Repository layer includes DataAccess and generic repository and the service layer contains business logic and some validation code.

Each layer has a separate responsibility “separation of concerns” so that your application is easier to maintain over time.

The Service layer communicates between a controller and repository layer.

For this application, folders are used in order to simplify as shown below.

Building ASP.Net Core Web Application with Dependency Injection passing objects between Layers

(2) Database and Entity Framework

From Advantureworks2019, the following tables are used: Department, EmployeeDepartmentHistory, Person, EmailAddress.

The Microsoft Entity Framework is an Object Relational Mapping (ORM) tool.

The Entity Framework eliminates most of the data access code and you can create queries using LINQ to manipulate data.

For .Net Core, an installation of EntityFrameworkCore.SQL server from Nuget is required.

(3) Models and DbContext for EF

Models

A model class defines a table in a database and properties of the model class map to columns in the table in the database.

For example, the code below is the Department class for HumanResouces.Department table in AdventureWorks2019.

[Table("Department", Schema = "HumanResources")]
public class Department {
    public short DepartmentID {
        get;
        set;
    }
    public string Name {
        get;
        set;
    }
}

You need to create the rest of the classes, EmployeeDepartmentHistory, Person, and EmailAddress in the same way.

DbContext

The DbContext class is the main part of Entity Framework.

This context class has DbSet<TEntity> properties for each entity in the model. 

public class deptempContext: DbContext {
    public virtual DbSet < Department > Department {
        get;
        set;
    }
    public virtual DbSet < EmployeeDepartmentHistory > EmployeeDepartmentHistory {
        get;
        set;
    }
    public virtual DbSet < Person > Person {
        get;
        set;
    }
    public virtual DbSet < PersonEmailAddress > EmailAddress {
        get;
        set;
    }
    public deptempContext(DbContextOptions < deptempContext > options): base(options) {}
}

(4) Connecting to SQL server and Register Services

In order to connect a database in SQL Server and establish Dependency Injection, add some code to appsettings.json and Start.up.cs

appsettings.json

Here is an example of Connection String for SQL server in appsettings.json.

The name of this connection string, "deptEmpConnstr", is also used in Startup class to connect to SQL Server.

"ConnectionStrings": {
    "deptEmpConnstr": "Server=XXXXXXX;Database=AdventureWorks2019; Trusted_Connection=True;MultipleActiveResultSets=true;",
},

Startup.cs

Since ASP.NET Core framework includes built-in IoC container for dependency injection, you just need to register DbContext and Services in the Configure method of Startup class.

public void ConfigureServices(IServiceCollection services) {
    services.AddControllersWithViews();
    services.AddMvc();
    services.AddDbContext < deptempContext > (options => 
    options.UseSqlServer(Configuration.GetConnectionString("deptEmpConnstr")));
    services.AddScoped < IDepartmentRepository, DepartmentRepository > ();
    services.AddScoped < IDeptHistoryRepository, DeptHistoryRepository > ();
    services.AddScoped < IDeptHistoryService, DeptHistoryService > ();
})

(5) Repository Layer

Repository layer includes the data access and a generic repository that can retrieve data from a database and map data to a business entity.

When you need to change some columns in tables in a Database, placing all of your database logic in a separate repository layer has less impact on your application.

The code below shows Repository class with Dependency Injection, in this case, Constructor Injection. 

After DbContext class is instantiated, each method uses LINQ to retrieve data from database. 

public class DeptHistoryRepository: IDeptHistoryRepository {
    private readonly deptempContext _context;
    public DeptHistoryRepository(deptempContext context) {
        _context = context;
    }
    //Get all departments
    public IEnumerable < Department > GetAllDepartment() {
        return _context.Department.ToList();
    }
    //Get Departments by Id
    public Department GetDeptById(short deptId) {
        var deptData = _context.Department.Where(d => d.DepartmentID == deptId).FirstOrDefault();
        return deptData;
    }
    public IEnumerable < EmployeeDepartmentHistory > GetEmpDeptById(short deptId) {
        var list = _context.EmployeeDepartmentHistory.Where(d => d.DepartmentID == deptId).ToList();
        return list;
    }
    //from Person.Person table, (int BusinessEtityID )
    public Person GetPersonById(int Id) {
        Person prn = _context.Person.Where(p => p.BusinessEntityID == Id).FirstOrDefault();
        return prn;
    }
    //from Person.EmailAddress table (int BusinessEtityID)
    public PersonEmailAddress GetEmailById(int Id) {
        PersonEmailAddress EAddress = _context.EmailAddress.Where(e => e.BusinessEntityID == Id).FirstOrDefault();
        return EAddress;
    }
}

(6) Service Layer

A service layer is an additional layer that contains business logic and validation logic. 

However, this application does not have complete CRUD operation, only Read operation, the validation logic is not included.

Here is an example of Service class.

public class DeptHistoryRepository: IDeptHistoryRepository {
    private readonly deptempContext _context;
    public DeptHistoryRepository(deptempContext context) {
        _context = context;
    }
    //Get all department
    public IEnumerable < Department > GetAllDepartment() {
        return _context.Department.ToList();
    }
    //Get Department by Id
    public Department GetDeptById(short deptId) {
        var deptData = _context.Department.Where(d => d.DepartmentID == deptId).FirstOrDefault();
        return deptData;
    }
    public IEnumerable < EmployeeDepartmentHistory > GetEmpDeptById(short deptId) {
        var list = _context.EmployeeDepartmentHistory.Where(d => d.DepartmentID == deptId).ToList();
        return list;
    }
    //from Person.Person table, (int BusinessEtityID )
    public Person GetPersonById(int Id) {
        Person prn = _context.Person.Where(p => p.BusinessEntityID == Id).FirstOrDefault();
        return prn;
    }
    //from Person.EmailAddress table (int BusinessEtityID)
    public PersonEmailAddress GetEmailById(int Id) {
        PersonEmailAddress EAddress = _context.EmailAddress.Where(e => e.BusinessEntityID == Id).FirstOrDefault();
        return EAddress;
    }
  }
}

(7) Controller

As mentioned earlier, putting all database logic and business logic into a controller is not a good idea over time. 

The Controller talks to the Service layer and the Service layer talks to the Repository layer and each one uses Constructor Injection for DI.

If there are any changes in business logics or databases, this approach is much easier to change the implementation without modifying the controller.

The code below, DeptHistoryController injects the DeptHistoryService and accesses it in Index action.

public class DeptHistoryController: Controller {
    private readonly IDeptHistoryService _srv;
    public DeptHistoryController(IDeptHistoryService srv) {
        _srv = srv;
    }
    public IActionResult Index() {
        var list = _srv.GetAllDepartment();
        return View(list);
    }
    //Department Partial View
    public IActionResult prvShow_subDept(string sId) {
        if (String.IsNullOrEmpty(sId)) {
            return PartialView("_PersonInDept");
        }
        short shortId = short.Parse(sId);
        //Get Dept Name from service
        var deptInfo = _srv.GetDeptById(shortId);
        //Pass Department name to PartialView(PersonInDept)
        ViewBag.vbDeptName = deptInfo.Name;
        //Get persons from EmployeeDepartmentHistory table
        var personsInfo = _srv.GetPersonsFromEmpDeptHistory(shortId);
        return PartialView("_PersonInDept", personsInfo);
    }
    //Personal Detail Pertial View
    public IActionResult prvShow_subPersonDetail(string sId) {
        if (string.IsNullOrEmpty(sId)) {
            return PartialView("_PersonDetail");
        }
        int iId = int.Parse(sId);
        vmPersonDetail personInfo = _srv.GetPersonDetail(iId);
        return PartialView("_PersonDetail", personInfo);
    }
}

(8) Views

In the Model-View-Controller (MVC) design pattern, the view handles data presentation and user interaction. It is like a Presentation Layer.

Below is the Index.cshtml. Because of using Partial Views that reduce the duplication of common markup content, this index page is easier to maintain.

<div id="mainDept" class="Box1">
    <h5 style="text-decoration:underline double;">Department List</h5>
    <div class="index_mainDev">
        <table id="deptTable" border="1">
            <tr class="tblHeader">
                <td>ID</td> <td>Department Name</td> <td>Click to Detail</td>
            </tr>
            @foreach (var item in Model){
                <tr>
                    <td>@Html.DisplayFor(modelItem => item.DepartmentID)</td>
                    <td>@Html.DisplayFor(modelItem => item.Name)</td>
                    <td><input type="button" class="btnDeptIdSelect" value="....." /></td>
                </tr>
            }
        </table>
    </div>
</div>
<div id="subDept" class="Box2">
    <h3>Person in Department</h3>
</div>
<div id="subPerson" class="Box3">
    <h3>Person Details</h3>
</div>

(9) jQuery

jQuery AJAX method updates parts of a Web page without reloading the whole page. 

The code below shows that jQuery Ajax method calls Controller/Action Method, in this case, url: '/DeptHistory/prvShow_subDept' , to open Partial View.

function open_subDept(selectedId) {
    $.ajax({
        url: '/DeptHistory/prvShow_subDept',
        type: 'GET',
        data: {
            sId: selectedId
        },
        success: function(result) {
            $("#subDept").empty().append(result);
            $("#subDept").show();
        },
        error: function() {
            alert("There is something wrong!");
        }
    });
}

Conclusion

This application is a large-scale web application and solves the issues of tight coupling and separation of concerns so that the application will be testable, maintainable, and dependable as most developers’ dream.