Sunday, August 10, 2025
0 comments

Enterprise ASP.NET Core Web API with Clean Architecture

6:45 PM

 

Enterprise ASP.NET Core Web API with Clean Architecture

I'll create a comprehensive solution for your requirements, focusing on scalability, large data handling, and clean architecture principles. Let's start with the project structure and then dive into implementation details.

Solution Architecture

Project Structure (Visual Studio 2022 Solution)

text
ECommerceSolution.sln
├───src
│   ├───ECommerce.API                 (ASP.NET Core Web API - Presentation Layer)
│   ├───ECommerce.Application          (Application Layer - CQRS, MediatR)
│   ├───ECommerce.Core                 (Domain Layer - Entities, Interfaces)
│   ├───ECommerce.Infrastructure       (Infrastructure Layer - Dapper, Repositories)
│   ├───ECommerce.Database            (Database Project - SQL Server Objects)
├───tests
│   ├───ECommerce.UnitTests
│   ├───ECommerce.IntegrationTests

1. Setting Up the Solution

1.1 Create Solution and Projects

First, let's create the solution and projects in Visual Studio 2022:

  1. Create a new Blank Solution named "ECommerceSolution"

  2. Add projects as shown above (Class Library projects for all except the API which is ASP.NET Core Web API)

  3. Set up dependencies:

    • API depends on Application and Infrastructure

    • Application depends on Core

    • Infrastructure depends on Core

1.2 Configure Base Project Structure

ECommerce.Core (Domain Layer)

text
Core/
├───Entities/
├───Enums/
├───Exceptions/
├───Interfaces/
├───Common/
└───Extensions/

ECommerce.Application (Application Layer)

text
Application/
├───Features/
│   ├───Authentication/
│   │   ├───Commands/
│   │   ├───Queries/
│   │   └───Models/
│   ├───Invoices/
│   │   ├───Commands/
│   │   ├───Queries/
│   │   └───Models/
├───Common/
│   ├───Behaviors/
│   ├───Interfaces/
│   └───Mappings/
└───Services/

ECommerce.Infrastructure (Infrastructure Layer)

text
Infrastructure/
├───Data/
│   ├───Migrations/
│   ├───Repositories/
│   └───Dapper/
├───Identity/
├───Logging/
├───Caching/
└───Services/

ECommerce.API (Presentation Layer)

text
API/
├───Controllers/
├───Middleware/
├───Extensions/
├───Filters/
└───wwwroot/

2. Database-First Approach with SQL Server

2.1 Database Project Setup

Create a SQL Server Database Project (ECommerce.Database) to manage all database objects:

text
Database/
├───Tables/
├───Stored Procedures/
│   ├───Authentication/
│   ├───Invoice/
│   └───Common/
├───Functions/
├───Views/
├───Types/
└───Scripts/

2.2 Sample Database Schema

Let's create tables for User Management and Invoices:

sql
-- Tables/Users.sql
CREATE TABLE [dbo].[Users] (
    [Id] UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
    [UserName] NVARCHAR(50) NOT NULL,
    [Email] NVARCHAR(100) NOT NULL,
    [PasswordHash] VARBINARY(MAX) NOT NULL,
    [PasswordSalt] VARBINARY(MAX) NOT NULL,
    [FirstName] NVARCHAR(50) NULL,
    [LastName] NVARCHAR(50) NULL,
    [IsActive] BIT NOT NULL DEFAULT 1,
    [CreatedDate] DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    [ModifiedDate] DATETIME2 NULL,
    CONSTRAINT [UK_Users_UserName] UNIQUE ([UserName]),
    CONSTRAINT [UK_Users_Email] UNIQUE ([Email])
);

-- Tables/Roles.sql
CREATE TABLE [dbo].[Roles] (
    [Id] UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
    [Name] NVARCHAR(50) NOT NULL,
    [Description] NVARCHAR(255) NULL,
    CONSTRAINT [UK_Roles_Name] UNIQUE ([Name])
);

-- Tables/UserRoles.sql
CREATE TABLE [dbo].[UserRoles] (
    [UserId] UNIQUEIDENTIFIER NOT NULL,
    [RoleId] UNIQUEIDENTIFIER NOT NULL,
    PRIMARY KEY ([UserId], [RoleId]),
    FOREIGN KEY ([UserId]) REFERENCES [dbo].[Users]([Id]),
    FOREIGN KEY ([RoleId]) REFERENCES [dbo].[Roles]([Id])
);

-- Tables/RefreshTokens.sql
CREATE TABLE [dbo].[RefreshTokens] (
    [Id] UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
    [UserId] UNIQUEIDENTIFIER NOT NULL,
    [Token] NVARCHAR(255) NOT NULL,
    [Expires] DATETIME2 NOT NULL,
    [Created] DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    [CreatedByIp] NVARCHAR(50) NULL,
    [Revoked] DATETIME2 NULL,
    [RevokedByIp] NVARCHAR(50) NULL,
    [ReplacedByToken] NVARCHAR(255) NULL,
    FOREIGN KEY ([UserId]) REFERENCES [dbo].[Users]([Id])
);

-- Tables/Invoices.sql
CREATE TABLE [dbo].[Invoices] (
    [Id] UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
    [InvoiceNumber] NVARCHAR(20) NOT NULL,
    [CustomerId] UNIQUEIDENTIFIER NOT NULL,
    [InvoiceDate] DATETIME2 NOT NULL,
    [DueDate] DATETIME2 NOT NULL,
    [TotalAmount] DECIMAL(18,2) NOT NULL,
    [Status] INT NOT NULL DEFAULT 0,
    [Notes] NVARCHAR(MAX) NULL,
    [CreatedBy] UNIQUEIDENTIFIER NOT NULL,
    [CreatedDate] DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    [ModifiedBy] UNIQUEIDENTIFIER NULL,
    [ModifiedDate] DATETIME2 NULL,
    CONSTRAINT [UK_Invoices_InvoiceNumber] UNIQUE ([InvoiceNumber]),
    FOREIGN KEY ([CreatedBy]) REFERENCES [dbo].[Users]([Id]),
    FOREIGN KEY ([ModifiedBy]) REFERENCES [dbo].[Users]([Id])
);

-- Tables/InvoiceItems.sql
CREATE TABLE [dbo].[InvoiceItems] (
    [Id] UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
    [InvoiceId] UNIQUEIDENTIFIER NOT NULL,
    [ItemName] NVARCHAR(100) NOT NULL,
    [Description] NVARCHAR(255) NULL,
    [Quantity] INT NOT NULL,
    [UnitPrice] DECIMAL(18,2) NOT NULL,
    [TaxRate] DECIMAL(5,2) NOT NULL DEFAULT 0,
    [Discount] DECIMAL(5,2) NOT NULL DEFAULT 0,
    [LineTotal] DECIMAL(18,2) NOT NULL,
    FOREIGN KEY ([InvoiceId]) REFERENCES [dbo].[Invoices]([Id])
);

2.3 Stored Procedures for User Management

sql
-- Stored Procedures/Authentication/usp_UserLogin.sql
CREATE PROCEDURE [dbo].[usp_UserLogin]
    @UserName NVARCHAR(50),
    @Password NVARCHAR(100)
AS
BEGIN
    SET NOCOUNT ON;
    
    DECLARE @UserId UNIQUEIDENTIFIER;
    DECLARE @PasswordHash VARBINARY(MAX);
    DECLARE @PasswordSalt VARBINARY(MAX);
    DECLARE @IsActive BIT;
    
    SELECT 
        @UserId = Id,
        @PasswordHash = PasswordHash,
        @PasswordSalt = PasswordSalt,
        @IsActive = IsActive
    FROM 
        [dbo].[Users]
    WHERE 
        UserName = @UserName;
    
    IF @UserId IS NULL OR @IsActive = 0
    BEGIN
        RETURN 0; -- User not found or inactive
    END
    
    -- Verify password (this would be done in application layer)
    -- For demo, we're just returning the user if found
    SELECT 
        u.Id,
        u.UserName,
        u.Email,
        u.FirstName,
        u.LastName,
        STRING_AGG(r.Name, ',') AS Roles
    FROM 
        [dbo].[Users] u
    LEFT JOIN 
        [dbo].[UserRoles] ur ON u.Id = ur.UserId
    LEFT JOIN 
        [dbo].[Roles] r ON ur.RoleId = r.Id
    WHERE 
        u.Id = @UserId
    GROUP BY
        u.Id, u.UserName, u.Email, u.FirstName, u.LastName;
    
    RETURN 1; -- Success
END

2.4 Stored Procedures for Invoice Operations

sql
-- Stored Procedures/Invoice/usp_CreateInvoice.sql
CREATE PROCEDURE [dbo].[usp_CreateInvoice]
    @InvoiceNumber NVARCHAR(20),
    @CustomerId UNIQUEIDENTIFIER,
    @InvoiceDate DATETIME2,
    @DueDate DATETIME2,
    @Notes NVARCHAR(MAX),
    @CreatedBy UNIQUEIDENTIFIER,
    @Items InvoiceItemsType READONLY
AS
BEGIN
    SET NOCOUNT ON;
    
    BEGIN TRY
        BEGIN TRANSACTION;
        
        DECLARE @InvoiceId UNIQUEIDENTIFIER = NEWID();
        DECLARE @TotalAmount DECIMAL(18,2) = 0;
        
        -- Calculate total from items
        SELECT @TotalAmount = SUM((Quantity * UnitPrice) * (1 - Discount/100) * (1 + TaxRate/100))
        FROM @Items;
        
        -- Insert invoice header
        INSERT INTO [dbo].[Invoices] (
            Id,
            InvoiceNumber,
            CustomerId,
            InvoiceDate,
            DueDate,
            TotalAmount,
            Notes,
            CreatedBy
        )
        VALUES (
            @InvoiceId,
            @InvoiceNumber,
            @CustomerId,
            @InvoiceDate,
            @DueDate,
            @TotalAmount,
            @Notes,
            @CreatedBy
        );
        
        -- Insert invoice items
        INSERT INTO [dbo].[InvoiceItems] (
            Id,
            InvoiceId,
            ItemName,
            Description,
            Quantity,
            UnitPrice,
            TaxRate,
            Discount,
            LineTotal
        )
        SELECT 
            NEWID(),
            @InvoiceId,
            ItemName,
            Description,
            Quantity,
            UnitPrice,
            TaxRate,
            Discount,
            (Quantity * UnitPrice) * (1 - Discount/100) * (1 + TaxRate/100) AS LineTotal
        FROM 
            @Items;
        
        COMMIT TRANSACTION;
        
        -- Return the created invoice
        EXEC [dbo].[usp_GetInvoiceById] @InvoiceId;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;
            
        THROW;
    END CATCH
END

3. Implementing Clean Architecture with ASP.NET Core

3.1 Core Layer (Domain)

Entities/User.cs

csharp
namespace ECommerce.Core.Entities
{
    public class User : BaseEntity
    {
        public string UserName { get; set; }
        public string Email { get; set; }
        public byte[] PasswordHash { get; set; }
        public byte[] PasswordSalt { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public bool IsActive { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime? ModifiedDate { get; set; }
        
        // Navigation properties
        public ICollection<UserRole> UserRoles { get; set; }
        public ICollection<RefreshToken> RefreshTokens { get; set; }
    }
}

Interfaces/IRepository.cs

csharp
namespace ECommerce.Core.Interfaces
{
    public interface IRepository<T> where T : BaseEntity
    {
        Task<T> GetByIdAsync(Guid id);
        Task<IReadOnlyList<T>> GetAllAsync();
        Task<T> AddAsync(T entity);
        Task UpdateAsync(T entity);
        Task DeleteAsync(T entity);
    }
}

3.2 Infrastructure Layer

Data/DapperContext.cs

csharp
namespace ECommerce.Infrastructure.Data
{
    public class DapperContext : IDapperContext
    {
        private readonly IConfiguration _configuration;
        private readonly string _connectionString;
        
        public DapperContext(IConfiguration configuration)
        {
            _configuration = configuration;
            _connectionString = _configuration.GetConnectionString("DefaultConnection");
        }
        
        public IDbConnection CreateConnection() => new SqlConnection(_connectionString);
        
        public async Task<T> WithConnection<T>(Func<IDbConnection, Task<T>> getData)
        {
            await using var connection = CreateConnection();
            await connection.OpenAsync();
            return await getData(connection);
        }
        
        public async Task WithConnection(Func<IDbConnection, Task> getData)
        {
            await using var connection = CreateConnection();
            await connection.OpenAsync();
            await getData(connection);
        }
    }
}

Repositories/UserRepository.cs

csharp
namespace ECommerce.Infrastructure.Data.Repositories
{
    public class UserRepository : IUserRepository
    {
        private readonly IDapperContext _context;
        private readonly ILogger<UserRepository> _logger;
        
        public UserRepository(IDapperContext context, ILogger<UserRepository> logger)
        {
            _context = context;
            _logger = logger;
        }
        
        public async Task<User> GetByIdAsync(Guid id)
        {
            const string sql = @"SELECT * FROM Users WHERE Id = @Id";
            
            try
            {
                return await _context.WithConnection(async conn =>
                {
                    return await conn.QueryFirstOrDefaultAsync<User>(sql, new { Id = id });
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error getting user by ID: {Id}", id);
                throw;
            }
        }
        
        public async Task<User> GetByUserNameAsync(string userName)
        {
            const string sql = @"SELECT * FROM Users WHERE UserName = @UserName";
            
            try
            {
                return await _context.WithConnection(async conn =>
                {
                    return await conn.QueryFirstOrDefaultAsync<User>(sql, new { UserName = userName });
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error getting user by username: {UserName}", userName);
                throw;
            }
        }
    }
}

3.3 Application Layer

Features/Authentication/Commands/LoginCommand.cs

csharp
namespace ECommerce.Application.Features.Authentication.Commands
{
    public class LoginCommand : IRequest<AuthenticationResponse>
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string IpAddress { get; set; }
    }
    
    public class LoginCommandHandler : IRequestHandler<LoginCommand, AuthenticationResponse>
    {
        private readonly IUserRepository _userRepository;
        private readonly IJwtService _jwtService;
        private readonly IRefreshTokenService _refreshTokenService;
        private readonly IPasswordService _passwordService;
        
        public LoginCommandHandler(
            IUserRepository userRepository,
            IJwtService jwtService,
            IRefreshTokenService refreshTokenService,
            IPasswordService passwordService)
        {
            _userRepository = userRepository;
            _jwtService = jwtService;
            _refreshTokenService = refreshTokenService;
            _passwordService = passwordService;
        }
        
        public async Task<AuthenticationResponse> Handle(LoginCommand request, CancellationToken cancellationToken)
        {
            var user = await _userRepository.GetByUserNameAsync(request.UserName);
            
            if (user == null || !user.IsActive)
                throw new AuthenticationException("Invalid username or password");
            
            if (!_passwordService.VerifyPasswordHash(request.Password, user.PasswordHash, user.PasswordSalt))
                throw new AuthenticationException("Invalid username or password");
            
            var jwtToken = _jwtService.GenerateJwtToken(user);
            var refreshToken = await _refreshTokenService.GenerateRefreshToken(user.Id, request.IpAddress);
            
            return new AuthenticationResponse
            {
                Id = user.Id,
                UserName = user.UserName,
                Email = user.Email,
                Token = jwtToken,
                RefreshToken = refreshToken.Token,
                Roles = await _userRepository.GetUserRolesAsync(user.Id)
            };
        }
    }
}

3.4 API Layer

Controllers/AuthenticationController.cs

csharp
namespace ECommerce.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AuthenticationController : ControllerBase
    {
        private readonly IMediator _mediator;
        
        public AuthenticationController(IMediator mediator)
        {
            _mediator = mediator;
        }
        
        [HttpPost("login")]
        public async Task<IActionResult> Login([FromBody] LoginRequest request)
        {
            var ipAddress = Request.HttpContext.Connection.RemoteIpAddress?.MapToIPv4().ToString();
            
            var command = new LoginCommand
            {
                UserName = request.UserName,
                Password = request.Password,
                IpAddress = ipAddress
            };
            
            var response = await _mediator.Send(command);
            
            setTokenCookie(response.RefreshToken);
            
            return Ok(response);
        }
        
        private void setTokenCookie(string token)
        {
            var cookieOptions = new CookieOptions
            {
                HttpOnly = true,
                Expires = DateTime.UtcNow.AddDays(7),
                Secure = true,
                SameSite = SameSiteMode.Strict
            };
            
            Response.Cookies.Append("refreshToken", token, cookieOptions);
        }
    }
}

4. Invoice Master-Detail Implementation

4.1 Database Table Type for Invoice Items

sql
-- Types/InvoiceItemsType.sql
CREATE TYPE [dbo].[InvoiceItemsType] AS TABLE (
    [ItemName] NVARCHAR(100) NOT NULL,
    [Description] NVARCHAR(255) NULL,
    [Quantity] INT NOT NULL,
    [UnitPrice] DECIMAL(18,2) NOT NULL,
    [TaxRate] DECIMAL(5,2) NOT NULL,
    [Discount] DECIMAL(5,2) NOT NULL
);

4.2 Application Layer - Invoice Commands

Features/Invoices/Commands/CreateInvoiceCommand.cs

csharp
namespace ECommerce.Application.Features.Invoices.Commands
{
    public class CreateInvoiceCommand : IRequest<InvoiceDto>
    {
        public string InvoiceNumber { get; set; }
        public Guid CustomerId { get; set; }
        public DateTime InvoiceDate { get; set; }
        public DateTime DueDate { get; set; }
        public string Notes { get; set; }
        public List<InvoiceItemDto> Items { get; set; }
        public Guid CreatedBy { get; set; }
    }
    
    public class CreateInvoiceCommandHandler : IRequestHandler<CreateInvoiceCommand, InvoiceDto>
    {
        private readonly IInvoiceRepository _invoiceRepository;
        private readonly IMapper _mapper;
        private readonly ILogger<CreateInvoiceCommandHandler> _logger;
        
        public CreateInvoiceCommandHandler(
            IInvoiceRepository invoiceRepository,
            IMapper mapper,
            ILogger<CreateInvoiceCommandHandler> logger)
        {
            _invoiceRepository = invoiceRepository;
            _mapper = mapper;
            _logger = logger;
        }
        
        public async Task<InvoiceDto> Handle(CreateInvoiceCommand request, CancellationToken cancellationToken)
        {
            try
            {
                // Create DataTable for items
                var itemsTable = new DataTable();
                itemsTable.Columns.Add("ItemName", typeof(string));
                itemsTable.Columns.Add("Description", typeof(string));
                itemsTable.Columns.Add("Quantity", typeof(int));
                itemsTable.Columns.Add("UnitPrice", typeof(decimal));
                itemsTable.Columns.Add("TaxRate", typeof(decimal));
                itemsTable.Columns.Add("Discount", typeof(decimal));
                
                foreach (var item in request.Items)
                {
                    itemsTable.Rows.Add(
                        item.ItemName,
                        item.Description,
                        item.Quantity,
                        item.UnitPrice,
                        item.TaxRate,
                        item.Discount);
                }
                
                // Call stored procedure
                var invoice = await _invoiceRepository.CreateInvoiceAsync(
                    request.InvoiceNumber,
                    request.CustomerId,
                    request.InvoiceDate,
                    request.DueDate,
                    request.Notes,
                    request.CreatedBy,
                    itemsTable);
                
                return _mapper.Map<InvoiceDto>(invoice);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error creating invoice");
                throw;
            }
        }
    }
}

4.3 Infrastructure Layer - Invoice Repository

Repositories/InvoiceRepository.cs

csharp
namespace ECommerce.Infrastructure.Data.Repositories
{
    public class InvoiceRepository : IInvoiceRepository
    {
        private readonly IDapperContext _context;
        private readonly ILogger<InvoiceRepository> _logger;
        
        public InvoiceRepository(IDapperContext context, ILogger<InvoiceRepository> logger)
        {
            _context = context;
            _logger = logger;
        }
        
        public async Task<Invoice> CreateInvoiceAsync(
            string invoiceNumber,
            Guid customerId,
            DateTime invoiceDate,
            DateTime dueDate,
            string notes,
            Guid createdBy,
            DataTable items)
        {
            const string sql = "EXEC [dbo].[usp_CreateInvoice] " +
                "@InvoiceNumber, @CustomerId, @InvoiceDate, @DueDate, " +
                "@Notes, @CreatedBy, @Items";
                
            try
            {
                return await _context.WithConnection(async conn =>
                {
                    var parameters = new DynamicParameters();
                    parameters.Add("@InvoiceNumber", invoiceNumber);
                    parameters.Add("@CustomerId", customerId);
                    parameters.Add("@InvoiceDate", invoiceDate);
                    parameters.Add("@DueDate", dueDate);
                    parameters.Add("@Notes", notes);
                    parameters.Add("@CreatedBy", createdBy);
                    parameters.Add("@Items", items.AsTableValuedParameter("dbo.InvoiceItemsType"));
                    
                    return await conn.QueryFirstOrDefaultAsync<Invoice>(sql, parameters);
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error creating invoice");
                throw;
            }
        }
        
        public async Task<Invoice> GetInvoiceByIdAsync(Guid id)
        {
            const string sql = "EXEC [dbo].[usp_GetInvoiceById] @Id";
            
            try
            {
                return await _context.WithConnection(async conn =>
                {
                    var invoice = await conn.QueryFirstOrDefaultAsync<Invoice>(sql, new { Id = id });
                    
                    if (invoice != null)
                    {
                        var items = await conn.QueryAsync<InvoiceItem>(
                            "SELECT * FROM InvoiceItems WHERE InvoiceId = @Id",
                            new { Id = id });
                            
                        invoice.Items = items.ToList();
                    }
                    
                    return invoice;
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error getting invoice by ID: {Id}", id);
                throw;
            }
        }
    }
}

4.4 API Controller for Invoices

Controllers/InvoicesController.cs

csharp
namespace ECommerce.API.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class InvoicesController : ControllerBase
    {
        private readonly IMediator _mediator;
        
        public InvoicesController(IMediator mediator)
        {
            _mediator = mediator;
        }
        
        [HttpPost]
        public async Task<ActionResult<InvoiceDto>> Create([FromBody] CreateInvoiceRequest request)
        {
            var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
            
            var command = new CreateInvoiceCommand
            {
                InvoiceNumber = request.InvoiceNumber,
                CustomerId = request.CustomerId,
                InvoiceDate = request.InvoiceDate,
                DueDate = request.DueDate,
                Notes = request.Notes,
                Items = request.Items,
                CreatedBy = Guid.Parse(userId)
            };
            
            var result = await _mediator.Send(command);
            
            return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
        }
        
        [HttpGet("{id}")]
        public async Task<ActionResult<InvoiceDto>> GetById(Guid id)
        {
            var query = new GetInvoiceByIdQuery { Id = id };
            var result = await _mediator.Send(query);
            
            if (result == null)
                return NotFound();
                
            return Ok(result);
        }
    }
}

5. Advanced Features Implementation

5.1 Global Error Handling Middleware

csharp
namespace ECommerce.API.Middleware
{
    public class ErrorHandlingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<ErrorHandlingMiddleware> _logger;
        
        public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }
        
        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                await HandleExceptionAsync(context, ex);
            }
        }
        
        private Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            var code = HttpStatusCode.InternalServerError;
            var result = string.Empty;
            
            switch (exception)
            {
                case ValidationException validationException:
                    code = HttpStatusCode.BadRequest;
                    result = JsonSerializer.Serialize(validationException.Errors);
                    break;
                case NotFoundException _:
                    code = HttpStatusCode.NotFound;
                    break;
                case AuthenticationException _:
                    code = HttpStatusCode.Unauthorized;
                    break;
                case ForbiddenException _:
                    code = HttpStatusCode.Forbidden;
                    break;
                default:
                    _logger.LogError(exception, "An unhandled exception has occurred");
                    result = JsonSerializer.Serialize(new { error = "An unexpected error occurred" });
                    break;
            }
            
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = (int)code;
            
            if (string.IsNullOrEmpty(result))
            {
                result = JsonSerializer.Serialize(new { error = exception.Message });
            }
            
            return context.Response.WriteAsync(result);
        }
    }
}

5.2 Logging Configuration

csharp
namespace ECommerce.API.Extensions
{
    public static class LoggingExtensions
    {
        public static IServiceCollection AddCustomLogging(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddLogging(loggingBuilder =>
            {
                loggingBuilder.ClearProviders();
                loggingBuilder.AddConfiguration(configuration.GetSection("Logging"));
                
                // Console logging for development
                loggingBuilder.AddConsole();
                
                // Application Insights for production
                if (!string.IsNullOrEmpty(configuration["ApplicationInsights:InstrumentationKey"]))
                {
                    loggingBuilder.AddApplicationInsights(
                        configureTelemetryConfiguration: (config) =>
                            config.ConnectionString = configuration["ApplicationInsights:ConnectionString"],
                        configureApplicationInsightsLoggerOptions: (options) => { });
                }
                
                // File logging
                loggingBuilder.AddFile(configuration.GetSection("Logging:File"));
            });
            
            return services;
        }
    }
}

5.3 Performance Monitoring with MiniProfiler

csharp
namespace ECommerce.API.Extensions
{
    public static class PerformanceExtensions
    {
        public static IServiceCollection AddPerformanceMonitoring(this IServiceCollection services)
        {
            services.AddMiniProfiler(options =>
            {
                options.RouteBasePath = "/profiler";
                options.ColorScheme = StackExchange.Profiling.ColorScheme.Auto;
                options.Storage = new SqlServerStorage(services.BuildServiceProvider()
                    .GetRequiredService<IConfiguration>()
                    .GetConnectionString("DefaultConnection"));
                options.TrackConnectionOpenClose = true;
                options.SqlFormatter = new StackExchange.Profiling.SqlFormatters.InlineFormatter();
            })
            .AddEntityFramework();
            
            return services;
        }
        
        public static IApplicationBuilder UsePerformanceMonitoring(this IApplicationBuilder app)
        {
            app.UseMiniProfiler();
            return app;
        }
    }
}

6. JWT Authentication Implementation

6.1 JWT Service

csharp
namespace ECommerce.Infrastructure.Services
{
    public class JwtService : IJwtService
    {
        private readonly IConfiguration _configuration;
        private readonly ILogger<JwtService> _logger;
        
        public JwtService(IConfiguration configuration, ILogger<JwtService> logger)
        {
            _configuration = configuration;
            _logger = logger;
        }
        
        public string GenerateJwtToken(User user)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Secret"]);
                
                var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                    new Claim(ClaimTypes.Name, user.UserName),
                    new Claim(ClaimTypes.Email, user.Email)
                };
                
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject = new ClaimsIdentity(claims),
                    Expires = DateTime.UtcNow.AddMinutes(Convert.ToDouble(_configuration["Jwt:ExpiryMinutes"])),
                    SigningCredentials = new SigningCredentials(
                        new SymmetricSecurityKey(key),
                        SecurityAlgorithms.HmacSha256Signature)
                };
                
                var token = tokenHandler.CreateToken(tokenDescriptor);
                return tokenHandler.WriteToken(token);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error generating JWT token");
                throw;
            }
        }
        
        public Guid? ValidateJwtToken(string token)
        {
            if (string.IsNullOrEmpty(token))
                return null;
                
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Secret"]);
            
            try
            {
                tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ClockSkew = TimeSpan.Zero
                }, out SecurityToken validatedToken);
                
                var jwtToken = (JwtSecurityToken)validatedToken;
                var userId = Guid.Parse(jwtToken.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value);
                
                return userId;
            }
            catch
            {
                return null;
            }
        }
    }
}

6.2 Refresh Token Service

csharp
namespace ECommerce.Infrastructure.Services
{
    public class RefreshTokenService : IRefreshTokenService
    {
        private readonly IRefreshTokenRepository _refreshTokenRepository;
        private readonly IConfiguration _configuration;
        private readonly ILogger<RefreshTokenService> _logger;
        
        public RefreshTokenService(
            IRefreshTokenRepository refreshTokenRepository,
            IConfiguration configuration,
            ILogger<RefreshTokenService> logger)
        {
            _refreshTokenRepository = refreshTokenRepository;
            _configuration = configuration;
            _logger = logger;
        }
        
        public async Task<RefreshToken> GenerateRefreshToken(Guid userId, string ipAddress)
        {
            try
            {
                // Remove old refresh tokens
                await _refreshTokenRepository.RemoveOldRefreshTokensForUser(userId);
                
                // Generate new refresh token
                var refreshToken = new RefreshToken
                {
                    UserId = userId,
                    Token = generateRandomToken(),
                    Expires = DateTime.UtcNow.AddDays(Convert.ToDouble(_configuration["Jwt:RefreshTokenExpiryDays"])),
                    Created = DateTime.UtcNow,
                    CreatedByIp = ipAddress
                };
                
                await _refreshTokenRepository.AddAsync(refreshToken);
                
                return refreshToken;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error generating refresh token");
                throw;
            }
        }
        
        public async Task<RefreshToken> RotateRefreshToken(string token, string ipAddress)
        {
            try
            {
                var refreshToken = await _refreshTokenRepository.GetByTokenAsync(token);
                
                if (refreshToken == null || !refreshToken.IsActive)
                    throw new AuthenticationException("Invalid token");
                
                // Revoke current token
                refreshToken.Revoked = DateTime.UtcNow;
                refreshToken.RevokedByIp = ipAddress;
                
                // Generate new token
                var newRefreshToken = new RefreshToken
                {
                    UserId = refreshToken.UserId,
                    Token = generateRandomToken(),
                    Expires = DateTime.UtcNow.AddDays(Convert.ToDouble(_configuration["Jwt:RefreshTokenExpiryDays"])),
                    Created = DateTime.UtcNow,
                    CreatedByIp = ipAddress,
                    ReplacedByToken = refreshToken.Token
                };
                
                await _refreshTokenRepository.UpdateAsync(refreshToken);
                await _refreshTokenRepository.AddAsync(newRefreshToken);
                
                return newRefreshToken;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error rotating refresh token");
                throw;
            }
        }
        
        private string generateRandomToken()
        {
            var randomNumber = new byte[32];
            using var rng = RandomNumberGenerator.Create();
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
        }
    }
}

7. Transaction Management

7.1 Transaction Behavior for MediatR

csharp
namespace ECommerce.Application.Common.Behaviors
{
    public class TransactionBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
        where TRequest : IRequest<TResponse>
    {
        private readonly IDapperContext _context;
        private readonly ILogger<TransactionBehavior<TRequest, TResponse>> _logger;
        
        public TransactionBehavior(
            IDapperContext context,
            ILogger<TransactionBehavior<TRequest, TResponse>> logger)
        {
            _context = context;
            _logger = logger;
        }
        
        public async Task<TResponse> Handle(
            TRequest request,
            RequestHandlerDelegate<TResponse> next,
            CancellationToken cancellationToken)
        {
            if (request is not ITransactional)
            {
                return await next();
            }
            
            await using var connection = _context.CreateConnection();
            await connection.OpenAsync(cancellationToken);
            
            await using var transaction = await connection.BeginTransactionAsync(cancellationToken);
            
            try
            {
                _logger.LogInformation("Begin transaction for {RequestName}", typeof(TRequest).Name);
                
                var response = await next();
                
                await transaction.CommitAsync(cancellationToken);
                
                _logger.LogInformation("Transaction committed for {RequestName}", typeof(TRequest).Name);
                
                return response;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error handling transaction for {RequestName}", typeof(TRequest).Name);
                
                await transaction.RollbackAsync(cancellationToken);
                
                throw;
            }
        }
    }
}

7.2 Using Transactions with Dapper

csharp
namespace ECommerce.Infrastructure.Data.Repositories
{
    public class InvoiceRepository : IInvoiceRepository
    {
        // ... other methods
        
        public async Task<Invoice> CreateInvoiceWithTransactionAsync(
            string invoiceNumber,
            Guid customerId,
            DateTime invoiceDate,
            DateTime dueDate,
            string notes,
            Guid createdBy,
            DataTable items)
        {
            const string headerSql = @"
                INSERT INTO Invoices (...) VALUES (...);
                SELECT SCOPE_IDENTITY();";
                
            const string itemsSql = @"
                INSERT INTO InvoiceItems (...) VALUES (...);";
                
            try
            {
                return await _context.WithConnection(async conn =>
                {
                    await using var transaction = await conn.BeginTransactionAsync();
                    
                    try
                    {
                        // Insert header
                        var invoiceId = await conn.ExecuteScalarAsync<Guid>(headerSql, new
                        {
                            InvoiceNumber = invoiceNumber,
                            CustomerId = customerId,
                            // ... other parameters
                        }, transaction: transaction);
                        
                        // Insert items
                        foreach (DataRow row in items.Rows)
                        {
                            await conn.ExecuteAsync(itemsSql, new
                            {
                                InvoiceId = invoiceId,
                                ItemName = row["ItemName"],
                                // ... other parameters
                            }, transaction: transaction);
                        }
                        
                        await transaction.CommitAsync();
                        
                        return await GetInvoiceByIdAsync(invoiceId);
                    }
                    catch
                    {
                        await transaction.RollbackAsync();
                        throw;
                    }
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error creating invoice with transaction");
                throw;
            }
        }
    }
}

8. Large Data Handling

8.1 Pagination with Dapper

csharp
namespace ECommerce.Infrastructure.Data.Repositories
{
    public class InvoiceRepository : IInvoiceRepository
    {
        // ... other methods
        
        public async Task<PagedList<Invoice>> GetInvoicesPagedAsync(
            int pageNumber,
            int pageSize,
            string searchTerm = null,
            string sortColumn = null,
            string sortOrder = "asc")
        {
            const string baseSql = @"
                SELECT * FROM Invoices
                WHERE (@SearchTerm IS NULL OR 
                      InvoiceNumber LIKE '%' + @SearchTerm + '%' OR
                      Notes LIKE '%' + @SearchTerm + '%')";
                      
            const string countSql = @"
                SELECT COUNT(*) FROM Invoices
                WHERE (@SearchTerm IS NULL OR 
                      InvoiceNumber LIKE '%' + @SearchTerm + '%' OR
                      Notes LIKE '%' + @SearchTerm + '%')";
                      
            var orderBy = !string.IsNullOrWhiteSpace(sortColumn)
                ? $"ORDER BY {sortColumn} {(sortOrder == "desc" ? "DESC" : "ASC")}"
                : "ORDER BY InvoiceDate DESC";
                
            var sql = $@"
                {baseSql}
                {orderBy}
                OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY";
                
            try
            {
                return await _context.WithConnection(async conn =>
                {
                    var offset = (pageNumber - 1) * pageSize;
                    
                    var parameters = new DynamicParameters();
                    parameters.Add("@SearchTerm", searchTerm);
                    parameters.Add("@Offset", offset);
                    parameters.Add("@PageSize", pageSize);
                    
                    var count = await conn.ExecuteScalarAsync<int>(countSql, parameters);
                    
                    var items = await conn.QueryAsync<Invoice>(sql, parameters);
                    
                    return new PagedList<Invoice>(
                        items.ToList(),
                        count,
                        pageNumber,
                        pageSize);
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error getting paged invoices");
                throw;
            }
        }
    }
}

8.2 Bulk Operations with Table-Valued Parameters

csharp
namespace ECommerce.Infrastructure.Data.Repositories
{
    public class BulkOperationsRepository : IBulkOperationsRepository
    {
        private readonly IDapperContext _context;
        private readonly ILogger<BulkOperationsRepository> _logger;
        
        public BulkOperationsRepository(
            IDapperContext context,
            ILogger<BulkOperationsRepository> logger)
        {
            _context = context;
            _logger = logger;
        }
        
        public async Task BulkInsertInvoicesAsync(IEnumerable<Invoice> invoices)
        {
            const string sql = "EXEC [dbo].[usp_BulkInsertInvoices] @Invoices";
            
            try
            {
                var invoiceTable = new DataTable();
                invoiceTable.Columns.Add("InvoiceNumber", typeof(string));
                invoiceTable.Columns.Add("CustomerId", typeof(Guid));
                // ... other columns
                
                foreach (var invoice in invoices)
                {
                    invoiceTable.Rows.Add(
                        invoice.InvoiceNumber,
                        invoice.CustomerId
                        // ... other values
                    );
                }
                
                await _context.WithConnection(async conn =>
                {
                    var parameters = new DynamicParameters();
                    parameters.Add("@Invoices", invoiceTable.AsTableValuedParameter("dbo.InvoiceBulkType"));
                    
                    await conn.ExecuteAsync(sql, parameters);
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error in bulk insert of invoices");
                throw;
            }
        }
    }
}

9. Caching Strategy

9.1 Distributed Cache Implementation

csharp
namespace ECommerce.Infrastructure.Caching
{
    public class DistributedCacheService : ICacheService
    {
        private readonly IDistributedCache _cache;
        private readonly ILogger<DistributedCacheService> _logger;
        private readonly DistributedCacheEntryOptions _defaultOptions;
        
        public DistributedCacheService(
            IDistributedCache cache,
            ILogger<DistributedCacheService> logger,
            IConfiguration configuration)
        {
            _cache = cache;
            _logger = logger;
            
            _defaultOptions = new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(
                    configuration.GetValue<int>("Cache:DefaultExpirationMinutes"))
            };
        }
        
        public async Task<T> GetAsync<T>(string key, CancellationToken cancellationToken = default)
        {
            try
            {
                var cachedData = await _cache.GetStringAsync(key, cancellationToken);
                
                if (string.IsNullOrEmpty(cachedData))
                    return default;
                    
                return JsonSerializer.Deserialize<T>(cachedData);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error getting cached data for key {Key}", key);
                return default;
            }
        }
        
        public async Task SetAsync<T>(
            string key,
            T value,
            DistributedCacheEntryOptions options = null,
            CancellationToken cancellationToken = default)
        {
            try
            {
                var serializedValue = JsonSerializer.Serialize(value);
                await _cache.SetStringAsync(
                    key,
                    serializedValue,
                    options ?? _defaultOptions,
                    cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error setting cached data for key {Key}", key);
            }
        }
        
        public async Task RemoveAsync(string key, CancellationToken cancellationToken = default)
        {
            try
            {
                await _cache.RemoveAsync(key, cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error removing cached data for key {Key}", key);
            }
        }
    }
}

9.2 Cache Invalidation with MediatR Notifications

csharp
namespace ECommerce.Application.Common.Behaviors
{
    public class CacheInvalidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
        where TRequest : IRequest<TResponse>
    {
        private readonly ICacheService _cache;
        private readonly ILogger<CacheInvalidationBehavior<TRequest, TResponse>> _logger;
        
        public CacheInvalidationBehavior(
            ICacheService cache,
            ILogger<CacheInvalidationBehavior<TRequest, TResponse>> logger)
        {
            _cache = cache;
            _logger = logger;
        }
        
        public async Task<TResponse> Handle(
            TRequest request,
            RequestHandlerDelegate<TResponse> next,
            CancellationToken cancellationToken)
        {
            var response = await next();
            
            if (request is ICacheInvalidator invalidator)
            {
                try
                {
                    foreach (var key in invalidator.CacheKeys)
                    {
                        await _cache.RemoveAsync(key, cancellationToken);
                        _logger.LogDebug("Cache invalidated for key {Key}", key);
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Error invalidating cache for request {Request}", typeof(TRequest).Name);
                }
            }
            
            return response;
        }
    }
}

10. Swagger Configuration with JWT Support

csharp
namespace ECommerce.API.Extensions
{
    public static class SwaggerExtensions
    {
        public static IServiceCollection AddCustomSwagger(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo
                {
                    Title = "ECommerce API",
                    Version = "v1",
                    Description = "ECommerce API with JWT Authentication",
                    Contact = new OpenApiContact
                    {
                        Name = "Your Name",
                        Email = "your.email@example.com"
                    }
                });
                
                // Add JWT Authentication support in Swagger
                var securityScheme = new OpenApiSecurityScheme
                {
                    Name = "JWT Authentication",
                    Description = "Enter JWT Bearer token **_only_**",
                    In = ParameterLocation.Header,
                    Type = SecuritySchemeType.Http,
                    Scheme = "bearer",
                    BearerFormat = "JWT",
                    Reference = new OpenApiReference
                    {
                        Id = JwtBearerDefaults.AuthenticationScheme,
                        Type = ReferenceType.SecurityScheme
                    }
                };
                
                c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme);
                c.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    { securityScheme, Array.Empty<string>() }
                });
                
                // Enable annotations
                c.EnableAnnotations();
                
                // Include XML comments
                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                if (File.Exists(xmlPath))
                {
                    c.IncludeXmlComments(xmlPath);
                }
            });
            
            return services;
        }
        
        public static IApplicationBuilder UseCustomSwagger(this IApplicationBuilder app)
        {
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "ECommerce API V1");
                c.RoutePrefix = "api-docs";
                c.DocumentTitle = "ECommerce API Documentation";
                c.DisplayRequestDuration();
                c.EnableDeepLinking();
                c.DefaultModelsExpandDepth(-1); // Hide schemas by default
            });
            
            return app;
        }
    }
}

11. Complete Startup Configuration

csharp
namespace ECommerce.API
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        
        public IConfiguration Configuration { get; }
        
        public void ConfigureServices(IServiceCollection services)
        {
            // Base services
            services.AddControllers()
                .AddJsonOptions(options =>
                {
                    options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
                    options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
                    options.JsonSerializerOptions.WriteIndented = true;
                });
                
            // Database
            services.AddDatabase(Configuration);
            
            // Authentication
            services.AddAuthentication(Configuration);
            
            // Application services
            services.AddApplicationServices();
            
            // Infrastructure services
            services.AddInfrastructureServices(Configuration);
            
            // Swagger
            services.AddCustomSwagger(Configuration);
            
            // Caching
            services.AddDistributedMemoryCache(); // Or Redis for production
            services.AddSingleton<ICacheService, DistributedCacheService>();
            
            // Performance monitoring
            services.AddPerformanceMonitoring();
            
            // Logging
            services.AddCustomLogging(Configuration);
            
            // Health checks
            services.AddHealthChecks()
                .AddSqlServer(Configuration.GetConnectionString("DefaultConnection"))
                .AddDbContextCheck<ApplicationDbContext>();
                
            // Cross-Origin Resource Sharing (CORS)
            services.AddCors(options =>
            {
                options.AddPolicy("AllowAll", builder =>
                {
                    builder.AllowAnyOrigin()
                           .AllowAnyMethod()
                           .AllowAnyHeader();
                });
            });
        }
        
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UsePerformanceMonitoring();
            }
            else
            {
                app.UseExceptionHandler("/error");
                app.UseHsts();
            }
            
            app.UseMiddleware<ErrorHandlingMiddleware>();
            
            app.UseHttpsRedirection();
            app.UseRouting();
            
            app.UseCors("AllowAll");
            
            app.UseAuthentication();
            app.UseAuthorization();
            
            app.UseCustomSwagger();
            
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapHealthChecks("/health");
            });
        }
    }
}

12. Deployment Considerations

12.1 Docker Configuration

dockerfile
# Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["ECommerce.API/ECommerce.API.csproj", "ECommerce.API/"]
COPY ["ECommerce.Application/ECommerce.Application.csproj", "ECommerce.Application/"]
COPY ["ECommerce.Core/ECommerce.Core.csproj", "ECommerce.Core/"]
COPY ["ECommerce.Infrastructure/ECommerce.Infrastructure.csproj", "ECommerce.Infrastructure/"]
RUN dotnet restore "ECommerce.API/ECommerce.API.csproj"
COPY . .
WORKDIR "/src/ECommerce.API"
RUN dotnet build "ECommerce.API.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "ECommerce.API.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ECommerce.API.dll"]

12.2 Kubernetes Deployment

yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ecommerce-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: ecommerce-api
  template:
    metadata:
      labels:
        app: ecommerce-api
    spec:
      containers:
      - name: ecommerce-api
        image: yourregistry/ecommerce-api:latest
        ports:
        - containerPort: 80
        envFrom:
        - configMapRef:
            name: ecommerce-config
        resources:
          requests:
            memory: "256Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        readinessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: ecommerce-api
spec:
  selector:
    app: ecommerce-api
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer

13. Testing Strategy

13.1 Unit Tests Example

csharp
namespace ECommerce.UnitTests.Application.Features.Authentication.Commands
{
    public class LoginCommandHandlerTests
    {
        private readonly Mock<IUserRepository> _userRepositoryMock;
        private readonly Mock<IJwtService> _jwtServiceMock;
        private readonly Mock<IRefreshTokenService> _refreshTokenServiceMock;
        private readonly Mock<IPasswordService> _passwordServiceMock;
        private readonly LoginCommandHandler _handler;
        
        public LoginCommandHandlerTests()
        {
            _userRepositoryMock = new Mock<IUserRepository>();
            _jwtServiceMock = new Mock<IJwtService>();
            _refreshTokenServiceMock = new Mock<IRefreshTokenService>();
            _passwordServiceMock = new Mock<IPasswordService>();
            
            _handler = new LoginCommandHandler(
                _userRepositoryMock.Object,
                _jwtServiceMock.Object,
                _refreshTokenServiceMock.Object,
                _passwordServiceMock.Object);
        }
        
        [Fact]
        public async Task Handle_WithValidCredentials_ReturnsAuthenticationResponse()
        {
            // Arrange
            var userId = Guid.NewGuid();
            var password = "Test@123";
            var passwordHash = new byte[] { 1, 2, 3 };
            var passwordSalt = new byte[] { 4, 5, 6 };
            
            var user = new User
            {
                Id = userId,
                UserName = "testuser",
                Email = "test@example.com",
                PasswordHash = passwordHash,
                PasswordSalt = passwordSalt,
                IsActive = true
            };
            
            var command = new LoginCommand
            {
                UserName = "testuser",
                Password = password,
                IpAddress = "127.0.0.1"
            };
            
            _userRepositoryMock.Setup(x => x.GetByUserNameAsync(command.UserName))
                .ReturnsAsync(user);
                
            _passwordServiceMock.Setup(x => x.VerifyPasswordHash(
                command.Password, passwordHash, passwordSalt))
                .Returns(true);
                
            _jwtServiceMock.Setup(x => x.GenerateJwtToken(user))
                .Returns("jwt_token");
                
            _refreshTokenServiceMock.Setup(x => x.GenerateRefreshToken(
                user.Id, command.IpAddress))
                .ReturnsAsync(new RefreshToken { Token = "refresh_token" });
                
            _userRepositoryMock.Setup(x => x.GetUserRolesAsync(user.Id))
                .ReturnsAsync(new List<string> { "User" });
                
            // Act
            var result = await _handler.Handle(command, CancellationToken.None);
            
            // Assert
            result.Should().NotBeNull();
            result.Token.Should().Be("jwt_token");
            result.RefreshToken.Should().Be("refresh_token");
            result.Roles.Should().Contain("User");
        }
    }
}

13.2 Integration Tests Example

csharp
namespace ECommerce.IntegrationTests.Controllers
{
    public class AuthenticationControllerTests : IClassFixture<CustomWebApplicationFactory<Startup>>
    {
        private readonly CustomWebApplicationFactory<Startup> _factory;
        private readonly HttpClient _client;
        
        public AuthenticationControllerTests(CustomWebApplicationFactory<Startup> factory)
        {
            _factory = factory;
            _client = _factory.CreateClient();
        }
        
        [Fact]
        public async Task Login_WithValidCredentials_ReturnsToken()
        {
            // Arrange
            var request = new LoginRequest
            {
                UserName = "admin",
                Password = "Admin@123"
            };
            
            var content = new StringContent(
                JsonSerializer.Serialize(request),
                Encoding.UTF8,
                "application/json");
                
            // Act
            var response = await _client.PostAsync("/api/authentication/login", content);
            
            // Assert
            response.EnsureSuccessStatusCode();
            
            var responseString = await response.Content.ReadAsStringAsync();
            var result = JsonSerializer.Deserialize<AuthenticationResponse>(
                responseString,
                new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
                
            result.Should().NotBeNull();
            result.Token.Should().NotBeNullOrEmpty();
            result.RefreshToken.Should().NotBeNullOrEmpty();
        }
    }
}

14. Monitoring and Observability

14.1 Application Insights Configuration

csharp
namespace ECommerce.API.Extensions
{
    public static class MonitoringExtensions
    {
        public static IServiceCollection AddApplicationMonitoring(this IServiceCollection services, IConfiguration configuration)
        {
            if (!string.IsNullOrEmpty(configuration["ApplicationInsights:ConnectionString"]))
            {
                services.AddApplicationInsightsTelemetry(options =>
                {
                    options.ConnectionString = configuration["ApplicationInsights:ConnectionString"];
                    options.EnableAdaptiveSampling = false;
                });
                
                services.AddApplicationInsightsKubernetesEnricher();
                
                services.AddSingleton<ITelemetryInitializer, CloudRoleNameTelemetryInitializer>();
            }
            
            return services;
        }
    }
    
    public class CloudRoleNameTelemetryInitializer : ITelemetryInitializer
    {
        public void Initialize(ITelemetry telemetry)
        {
            telemetry.Context.Cloud.RoleName = "ECommerce.API";
        }
    }
}

14.2 Health Checks with UI

csharp
namespace ECommerce.API.Extensions
{
    public static class HealthCheckExtensions
    {
        public static IServiceCollection AddCustomHealthChecks(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddHealthChecks()
                .AddSqlServer(
                    configuration.GetConnectionString("DefaultConnection"),
                    name: "SQL Server",
                    tags: new[] { "database", "sql" })
                .AddRedis(
                    configuration.GetConnectionString("Redis"),
                    name: "Redis",
                    tags: new[] { "cache", "redis" })
                .AddApplicationInsightsPublisher();
                
            services.AddHealthChecksUI(setup =>
                {
                    setup.AddHealthCheckEndpoint("API", "/health");
                    setup.SetEvaluationTimeInSeconds(60);
                    setup.SetMinimumSecondsBetweenFailureNotifications(300);
                })
                .AddInMemoryStorage();
                
            return services;
        }
        
        public static IApplicationBuilder UseCustomHealthChecks(this IApplicationBuilder app)
        {
            app.UseHealthChecks("/health", new HealthCheckOptions
            {
                Predicate = _ => true,
                ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
            });
            
            app.UseHealthChecksUI(options =>
            {
                options.UIPath = "/health-ui";
                options.ApiPath = "/health-api";
            });
            
            return app;
        }
    }
}

15. Security Best Practices

15.1 Security Headers Middleware

csharp
namespace ECommerce.API.Middleware
{
    public class SecurityHeadersMiddleware
    {
        private readonly RequestDelegate _next;
        
        public SecurityHeadersMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        
        public async Task Invoke(HttpContext context)
        {
            // Add security headers
            context.Response.Headers.Add("X-Frame-Options", "DENY");
            context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
            context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
            context.Response.Headers.Add("Referrer-Policy", "no-referrer");
            context.Response.Headers.Add("Content-Security-Policy", 
                "default-src 'self'; " +
                "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +
                "style-src 'self' 'unsafe-inline'; " +
                "img-src 'self' data:; " +
                "font-src 'self'; " +
                "connect-src 'self'; " +
                "media-src 'self'; " +
                "object-src 'none'; " +
                "frame-src 'none'; " +
                "base-uri 'self'; " +
                "form-action 'self'; " +
                "frame-ancestors 'none';");
                
            await _next(context);
        }
    }
}

15.2 Rate Limiting

csharp
namespace ECommerce.API.Extensions
{
    public static class RateLimitingExtensions
    {
        public static IServiceCollection AddCustomRateLimiting(this IServiceCollection services)
        {
            services.AddRateLimiter(options =>
            {
                options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
                {
                    return RateLimitPartition.GetFixedWindowLimiter(
                        partitionKey: context.Request.Headers.Host.ToString(),
                        factory: partition => new FixedWindowRateLimiterOptions
                        {
                            AutoReplenishment = true,
                            PermitLimit = 100,
                            QueueLimit = 10,
                            Window = TimeSpan.FromMinutes(1)
                        });
                });
                
                options.OnRejected = async (context, token) =>
                {
                    context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
                    await context.HttpContext.Response.WriteAsync(
                        "Too many requests. Please try again later.", 
                        cancellationToken: token);
                };
            });
            
            return services;
        }
    }
}

Conclusion

This comprehensive architecture provides a solid foundation for building scalable ASP.NET Core Web APIs with:

  1. Clean Architecture separation of concerns

  2. Database-first approach with SQL Server stored procedures

  3. Dapper for efficient data access

  4. CQRS pattern with MediatR

  5. JWT authentication with refresh tokens

  6. Transaction management for data integrity

  7. Large data handling techniques

  8. Comprehensive logging and monitoring

  9. Robust error handling

  10. API documentation with Swagger

  11. Security best practices

  12. Testing strategies

The implementation demonstrates key scenarios like user authentication and master-detail invoice operations while following enterprise-grade patterns and practices.

To complete your solution, you would:

  1. Implement the remaining repositories and services

  2. Add more domain entities and features

  3. Configure deployment pipelines

  4. Set up monitoring dashboards

  5. Implement frontend integration (Angular/React/Flutter)

This architecture is designed to handle large-scale applications with complex business requirements while maintaining performance, scalability, and maintainability.

Next
This is the most recent post.
Older Post

0 comments:

Featured Post

Enterprise ASP.NET Core Web API with Clean Architecture

  Enterprise ASP.NET Core Web API with Clean Architecture I'll create a comprehensive solution for your requirements, focusing on scalab...

 
Toggle Footer
Top