Introduction
In my first two articles, we first looked at the four common types of .NET applications we may find out in the field. We briefly discussed these four types of applications and then in the second article, we looked at the design and architecture at a high level which would apply to all these applications in general terms. Next, we build the first type of application, the .NET desktop application. Today, we will look at the second and probably the most common type of application, the .NET web application.
Requirements for building this application
It is assumed the reader of this article has some basic knowledge and understanding of .NET development and C#, which is the programming language used for developing all the samples. In addition, the following tools would be required.
- SQL Server 2008 Express Edition
- SQL Server Management Studio
- Visual Studio 2019 Community Edition
All the above tools are free to download and use. We will be building the application using the .NET Core 3.1 and its related technologies. So, let’s begin. The advantage of using .NET Core over the .NET framework is that this solution can be deployed to Linux and Mac environments in addition to Windows.
The Data Source
The first step is to build the data source, where we would be saving the data. Here, I will create a new database called “EmployeeDB”. However, we will not be creating the Employee table, as this would be done using the Entity Framework Core and the Code First approach. This code would also be used to add the initial data, also known as seeding the database. This will all be done in the data access layer. So, let’s move directly to the data access layer.
The Data Access Layer
The data access layer would be created as a Web API application. A Web API application is an application that exposes functionality over an HTTP protocol via calls made to it and returns data, most commonly in the JSON format. This data can then be used in the application as required.
We start by creating a new ASP.NET Core Web application of type “API”. Also, remember to uncheck the “Configure for HTTPS” option and ensure “No Authentication” is selected for the Authentication. We will discuss authentication in some future articles.
Here, we have created a solution by the name of “EmployeeWeb” and a project by the name of “EmployeeDALWebApi”.
Now, we start to create the pieces of the data access layer. The first thing we need to create is the employee model class. This will be the model which will then be created in the database. Here, we create a new folder called “DataAccessLayer” and inside it another folder called “Models”. Inside this folder, we create a new class called “Employee” with the below code.
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace EmployeeDALWebApi.DataAccessLayer.Models
{
public class Employee
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Employee_ID { get; set; }
[Required]
[Column(TypeName = "nvarchar(50)")]
public string First_Name { get; set; }
[Required]
[Column(TypeName = "nvarchar(50)")]
public string Last_Name { get; set; }
[Required]
[Column(TypeName = "smalldatetime")]
public DateTime Date_Birth { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string Street_Address { get; set; }
[Required]
[Column(TypeName = "nvarchar(50)")]
public string City { get; set; }
[Required]
[Column(TypeName = "char(2)")]
public string Province { get; set; }
[Required]
[Column(TypeName = "smalldatetime")]
public DateTime Date_Joining { get; set; }
[Column(TypeName = "smalldatetime")]
public DateTime? Date_Leaving { get; set; }
}
}
Please note that the following NuGet packages are required for the solution.
As we will be using the Entity Framework Core technology, we need to create the Context class which is used to communicate with the underlying data store. Hence, we will next create a class called “EmployeeContext” in the “DataAccessLayer” folder.
using Microsoft.EntityFrameworkCore;
using EmployeeDALWebApi.DataAccessLayer.Models;
using System;
namespace EmployeeDALWebApi.DataAccessLayer
{
public class EmployeeContext : DbContext
{
public EmployeeContext(DbContextOptions options) : base(options) {}
public DbSet<Employee> Employees { get; set; }
}
}
Now before we can create the table in the database using the Code First approach, we need to specify the database connection. This is done in the appsettings.json file, as shown below.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionString": {
"EmployeeCS": "Data Source=localhost\\SQLEXPRESS;Initial Catalog=EmployeeDB;Integrated Security=True"
},
"AllowedHosts": "*"
}
Also, remember to update the “Startup.cs” file as mentioned later in this article section. We can run the below commands and create the table in the EmployeeDB database.
After this, we will see that a Migrations folder is created that has classes related to migration and a rollback for this migration as well.
Next, we will add the code to create the initial data also known as seeding the database. See the below changes to the EmployeeContext class.
using Microsoft.EntityFrameworkCore;
using EmployeeDALWebApi.DataAccessLayer.Models;
using System;
namespace EmployeeDALWebApi.DataAccessLayer
{
public class EmployeeContext : DbContext
{
public EmployeeContext(DbContextOptions options) : base(options) {}
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>().HasData(new Employee {
Employee_ID = 1,
First_Name = "John",
Last_Name = "Doe",
Date_Birth = new DateTime(1980, 10, 01),
Street_Address = "100 Street West",
City = "Toronto",
Province = "ON",
Date_Joining = new DateTime(2020, 01, 15)
});
}
}
}
Next, we do another migration called “SeedEmployeesTable”. Now, we have a table created with an initial record, as shown below.
The next step will be to create a repository pattern to access the context to maintain and view the data. This will be done by creating the below classes in a new folder called “Repositories”. Please note that here we will have only one repository as we only have one table.
using System.Collections.Generic;
namespace EmployeeDALWebApi.Repositories
{
public interface IDataRepository<TEntity>
{
IEnumerable<TEntity> GetAll();
TEntity Get(long id);
void Add(TEntity entity);
void Update(TEntity dbEntity, TEntity entity);
void Delete(TEntity entity);
}
}
using System.Collections.Generic;
using System.Linq;
using EmployeeDALWebApi.DataAccessLayer.Models;
using EmployeeDALWebApi.DataAccessLayer;
using System;
namespace EmployeeDALWebApi.Repositories
{
public class EmployeeRepository : IDataRepository<Employee>
{
readonly EmployeeContext _employeeContext;
public EmployeeRepository(EmployeeContext context)
{
_employeeContext = context;
}
public IEnumerable<Employee> GetAll()
{
return _employeeContext.Employees.ToList();
}
public Employee Get(long id)
{
throw new NotImplementedException();
}
public void Add(Employee entity)
{
throw new NotImplementedException();
}
public void Update(Employee employee, Employee entity)
{
throw new NotImplementedException();
}
public void Delete(Employee employee)
{
throw new NotImplementedException();
}
}
}
The final step in the data access layer will be to create a controller class and add actions to it. This is done by creating a new controller class under the “Controllers” folder as shown below.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using EmployeeDALWebApi.Repositories;
using EmployeeDALWebApi.DataAccessLayer.Models;
namespace EmployeeDALWebApi.Controllers
{
[Route("api/employee")]
[ApiController]
public class EmployeeController : ControllerBase
{
private readonly IDataRepository<Employee> _employeeRepository;
public EmployeeController(IDataRepository<Employee> employeeRepository)
{
_employeeRepository = employeeRepository;
}
[HttpGet]
public IActionResult Get()
{
IEnumerable<Employee> employees = _employeeRepository.GetAll();
return Ok(employees);
}
}
}
As you might have noticed, we are using the dependency injection of .NET Core to pass the “EmployeeContext” to the employee repository and to pass the “EmployeeRepository” to the employee controller. Hence, these need to be configured in the “Startup.cs” file, as shown below.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using EmployeeDALWebApi.DataAccessLayer;
using Microsoft.EntityFrameworkCore;
using EmployeeDALWebApi.Repositories;
using EmployeeDALWebApi.DataAccessLayer.Models;
namespace EmployeeDALWebApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<EmployeeContext>(opts => opts.UseSqlServer(Configuration["ConnectionString:EmployeeCS"]));
services.AddScoped<IDataRepository<Employee>, EmployeeRepository>();
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Please note that the “AddDbContext” must be done before the migrations can be run to create the employee table and add the initial data.
Now that the data access layer is complete, we can run the solution and browse to “API/employee”. We will see the below screenshot in our browser.
Next, we move to the business logic layer.
The Business Logic Layer
This layer will be the one to make the call to the data access layer and get data from it. Next, it will transform it as required and then forward it to the front end or presentation layer. It is in this layer that any business logic is written. However, for this application, we only need to do some simple tasks, like calculate the age of the employee from the date of birth, etc.
We create a new Class Library project for .NET Core called “EmployeeBAL” and create the following inside it.
using System;
namespace EmployeeBAL
{
public class EmployeeUI
{
public int Employee_ID { get; set; }
public string Employee_Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public DateTime Date_Joining { get; set; }
public DateTime? Date_Leaving { get; set; }
}
}
using System.Collections.Generic;
using System.Threading.Tasks;
namespace EmployeeBAL
{
public interface IEmployeeBAL
{
Task<List<EmployeeUI>> GetAllEmployees();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace EmployeeBAL
{
public class EmployeeBAL : IEmployeeBAL
{
public async Task<List<EmployeeUI>> GetAllEmployees()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:54611/api/");
var result = await client.GetAsync("employee");
var response = await result.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<List<EmployeeStore>>(response);
var employeesUI = data.Select(e => new EmployeeUI {
Employee_ID = e.Employee_ID,
Employee_Name = e.First_Name + " " + e.Last_Name,
Age = GetAge(e.Date_Birth),
Address = e.Street_Address + ", " + e.City + ", " + e.Province,
Date_Joining = e.Date_Joining,
Date_Leaving = e.Date_Leaving
}).ToList();
return employeesUI;
}
}
private int GetAge(DateTime Birth_Date)
{
return Convert.ToInt32(((DateTime.Now - Birth_Date).TotalDays) / 365);
}
public class EmployeeStore
{
public int Employee_ID;
public string First_Name;
public string Last_Name;
public DateTime Date_Birth;
public string Street_Address;
public string City;
public string Province;
public DateTime Date_Joining;
public DateTime? Date_Leaving;
}
}
}
You might wonder why we created a class “EmployeeStore” here. This is to store the data we receive from the data access layer without keeping any direct connection between it and the business access layer. Please remember to change the base address according to the address of your data access layer Web API.
The Front End or Presentation Layer
We now create the front end or presentation layer. Here, we will create an MVC Core application, as shown below.
Here, we will not create a new controller or view. Instead, we will modify the Home Controller, as shown below.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using EmployeeWebApp.Models;
using EmployeeBAL;
namespace EmployeeWebApp.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IEmployeeBAL _employeeBAL;
public HomeController(ILogger<HomeController> logger, IEmployeeBAL employeeBAL)
{
_logger = logger;
_employeeBAL = employeeBAL;
}
public async Task<IActionResult> Index()
{
var employees = await _employeeBAL.GetAllEmployees();
var employee = employees.FirstOrDefault();
return View(employee);
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel {
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
});
}
}
}
Add the following changes in the “Views/Home/Index.cshtml” file, as shown below.
@model EmployeeBAL.EmployeeUI
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Employee Details</h1>
<div class="form-group">
<hr />
<dl class="row">
<dt class="col-sm-2">
<label asp-for="Employee_ID" class="col-md-2 control-label"></label>
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Employee_ID)
</dd>
<dt class="col-sm-2">
<label asp-for="Employee_Name" class="col-md-2 control-label"></label>
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Employee_Name)
</dd>
<dt class="col-sm-2">
<label asp-for="Address" class="col-md-2 control-label"></label>
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Address)
</dd>
<dt class="col-sm-2">
<label asp-for="Age" class="col-md-2 control-label"></label>
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Age)
</dd>
<dt class="col-sm-2">
<label asp-for="Date_Joining" class="col-md-2 control-label"></label>
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Date_Joining)
</dd>
<dt class="col-sm-2">
<label asp-for="Date_Leaving" class="col-md-2 control-label"></label>
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Date_Leaving)
</dd>
</dl>
</div>
</div>
As we are using dependency injection to pass the “EmployeeBAL” class into the controller, we need to specify this in the “Startup.cs” file.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace EmployeeWebApp
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<EmployeeBAL.IEmployeeBAL, EmployeeBAL.EmployeeBAL>();
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
Before we run the application, ensure that we set the solution to run multiple projects, as we need to start the Web API and MVC Core application at the same time, shown below.
We now run the application and should see the web application display the employee data as below in the browser.
The complete solution should look like the below screenshot.
Summary
We have just covered a simple .NET web application, where we went through the different layers to store the data, read the data, convert data to our requirements and then present it to the end-user. This was all done using the latest .NET Core MVC, Web API and Entity Framework Core technologies. We also used dependency injection, middleware or the request pipeline and tag helpers in the razor view of the MVC Core application. In the next article, we will look to move this application to the cloud in a Microsoft Azure environment.