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