Think with Enlab

Diving deep into the ocean of technology

Stay Connected. No spam!
  • Home
  • Development
  • How to build and deploy a 3-layer architecture application with C#

How to build and deploy a 3-layer architecture application with C#

 

 

What’s 3-layer architecture?

Layer indicates the logical separation of components. Layered architecture concentrates on grouping related functionality within an application into distinct layers that are stacked vertically on top of each other. Each layer has unique namespaces and classes.

An N-layer application may reside on the same physical computer (same tier); each layer's components communicate with another layer’s components by well-defined interfaces.

In C#, each layer is often implemented in a Dynamic Link Libraries (DLL) file.

To help you understand more about the n-layer architecture, I’ve put together all pieces of information about 3-layer with a practical example in this article as follows:

 

  • Presentation layer
    It is the first and topmost layer present in the application where users can interact with the application.
  • Business Logic layer
    This is the middle layer - the heart of the application. It contains all business logic of the application, describes how business objects interact with each other, where the Presentation layer and Data Access layer can indirectly communicate with each other.
  • Data Access layer
    The Data Access layer enforces rules regarding accessing data, providing simplified access to data stored in persistent storage, such as SQL Server.
    It is noteworthy that this layer only focuses on data access instead of data storage. It may create a bit of confusion with the Data-tier in 3-tier architecture.

Advantages of 3-layer architecture

  • Explicit code: The code is separated into each layer. Each one is dedicated to a single responsibility such as interface, business processing, and querying instead of bundling all the code in one place.
  • Easy to maintain: As its roles separate each layer, it would be easier to change something. The modification can be isolated in a single layer or affected only the nearest layer without affecting the whole program.
  • Easy to develop, reuse: When we want to modify a function, we can easily do that as we already have a standard architecture. In case we want to alter a complete layer such as from Winform to Web form, we only need to implement to replace the Presentation layer; other layers can be reused completely.
  • Easy to transfer: We could save time on moving the application to others as they have a standard architecture to follow and apply.
  • Easy to distribute the workloads: By organizing the code into different layers based on its responsibility, each team/member can write their code on each layer independently, which in turn helps developers control their workload.

How does it work?

In 3-layer architecture, the Presentation layer doesn’t communicate directly with the Data Access layer. The Business Logic layer works as a bridge between the Presentation layer and the Data Access layer. The 3-layer architecture works as follows:

  • The Presentation layer is the only class that is directly interacted with the user. It is mainly used for presenting/collecting data from users, then passes them to the Business Logic layer for further processing.
  • After receiving information from the Presentation layer, the Business Logic layer does some business logic on the data. During this process, this layer may retrieve or update some data from the application database. However, this layer doesn’t take responsibility for accessing the database; it sends requests to the next layer, the Data Access layer.
  • The Data Access layer receives requests from the Business Logic layer and builds some queries to the database to handle these requests. Once the execution gets done, it sends the result back to the Business Logic layer.
  • The Business Logic layer gets responses from the Data Access layer, then completes the process and sends the result to the Presentation Layer.
  • The Presentation layer gets the responses and presents them to users via UI components.

The diagram below illustrates how 3-layer architecture works.

 

How to build and deploy 3-layer architecture application with C#

 

How to build and deploy a 3-layer application in C#?

To guide you build and deploy a 3-layer application, I have prepared a demo with the following components:

  • Business Objects - Entity(Data Transfer Objects) layer: .NET Core class library
  • Data Access layer: .NET Core class library
  • Business Logic layer: .NET Core class library
  • Presentation Layer: ASP.NET Core 5.0 Razor pages

The Business Objects layer includes objects that are used in the application and common helper functions (without logic) used for all layers. In a 3-layer architecture, this layer is optional. However, as we follow the OOP with C#, we should reduce the duplicate codes as much as possible. Therefore, using a layer to keep common codes instead of holding them in each layer is essential.

To easily follow the article, you can download the demo of the 3-layer architecture sample. It is built based on Repository + UnitOfWork pattern, which’s a well-designed pattern in C#. Here are the demo and practical examples of the Repository + UnitOfWork pattern.

The diagram below explains how the application was designed.

 

How is a 3-layer application designed?

3-layer sample.web

Database

Firstly, create a database with the design below to store the application’s data. You can execute the ThreeLayerSample.Database.sql which was placed inside the Data Access layer to create it.

3-layer database

The demo will focus on getting the Work table items before representing them to users.

 

Business Objects/Entity layer

ThreeLayerSample.Domain class project in this example.

1. Connect to the database with Entity Framework Core

When we have the database, we need to create a mapping between the database and the application.
Let’s open your Package Manager console in this layer, run a Scaffolding command, then replace SERVER, DATABASE, USER, PASSWORD with suitable values based on your SQL Server settings.

 


Scaffold-DbContext "Data Source=SERVER;Initial Catalog=DATABASE;Persist Security Info=True;User ID=USER;Password=PASSWORD;MultipleActiveResultSets=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Entities -ContextDir Context -Context DemoContext -fCode language: C# (cs)

Once the command has finished, the database tables should be created into the code, name entities. As the entities could be used in all layers without any database or business logic, we should keep them in this layer.

3-layer sample

A DataContext class named “DemoContext” is also created. This class provides database access, so it should be placed in the Data Access layer.

democontext

2. Create Generic interfaces for Repository and UnitOfWork

All data entities should have CRUD actions, so let’s create a generic interface named IRepository, and then the repository of each entity should implement this interface.

ThreeLayerDomain/Interfaces/IRepository.cs

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace ThreeLayerSample.Domain.Interfaces
{
        public interface IRepository where T : class
    {
        DbSet Entities { get; }
        DbContext DbContext { get; }
        ///
        /// Get all items of an entity by asynchronous method
        /// 
        /// 
        Task<IList> GetAllAsync();
        ///
        /// Fin one item of an entity synchronous method
        /// 
        ///
        /// 
        T Find(params object[] keyValues);
        ///
        /// Find one item of an entity by asynchronous method
        /// 
        ///
        /// 
        Task FindAsync(params object[] keyValues);
        ///
        /// Insert item into an entity by asynchronous method
        /// 
        ///
        ///
        /// 
        Task InsertAsync(T entity, bool saveChanges = true);
        ///
        /// Insert multiple items into an entity by asynchronous method
        /// 
        ///
        ///
        /// 
        Task InsertRangeAsync(IEnumerable entities, bool saveChanges = true);
        ///
        /// Remove one item from an entity by asynchronous method
        /// 
        ///
        ///
        /// 
        Task DeleteAsync(int id, bool saveChanges = true);
        ///
        /// Remove one item from an entity by asynchronous method
        ///
        /// 
        ///
        ///
        /// 
        Task DeleteAsync(T entity, bool saveChanges = true);
        ///
        /// Remove multiple items from an entity by asynchronous method
        /// 
        ///
        ///
        /// 
        Task DeleteRangeAsync(IEnumerable entities, bool saveChanges = true);
    }
}
    
    Code language: C# (cs)

We need another interface named IUnitOfWork as follows:

ThreeLayerDomain/Interfaces/IUnitOfWork.cs


using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using ThreeLayerSample.Domain.Interfaces;
namespace ThreeLayerSample.Infrastructure
{
        public class Repository : IRepository where T : class
        {
                public DbSet Entities => DbContext.Set();
        public DbContext DbContext { get; private set; }
                public Repository(DbContext dbContext)
                {
                        DbContext = dbContext;
        }
                public async Task DeleteAsync(int id, bool saveChanges = true)
                {
                        var entity = await Entities.FindAsync(id);
                        await DeleteAsync(entity);
                        if (saveChanges)
                        {
                            await DbContext.SaveChangesAsync();
                        }
                }
                public async Task DeleteAsync(T entity, bool saveChanges = true)
                {
                        Entities.Remove(entity);
                        if (saveChanges)
                        {
                            await DbContext.SaveChangesAsync();
                        }
                }
                public async Task DeleteRangeAsync(IEnumerable entities, bool saveChanges = true)
                {
            var enumerable = entities as T[] ?? entities.ToArray();
            if (enumerable.Any())
                        {
                            Entities.RemoveRange(enumerable);
                        }
                        if (saveChanges)
                        {
                            await DbContext.SaveChangesAsync();
                        }
                }
        public async Task<IList> GetAllAsync()
                {
                        return await Entities.ToListAsync();
                }
                public T Find(params object[] keyValues)
                {
                        return Entities.Find(keyValues);
                }
                public virtual async Task FindAsync(params object[] keyValues)
                {
                        return await Entities.FindAsync(keyValues);
                }
                public async Task InsertAsync(T entity, bool saveChanges = true)
                {
                        await Entities.AddAsync(entity);
                        if (saveChanges)
                        {
                            await DbContext.SaveChangesAsync();
                        }
                }
                public async Task InsertRangeAsync(IEnumerable entities, bool saveChanges = true)
                {
                        await DbContext.AddRangeAsync(entities);
                        if (saveChanges)
                        {
                            await DbContext.SaveChangesAsync();
                        }
                }
        }
}
    ​
    ​Code language: C# (cs)

They are interfaces without business/database logic here, so I put it in this layer.

 

Data Access layer

ThreeLayerSample.Infrastructure class project in this example.

1. Implement generic classes

By applying Generic Repository and Unit of Work design patterns, the data access layer classes are implemented as follows:

ThreeLayerSample.Infrastructure/Repository.cs

using System.Collections.Generic;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using ThreeLayerSample.Domain.Interfaces;
namespace ThreeLayerSample.Infrastructure
{
        public class UnitOfWork : IUnitOfWork
        {
                public DbContext DbContext { get; private set; }
        private Dictionary<string, object> Repositories { get; }
        private IDbContextTransaction _transaction;
        private IsolationLevel? _isolationLevel;
        public UnitOfWork(DbFactory dbFactory)
                {
                        DbContext = dbFactory.DbContext;
            Repositories = new Dictionary<string, dynamic>();
        }
        public async Task SaveChangesAsync(CancellationToken cancellationToken = default)
                {
                        return await DbContext.SaveChangesAsync(cancellationToken);
                }
                private async Task StartNewTransactionIfNeeded()
        {
            if (_transaction == null)
            {
                _transaction =  _isolationLevel.HasValue ?
                    await DbContext.Database.BeginTransactionAsync(_isolationLevel.GetValueOrDefault()) : await DbContext.Database.BeginTransactionAsync();
            }
        }
        public async Task BeginTransaction()
        {
            await StartNewTransactionIfNeeded();
        }
        public async Task CommitTransaction()
        {
            /*
                do not open transaction here, because if during the request
                nothing was changed(only select queries were run), we don't
                want to open and commit an empty transaction -calling SaveChanges()
                on _transactionProvider will not send any sql to database in such case
            */
            await DbContext.SaveChangesAsync();
            if (_transaction == null) return;
            await _transaction.CommitAsync();
            await _transaction.DisposeAsync();
            _transaction = null;
        }
        public async Task RollbackTransaction()
        {
            if (_transaction == null) return;
            await _transaction.RollbackAsync();
            await _transaction.DisposeAsync();
            _transaction = null;
        }
                public void Dispose()
                {
                        if (DbContext == null)
                            return;
                        //
                        // Close connection
                        if (DbContext.Database.GetDbConnection().State == ConnectionState.Open)
                        {
                            DbContext.Database.GetDbConnection().Close();
                        }
                        DbContext.Dispose();
                        DbContext = null;
                }
        public IRepository Repository() where TEntity : class
                {
                        var type = typeof(TEntity);
                        var typeName = type.Name;
                        lock (Repositories)
                        {
                            if (Repositories.ContainsKey(typeName))
                {
                    return (IRepository) Repositories[typeName];
                }
                var repository = new Repository(DbContext);
                            Repositories.Add(typeName, repository);
                            return repository;
                        }
                }
    }
}
    
    Code language: C# (cs)

ThreeLayerSample.Infrastructure/UnitOfWork.cs

 

using System;
using Microsoft.EntityFrameworkCore;
namespace ThreeLayerSample.Infrastructure
{
    public class DbFactory : IDisposable
    {
        private bool _disposed;
        private Func _instanceFunc;
        private DbContext _dbContext;
        public DbContext DbContext => _dbContext ?? (_dbContext = _instanceFunc.Invoke());
        public DbFactory(Func dbContextFactory)
        {
            _instanceFunc = dbContextFactory;
        }
        public void Dispose()
        {
            if (!_disposed && _dbContext != null)
            {
                _disposed = true;
                _dbContext.Dispose();
            }
        }
    }
}
    
    Code language: C# (cs)

And another DbFactory class that will initialize a DbContext when we use it.

ThreeLayerSample.Infrastructure/DbFactory.cs

 

using System.Collections.Generic;
using System.Threading.Tasks;
using ThreeLayerSample.Domain.Entities;
namespace ThreeLayerSample.Domain.Interfaces.Services
{
    public interface IWorkService
    {
        ///
        /// Get all items of Work table
        /// 
        /// 
        Task<IList> GetAll();
        Task GetOne(int workId);
        Task Update(Work work);
        Task Add(Work work);
        Task Delete(int workId);
    }
}
    
    Code language: C# (cs)

 

Business Logic layer

ThreeLayerSample.Service class project in this example.

1. Create an interface for the service

Let’s create the interface for the WorkService that the Presentation layer depends on as we don’t want to tight the Presentation layer and Business Logic layer with a concrete implementation.

ThreeLayerSample.Domain/Interfaces/Services/IWorkService.cs

 

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ThreeLayerSample.Domain.Entities;
using ThreeLayerSample.Domain.Interfaces;
using ThreeLayerSample.Domain.Interfaces.Services;
namespace ThreeLayerSample.Service
{
    public class WorkService: IWorkService
    {
        private readonly IUnitOfWork _unitOfWork;
        public WorkService(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }
        public async Task<IList> GetAll()
        {
            return await _unitOfWork.Repository().GetAllAsync();
        }
        public async Task GetOne(int workId)
        {
            return await _unitOfWork.Repository().FindAsync(workId);
        }
        public async Task Update(Work workInput)
        {
            try
            {
                await _unitOfWork.BeginTransaction();
                var workRepos = _unitOfWork.Repository();
                var work = await workRepos.FindAsync(workInput.Id);
                if (work == null)
                    throw new KeyNotFoundException();
                work.Name = work.Name;
                await _unitOfWork.CommitTransaction();
            }
            catch (Exception e)
            {
                await _unitOfWork.RollbackTransaction();
                throw;
            }
        }
        public async Task Add(Work workInput)
        {
            try
            {
                await _unitOfWork.BeginTransaction();
                var workRepos = _unitOfWork.Repository();
                await workRepos.InsertAsync(workInput);
                await _unitOfWork.CommitTransaction();
            }
            catch (Exception e)
            {
                await _unitOfWork.RollbackTransaction();
                throw;
            }
        }
        public async Task Delete(int workId)
        {
            try
            {
                await _unitOfWork.BeginTransaction();
                var workRepos = _unitOfWork.Repository();
                var work = await workRepos.FindAsync(workId);
                if (work == null)
                    throw new KeyNotFoundException();
                await workRepos.DeleteAsync(work);
                await _unitOfWork.CommitTransaction();
            }
            catch (Exception e)
            {
                await _unitOfWork.RollbackTransaction();
                throw;
            }
        }
    }
}
    
    Code language: C# (cs)

2. Implement code for the service

Next, implement business logic processing the Work service in a class named WorkService as follows. This is the business logic processing code, so we put this class in the Business Logic layer. As you can see, the service requests to get all items of the Work table via the Repository instance of Work entity, which was implemented in the generic repository.

ThreeLayerSample.Service/WorkService.cs

 

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ThreeLayerSample.Domain.Interfaces;
using ThreeLayerSample.Domain.Interfaces.Services;
using ThreeLayerSample.Domain.Models;
using ThreeLayerSample.Infrastructure;
using ThreeLayerSample.Service;
namespace ThreeLayerSample.Web_Razor_.Extensions
{
        public static class ServiceCollectionExtensions
    {
        ///
        /// Add needed instances for database
        /// 
        ///
        ///
        /// 
        public static IServiceCollection AddDatabase(this IServiceCollection services, IConfiguration configuration)
        {
            // Configure DbContext with Scoped lifetime  
            services.AddDbContext(options =>
                {
                    options.UseSqlServer(AppSettings.ConnectionString,
                        sqlOptions => sqlOptions.CommandTimeout(120));
                    options.UseLazyLoadingProxies();
                }
            );
            services.AddScoped<Func>((provider) => () => provider.GetService());
            services.AddScoped();
            services.AddScoped<IUnitOfWork, UnitOfWork>();
            return services;
        }
        ///
        /// Add instances of in-use services
        /// 
        ///
        /// 
        public static IServiceCollection AddServices(this IServiceCollection services)
        {
            return services.AddScoped<IWorkService, WorkService>();
        }
    }
}
    
    Code language: C# (cs)

 

Presentation layer

ThreeLayerSample.Web(Razor) ASP.NET Core Web App in this example.

Follow this tutorial to create an ASP.NET Core Razor page application.
Once the application is created, create a ServiceCollectionExtensions class under the Extensions folder.

ThreeLayerSample.Web_Razor_/Extensions/ServiceCollectionExtensions .cs

 

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ThreeLayerSample.Domain.Interfaces;
using ThreeLayerSample.Domain.Interfaces.Services;
using ThreeLayerSample.Domain.Models;
using ThreeLayerSample.Infrastructure;
using ThreeLayerSample.Service;
namespace ThreeLayerSample.Web_Razor_.Extensions
{
        public static class ServiceCollectionExtensions
    {
        ///
        /// Add needed instances for database
        /// 
        ///
        ///
        /// 
        public static IServiceCollection AddDatabase(this IServiceCollection services, IConfiguration configuration)
        {
            // Configure DbContext with Scoped lifetime  
            services.AddDbContext(options =>
                {
                    options.UseSqlServer(AppSettings.ConnectionString,
                        sqlOptions => sqlOptions.CommandTimeout(120));
                    options.UseLazyLoadingProxies();
                }
            );
            services.AddScoped<Func>((provider) => () => provider.GetService());
            services.AddScoped();
            services.AddScoped<IUnitOfWork, UnitOfWork>();
            return services;
        }
        ///
        /// Add instances of in-use services
        /// 
        ///
        /// 
        public static IServiceCollection AddServices(this IServiceCollection services)
        {
            return services.AddScoped<IWorkService, WorkService>();
        }
    }
}
    
    Code language: C# (cs)

Add your connection string into the appsettings.json file, and it’s the connection string which the Data Access layer will employ to establish a connection to the database (same value as the connection string in the Scaffold command).

ThreeLayerSample.Web_Razor_/appsettings.json

 

{
    "Logging": {
      "LogLevel": {
        "Default": "Information",
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    },
    "AppSettings": {
      "ConnectionString": "Data Source=(local);Initial Catalog=ThreeLayerSample.Database;Persist Security Info=True;User ID=sa;Password=PASSWORD;MultipleActiveResultSets=True"
    },
    "AllowedHosts": "*"
  }Code language: JSON / JSON with Comments (json)

Add an AppSettings class to the Entity layer.

ThreeLayerSample.Domain/Models/AppSettings.cs

namespace ThreeLayerSample.Domain.Models
    {
        public class AppSettings
        {
            public static string ConnectionString { get; private set; }
        }
    }
    
    Code language: C# (cs)

Open the Startup.cs file then add the following codes.

At its constructor: read data from appsettings.json, then store it in the created AppSettings class.

In the ConfigureServices: register instances for DataContext, its Factory, UnitOfWork, and WorkService to the application (using extension methods in ServiceCollectionExtensions class).

ThreeLayerSample.Web_Razor_/Startup.cs

 

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using ThreeLayerSample.Domain.Models;
using ThreeLayerSample.Web_Razor_.Extensions;
namespace ThreeLayerSample.Web_Razor_
{
    public class Startup
    {
        public Startup(IWebHostEnvironment env)
        {
            Configuration = InitConfiguration(env);
        }
        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.AddDatabase(Configuration)
                .AddServices();
            //
            services.AddRazorPages();
        }
        // 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("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
            });
        }
        private IConfiguration InitConfiguration(IWebHostEnvironment env)
        {
            // Config the app to read values from appsettings base on current environment value.
            var configuration = new ConfigurationBuilder()
                .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
                .AddJsonFile("appsettings.json", false, true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true)
                .AddEnvironmentVariables().Build();
            //
            // Map AppSettings section in appsettings.json file value to AppSetting model
            configuration.GetSection("AppSettings").Get(options => options.BindNonPublicProperties = true);
            return configuration;
        }
    }
}
    
    Code language: C# (cs)

Open Index.cshtml.cs file, add the following code to inject WorkService, then get data from WorkService and set it to Works property.

ThreeLayerSample.Web_Razor_/Pages/Index.cshtml.cs

 

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ThreeLayerSample.Domain.Entities;
using ThreeLayerSample.Domain.Interfaces.Services;
namespace ThreeLayerSample.Web_Razor_.Pages
{
    public class IndexModel : PageModel
    {
        private readonly ILogger _logger;
        private readonly IWorkService _workService;
        public IList Works { get; private set; }
        public IndexModel(ILogger logger, IWorkService workService)
        {
            _logger = logger;
            _workService = workService;
        }
        public async Task OnGetAsync()
        {
            Works = await _workService.GetAll();
        }
    }
}
    
    Code language: C# (cs)

In the Index.cshtml file, add the following code to present data to the UI.

ThreeLayerSample.Web_Razor_/Pages/Index.cshtml

 

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}</code></pre>
<div class="text-center">
<h1 class="display-4">Welcome To Enlab Demo</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>

@foreach (var item in Model.Works) {

<div>@item.Name</div>

}</div>
<pre class="wp-block-code"><code>Code language: C# (cs)

Testing

Run the application to check for the result.

Testing 3-layer

 

Deployment

Create a folder to store your source code.

deployment 3-layer

Open your IIS, then choose Create Website to host your application. Provide a name, a specific port, and a physical path to the source code folder for the Content Directory section. Make sure “Start website immediately” is ticked.

3-layer deployment 2

Open your solution in your Visual Studio, then follow these steps:

Right-click on ThreeLayerSample.Web(Razor), select Publish.

publish 3-layer

Select “Folder”.

Publish a 3-layer architecture application

Enter “Folder location” by creating the source code folder path below.

publish location

Click to publish the project.

Publish

Once you’ve done publishing, open the application on your browser to check for the result.

Publish a 3-layer architecture application

 

Conclusion

The layered architecture is well-designed for software development that helps organize your code with high maintainability, reusability, and readability. However, even though codes are well-organized in layers, they are deployed and run on the same physical machine, even on the same process. This may not be the right choice for complex applications, which require high availability and stability. We need to upgrade this architecture to another higher level named 3-tier architecture which I will share in my next article.

Thank you for reading, and happy coding!

 

Contact us

References
1. Loc Nguyen, How to implement Repository & Unit of Work design patterns in .NET Core with practical examples [Part 1], https://enlabsoftware.com/, 2021.
2. Anant Patil, 3 Layered Architecture, https://www.ecanarys.com/
3. Mark Richards, Software Architecture Patterns, https://www.oreilly.com/

About the author

Uyen Luu

As a full-stack developer at Enlab Software, he is fluent in C# and VB programming languages. He also specializes in ASP.NET, .NET Core, Angular, Devextreme UI, Telerik Web controls, Kendo UI, etc. He has helped clients to develop several software applications, which contributed to his working experience in IIS, SQL Server, Power BI, Azure Development.

Up Next

Domain-Driven Design in ASP.NET Core applications
May 31,2021 by Loc Nguyen
The technology industry has been thriving for the second half of the last century. It's...
How to create real time chat applications using WebSocket APIs in API Gateway
March 26,2021 by Trong Pham
My experience developing real-time messaging applications leveraging AWS services inspires me to share with the...
How to build and deploy a 3-tier architecture application with C#
March 11,2021 by Uyen Luu
Hi, back to the architecture patterns, in the last article I explained what the 3-layer...
Repository and Unit of Work design patterns in .NET core
February 03,2021 by Loc Nguyen
Hi, back to the topic Repository & Unit of Work pattern, last time I explained...
Roll to Top

Can we send you our next blog posts? Only the best stuffs.

Subscribe