Clean Architecture End To End In .NET Core 5

In this article 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.

Step 1 
Create a Project in Visual Studio
Step 2
 
 Make Sure to Select the ASP.NET Core 5.0 and enabling the OpenAPI support helps to add the swagger by default in our project without installing manually again.
Step 3
Entity Framework Code First Approach
Create a Class library (.NET Core) named DataAccessLayer, which contains:
ApplicationDbContext
Wrapping all the classes using conventions:
using DataAccessLayer.EntityMappers; 
using DataAccessLayer.Models; 
using DataAccessLayer.SeedData; 
using Microsoft.EntityFrameworkCore; 
using System; 
using System.Collections.Generic; 
using System.Text; 

namespace DataAccessLayer.ApplicationDbContext 
{ 
public partial class CFCDbContext : DbContext 
{ 
public CFCDbContext(DbContextOptions options) : base(options) 
{ 

} 
public DbSet<User> users { get; set; } 
public DbSet<UserRoles> userRoles { get; set; } 

protected override void OnModelCreating(ModelBuilder modelBuilder) 
{ 
modelBuilder.ApplyConfiguration(new UserMap()); 
modelBuilder.ApplyConfiguration(new UserRoleMap()); 
modelBuilder.ApplyConfiguration(new BranchMap()); 
base.OnModelCreating(modelBuilder); 
modelBuilder.Seed(); 
} 
} 
}
Entity Mappers
Creating a Tables with relations using Model Objects
using DataAccessLayer.Models; 
using Microsoft.EntityFrameworkCore; 
using Microsoft.EntityFrameworkCore.Metadata.Builders; 

namespace DataAccessLayer.EntityMappers 
{ 
public class BranchMap : IEntityTypeConfiguration<Branches> 
{ 
public void Configure(EntityTypeBuilder<Branches> builder) 
{ 
builder.ToTable("branches"); 
builder.HasKey(x => x.BranchId) 
.HasName("pk_branch_id"); 
builder.Property(x => x.BranchId) 
.ValueGeneratedOnAdd() 
.HasColumnName("branch_id") 
.HasColumnType("INT"); 
builder.Property(x => x.BranchName) 
.HasColumnName("branch_name") 
.HasColumnType("NVARCHAR(100)") 
.IsRequired(); 
builder.Property(x => x.BranchManager) 
.HasColumnName("branch_manager") 
.HasColumnType("NVARCHAR(100)") 
.IsRequired(); 
builder.Property(x => x.BranchLocation) 
.HasColumnName("branch_location") 
.HasColumnType("NVARCHAR(100)") 
.IsRequired(); 
builder.Property(x => x.BranchNumber) 
.HasColumnName("branch_number") 
.HasColumnType("BIGINT") 
.IsRequired(); 
builder.Property(x => x.CreatedDate) 
.HasColumnName("created_date") 
.HasColumnType("DATETIME"); 
builder.Property(x => x.ModifiedDate) 
.HasColumnName("modified_date") 
.HasColumnType("DATETIME"); 
builder.Property(x => x.IsActive) 
.HasColumnName("is_active") 
.HasColumnType("BIT"); 
} 
} 
}
Migrations
Includes all our Migrations respective to tables which we are consuming
Models
Defining the Table Models using Classes
using Newtonsoft.Json; 
using System; 
using System.Collections.Generic; 
using System.Text; 

namespace DataAccessLayer.Models 
{ 
public class Branches : BaseModel 
{ 
[JsonProperty(PropertyName = "branch_id")] 
public int BranchId { get; set; } 
[JsonProperty(PropertyName = "branch_name")] 
public string BranchName { get; set; } 
[JsonProperty(PropertyName = "branch_manager")] 
public string BranchManager { get; set; } 
[JsonProperty(PropertyName = "branch_number")] 
public long BranchNumber { get; set; } 
[JsonProperty(PropertyName = "branch_location")] 
public string BranchLocation { get; set; } 
} 
}
Seed Data
Static Data for Tables.
using DataAccessLayer.Models; 
using Microsoft.EntityFrameworkCore; 
using System; 
using System.Collections.Generic; 
using System.Text; 

namespace DataAccessLayer.SeedData 
{ 
public static class ModelBuilderExtension 
{ 
public static void Seed(this ModelBuilder modelBuilder) 
{ 
// Seed Data for Admin Roles 
modelBuilder.Entity<UserRoles>().HasData( 
new UserRoles { RoleId = 1,RoleName = "SuperAdmin",IsActive = true }, 
new UserRoles { RoleId = 2,RoleName = "Admin",IsActive = true } 
); 
} 
} 
}

Folder Structure

Here i am maintaining the Folder Structure to have a deeper undestanding and naming conventions as per my standard.
Dependency Injection
Create a Class Library(.Net Core) named as Services in which we are maintaing all the Business logic and Core Functionality.
Folder Structure
Mapper : Automapper – Obeject – Object Mapping 
using AutoMapper; 
using DataAccessLayer.Models; 
using System; 
using System.Collections.Generic; 
using System.Text; 
using static Services.ViewModels.CommonModel; 

namespace Services.Mapper 
{ 
public class Mapper : Profile 
{ 
public Mapper() 
{ 
AllowNullDestinationValues = true; 
//Source -> Destination 
CreateMap<UserRoles, RolesModel>() 
.ForMember(dto => dto.RoleId, opt => opt.MapFrom(src => src.RoleId)) 
.ForMember(dto => dto.RoleName, opt => opt.MapFrom(src => src.RoleName)); 
CreateMap<User, LoginModel>() 
.ForMember(dto => dto.UserName, opt => opt.MapFrom(src => src.Email)); 
} 
} 
}

Repository Pattern using Interfaces

using DataAccessLayer.ApplicationDbContext; 
using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Threading.Tasks; 
using DataAccessLayer.Models; 
using System.Linq; 
using static Services.ViewModels.CommonModel; 
using AutoMapper; 
using Microsoft.EntityFrameworkCore; 
using System.Security.Claims; 
using System.IdentityModel.Tokens.Jwt; 
using Microsoft.IdentityModel.Tokens; 
using Microsoft.Extensions.Configuration; 

namespace Services.RepositoryPattern.UserLogin 
{ 
public class UserService : IUserService 
{ 
#region Property 
private readonly CFCDbContext _cFCDbContext; 
private readonly IMapper _mapper; 

#endregion 

#region Constructor 
public UserService(CFCDbContext cFCDbContext, IMapper mapper) 
{ 
_cFCDbContext = cFCDbContext; 
_mapper = mapper; 

} 
#endregion 

#region Get User Roles 
/// <summary> 
/// Get User Roles from Db 
/// </summary> 
/// <returns></returns> 
public async Task<List<RolesModel>> GetUserRolesAsync() 
{ 
try 
{ 
var userRoles = await _cFCDbContext.userRoles.Where(c => c.IsActive.Equals(true)).ToListAsync(); 
return _mapper.Map<List<RolesModel>>(userRoles); 
} 
catch (Exception ex) 
{ 
throw ex; 
} 
} 
#endregion
Common model
using Newtonsoft.Json; 
using System; 
using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations; 
using System.Text; 

namespace Services.ViewModels 
{ 
public class CommonModel 
{ 
public class UserModel 
{ 
[JsonProperty(PropertyName = "firstname")] 
[Required] 
public string FirstName { get; set; } 
[JsonProperty(PropertyName = "lastname")] 
[Required] 
public string LastName { get; set; } 
[JsonProperty(PropertyName = "phonenumber")] 
[Required] 
public long PhoneNumber { get; set; } 
[JsonProperty(PropertyName = "password")] 
[Required] 
public string Password { get; set; } 
[JsonProperty(PropertyName = "email")] 
[Required] 
public string Email { get; set; } 
[JsonProperty(PropertyName = "rolename")] 
[Required] 
public string RoleName { get; set; } 
} 

public class RolesModel 
{ 
[JsonProperty(PropertyName = "role_id")] 
public int RoleId { get; set; } 
[JsonProperty(PropertyName = "role_name")] 
public string RoleName { get; set; } 
} 
public class LoginModel 
{ 
[JsonProperty(PropertyName = "username")] 
[Required] 
public string UserName { get; set; } 
[JsonProperty(PropertyName = "password")] 
[Required] 
public string Password { get; set; } 
} 

} 
}

JWT Authentication, Swagger & Versioning

I followed C# Regions to improve the code readability, so in this configure services, I have separated everything with regions
Startup.cs
using AutoMapper; 
using CFC_API.Versioning; 
using DataAccessLayer.ApplicationDbContext; 
using Services.RepositoryPattern.UserLogin; 
using DataAccessLayer.ApplicationDbContext; 
using Microsoft.AspNetCore.Authentication.JwtBearer; 
using Microsoft.AspNetCore.Builder; 
using Microsoft.AspNetCore.Hosting; 
using Microsoft.AspNetCore.HttpsPolicy; 
using Microsoft.AspNetCore.Identity; 
using Microsoft.AspNetCore.Mvc; 
using Microsoft.AspNetCore.Mvc.Versioning; 
using Microsoft.EntityFrameworkCore; 
using Microsoft.Extensions.Configuration; 
using Microsoft.Extensions.DependencyInjection; 
using Microsoft.Extensions.Hosting; 
using Microsoft.Extensions.Logging; 
using Microsoft.IdentityModel.Tokens; 
using Microsoft.OpenApi.Models; 
using Services.RepositoryPattern.UserLogin; 
using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Reflection; 
using System.Text; 
using System.Threading.Tasks; 

namespace CleanArchitecture 
{ 
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.AddControllers(); 

#region API Versioning 
services.AddApiVersioning(options => 
{ 
options.ReportApiVersions = true; 
options.DefaultApiVersion = new ApiVersion(1, 0); 
options.AssumeDefaultVersionWhenUnspecified = true; 
options.ApiVersionReader = 
new HeaderApiVersionReader("X-API-Version"); 
}); 
#endregion 

#region Connection String 
services.AddDbContext<CFCDbContext>(item => item.UseSqlServer(Configuration.GetConnectionString("myconn"))); 
#endregion 

#region Enable Cors 
services.AddCors(); 
#endregion 

#region Swagger 
services.AddSwaggerGen(swagger => 
{ 
swagger.SwaggerDoc("v1", new OpenApiInfo 
{ 
Version = "v1", 
Title = " Clean Architecture v1 API's", 
Description = $"Clean Architecture API's for integration with UI \r\n\r\n © Copyright {DateTime.Now.Year} JK. All rights reserved." 
}); 
swagger.SwaggerDoc("v2", new OpenApiInfo 
{ 
Version = "v2", 
Title = "Clean Architecture v2 API's", 
Description = $"Clean Architecture API's for integration with UI \r\n\r\n © Copyright {DateTime.Now.Year} JK. All rights reserved." 
}); 
swagger.ResolveConflictingActions(a => a.First()); 
swagger.OperationFilter<RemoveVersionFromParameterv>(); 
swagger.DocumentFilter<ReplaceVersionWithExactValueInPath>(); 

#region Enable Authorization using Swagger (JWT) 
swagger.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() 
{ 
Name = "Authorization", 
Type = SecuritySchemeType.ApiKey, 
Scheme = "Bearer", 
BearerFormat = "JWT", 
In = ParameterLocation.Header, 
Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"", 
}); 

swagger.AddSecurityRequirement(new OpenApiSecurityRequirement 
{ 
{ 
new OpenApiSecurityScheme 
{ 
Reference = new OpenApiReference 
{ 
Type = ReferenceType.SecurityScheme, 
Id = "Bearer" 
} 
}, 
new string[] {} 

} 
}); 
#endregion 
}); 
#endregion 

#region Swagger Json property Support 
services.AddSwaggerGenNewtonsoftSupport(); 
#endregion 

#region JWT 

// Adding Authentication 
services.AddAuthentication(options => 
{ 
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; 
}) 

// Adding Jwt Bearer 
.AddJwtBearer(options => 
{ 
options.SaveToken = true; 
options.RequireHttpsMetadata = false; 
options.TokenValidationParameters = new TokenValidationParameters() 
{ 
ValidateIssuer = true, 
ValidateAudience = true, 
ValidAudience = Configuration["Jwt:Issuer"], 
ValidIssuer = Configuration["Jwt:Issuer"], 
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])) 
}; 
}); 
#endregion 

#region Dependency Injection 
services.AddTransient<IUserService, UserService>(); 
#endregion 

#region Automapper 
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); 
#endregion 
} 

// 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.UseSwagger(); 
app.UseSwaggerUI(c => 
{ 
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"); 
c.SwaggerEndpoint("/swagger/v2/swagger.json", "API v2"); 
}) ; 
} 

app.UseHttpsRedirection(); 

app.UseRouting(); 

app.UseAuthentication(); 

app.UseAuthorization(); 

#region Global Cors Policy 
app.UseCors(x => x 
.AllowAnyMethod() 
.AllowAnyHeader() 
.SetIsOriginAllowed(origin => true) // allow any origin 
.AllowCredentials()); // allow credentials 
#endregion 

app.UseEndpoints(endpoints => 
{ 
endpoints.MapControllers(); 
}); 
} 
} 
}

appsettings.json

{ 
"Logging": { 
"LogLevel": { 
"Default": "Information", 
"Microsoft": "Warning", 
"Microsoft.Hosting.Lifetime": "Information" 
} 
}, 
"AllowedHosts": "*", 
"Jwt": { 
"Key": "BB698DAF-6E3F-45FF-8493-06ECCF2F60D0", 
"Issuer": "https://localhost:44393", 
}, 
"ConnectionStrings": { 
"myconn": "server= Your Connection String; database=CFCDb;Trusted_Connection=True;" 
} 
}
Used to store configuration settings such as database connections strings, any application scope global variables.
UserController
using Services.RepositoryPattern.UserLogin; 
using Microsoft.AspNetCore.Authorization; 
using Microsoft.AspNetCore.Http; 
using Microsoft.AspNetCore.Mvc; 
using Microsoft.Extensions.Configuration; 
using Microsoft.IdentityModel.Tokens; 
using System; 
using System.Collections.Generic; 
using System.IdentityModel.Tokens.Jwt; 
using System.Linq; 
using System.Security.Claims; 
using System.Text; 
using System.Threading.Tasks; 
using static Services.ViewModels.CommonModel; 

namespace CleanArchitecture.Controllers 
{ 
[ApiVersion("1.0")] 
[ApiExplorerSettings(GroupName = "v1")] 
public class UserController : BaseController 
{ 
#region Property 
private readonly IUserService _userService; 
private readonly IConfiguration _configuration; 
#endregion 

#region Constructor 
public UserController(IUserService userService, IConfiguration configuration) 
{ 
_userService = userService; 
_configuration = configuration; 
} 
#endregion 

#region Create User 
/// <summary> 
/// To Create a User 
/// </summary> 
/// <param name="userModel"></param> 
/// <returns></returns> 
[HttpPost(nameof(CreateUser))] 
public async Task<IActionResult> CreateUser([FromBody]UserModel userModel) 
{ 
try 
{ 
if (ModelState.IsValid) 
{ 
var result = await _userService.CreateUserAsync(userModel); 
return Ok(result); 
} 
else 
{ 
return BadRequest("Please fill all the required parameters"); 
} 

} 
catch (Exception ex) 
{ 
return BadRequest(ex); 
throw; 
} 
} 
#endregion 

#region User Login 
/// <summary> 
/// Login Authentication 
/// </summary> 
/// <param name="loginModel"></param> 
/// <returns></returns> 
[HttpPost(nameof(Login)), AllowAnonymous] 
public async Task<IActionResult> Login([FromBody]LoginModel loginModel) 
{ 
try 
{ 
var response = await _userService.UserLoginAsync(loginModel); 
if (response is true) 
{ 
var userRoles = await _userService.GetUserRolesAsync(); 
var authClaims = new List<Claim> 
{ 
new Claim(ClaimTypes.Name, loginModel.UserName), 
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), 
}; 

foreach (var userRole in userRoles) 
{ 
authClaims.Add(new Claim(ClaimTypes.Role, userRole.RoleName)); 
} 

var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])); 

var token = new JwtSecurityToken( 
issuer: _configuration["Jwt:Issuer"], 
audience: _configuration["Jwt:Issuer"], 
expires: DateTime.Now.AddHours(3), 
claims: authClaims, 
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256) 
); 
return Ok(new 
{ 
token = new JwtSecurityTokenHandler().WriteToken(token), 
expiration = token.ValidTo 
}); 
} 
return Unauthorized(); 

} 

catch (Exception ex) 
{ 
return BadRequest(ex); 
throw; 
} 
} 
#endregion 
} 
}

BaseController

using Microsoft.AspNetCore.Authorization; 
using Microsoft.AspNetCore.Http; 
using Microsoft.AspNetCore.Mvc; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Net; 
using System.Threading.Tasks; 
using static CleanArchitecture.ViewModels.Common.ResultModel; 

namespace CleanArchitecture.Controllers 
{ 
[Route("api/v{version:apiversion}/[controller]")] 
[Authorize(AuthenticationSchemes = Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme)] 
public class BaseController : ControllerBase 
{ 
#region Protected Members 
/// <summary> 
/// Detailed Exception 
/// </summary> 
/// <param name="ex"></param> 
/// <returns></returns> 
protected object DetailedException(Exception ex) 
{ 
var errormessage = ex.Message; 
if (ex.InnerException != null) 
{ 
errormessage = "\n\nException: " + GetInnerException(ex); 
} 
var result = new Result 
{ 
status = new Status 
{ 
code = (int)HttpStatusCode.InternalServerError, 
message = errormessage 
} 
}; 
return result; 
} 

/// <summary> 
/// Get Inner Exception 
/// </summary> 
/// <param name="ex"></param> 
/// <returns></returns> 
private string GetInnerException(Exception ex) 
{ 
if (ex.InnerException != null) 
{ 
return 
$"{ex.InnerException.Message + "( \n " + ex.Message + " \n )"} > {GetInnerException(ex.InnerException)} "; 
} 
return string.Empty; 
} 
#endregion 
} 
}

RolesController

using Microsoft.AspNetCore.Http; 
using Microsoft.AspNetCore.Mvc; 
using Services.RepositoryPattern.UserLogin; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Threading.Tasks; 

namespace CleanArchitecture.Controllers 
{ 
[ApiVersion("2.0")] 
[ApiExplorerSettings(GroupName = "v2")] 
public class RoleController : BaseController 
{ 
#region Property 
private readonly IUserService _userService; 
#endregion 

#region Constructor 
public RoleController(IUserService userService) 
{ 
_userService = userService; 
} 
#endregion 

#region GetRoles 
/// <summary> 
/// Get the User Roles 
/// </summary> 
/// <returns></returns> 
[HttpGet(nameof(GetUserRoles))] 
public async Task<IActionResult> GetUserRoles() 
{ 
try 
{ 
var result = await _userService.GetUserRolesAsync(); 
if (result is not null) return Ok(result); else return BadRequest("No Data Found"); 
} 
catch (Exception ex) 
{ 
return BadRequest(ex); 
throw; 
} 
} 
#endregion 
} 
}
This is the entire end to end clean architecture with the latest .NET 5, I hope this article helps you.

Anjali Punjab

Anjali Punjab is a freelance writer, blogger, and ghostwriter who develops high-quality content for businesses. She is also a HubSpot Inbound Marketing Certified and Google Analytics Qualified Professional.