
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)
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:
- Create a new Blank Solution named "ECommerceSolution"
- Add projects as shown above (Class Library projects for all except the API, which is an ASP.NET Core Web API)
- 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)
Core/
├───Entities/
├───Enums/
├───Exceptions/
├───Interfaces/
├───Common/
└───Extensions/
ECommerce.Application (Application Layer)
Application/
├───Features/
│ ├───Authentication/
│ │ ├───Commands/
│ │ ├───Queries/
│ │ └───Models/
│ ├───Invoices/
│ │ ├───Commands/
│ │ ├───Queries/
│ │ └───Models/
├───Common/
│ ├───Behaviors/
│ ├───Interfaces/
│ └───Mappings/
└───Services/
ECommerce.Infrastructure (Infrastructure Layer)
Infrastructure/
├───Data/
│ ├───Migrations/
│ ├───Repositories/
│ └───Dapper/
├───Identity/
├───Logging/
├───Caching/
└───Services/
ECommerce.API (Presentation Layer)
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:
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:
-- 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
-- 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
-- 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
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
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
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
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
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
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
-- 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
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
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
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
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
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
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;
}
}
}

Janardhana KondetiPosted Mar 18, 2026, 11:03 AM
Can you share the solution Project?