Asynchronous Programming in ASP.NET Core Minimal API

Asynchronous Programming in ASP.NET Core Minimal API

In this article, I will discuss How to Implement Asynchronous Programming in ASP.NET Core Minimal API with examples. Please read our previous articles discussing How to Implement Error Handling and Logging in ASP.NET Core Minimal API with Examples. We will be working with the same project as we worked so far with ASP.NET Core Minimal API.

Asynchronous Programming in ASP.NET Core Minimal API

Using Asynchronous Programming in ASP.NET Core Minimal APIs can enhance the performance and scalability of your application, especially when dealing with I/O-bound operations such as database access, file handling, or network calls. Using Asynchronous Programming with Minimal APIs in ASP.NET Core allows our application to handle multiple requests more efficiently by utilizing non-blocking I/O operations. Let us proceed and understand how to implement Asynchronous Programming in ASP.NET Core Minimal API step by step. 

Modifying the Employee Service to Use Async Operations

Let’s update the IEmployeeService interface and implement EmployeeService using asynchronous methods. We will simulate asynchronous operations using Task and Task.FromResult, but in a real-world application, you would typically interact with a database asynchronously, which we will discuss in our next article.

IEmployeeService Interface:
namespace MinimalAPIDemo.Models
{
    public interface IEmployeeService
    {
        Task<IEnumerable<Employee>> GetAllEmployeesAsync();
        Task<Employee> GetEmployeeByIdAsync(int id);
        Task<Employee> AddEmployeeAsync(Employee newEmployee);
        Task<Employee> UpdateEmployeeAsync(int id, Employee updatedEmployee);
        Task<bool> DeleteEmployeeAsync(int id);
    }
}
EmployeeService Implementation:
namespace MinimalAPIDemo.Models
{
    public class EmployeeService : IEmployeeService
    {
        private readonly List<Employee> _employeeList;

        public EmployeeService()
        {
            _employeeList = new List<Employee>
            {
                new Employee { Id = 1, Name = "John Doe", Position = "Software Engineer", Salary = 60000 },
                new Employee { Id = 2, Name = "Jane Smith", Position = "Project Manager", Salary = 80000 }
            };
        }

        public Task<IEnumerable<Employee>> GetAllEmployeesAsync()
        {
            return Task.FromResult<IEnumerable<Employee>>(_employeeList);
        }

        public Task<Employee> GetEmployeeByIdAsync(int id)
        {
            var employee = _employeeList.FirstOrDefault(e => e.Id == id);
            return Task.FromResult(employee);
        }

        public Task<Employee> AddEmployeeAsync(Employee newEmployee)
        {
            newEmployee.Id = _employeeList.Count > 0 ? _employeeList.Max(emp => emp.Id) + 1 : 1;
            _employeeList.Add(newEmployee);
            return Task.FromResult(newEmployee);
        }

        public Task<Employee> UpdateEmployeeAsync(int id, Employee updatedEmployee)
        {
            var employee = _employeeList.FirstOrDefault(emp => emp.Id == id);
            if (employee == null) 
                return Task.FromResult<Employee>(null);

            employee.Name = updatedEmployee.Name;
            employee.Position = updatedEmployee.Position;
            employee.Salary = updatedEmployee.Salary;
            return Task.FromResult(employee);
        }

        public Task<bool> DeleteEmployeeAsync(int id)
        {
            var employee = _employeeList.FirstOrDefault(emp => emp.Id == id);
            if (employee == null) 
                return Task.FromResult(false);

            _employeeList.Remove(employee);
            return Task.FromResult(true);
        }
    }
}
Implementing Asynchronous Endpoints in Program.cs File:

By using async and await keywords with repository methods or service calls, our API can handle concurrent requests efficiently, ensuring optimal performance and responsiveness in handling CRUD operations or any other I/O-bound tasks. This approach enhances the overall user experience and supports better resource utilization within our application. Now, update the endpoints in Program.cs to use the new asynchronous methods from the EmployeeService. So, modify the Program class as follows:

using Microsoft.AspNetCore.Mvc;
using MinimalAPIDemo.Models;

// Create a builder for the web application
var builder = WebApplication.CreateBuilder(args);

// Configure logging
builder.Logging.ClearProviders(); // Clear default providers
builder.Logging.AddConsole(); // Add Console logging provider
builder.Logging.AddDebug(); // Add Debug logging provider

// Add services to the DI container
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Register EmployeeService in the DI container
builder.Services.AddSingleton<IEmployeeService, EmployeeService>();

// Build the application
var app = builder.Build();

// Use the custom error handling middleware
app.UseMiddleware<ErrorHandlerMiddleware>();

// Configure the HTTP request pipeline for the development environment
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

// CRUD operations for Employee model
// The EmployeeService and ILogger Services are injected into the endpoints

// Endpoint to retrieve all employees
app.MapGet("/employees", async (IEmployeeService employeeService, ILogger<Program> logger) =>
{
    try
    {
        logger.LogInformation("Retrieving all employees");
        var employees = await employeeService.GetAllEmployeesAsync();
        return Results.Ok(employees);
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "An error occurred while retrieving all employees");
        return Results.Problem(ex.Message);
    }
});

// Endpoint to retrieve a single employee by their ID
app.MapGet("/employees/{id}", async (int id, IEmployeeService employeeService, ILogger<Program> logger) =>
{
    try
    {
        logger.LogInformation("Retrieving employee with ID {Id}", id);
        var employee = await employeeService.GetEmployeeByIdAsync(id);
        if (employee == null)
        {
            logger.LogWarning("Employee with ID {Id} not found", id);
            return Results.NotFound(new { Message = $"Employee with ID {id} not found" });
        }
        return Results.Ok(employee);
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "An error occurred while retrieving employee with ID {Id}", id);

        var problemDetails = new ProblemDetails
        {
            Status = 500,
            Title = "An unexpected error occurred.",
            Detail = ex.Message,
            Instance = $"/employees/{id}"
        };

        return Results.Problem(ex.Message);
    }
});

// Endpoint to create a new employee with validation
app.MapPost("/employees", async (Employee newEmployee, IEmployeeService employeeService, ILogger<Program> logger) =>
{
    try
    {
        if (!ValidationHelper.TryValidate(newEmployee, out var validationResults))
        {
            // Return 400 Bad Request if validation fails
            return Results.BadRequest(validationResults);
        }

        logger.LogInformation("Creating a new employee");
        var createdEmployee =await employeeService.AddEmployeeAsync(newEmployee);
        return Results.Created($"/employees/{createdEmployee.Id}", createdEmployee);
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "An error occurred while creating a new employee");
        return Results.Problem(ex.Message);
    }
});

// Endpoint to update an existing employee
app.MapPut("/employees/{id}", async (int id, Employee updatedEmployee, IEmployeeService employeeService, ILogger<Program> logger) =>
{
    try
    {
        if (!ValidationHelper.TryValidate(updatedEmployee, out var validationResults))
        {
            // Return 400 Bad Request if validation fails
            return Results.BadRequest(validationResults);
        }

        logger.LogInformation("Updating employee with ID {Id}", id);
        var employee =await employeeService.UpdateEmployeeAsync(id, updatedEmployee);
        if (employee == null)
        {
            logger.LogWarning("Employee with ID {Id} not found", id);
            return Results.NotFound(new { Message = $"Employee with ID {id} not found" });
        }
        return Results.Ok(employee);
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "An error occurred while updating employee with ID {Id}", id);
        return Results.Problem(ex.Message);
    }
});

// Endpoint to delete an employee
app.MapDelete("/employees/{id}", async (int id, IEmployeeService employeeService, ILogger<Program> logger) =>
{
    try
    {
        logger.LogInformation("Deleting employee with ID {Id}", id);
        var result = await employeeService.DeleteEmployeeAsync(id);
        if (!result)
        {
            logger.LogWarning("Employee with ID {Id} not found", id);
            return Results.NotFound(new { Message = $"Employee with ID {id} not found" });
        }
        return Results.NoContent();
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "An error occurred while deleting employee with ID {Id}", id);
        return Results.Problem(ex.Message);
    }
});

// Run the application
app.Run();

Now, run the application and test the endpoints, and it should work as expected.

Benefits of Asynchronous Programming

Asynchronous programming helps improve the application’s performance, especially when dealing with I/O-bound operations like database queries or network requests. We will get the following benefits when using Asynchronous Programming with ASP.NET Core Minimal APIs.

  • Improved Scalability: Asynchronous programming allows your server to handle more requests by freeing up threads while waiting for I/O operations to complete.
  • Better Resource Utilization: By not blocking threads, asynchronous APIs make better use of server resources, which can lead to cost savings and improved performance under load.
  • Enhanced Responsiveness: For user-facing applications, using asynchronous operations can lead to a more responsive application, as the API can handle other tasks while waiting for I/O operations to complete.

In the next article, I will discuss ASP.NET Core Minimal API using Entity Framework Core with Examples. In this article, I explain How to Implement Asynchronous Programming in ASP.NET Core Minimal API with Examples. I hope you enjoy this article.

Leave a Reply

Your email address will not be published. Required fields are marked *