EcommerceSearch
A clean-architecture ASP.NET Core 8 Web API for searching an ecommerce product catalog, with a companion Model Context Protocol (MCP) server, integration tests, and BenchmarkDotNet benchmarks.
Ask AI about EcommerceSearch
Powered by Claude Β· Grounded in docs
I know everything about EcommerceSearch. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
EcommerceSearch
A clean-architecture ASP.NET Core 8 Web API for searching an ecommerce product catalog, with a companion Model Context Protocol (MCP) server, integration tests, and BenchmarkDotNet benchmarks.
Projects
| Project | Description |
|---|---|
EcommerceSearch.Api | ASP.NET Core REST API β product catalog CRUD + NL search |
EcommerceSearch.McpServer | MCP stdio server that wraps the REST API for AI tool use |
EcommerceSearch.Tests | xUnit integration tests with in-memory SQLite |
EcommerceSearch.Benchmarks | BenchmarkDotNet comparisons: EF Core vs Dapper |
Architecture
Client
βββΆ ProductsController (ASP.NET Core Controller)
βββΆ IProductService (Service abstraction)
βββΆ IProductRepository (EF Core reads β AsNoTracking)
βββΆ IDapperProductReadRepository (Dapper raw SQL reads)
βββΆ ProductDbContext / SQLite
Entities never leave the service layer β controllers only receive ProductDto.
Tech Stack
- .NET 8 / ASP.NET Core 8
- Entity Framework Core 8 with SQLite driver β all reads use
AsNoTracking() - Dapper 2 with
Microsoft.Data.Sqlitefor alternative read endpoints - xUnit 2 +
Microsoft.AspNetCore.Mvc.Testingfor integration tests - FluentAssertions for expressive test assertions
- BenchmarkDotNet for micro-benchmarks
- ModelContextProtocol SDK for the MCP server
Domain
Product
{
string Id // "p1" β¦ "p100"
string Name
string Brand
string Description
decimal Price
string Category // electronics | clothing | furniture | sports | books
}
100 products are seeded at startup across 5 categories (20 each).
Getting Started
Prerequisites
Build
dotnet build EcommerceSearch.sln
Run the API
cd EcommerceSearch.Api
dotnet run
The API starts at http://localhost:5000 (HTTP) / https://localhost:5001 (HTTPS).
Swagger UI is available at http://localhost:5000/swagger in Development mode.
The SQLite database (ecommerce.db) is created and seeded automatically on first run.
Run the MCP Server
The MCP server reads the REST API base URL from appsettings.json:
{ "ApiBaseUrl": "http://localhost:5000/" }
Start the API first, then:
cd EcommerceSearch.McpServer
dotnet run
The server communicates over stdio (standard MCP transport).
Run Tests
dotnet test EcommerceSearch.Tests
Tests use an isolated in-memory SQLite database β no file is written to disk.
Run Benchmarks
cd EcommerceSearch.Benchmarks
dotnet run -c Release
API Endpoint Reference
GET /api/products/{id}
Fetch a single product by its ID.
| Parameter | Location | Type | Description |
|---|---|---|---|
id | path | string | Product identifier, e.g. p1 |
Responses
| Code | Description |
|---|---|
200 OK | ProductDto |
404 Not Found | Product does not exist |
Example
curl http://localhost:5000/api/products/p1
GET /api/products
Paginated product list via EF Core.
| Parameter | Location | Type | Default | Description |
|---|---|---|---|---|
category | query | string | β | Filter by category (case-insensitive) |
page | query | int | 1 | Page number (1-based) |
pageSize | query | int | 20 | Items per page (max 100) |
Response β PagedResult<ProductDto>
Example
curl "http://localhost:5000/api/products?category=electronics&page=1&pageSize=10"
GET /api/products/dapper
Same paginated list but executed with raw SQL via Dapper.
| Parameter | Location | Type | Default | Description |
|---|---|---|---|---|
category | query | string | β | Filter by category |
page | query | int | 1 | Page number |
pageSize | query | int | 20 | Items per page (max 100) |
Example
curl "http://localhost:5000/api/products/dapper?category=clothing&page=2&pageSize=5"
POST /api/products/search
Natural language product search β parses a free-text query into structured filters.
Request body
{
"query": "show me electronics under 500",
"page": 1,
"pageSize": 20
}
Response β PagedResult<ProductDto>
Supported filter patterns
| Intent | Example phrases |
|---|---|
| Category | "electronics", "clothing", "furniture", "sports", "books" |
| Brand | Any capitalized word not in the stop-word list, e.g. "Nike", "Samsung" |
| Max price | "under 100", "below 50", "less than 200", "max 300" |
| Similarity | "similar to iPhone 14", "similar to MacBook Pro" |
Filters combine: only products matching all parsed filters are returned.
Natural Language Search Examples
# Electronics under $500
curl -X POST http://localhost:5000/api/products/search \
-H "Content-Type: application/json" \
-d '{"query": "show me electronics under 500", "page": 1, "pageSize": 20}'
# Nike shoes below $200
curl -X POST http://localhost:5000/api/products/search \
-H "Content-Type: application/json" \
-d '{"query": "Nike shoes below 200"}'
# Similar to MacBook Pro
curl -X POST http://localhost:5000/api/products/search \
-H "Content-Type: application/json" \
-d '{"query": "similar to MacBook Pro"}'
# Samsung furniture
curl -X POST http://localhost:5000/api/products/search \
-H "Content-Type: application/json" \
-d '{"query": "Samsung furniture"}'
# Cheap books
curl -X POST http://localhost:5000/api/products/search \
-H "Content-Type: application/json" \
-d '{"query": "books under 20"}'
Rate Limiting
Per-IP fixed-window rate limiting:
| Setting | Default |
|---|---|
| Permit limit | 30 requests |
| Window | 60 seconds |
| Queue limit | 5 requests |
Requests that exceed the limit and queue receive 429 Too Many Requests.
Configure in appsettings.json:
{
"RateLimit": {
"PermitLimit": 30,
"WindowSeconds": 60,
"QueueLimit": 5
}
}
MCP Tools
The EcommerceSearch.McpServer exposes three tools to AI agents:
| Tool | Description |
|---|---|
get_product_by_id | Retrieve a product by ID |
search_products | Natural language product search |
find_similar_products | Find products similar to a reference name/phrase |
The MCP server never accesses the database directly β it calls the REST API over HTTP.
Project Structure
EcommerceSearch/
βββ EcommerceSearch.Api/
β βββ Controllers/
β β βββ ProductsController.cs # GET /{id}, GET /, GET /dapper, POST /search
β βββ Services/
β β βββ IProductService.cs
β β βββ ProductService.cs # NL query parser + business logic
β βββ Data/
β β βββ ProductDbContext.cs
β β βββ IProductRepository.cs
β β βββ EfProductRepository.cs # EF Core + AsNoTracking
β β βββ IDapperProductReadRepository.cs
β β βββ DapperProductReadRepository.cs
β β βββ ProductSeeder.cs # 100 seed products
β βββ Contracts/
β β βββ ProductDto.cs
β β βββ PagedResult.cs
β β βββ ProductSearchFilters.cs
β β βββ SearchProductsRequest.cs
β βββ Models/
β β βββ Product.cs
β βββ Filters/
β β βββ GlobalActionLoggerAsync.cs # Request/response timing
β β βββ GlobalExceptionHandlingFilter.cs # ProblemDetails error responses
β βββ Options/
β β βββ RateLimitOptions.cs
β βββ Program.cs
βββ EcommerceSearch.McpServer/
β βββ Clients/
β β βββ ProductApiClient.cs # Typed HTTP client for the REST API
β βββ Models/
β β βββ ProductDto.cs
β β βββ PagedResult.cs
β βββ Tools/
β β βββ ProductTools.cs # MCP tool definitions
β βββ Program.cs
βββ EcommerceSearch.Tests/
β βββ CustomWebApplicationFactory.cs # In-memory SQLite, no file I/O
β βββ ProductsControllerTests.cs
β βββ ProductDapperTests.cs
β βββ NaturalLanguageSearchTests.cs
β βββ RateLimitingTests.cs
β βββ GlobalExceptionHandlingFilterTests.cs
βββ EcommerceSearch.Benchmarks/
β βββ ProductCategoryQueryBenchmark.cs
β βββ Program.cs
βββ EcommerceSearch.sln
Coding Conventions
- All identifiers and comments in English
- Interfaces prefixed with
I(e.g.IProductService) - EF Core implementations suffixed with
Ef(e.g.EfProductRepository) - Controllers are plural (
ProductsController) - DTOs suffixed with
Dto(ProductDto) - Raw entities never leave the service layer β always mapped to DTOs
- Exception handling centralised in
GlobalExceptionHandlingFilter - Logging centralised in
GlobalActionLoggerAsync
