Большинство примеров, которые я нахожу, либо слишком фрагментированы, либо не показывают, как части сочетаются друг с другом в реальном проекте.
Итак, здесь я делюсь небольшим структурированным руководством по ASP.NET Core Web API (.NET 9) с полный минимальный пример Todo API. Цель — показать, как чистая архитектура выглядит на практике, включая:
- EF Core DbContext (InMemory)
- Разделение сущностей и DTO
- Проверку с использованием DataAnnotations
- Асинхронный доступ к базе данных
- Примеры маршрутизации
- Базовое заполнение
- Настройка Swagger
Основная идея этого примера:
Держите сущности, модели API (DTO) и контроллеры отдельно, чтобы поддерживать чистую архитектуру и избегать утечки внутренних структур данных.
Структура проекта
Простой веб-API обычно разделяется в:
- Модели (сущности) → представление базы данных
- DTO → то, что API предоставляет внешнему виду
- DbContext → доступ к базе данных EF Core
- Контроллеры → Конечные точки HTTP
Это внутреннее представление, хранящееся в базе данных.
Почему это существует
Сущность не должна предоставляться непосредственно API, поскольку она может содержать внутренние или конфиденциальные поля.
Код: Models/TodoItem.cs
namespace TodoApi.Models;
public class TodoItem
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
// Internal field, NOT exposed via API
public string? Secret { get; set; }
}
2. DTO (объект передачи данных)
Почему DTO важны
DTO определяют, что API открывает внешнему миру.
Без DTO:
- внутренние поля могут протекать
- проверку становится сложнее контролировать
- API становится тесно связанным со структурой базы данных
using System.ComponentModel.DataAnnotations;
namespace TodoApi.Models;
public class TodoItemDTO
{
public long Id { get; set; }
//Different Types of Data Annotations
//[MinLength(6,ErrorMessage ="Min 6 Charecters!")] //Length
//[RegularExpression(@"^\d{4}$",ErrorMessage ="Keine Zahl")] //4 Digit Number
//[Phone(ErrorMessage = "Invalid phone number!")]
//[RegularExpression(@"^[a-z]{4}[A-Z]+$", ErrorMessage ="keine Kleinbuchstaben")]
//[RegularExpression(@"^\d+[a-zA-Z]{5}\d{4}$")] //1533333abCDq1234
//[Required]
//[DataType(DataType.Text)]
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Что здесь происходит?
- [Required] → гарантирует, что поле не пустое.
- [StringLength] → ограничивает размер ввода
- [RegularExpression] → применяет правила формата
3. DbContext (базовый уровень EF)
Почему это существует
DbContext — это мост между:
- объектами C#
- базой данных (InMemory / SQL / и т. д.)
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models;
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions options)
: base(options)
{
}
public DbSet TodoItems { get; set; } = null!;
}
Пояснение
- DbSet представляет таблицу базы данных.
- EF Core автоматически сопоставляет ее с хранилищем.
- В этом примере для простоты мы используем InMemory DB
Что делает этот файл
Это точка входа приложения. Он настраивает:
- Внедрение зависимостей
- Поставщик базы данных
- Swagger
- Промежуточный конвейер
- Заполнение данных
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Register EF Core InMemory database
builder.Services.AddDbContext(opt =>
opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
// Seed database with initial data
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService();
if (!context.TodoItems.Any())
{
context.TodoItems.AddRange(
new TodoItem { Name = "Buy groceries", IsComplete = false },
new TodoItem { Name = "Walk the dog", IsComplete = true },
new TodoItem { Name = "Write report", IsComplete = false }
);
context.SaveChanges();
}
}
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Что здесь важно?
- Регистры внедрения зависимостей TodoContext
- База данных создается в памяти во время выполнения
- Заполнение данных добавляет исходные тестовые данные
- Swagger предоставляет API автоматическое тестирование пользовательского интерфейса
Почему существуют контроллеры
Контроллеры определяют:
- HTTP-маршруты
- логику API
- связь между DTO и базой данных
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
namespace TodoApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;
public TodoItemsController(TodoContext context)
{
_context = context;
}
// GET: api/todoitems
[HttpGet]
public async Task GetTodoItems()
{
return await _context.TodoItems
.Select(ToDTO)
.ToListAsync();
}
// GET: api/todoitems/5
[HttpGet("{id:long}")]
public async Task GetTodoItem(long id)
{
var item = await _context.TodoItems.FindAsync(id);
if (item == null)
return NotFound();
return ToDTO(item);
}
// GET: api/todoitems/complete/true
[HttpGet("complete/{isComplete:bool}")]
public async Task GetByCompletion(bool isComplete)
{
var items = await _context.TodoItems
.Where(x => x.IsComplete == isComplete)
.Select(ToDTO)
.ToListAsync();
if (!items.Any())
return NotFound();
return items;
}
// DTO mapping helper
private static TodoItemDTO ToDTO(TodoItem item) => new()
{
Id = item.Id,
Name = item.Name,
IsComplete = item.IsComplete
};
}