Synchronous vs Asynchronous Programming in ASP.NET Core Web API

Synchronous Programming

In synchronous programming, tasks are executed one after the other, sequentially. When a request is made to a synchronous API, the server processes the request and waits for it to be completed before moving on to the next task. This means that if one operation takes a long time to finish, it can block the execution of subsequent operations, potentially leading to slower response times for clients.

Asynchronous Programming

On the other hand, asynchronous programming allows tasks to be executed concurrently. When a request is made to an asynchronous API, the server can initiate tasks and continue processing other requests without waiting for the previous tasks to be completed. This can lead to better scalability and responsiveness, especially in scenarios where certain operations, such as I/O operations, may take some time.

ASP.NET Core Web API

In ASP.NET Core Web API, you have the flexibility to choose between synchronous and asynchronous programming models. The framework supports both approaches. Asynchronous programming is particularly useful when dealing with I/O-bound operations, such as accessing a database or making external API calls, where the application can continue processing other tasks while waiting for the I/O operation to complete.

To implement asynchronous programming in ASP.NET Core Web API, you can use the `async` and `await` keywords in your controller methods, allowing you to write non-blocking code. This helps improve the overall performance and responsiveness of your API, especially in scenarios with high concurrency.

while synchronous programming is straightforward, asynchronous programming in ASP.NET Core Web API can enhance scalability and responsiveness by allowing the server to handle multiple tasks concurrently. The choice between synchronous and asynchronous programming depends on the specific requirements and characteristics of your application.

Let's Start the complete Implementation.

Create Model

namespace SyncVsAsyncProgrammingIn.NETCoreAPI.Model
{
    public class User
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
        public string Country { get; set; }
        public string ZipCode { get; set; }
        
    }
}

Application Db Context

using Microsoft.EntityFrameworkCore;
using SyncVsAsyncProgrammingIn.NETCoreAPI.Model;
using System.Collections.Generic;

namespace SyncVsAsyncProgrammingIn.NETCoreAPI.ApplicationDbContext
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
        {

        }
        public DbSet<User> Users { get; set; }

    }
}

IUserRepository Interface For User Repository

using SyncVsAsyncProgrammingIn.NETCoreAPI.Model;

namespace SyncVsAsyncProgrammingIn.NETCoreAPI.IRepository
{
    public interface IUserRepository
    {
        Task<User> GetUserByIdAsync(int id);
        User GetUserByIdSync(int id);
    }
}

User Repository

using Microsoft.EntityFrameworkCore;
using SyncVsAsyncProgrammingIn.NETCoreAPI.ApplicationDbContext;
using SyncVsAsyncProgrammingIn.NETCoreAPI.IRepository;
using SyncVsAsyncProgrammingIn.NETCoreAPI.Model;

namespace SyncVsAsyncProgrammingIn.NETCoreAPI.Repository
{
    public class UserRepository : IUserRepository
    {
        private readonly AppDbContext _dbContext;
        private readonly ILogger<UserRepository> _logger;

        public UserRepository(AppDbContext dbContext, ILogger<UserRepository> logger)
        {
            _dbContext = dbContext;
            _logger = logger;   
        }

        public User GetUserByIdSync(int id)
        {
            try
            {
                // Synchronous database query
                return _dbContext.Users.FirstOrDefault(u => u.Id == id);
            }
            catch(Exception ex)
            {
                _logger.LogError($"Error in UserRepository.GetUserById: {ex.Message}");
                throw new ApplicationException("An error occurred while fetching user details. Please try again later.");
            }
        }

        public async Task<User> GetUserByIdAsync(int id)
        {
            // Asynchronous database query
            try
            {
                return await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == id);
            }
            catch(Exception ex)
            {

                _logger.LogError($"Error in UserRepository.GetUserById: {ex.Message}");
                throw new ApplicationException("An error occurred while fetching user details. Please try again later.");
            }
        }
    }
}

Register the Service in the Program.cs Class

using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using SyncVsAsyncProgrammingIn.NETCoreAPI.ApplicationDbContext;
using SyncVsAsyncProgrammingIn.NETCoreAPI.IRepository;
using SyncVsAsyncProgrammingIn.NETCoreAPI.Repository;

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var configuration = builder.Configuration;

// Add services to the container.
builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")); // Replace with your database provider and connection string
});
// Add services to the container.
builder.Services.AddTransient<IUserRepository , UserRepository>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Sync Vs Async Programming In Asp.Net Core Web API", Version = "v1" });

});
var app = builder.Build();

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

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

ASyncUserController

using Microsoft.AspNetCore.Mvc;
using SyncVsAsyncProgrammingIn.NETCoreAPI.IRepository;

namespace SyncVsAsyncProgrammingIn.NETCoreAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ASyncUserController : ControllerBase
    {
        private readonly IUserRepository _userRepository;
        public ASyncUserController(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }

        [HttpGet(nameof(GetUserByIdAsync))]
        public async Task<IActionResult> GetUserByIdAsync(int id)
        {
            try
            {
                // Asynchronous database call
                var user = await _userRepository.GetUserByIdAsync(id);

                if(user == null)
                {
                    return NotFound(); // 404 Not Found
                }

                return Ok(user); // 200 OK with user details
            }
            catch(Exception ex)
            {
                // Handle exceptions
                return StatusCode(500, $"Internal Server Error: {ex.Message}");
            }
        }
    }
}

SyncUserController

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SyncVsAsyncProgrammingIn.NETCoreAPI.IRepository;

namespace SyncVsAsyncProgrammingIn.NETCoreAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class SyncUserController : ControllerBase
    {
        private readonly IUserRepository _userRepository;

        public SyncUserController(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }

        [HttpGet(nameof(GetUserById))]
        public IActionResult GetUserById(int id)
        {
            try
            {
                // Synchronous database call
                var user = _userRepository.GetUserByIdSync(id);

                if(user == null)
                {
                    return NotFound(); // 404 Not Found
                }

                return Ok(user); // 200 OK with user details
            }
            catch(Exception ex)
            {
                // Handle exceptions
                return StatusCode(500, $"Internal Server Error: {ex.Message}");
            }
        }
    }
}

Output

Swagger

GitHub Project URL: https://github.com/SardarMudassarAliKhan/SyncVsAsyncProgrammingIn.NETCoreAPI

Conclusion

Mastering the balance between synchronous and asynchronous programming is crucial for optimizing the performance and scalability of ASP.NET Core Web API applications. While synchronous operations are suitable for simple and quick tasks, leveraging asynchronous programming becomes essential when dealing with time-consuming operations, such as database queries or external API calls. By understanding the strengths and use cases of each approach, developers can make informed decisions to create responsive and efficient web APIs that meet the demands of modern, high-performance applications.