Addressing Common Code Smells in ASP.NET Core

Introduction

Code smells are common issues in software development that indicate potential problems in the code. In ASP.NET Core or any other programming language, addressing code smells is essential for maintaining a clean and maintainable codebase. Let's go through an example and address some common code smells step by step.

We'll create a simple ASP.NET Core web application for managing a list of tasks. We'll use Visual Studio and C# for this example.

Step 1. Create a New ASP.NET Core Project

  1. Open Visual Studio.
  2. Click on "Create a new project."
  3. Select "ASP.NET Core Web Application."
  4. Choose "Web Application (Model-View-Controller)" as the project template.
  5. Name your project and click "Create."

Step 2. Define a Model

In this step, we'll define a Task model to represent our tasks.

Author: Sardar Mudassar Ali Khan
public class Task
{
    public int Id { get; set; }
    public string Title { get; set; }
    public bool IsComplete { get; set; }
}

Step 3. Create a Data Repository

Let's create an interface for a data repository and implement it. This will help address code smells related to maintainability and separation of concerns.

Author: Sardar Mudassar Ali Khan
public interface ITaskRepository
{
    Task<IEnumerable<Task>> GetAllTasks();
    Task<Task> GetTaskById(int id);
    Task<Task> AddTask(Task task);
    Task<Task> UpdateTask(Task task);
    Task<Task> DeleteTask(int id);
}

Let's write the code about the task Repository.

Author: Sardar Mudassar Ali Khan
// Repositories/TaskRepository.cs
public class TaskRepository : ITaskRepository
{
    private List<Task> _tasks = new List<Task>();

    public async Task<IEnumerable<Task>> GetAllTasks()
    {
        return _tasks;
    }

    public async Task<Task> GetTaskById(int id)
    {
        return _tasks.FirstOrDefault(t => t.Id == id);
    }

    public async Task<Task> AddTask(Task task)
    {
        task.Id = _tasks.Count + 1;
        _tasks.Add(task);
        return task;
    }

    public async Task<Task> UpdateTask(Task task)
    {
        var existingTask = await GetTaskById(task.Id);
        if (existingTask != null)
        {
            existingTask.Title = task.Title;
            existingTask.IsComplete = task.IsComplete;
        }
        return existingTask;
    }

    public async Task<Task> DeleteTask(int id)
    {
        var existingTask = await GetTaskById(id);
        if (existingTask != null)
        {
            _tasks.Remove(existingTask);
        }
        return existingTask;
    }
}

Step 4. Create a Controller

Create a controller for managing tasks. This helps address code smells related to maintainability and separation of concerns.

Author: Sardar Mudassar Ali Khan
// Controllers/TaskController.cs
[Route("api/tasks")]
[ApiController]
public class TaskController : ControllerBase
{
    private readonly ITaskRepository _taskRepository;

    public TaskController(ITaskRepository taskRepository)
    {
        _taskRepository = taskRepository;
    }

    [HttpGet]
    public async Task<IEnumerable<Task>> GetAllTasks()
    {
        return await _taskRepository.GetAllTasks();
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetTask(int id)
    {
        var task = await _taskRepository.GetTaskById(id);
        if (task == null)
        {
            return NotFound();
        }
        return Ok(task);
    }

    [HttpPost]
    public async Task<IActionResult> CreateTask([FromBody] Task task)
    {
        var createdTask = await _taskRepository.AddTask(task);
        return CreatedAtAction(nameof(GetTask), new { id = createdTask.Id }, createdTask);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateTask(int id, [FromBody] Task task)
    {
        if (id != task.Id)
        {
            return BadRequest();
        }
        var updatedTask = await _taskRepository.UpdateTask(task);
        if (updatedTask == null)
        {
            return NotFound();
        }
        return Ok(updatedTask);
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteTask(int id)
    {
        var deletedTask = await _taskRepository.DeleteTask(id);
        if (deletedTask == null)
        {
            return NotFound();
        }
        return NoContent();
    }
}

Step 5. Addressing Code Smells

In the provided code, we have addressed some common code smells:

  • Maintainability: The code is organized into separate folders for models, interfaces, and controllers, promoting a clean code structure.
  • Separation of Concerns: We've separated the data repository (TaskRepository) from the controller (TaskController) to adhere to the Single Responsibility Principle.
  • Error Handling: We handle errors by returning appropriate status codes (e.g., 404 for not found) and messages.
  • Use of Interfaces: We use an interface (ITaskRepository) for the repository, which allows for easy mocking and testing.
  • Consistency and Naming: We use consistent naming conventions for methods and variables.
  • Validation: We validate input data, such as checking if the ID in the URL matches the ID in the request body in the UpdateTask method.

This example provides a basic structure for an ASP.NET Core application while addressing common code smells. You can further enhance it by adding validation, authentication, and additional features as needed.