ASP.NET Core Web API Development with Onion Architecture using Prototype Design Pattern

I will be implementing a CRUD (Create, Read, Update, Delete) operation in an ASP.NET Core Web API using the Onion Architecture and Prototype Design Pattern. Note that this example is simplified for demonstration purposes, and in a real-world scenario, you might want to add more features, error handling, validation, and security measures.

Onion Architecture Overview

Onion Architecture is a software architectural pattern that emphasizes the separation of concerns into different layers. The typical layers include Core, Infrastructure, and Presentation.

  1. Core Layer: Contains the application's business logic, entities, and interfaces.
  2. Infrastructure Layer: Includes data access, external services, and any infrastructure-related concerns.
  3. Presentation Layer: Represents the user interface, such as web API controllers.

Create a new ASP.NET Core Web API Project

Use the following commands in the terminal or command prompt:

dotnet new webapi -n OnionArchitectureDemo
cd OnionArchitectureDemo

Create Core Layer

  • Add a class library for the core layer.

    dotnet new classlib -n OnionArchitectureDemo.Core
    
  • Create a model class in the OnionArchitectureDemo.Core project.

    // Sardar Mudassar Ali Khan
    // OnionArchitectureDemo.Core/Models/Article.cs
    namespace OnionArchitectureDemo.Core.Models
    {
        public class Article
        {
            public int Id { get; set; }
            public string Title { get; set; }
            public string Content { get; set; }
        }
    }
    

Create Infrastructure Layer

  • Add a class library for the infrastructure layer.

    dotnet new classlib -n OnionArchitectureDemo.Infrastructure
    
  • Implement a repository interface and a fake repository for testing purposes in the OnionArchitectureDemo.Infrastructure project.

    // Sardar Mudassar Ali Khan
    // OnionArchitectureDemo.Infrastructure/Repositories/IArticleRepository.cs
    using OnionArchitectureDemo.Core.Models;
    using System.Collections.Generic;
    
    namespace OnionArchitectureDemo.Infrastructure.Repositories
    {
        public interface IArticleRepository
        {
            IEnumerable<Article> GetAll();
            Article GetById(int id);
            void Add(Article article);
            void Update(Article article);
            void Delete(int id);
        }
    }
    
    // Sardar Mudassar Ali Khan
    // OnionArchitectureDemo.Infrastructure/Repositories/FakeArticleRepository.cs
    using OnionArchitectureDemo.Core.Models;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace OnionArchitectureDemo.Infrastructure.Repositories
    {
        public class CSharpCornerArticleRepository : IArticleRepository
        {
            private List<Article> _articles;
    
            public FakeArticleRepository()
            {
                _articles = new List<Article>();
            }
    
            public IEnumerable<Article> GetAll()
            {
                return _articles;
            }
    
            public Article GetById(int id)
            {
                return _articles.FirstOrDefault(a => a.Id == id);
            }
    
            public void Add(Article article)
            {
                _articles.Add(article);
            }
    
            public void Update(Article article)
            {
                // Implement update logic
            }
    
            public void Delete(int id)
            {
                var article = _articles.FirstOrDefault(a => a.Id == id);
                if (article != null)
                    _articles.Remove(article);
            }
        }
    }
    

Implement Services in the Core Layer

Create a service interface and a service class in the OnionArchitectureDemo.Core project.

// Sardar Mudassar Ali Khan
// OnionArchitectureDemo.Core/Services/IArticleService.cs
using OnionArchitectureDemo.Core.Models;
using System.Collections.Generic;

namespace OnionArchitectureDemo.Core.Services
{
    public interface IArticleService
    {
        IEnumerable<Article> GetAllArticles();
        Article GetArticleById(int id);
        void AddArticle(Article article);
        void UpdateArticle(Article article);
        void DeleteArticle(int id);
    }
}
// Sardar Mudassar Ali Khan
// OnionArchitectureDemo.Core/Services/ArticleService.cs
using OnionArchitectureDemo.Core.Models;
using OnionArchitectureDemo.Infrastructure.Repositories;
using System.Collections.Generic;

namespace OnionArchitectureDemo.Core.Services
{
    public class ArticleService: IArticleService
    {
        private readonly IArticleRepository _articleRepository;

        public ArticleService(IArticleRepository articleRepository)
        {
            _articleRepository = articleRepository;
        }

        public IEnumerable<Article> GetAllArticles()
        {
            return _articleRepository.GetAll();
        }

        public Article GetArticleById(int id)
        {
            return _articleRepository.GetById(id);
        }

        public void AddArticle(Article article)
        {
            _articleRepository.Add(article);
        }

        public void UpdateArticle(Article article)
        {
            _articleRepository.Update(article);
        }

        public void DeleteArticle(int id)
        {
            _articleRepository.Delete(id);
        }
    }
}

Implement API Controllers in the Presentation Layer

Use the default ValuesController for simplicity.

// Sardar Mudassar Ali Khan
// OnionArchitectureDemo/Controllers/ValuesController.cs
using Microsoft.AspNetCore.Mvc;
using OnionArchitectureDemo.Core.Models;
using OnionArchitectureDemo.Core.Services;
using System.Collections.Generic;

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IArticleService _articleService;

    public ValuesController(IArticleService articleService)
    {
        _articleService = articleService;
    }

    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<Article>> Get()
    {
        var articles = _articleService.GetAllArticles();
        return Ok(articles);
    }

    // GET api/values/5
    [HttpGet("{id}")]
    public ActionResult<Article> Get(int id)
    {
        var article = _articleService.GetArticleById(id);
        if (article == null)
            return NotFound();

        return Ok(article);
    }

    // POST api/values
    [HttpPost]
    public IActionResult Post([FromBody] Article article)
    {
        _articleService.AddArticle(article);
        return CreatedAtAction(nameof(Get), new { id = article.Id }, article);
    }

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id, [FromBody] Article article)
    {
        var existingArticle = _articleService.GetArticleById(id);
        if (existingArticle == null)
            return NotFound();

        article.Id = id;
        _articleService.UpdateArticle(article);

        return NoContent();
    }

    // DELETE api/values/5
    [HttpDelete("{id}")]
    public IActionResult Delete(int id)
    {
        var existingArticle = _articleService.GetArticleById(id);
        if (existingArticle == null)
            return NotFound();

        _articleService.DeleteArticle(id);

        return NoContent();
    }
}

Register Dependencies in Startup.cs

In the Startup.cs file, configure dependency injection.

// Sardar Mudassar Ali Khan
// OnionArchitectureDemo/Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OnionArchitectureDemo.Core.Services;
using OnionArchitectureDemo.Infrastructure.Repositories;

namespace OnionArchitectureDemo
{
    public class Startup
    {
        // Other methods...

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddScoped<IArticleRepository, FakeArticleRepository>();
            services.AddScoped<IArticleService, ArticleService>();
        }
    }
}

Run the Application

  • Run the application using the following command:

    dotnet run
    

The API should be accessible at https://localhost:5001/api/values.

That's a simple example of implementing a CRUD operation in an ASP.NET Core Web API using Onion Architecture. In a real-world scenario, you might want to replace the fake repository with a database repository and implement error handling, validation, and other necessary features.

Conclusion

In conclusion, this guide has demonstrated the creation of a robust ASP.NET Core Web API using the Onion Architecture pattern. The implementation includes a comprehensive set of CRUD operations on an Article model, showcasing the separation of concerns across layers for better maintainability and scalability.

By adopting the Onion Architecture, we've established clear boundaries between the Core, Infrastructure, and Presentation layers. The Core layer contains the business logic and entities, while the Infrastructure layer handles data access through repositories. The Presentation layer, in the form of API controllers, interacts with the Core layer, promoting a clean and modular design.

Moreover, the Prototype Design Pattern has been employed to structure the creation of objects, ensuring a consistent and efficient approach to managing instances of the Article model. This design pattern facilitates the implementation of a flexible and reusable solution.

As you continue to build upon this foundation, consider enhancing the application with additional features, such as error handling, validation, authentication, and real database integration. This example serves as a starting point, and further refinement based on specific project requirements is encouraged.

By following the principles outlined in this guide, you are well-equipped to create maintainable and scalable ASP.NET Core Web APIs, fostering a foundation for the development of robust and efficient applications.


Similar Articles