In this Tutorial, we are going to cover clean architecture with end to end support in ASP.NET 5.0. As we all know, its newly launched Framework officially released in the month of November 2020.
In this diagram, dependencies flow toward the innermost circle. The Application Core takes its name from its position at the core of this diagram. And you can see on the diagram that the Application Core has no dependencies on other application layers. The application’s entities and interfaces are at the very center. Just outside, but still in the Application Core, are domain services, which typically implement interfaces defined in the inner circle. Outside of the Application Core, both the UI and the Infrastructure layers depend on the Application Core, but not on one another (necessarily).
Prerequisites
The following must be installed in your system:
Visual Studio 2019 16.4 or later with the ASP.NET and web development workload
.NET 5
Basic understanding about .NET Core Web Applications, MVC, C#, SQL Server, Migrations, Identity, Entity Framework and Visual Studio
Blank Solution
On the start page, choose Create a new project.
Start with create a blank solution. Search for the blank solution project in File -> New -> Project. Select ‘Blank Solution’ and click ‘Next’.
Give your solution a name i.e. DotNetCore-Clean-ArchitectureSln and give your solution a location where it’ll be saved and click Create.
Now your solution explorer should look like this.
Now let’s, create the folders which we will use to store individual projects. Create following folders inside your solution, this is just a suggestion.
⦁ Right click on the solution -> Add -> New Solution Folder. Create first folder with name “CoreCleanArchitecture.UI.MVC” as below:
⦁ Create following all folders same as above:
⦁ CoreCleanArchitecture.Application
⦁ CoreCleanArchitecture.Domain
⦁ CoreCleanArchitecture.Infrastructure.Data
⦁ CoreCleanArchitecture.Infrastructure.Ioc
Application will take care of our interfaces, services, business rules. Domain consists of Entities, Data knows about how to access our data, IoC (Inversion of Control) will help us to dependency injection.
User Interface
Let’s create the MVC web application under UI.MVC folder and create new project on folder “CoreCleanArchitecture.Web”.
⦁ Select ASP.NET Core Web Application Click Next.
⦁ Now, you need to choose the framework version and ASP.NET Core Web App (Model-View-Controller), click on change button on Authentication and select Individual User Accounts:
After selection of Authentication click on Create button as below:
After added CoreCleanArchitecture.Web project your solution should look like this:
Create Identity Database using Migration:
⦁ Now, you need to configure you database connection and it will create database for identity using core first approach. As below open “appsettings.json” and edit you connection string.
“ConnectionStrings”: {
“DefaultConnection”: “Server=DESKTOP-JKRRO7I;Database=aspnet-CoreCleanArchitecture.Web-75CCCCEC-3C58-4A33-9B36-FE792EA54795;Trusted_Connection=True;MultipleActiveResultSets=true”
},
⦁ Open Package Manager Console from the Tools Menu and select the Default project for which you would like to generate migrations code.
⦁ For creating the migration code, we use ‘add-migration MigrationName’ command. So, let’s perform this operation and see what happens. Therefore, in the Package Manager Console, just type ‘add-migration initialmigration’ command and press Enter.
After the add migration command executes successfully, it creates a folder name as ‘Migration’ in the project and creates the class with the same name [MigrationName] as we have provided while executing add migration command with some name.
⦁ We have only created the migration script which is responsible for creating the database and its table. But we’ve not created the actual database and tables. So, let’s execute the migration script and generate the database and tables. Therefore, executing the migration scripts we have to execute ‘update-database’ command.
After the add update-database command executes successfully, you will received message for “Done” and you can see you database also created on SQL server.
Domain
Let’s add a Class Library project to our domain layer which will hold the core entities.
Select Class Library (.NET Core) and select target framework as .NET 5
Remove Class1.cs and add a new folder Models (or Entities), for the sake of this article let’s assume we are building a library system, so we will add Project model to the Models folder.
Infrastructure
Now let’s create a new database context so we can update the database with this new model. We will be doing it in our Infrastructure layer. Under CoreCleanArchitecture.Infrastructure.Data add new class library called CleanArchitecture.Infra.Data and under that folder named Context.
⦁ Now it is ready for add database context:
Right-click the Models folder and select Add > Class. Name the class ProjectDbContext and click Add and change code in ProjectDbContext.cs as following:
Before code in ProjectDbContext.cs you need to add Domain Project referece to Infrastructure project as below:
– Let’s go ahead and add packages we need to configure this project. But before that, in Package Manager Console, execute dotnet restore for all 3 projects we have so far. After that do Build -> Rebuild Solution
⦁ Right click on Dependencies of CleanArchitecture.Infra.Data and select Manage NuGet Packages and Select the Browse tab, and then enter Microsoft.EntityFrameworkCore in the search box, select Latest Stable Version and Install.
⦁ Now, install Following all as same as above steps.
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Let’s configure this new DbContext in Startup.cs of UI project. Add following lines to ConfigureServices method.
services.AddDbContext<ProjectDbContext>(options =>
{
options.UseSqlServer(
Configuration.GetConnectionString(“ProjectConnection”));
});
Notice the ProjectConnection connection string, so we need to add it to appsettings.json. This new connection string points to a different database, where we will store our domain entities.
“ConnectionStrings”: {
“DefaultConnection”: “Server=DESKTOP-JKRRO7I;Database=aspnet-CoreCleanArchitecture.Web-75CCCCEC-3C58-4A33-9B36-FE792EA54795;Trusted_Connection=True;MultipleActiveResultSets=true”,
“ProjectConnection”: “Server=DESKTOP-JKRRO7I;Database=aspnet-CoreCleanArchitecture.Web-75CCCCEC-3C58-4A33-9B36-FE792EA54795;Trusted_Connection=True;MultipleActiveResultSets=true”
},
After that is done, execute add-migration against ProjectDbContext in Package Manager Console. Make sure you have selected CleanArchitecture.Infra.Data as the default project.
This will create a new migration under the infrastructure.
Now, Execute Update-Database against ProjectDbContext in Package Manager Console
update-database -Context ProjectDbContext
After the add update-database command executes successfully, you will received message for “Done” and you can see you database also created on SQL server.
Application Core
All of our services, interfaces and ViewModels will go here. Like in previous steps, create a new Class Library (.NET Core) under ‘CoreCleanArchitecture.Application’ folder named CleanArchitecture.Application and add folders for them as follows.
⦁ After added new project CleanArchitecture.Application looking like this:
⦁ Now, remove default class1.cs and add following folders on project CleanArchitecture.Application.
⦁ Let’s work on the ViewModels now, A view model represents the data that you want to display on your view/page, whether it be used for static text or for input values (like textboxes and dropdown lists) that can be added to the database (or edited). It is something different than your domain model. It is a model for the view. In other words, it creates a mask for the domain models.
⦁ Before go ahead, First you need add CleanArchitecture.Domain project reference in CleanArchitecture.Application Project:
⦁ Create a new class under ViewModels folder named ProjectViewModel. For the time being we will get a list of Products from the database. Add following code to ProjectViewModel.cs , notice we are bringing in the Project from CleanArchitectureDomain.
⦁ Create a new interface to act as a contract to the functionality that we’re trying to implement. I hope you are familiar with interfaces and why we create interfaces in OOP, since those things are out of the scope of this article I am not going to discuss them here. Under Interfaces folder create a new interface,
⦁ named, IProjectService.cs
public interface IProjectService
{
ProjectViewModel GetProjects();
}
⦁ When implemented this method will return list of Projects, and it only knows about the ViewModel, not about the core domain model Project, so we are abstracting the core entity by doing this, rather than having everything in one place.
⦁ Before we write the implementation for IProjectService, we have to define a way to get the data from the database, to do that what we normally use in .NET is an ORM called Entity Framework, but we will use the Repository pattern to decouple the business logic and the data access layers in our application.
⦁ The Repository Design Pattern in C# Mediates between the domain and the data mapping layers using a collection-like interface for accessing the domain objects. In other words, we can say that a Repository Design Pattern acts as a middleman or middle layer between the rest of the application and the data access logic.
⦁ Add another folder called Interfaces under CleanArchitecture.Domain project. Add a new interface named IProjectRepository.cs.
At this point our project should look like this.
⦁ Add following method, notice this time it’s not the ViewModel, it’s the domain entity itself, Project.
public interface IProjectRepository{
IEnumerable GetProjects();
}
Okay so at this point, if the MVC project, or the Presentation Layer (which has no idea about the domain entity Products) says, “hey, I want a list of Projects!” , it needs to talk to the ProjectService (which we haven’t implemented yet, using IProjectService), and ProjectService need to get it from ProjectRepository (which also we haven’t implemented yet, using IProjectRepository)
So let’s implement them. First, ProjectService.
Under CleanArchitecture.Application project, under services folder, add a new class, ProjectService.cs, and inherit it from IProjectService.
public class ProjectService : IProjectService
{
public ProjectViewModel GetProjects()
{
throw new NotImplementedException();
}
}
Replace ProjectService code with following code:
public class ProjectService : IProjectService
{
public IProjectRepository _projectRepository;
public ProjectService(IProjectRepository projectRepository)
{
_projectRepository = projectRepository;
}
public ProjectViewModel GetProjects()
{
return new ProjectViewModel()
{
Projects = _projectRepository.GetProjects()
};
}
}
Implement ProjectRepository under CleanArchitecture.Infra.Data Project with Create new Repositories Folder and create new class with name “ProjectRepository” under this new Repositories Folder.
⦁ Now we need to inject the I ProjectRepository, Inject it as you would normally do dependency injection in .NET.
public class ProjectRepository : IProjectRepository
{
public ProjectDbContext _context;
public ProjectRepository(ProjectDbContext context)
{
_context = context;
}
public IEnumerable<Project> GetProjects()
{
return _context.Projects;
}
}
Next we need to look at the implementation of IoC project, which will help us to contain and separate the dependencies.
Inversion of Control
So under the CoreCleanArchitecture.Infrastructure.Ioc, create a new .NET Core Class Library just like we did in earlier projects named CleanArchitecture.Infra.IoC.
Right click on dependancies -> Manage NuGet Packages -> go to the browse tab and search for,
Microsoft.Extensions.DependencyInjection
Make sure you install the same version as the previous dependencies we installed and are stable versions.
Now let’s create the Dependency Container class, under CleanArchitecture.Infra.IoC project.
public class DependencyContainer
{
public static void RegisterServices(IServiceCollection services)
{
services.AddScoped<IProjectService, ProjectService>();
services.AddScoped<IProjectRepository, ProjectRepository>();
services.AddTransient<IEmailSender, EmailSender>();
}
}
Notice how it connects our interfaces and their implementations from multiple projects into single point of reference. That is the purpose of IoC layer. Also, notice AddScoped, if you want to know the differences read more here.
Now we need to tell about this new services container to MVC project. Head over to Startup.cs, after the Configure method add following method.
private static void RegisterServices(IServiceCollection services) {
DependencyContainer.RegisterServices(services);
}
Now inside the ConfigureServices method we need to call this method.
So now all our layers are ready. So next we will take a look at how to create the Controllers, using everything we discussed here and implement the UI.
First let’s add some data to the database, since we are only implementing the GetProjects method, we need to add some data manually.
Once you have the data in the database, let’s create a controller in CoreCleanArchitecture.Web project, under Controllers folder.
Right click Controllers folder -> Add -> Controller, name it as ProjectController following the MVC convention. Then we will inject the ProjectService to this controller as follows.
Notice we are referring to the IProjectService in Application layer.
Here our Full ProjectController Look likes:
public class ProjectController : Controller
{
private IProjectService _projectService;
public ProjectController (IProjectService projectService)
{
_projectService = projectService;
}
// GET: ProjectController
public ActionResult Index()
{
return View(_projectService.GetProjects());
}
// GET: ProjectController/Details/5
public ActionResult Details(int id)
{
return View();
}
// GET: ProjectController/Create
public ActionResult Create()
{
return View();
}
// POST: ProjectController/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(IFormCollection collection)
{
try
{
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
// GET: ProjectController/Edit/5
public ActionResult Edit(int id)
{
return View();
}
// POST: ProjectController/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, IFormCollection collection)
{
try
{
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
// GET: ProjectController/Delete/5
public ActionResult Delete(int id)
{
return View();
}
// POST: ProjectController/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(int id, IFormCollection collection)
{
try
{
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
}
Right click on Index Action Method on controller and select Add View option and select Razor View – Empty.
You can see index.chtml added under Porject folder in views folder, Now change Project Index.chtml with following code:
View we just created. Notice it points to the Project controller and Index action.
Now you can run the MVC project and navigate to https://localhost:5001/Project to see the Projects!
But we’re not logged in yet, so allowing users to view the data without authentication is a problem, which is easy to solve.
Add [Authorize] attribute to the Controller itself and then try to navigate to https://localhost:5001/Project again.
You will redirect to login if you are not login and try to access Project Index.
SendGrid
⦁ Now, we are going to complete our register process with conformation email. We are already done registration and login using Identity but we need to setup SendGrid for send conformation email to register email.
⦁ Let’s add dependency for SendGrid using NuGet Package on CleanArchitecture.Application Project.
⦁ Add new interface IEmailSender with following code on CleanArchitecture.Application Project.
public interface IEmailSender
{
Task<Response> SendEmailAsync(string email, string subject, string message);
Task<Response> Execute(string apiKey, string subject, string message, string email);
}
⦁ Right click on Services folder on CleanArchitecture.Application Project and implement EmailSender as following code.
public class EmailSender : IEmailSender
{
public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}
public AuthMessageSenderOptions Options { get; } //set only via Secret Manager
public async Task<Response> SendEmailAsync(string email, string subject, string message)
{
return await Execute(Options.SendGridKey, subject, message, email);
}
public async Task<Response> Execute(string apiKey, string subject, string message, string email)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress(“patelalpeshn@gmail.com”, Options.SendGridUser),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
msg.SetClickTracking(false, false);
var response = await client.SendEmailAsync(msg);
return response;
}
}
⦁ Now, you need to configure your sendgrid credential in “appsettings.json”.
⦁ Configure your sendgrid in startup.cs as below:
services.Configure<AuthMessageSenderOptions>(Configuration.GetSection(“SendGrid”));
⦁ Let’s use this sendgrid in register conformation, we are using identity for register and login so first we need to add default identity pages on our project if we want to change it as below:
⦁ Select Identity folder under Areas folder and right click on Identity folder and Click on select “New Scaffoldered Item”
⦁ Now select Identity on Add New Scaffoldered Item popup.
⦁ Select following options on Idetity popup and select your DBContetxt as below:
⦁ Now go to Areas Identity Pages and find all pages for Sender reference and change as below:
Now you will receive conformation email also when you register and you can login.