.NET Core Extented Architecture And Separation Of Concerns

Introduction

Most projects start as small projects and with time they turn into medium or even large projects. By the time that we realize this is happening, it might be too late and can cost us a lot to change the project architecture.

So why don't we do the whole process in a smarter way and think about this from the beginning?

The separation of concerns is often a neglected topic when dealing with web applications. Everyone who has created even one basic web app knows that by default .NET has a decent template.

That's okay for starters,  and for people that are new to these concepts, but if you want to create easily maintainable and extendable applications you should absolutely put in some effort to extend the basic MVC template.

Building the Sample

Building this template requires installed Visual Studio 2015 or later with included .NET Core. Once you have it just build the solution, which will trigger package restore and after a few seconds your solution will be built!

This will trigger bower restore and NuGet package restore.

*If bower restore fails, just save the bower.json and .bowerrc files like this:

  • Open the file in Visual Studio
  • Click on "Save file as" under File menu
  • Click on the down arrow sign on the right side of Save button
  • Select "Save with Encoding"
  • Agree to replace the file
  • Change the Encoding type to "Western European (Windows) - Codepage 1252" and leave the Line endings to "Current Settings"
  • Hit Save button

Description

What does this architecture give you more than the usual boilerplate templates?

<samp>(Those advantages are applicable for medium to large projects)</samp>

  • Easier development - one look at the project structure tells you where all the different components are situated
  • A lower barrier to understanding, when new people are working on the project - when introducing new people to some project that is already in progress it's always better to do that step by step. And when these components are well structured and neat it is easier to go through them
  • Improved maintenance - whether you have created the project from scratch or not, when a problem occurs it's a lot easier to define where this problem comes from
  • Easier extension of the project - having all the components separated makes it a lot easier to extend them

Detailed explanation of the architecture

Data

  • Project for the database tables - contains the Db Context and the configurations of the migrations
  • Project for abstraction levels over the database - contains implementation of different abstraction levels over the database like Unit of work and Repository pattern
  • Project for the database models - contains all the DB models including these from the ASP.NET identity which are moved from the web project

Services

  • Project for the services related to the database access - these services are not Web API services or any other ASP.NET services. They are just called that. They might be called 'Providers', or whatever descriptive name you like. They are just regular classes plus interfaces that are responsible for the containment of the business logic of the application as well as the tasks related to CRUD operations over the database.
  • Project for the services related to the web operations - the same thing is valid here, but here are the services related to more complex logic that is not related to the database like Caching services, Email services and etc.

Tests

  • Project for the services tests - just to keep the different types of tests separately
  • Project for the controllers' tests
  • Project for the routes tests

Web

  • Project for the web application - this is the classic MVC project which is stripped of the Db context dependency as well as the DB models dependencies. Contains configurations for the Dependency Injection, Automapper, and other external libraries. The architecture is supposed to keep all your controller actions very tiny and do all of your business logic in the services, which are injected by your IoC (inversion of control) container. In our case this is Autofac
  • Project for the web application infrastructure -this project contains source code which is related to the Web application but might be reusable, and this is why it is separated by the Web App. For example, here we can put our View Models, custom filters and annotations, HTML extension methods and helpers, and etc.
Lastly there is a separate project intended to combine all the common classes between all the projects like Global Constants, Global Helper and etc.

Additional Features

  • Extended database models
    This is needed because we always have similar fields in our database tables. This can be avoided using the principles of the Object Oriented Programming like inheritance.
C#
  1. public interface IDeletableEntity  
  2. {  
  3.    bool IsDeleted { get; set; }  
  4.    DateTime? DeletedOn { get; set; }  
  5. }  
C#
  1. public interface IAuditInfo  
  2. {  
  3.    DateTime CreatedOn { get; set; }  
  4.    DateTime? ModifiedOn { get; set; }  
  5.  }  
C#
  1. public abstract class BaseModel<TKey> : IAuditInfo  
  2. {  
  3.    [Key] public TKey Id { get; set; }  
  4.    public DateTime CreatedOn { get; set; }  
  5.    public DateTime? ModifiedOn { get; set; }  
  6. }  
C#
  1. public abstract class BaseDeletableModel<TKey> : BaseModel<TKey>, DeletableEntity  
  2. {  
  3.    public bool IsDeleted { get; set; }  
  4.    public DateTime? DeletedOn { get; set; }  
  5. }  
Now we can use these base classes and prevent adding their properties in each of our classes separately like this.
Generic Repositiry Pattern

Creating some kind of abstraction over the database is almost mandatory when talking about a real project.
C#
  1. public interface IRepository < TEntity >: IDisposable where TEntity: class   
  2. {  
  3.     IQueryable < TEntity > All();  
  4.     IQueryable < TEntity > AllAsNoTracking();  
  5.     Task < TEntity > GetByIdAsync(params object[] id);  
  6.     void Add(TEntity entity);  
  7.     void Update(TEntity entity);  
  8.     void Delete(TEntity entity);  
  9.     Task < int > SaveChangesAsync();  
  10. }  
Our repository shoud contain all operations needed for work with our database like:
  • Create
  • Read
  • Updated
  • Delete

or so called CRUD operations. In the provided solution we have two implementaions of the interface EfRepository.csand EfDeletableEntityRepository.cs

Source Code Files

  • EfRepository.cs
    We use this repository for entities that are not implementing the BaseDeletableModel class.

  • EfDeletableEntityRepository.cs
    We use this repository implementation for entities that implement a BaseDeletableModel class and it handles the deleted items automatically.

  • AutoMapperConfig
    Additional configurations for the automapper which allow us to set the mappings like this:

    C#
    1. public class SettingViewModel: IMapFrom<Setting>  
    2.  {  
    3.    public int Id { get; set; }  
    4.    public string Name { get; set; }  
    5.    public string Value { get; set; }  
    6.  }