Базовое руководство по WebAPIC#

Место общения программистов C#
Anonymous
Базовое руководство по WebAPI

Сообщение Anonymous »

Я видел много вопросов о создании веб-API с помощью ASP.NET Core, особенно о структуре, EF Core, DTO, проверке и маршрутизации.
Большинство примеров, которые я нахожу, либо слишком фрагментированы, либо не показывают, как части сочетаются друг с другом в реальном проекте.
Итак, здесь я делюсь небольшим структурированным руководством по ASP.NET Core Web API (.NET 9) с полный минимальный пример Todo API. Цель — показать, как чистая архитектура выглядит на практике, включая:
  • EF Core DbContext (InMemory)
  • Разделение сущностей и DTO
  • Проверку с использованием DataAnnotations
  • Асинхронный доступ к базе данных
  • Примеры маршрутизации
  • Базовое заполнение
  • Настройка Swagger
Это минимальный, но полный Todo API, созданный с помощью ASP.NET Core Web API. Он демонстрирует, как структурирован реальный бэкэнд и как разные уровни работают вместе.
Основная идея этого примера:

Держите сущности, модели API (DTO) и контроллеры отдельно, чтобы поддерживать чистую архитектуру и избегать утечки внутренних структур данных.

Структура проекта
Простой веб-API обычно разделяется в:
  • Модели (сущности) → представление базы данных
  • DTO → то, что API предоставляет внешнему виду
  • DbContext → доступ к базе данных EF Core
  • Контроллеры → Конечные точки HTTP
1. Модель сущности (уровень базы данных)
Это внутреннее представление, хранящееся в базе данных.
Почему это существует
Сущность не должна предоставляться непосредственно 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 становится тесно связанным со структурой базы данных
Код: Models/TodoItemDTO.cs
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 / и т. д.)
Код: Models/TodoContext.cs
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
4. Program.cs (Настройка приложения)
Что делает этот файл
Это точка входа приложения. Он настраивает:
  • Внедрение зависимостей
  • Поставщик базы данных
  • Swagger
  • Промежуточный конвейер
  • Заполнение данных
Код: Program.cs
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 автоматическое тестирование пользовательского интерфейса
5. Контроллер (уровень API)
Почему существуют контроллеры
Контроллеры определяют:
  • HTTP-маршруты
  • логику API
  • связь между DTO и базой данных
Код: Controllers/TodoItemsController.cs
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
};
}

Вернуться в «C#»