Implementing the Visitor Pattern in ASP.NET Core Web API Using 3-Tier Architecture

In an ASP.NET Core Web API employing a 3-tier architecture, the Visitor Pattern is utilized for efficient data manipulation in the C# Article model. The model, CSharpArticle, includes essential properties. The architecture involves a Data Access Layer with a repository managing database interactions, a Business Layer housing the Visitor interface and Article Service, and a Presentation Layer containing the API controllers. The Visitor Pattern is employed in the Business Layer to perform operations on articles, allowing for clean separation of concerns and enabling reusable, structured, and scalable code. This design ensures that CRUD operations benefit from the Visitor Pattern's flexibility while maintaining a clear division of responsibilities across the layers of the application.

3-Tier Architecture Overview

  • Presentation Layer: This layer handles the API controllers and interacts with the client.
  • Business Layer: Contains the business logic, data validation, and any specific business rules.
  • Data Access Layer: Manages data retrieval and storage, interacting with the database.

C# Article Model

Let's create the Article model first.

public class CSharpArticle
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

Data Access Layer

Repository Interface:

public interface IArticleRepository
{
    CSharpArticle GetArticleById(int id);
    void AddArticle(CSharpArticle article);
    void UpdateArticle(CSharpArticle article);
    void DeleteArticle(int id);
}

Repository Implementation:

public class ArticleRepository: IArticleRepository
{
    private readonly CSharpCornerArticleProject _context;

    public ArticleRepository(YourDbContext context)
    {
        _context = context;
    }

    public CSharpArticle GetArticleById(int id)
    {
        return _context.Articles.FirstOrDefault(a => a.Id == id);
    }

    public void AddArticle(CSharpArticle article)
    {
        _context.Articles.Add(article);
        _context.SaveChanges();
    }

    public void UpdateArticle(CSharpArticle article)
    {
        _context.Articles.Update(article);
        _context.SaveChanges();
    }

    public void DeleteArticle(int id)
    {
        var article = _context.Articles.FirstOrDefault(a => a.Id == id);
        if (article != null)
        {
            _context.Articles.Remove(article);
            _context.SaveChanges();
        }
    }

    public void Accept(IArticleVisitor visitor, int articleId)
    {
        var article = GetArticleById(articleId);
        visitor.Visit(article);
        UpdateArticle(article);
    }
}

Business Layer
 

Visitor Interface

public interface IArticleVisitor
{
    void Visit(CSharpArticle article);
}

public class ContentAnalyzerVisitor: IArticleVisitor
{
    public void Visit(CSharpArticle article)
    {
        if (article != null)
        {
            int wordCount = CountWords(article.Content);
            bool hasKeywords = CheckForKeywords(article.Content);

            article.WordCount = wordCount;
            article.HasKeywords = hasKeywords;
        }
    }

    private int CountWords(string content)
    {
        if (string.IsNullOrWhiteSpace(content))
            return 0;

        string[] words = content.Split(new char[] { ' ', '.', ',', ';', '!', '?' }, StringSplitOptions.RemoveEmptyEntries);
        return words.Length;
    }

    private bool CheckForKeywords(string content)
    {
        string[] keywordsToCheck = { "C#", "ASP.NET", "Entity Framework" }; if needed
        foreach (string keyword in keywordsToCheck)
        {
            if (content.Contains(keyword, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
        }
        return false;
    }
}

Article Service:

public class ArticleService
{
    private readonly IArticleRepository _repository;

    public ArticleService(IArticleRepository repository)
    {
        _repository = repository;
    }

    public void Accept(IArticleVisitor visitor, int articleId)
    {
        CSharpArticle article = _repository.GetArticleById(articleId);
        visitor.Visit(article);
        _repository.UpdateArticle(article);
    }

    public void Publish(int articleId)
    {
        CSharpArticle article = _repository.GetArticleById(articleId);
        article.Publish();
        _repository.UpdateArticle(article);
    }

    public void Archive(int articleId)
    {
        CSharpArticle article = _repository.GetArticleById(articleId);
        article.Archive();
        _repository.UpdateArticle(article);
    }
}

Presentation Layer (Controller)

Controller:

[Route("api/articles")]
[ApiController]
public class CSharpArticleController : ControllerBase
{
    private readonly ArticleService _articleService;

    public CSharpArticleController(ArticleService articleService)
    {
        _articleService = articleService;
    }

    [HttpGet("{id}")]
    public IActionResult Get(int id)
    {
        var article = _articleService.GetArticle(id);
        if (article == null)
        {
            return NotFound();
        }
        return Ok(article);
    }

    [HttpPost]
    public IActionResult Post([FromBody] CSharpArticle article)
    {
        if (article == null)
        {
            return BadRequest();
        }
        _articleService.CreateArticle(article);
        return CreatedAtAction("Get", new { id = article.Id }, article);
    }

    [HttpPut("{id}")]
    public IActionResult Put(int id, [FromBody] CSharpArticle article)
    {
        if (id != article.Id)
        {
            return BadRequest();
        }
        _articleService.UpdateArticle(article);
        return NoContent();
    }

    [HttpDelete("{id}")]
    public IActionResult Delete(int id)
    {
        var existingArticle = _articleService.GetArticle(id);
        if (existingArticle == null)
        {
            return NotFound();
        }
        _articleService.DeleteArticle(id);
        return NoContent();
    }
}

Conclusion

This structure separates concerns and allows the Visitor Pattern to perform operations on the Article model across layers, ensuring a clean separation of logic and reusability. The implementation of the repository, service, and controller might differ based on the database, ORM, or specific requirements of your application while the Visitor Pattern can be powerful for certain scenarios, it might not be necessary for every CRUD operation. Always assess the actual need for the pattern in the context of your application's complexity and requirements.

In an ASP.NET Core Web API following a 3-tier architecture, the implementation of the Visitor Pattern for C# Article management offers a robust structure for handling CRUD operations. The CSharpArticle model forms the core of data operations, managed by the Data Access Layer through the ArticleRepository. This repository interfaces with the database and facilitates CRUD functionalities. The Business Layer, represented by the  ArticleService, orchestrates operations on articles, employing the Visitor Pattern to execute specific tasks via visitors, such as content analysis, publishing, archiving, and other relevant actions.

The CSharpArticleController in the Presentation Layer acts as the interface for external interactions. It integrates with the ArticleService to handle HTTP requests, providing functionalities for retrieving, creating, updating, and deleting articles. Each method corresponds to a specific HTTP verb, allowing seamless communication with the underlying layers, ultimately ensuring efficient and controlled article management.

This structured architecture allows for modular, scalable, and maintainable code. It enables the application to accommodate new features or modifications without disturbing the core functionalities. The Visitor Pattern's implementation within the three-tier architecture enriches the system's flexibility, promoting a clear separation of concerns and enhancing the overall performance and maintainability of the API.


Similar Articles