Starter ASP.NET Core Web API project that follows all the best practices listed above.
🛠️ Starter Project Overview
We’ll build a Product Catalog API with the following features:
🔧 Tech Stack:
-
ASP.NET Core 8
-
Entity Framework Core (In-Memory for now)
-
DTOs and Services
-
Validation
-
API Versioning
-
Swagger (OpenAPI)
-
Global Exception Handling
-
Logging
-
Async support
-
Dependency Injection
-
Clean architecture (Controllers → Services → Repository)
📁 Project Structure:
mathematica
ProductCatalogApi/
├── Controllers/
│ └── v1/
│ └── ProductsController.cs
├── Dtos/
│ └── ProductDto.cs
├── Entities/
│ └── Product.cs
├── Interfaces/
│ └── IProductService.cs
│ └── IProductRepository.cs
├── Repositories/
│ └── ProductRepository.cs
├── Services/
│ └── ProductService.cs
├── Middlewares/
│ └── ExceptionMiddleware.cs
├── Data/
│ └── ApplicationDbContext.cs
├── Program.cs
└── ProductCatalogApi.csproj
✅ 1. Initialize Project
Create the project:
bash
dotnet new webapi -n ProductCatalogApi
cd ProductCatalogApi
Enable nullable and implicit usings in csproj
(optional):
xml
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
✅ 2. Create Product
Entity
Entities/Product.cs
csharp
namespace ProductCatalogApi.Entities;
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
✅ 3. Create DTO
Dtos/ProductDto.cs
csharp
using System.ComponentModel.DataAnnotations;
namespace ProductCatalogApi.Dtos;
public class ProductDto
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; } = string.Empty;
[Range(0.01, 10000)]
public decimal Price { get; set; }
}
✅ 4. ApplicationDbContext
Data/ApplicationDbContext.cs
csharp
using Microsoft.EntityFrameworkCore;
using ProductCatalogApi.Entities;
namespace ProductCatalogApi.Data;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) { }
public DbSet<Product> Products => Set<Product>();
}
✅ 5. Repository
Interfaces/IProductRepository.cs
csharp
using ProductCatalogApi.Entities;
namespace ProductCatalogApi.Interfaces;
public interface IProductRepository
{
Task<IEnumerable<Product>> GetAllAsync();
Task<Product?> GetByIdAsync(int id);
Task<Product> AddAsync(Product product);
Task UpdateAsync(Product product);
Task DeleteAsync(Product product);
}
Repositories/ProductRepository.cs
csharp
using Microsoft.EntityFrameworkCore;
using ProductCatalogApi.Data;
using ProductCatalogApi.Entities;
using ProductCatalogApi.Interfaces;
namespace ProductCatalogApi.Repositories;
public class ProductRepository : IProductRepository
{
private readonly ApplicationDbContext _context;
public ProductRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Product>> GetAllAsync() => await _context.Products.ToListAsync();
public async Task<Product?> GetByIdAsync(int id) => await _context.Products.FindAsync(id);
public async Task<Product> AddAsync(Product product)
{
_context.Products.Add(product);
await _context.SaveChangesAsync();
return product;
}
public async Task UpdateAsync(Product product)
{
_context.Products.Update(product);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(Product product)
{
_context.Products.Remove(product);
await _context.SaveChangesAsync();
}
}
✅ 6. Service Layer
Interfaces/IProductService.cs
csharp
using ProductCatalogApi.Dtos;
namespace ProductCatalogApi.Interfaces;
public interface IProductService
{
Task<IEnumerable<ProductDto>> GetAllAsync();
Task<ProductDto?> GetByIdAsync(int id);
Task<ProductDto> CreateAsync(ProductDto dto);
Task<bool> UpdateAsync(int id, ProductDto dto);
Task<bool> DeleteAsync(int id);
}
Services/ProductService.cs
csharp
using ProductCatalogApi.Dtos;
using ProductCatalogApi.Entities;
using ProductCatalogApi.Interfaces;
namespace ProductCatalogApi.Services;
public class ProductService : IProductService
{
private readonly IProductRepository _repo;
public ProductService(IProductRepository repo)
{
_repo = repo;
}
public async Task<IEnumerable<ProductDto>> GetAllAsync()
{
var products = await _repo.GetAllAsync();
return products.Select(p => new ProductDto { Id = p.Id, Name = p.Name, Price = p.Price });
}
public async Task<ProductDto?> GetByIdAsync(int id)
{
var product = await _repo.GetByIdAsync(id);
if (product == null) return null;
return new ProductDto { Id = product.Id, Name = product.Name, Price = product.Price };
}
public async Task<ProductDto> CreateAsync(ProductDto dto)
{
var product = new Product { Name = dto.Name, Price = dto.Price };
var result = await _repo.AddAsync(product);
return new ProductDto { Id = result.Id, Name = result.Name, Price = result.Price };
}
public async Task<bool> UpdateAsync(int id, ProductDto dto)
{
var existing = await _repo.GetByIdAsync(id);
if (existing == null) return false;
existing.Name = dto.Name;
existing.Price = dto.Price;
await _repo.UpdateAsync(existing);
return true;
}
public async Task<bool> DeleteAsync(int id)
{
var existing = await _repo.GetByIdAsync(id);
if (existing == null) return false;
await _repo.DeleteAsync(existing);
return true;
}
}
✅ 7. Controller
Controllers/v1/ProductsController.cs
csharp
using Microsoft.AspNetCore.Mvc;
using ProductCatalogApi.Dtos;
using ProductCatalogApi.Interfaces;
namespace ProductCatalogApi.Controllers.v1;
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _service;
public ProductsController(IProductService service)
{
_service = service;
}
[HttpGet]
public async Task<IActionResult> GetAll() =>
Ok(await _service.GetAllAsync());
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var product = await _service.GetByIdAsync(id);
if (product == null) return NotFound();
return Ok(product);
}
[HttpPost]
public async Task<IActionResult> Create(ProductDto dto)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
var product = await _service.CreateAsync(dto);
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}
[HttpPut("{id}")]
public async Task<IActionResult> Update(int id, ProductDto dto)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
var updated = await _service.UpdateAsync(id, dto);
return updated ? NoContent() : NotFound();
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var deleted = await _service.DeleteAsync(id);
return deleted ? NoContent() : NotFound();
}
}
✅ 8. Middleware for Error Handling
Middlewares/ExceptionMiddleware.cs
csharp
namespace ProductCatalogApi.Middlewares;
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled Exception");
context.Response.StatusCode = 500;
await context.Response.WriteAsJsonAsync(new { error = "An unexpected error occurred." });
}
}
}
✅ 9. Configure Program.cs
Program.cs
csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ProductCatalogApi.Data;
using ProductCatalogApi.Interfaces;
using ProductCatalogApi.Middlewares;
using ProductCatalogApi.Repositories;
using ProductCatalogApi.Services;
var builder = WebApplication.CreateBuilder(args);
// DB
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase("ProductDb"));
// Services
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
// Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Versioning
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
});
// Controllers
builder.Services.AddControllers();
var app = builder.Build();
// Middlewares
app.UseMiddleware<ExceptionMiddleware>();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
✅ 10. Run the App
dotnet run
Visit:
-
Swagger:
https://localhost:{port}/swagger
-
Try endpoints like:
-
GET /api/v1/products
-
POST /api/v1/products
-
No comments:
Post a Comment