Справочник по ASP.NET
Назначение
Справочник-шпаргалка по ASP.NET — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс: раздел. Официальные учебники и хаб документации: Документация и практика ASP.NET (Microsoft Learn) — Microsoft Learn.
Краткое пояснение
Компоненты и ключевые особенности фреймворка.
Быстрый старт
dotnet add package Swashbuckle.AspNetCore
Справочные таблицы
Хостинг, Конфигурация, DI, Middleware, Маршрутизация
1. Хостинг и жизненный цикл приложения
1.1. Типы хостов
IHost— корневой интерфейс для .NET Generic Host (начиная с .NET Core 2.1).IWebHost— устаревший (deprecated) интерфейс для веб-хоста (ASP.NET Core ≤ 2.2).- В .NET 6+ используется minimal hosting model:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
1.2. Жизненный цикл
Основные события (через IHostedService и IHostApplicationLifetime):
| Интерфейс / событие | Описание |
|---|---|
IHostedService.StartAsync | Вызывается при старте приложения (до обработки первого запроса). Блокирующий — задержка старта. |
IHostedService.StopAsync | Вызывается при graceful shutdown. |
IHostApplicationLifetime.ApplicationStarted | Флаг и событие — приложение запущено и готово принимать запросы (app.Run() уже вызван). |
IHostApplicationLifetime.ApplicationStopping | Отправляется до вызова StopAsync всех hosted-сервисов. Можно подписаться. |
IHostApplicationLifetime.ApplicationStopped | Все сервисы остановлены, приложение завершено. |
Пример регистрации hosted-сервиса
builder.Services.AddHostedService<MyBackgroundService>();
// или через фабрику:
builder.Services.AddHostedService(sp => new MyBackgroundService(sp.GetRequiredService<ILogger<MyBackgroundService>>()));
1.3. Startup-класс (только для non-minimal hosting, .NET ≤ 5 или явное использование)
public class Startup
{
public Startup(IConfiguration configuration) { Configuration = configuration; }
public IConfiguration Configuration { get; }
// ConfigureServices устарел в .NET 6+; заменён на builder.Services
public void ConfigureServices(IServiceCollection services) { }
// Configure заменён на app.Use... в minimal model
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { }
}
⚠️ В .NET 6+
Startupне требуется. Всё настраивается черезWebApplicationBuilder.
2. Конфигурация (IConfiguration)
2.1. Источники конфигурации (по умолчанию, порядок важен — последний побеждает)
| Провайдер | Путь / Примечание |
|---|---|
appsettings.json | Базовый файл. UTF-8 без BOM. |
appsettings.{Environment}.json | e.g. appsettings.Production.json. Автозагрузка по ASPNETCORE_ENVIRONMENT. |
| User secrets | Только в Development — см. блок ниже. |
| Environment variables | Префикс ASPNETCORE_ или DOTNET_. Вложенность: ConnectionStrings__Default = ConnectionStrings:Default. |
| Command-line args | --key value, /key=value, --key=value. |
User secrets (локальная разработка, не коммитить):
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:Default" "Server=localhost;..."
dotnet user-secrets list
Файл: %APPDATA%\Microsoft\UserSecrets\{GUID}\secrets.json (Windows) или ~/.microsoft/usersecrets/ (Linux/macOS).
Добавление кастомного провайдера
builder.Configuration.AddJsonFile("custom.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables("MYAPP_");
2.2. Чтение значений
var value = builder.Configuration["Section:Key"]; // string
var intValue = builder.Configuration.GetValue<int>("Count", defaultValue: 10);
var section = builder.Configuration.GetSection("Database");
var options = section.Get<DatabaseOptions>(); // требует Microsoft.Extensions.Options.ConfigurationExtensions
2.3. Опции (IOptions<T>, IOptionsSnapshot<T>, IOptionsMonitor<T>)
| Интерфейс | Жизненный цикл | Reload | Применение |
|---|---|---|---|
IOptions<T> | Singleton | ❌ | Конфигурация, не меняющаяся после старта. |
IOptionsSnapshot<T> | Scoped | ✅ (на запрос) | Настройки, зависящие от request (e.g. tenant-specific). |
IOptionsMonitor<T> | Singleton | ✅ (callback) | Реакция на изменение (e.g. logging, cache invalidation). |
Регистрация
builder.Services.Configure<SmtpOptions>(builder.Configuration.GetSection("Smtp"));
// или с валидацией:
builder.Services.AddOptions<SmtpOptions>()
.BindConfiguration("Smtp")
.ValidateDataAnnotations()
.Validate(o => !string.IsNullOrWhiteSpace(o.Host));
3. Внедрение зависимостей (IServiceCollection)
3.1. Жизненные циклы сервисов
| Регистрация | Интерфейс | Примечание |
|---|---|---|
AddSingleton<T>() | T, IServiceProvider.GetService<T>() | Один экземпляр на всё приложение. Осторожно с state и scoped-зависимостями! |
AddScoped<T>() | T | Один экземпляр на HTTP-запрос (или на scope, созданный вручную). |
AddTransient<T>() | T | Новый экземпляр каждый раз при запросе. |
Примеры
Код ITЗагрузка примера кода…
3.2. Встроенные сервисы (часто используемые)
| Тип | Регистрация | Описание |
|---|---|---|
IConfiguration | Авто | Объект конфигурации. |
IWebHostEnvironment / IHostEnvironment | Авто | Информация об окружении (EnvironmentName, ContentRootPath, WebRootPath). |
ILogger<T> | Авто | Логгер с категорией T. |
HttpContextAccessor | Требует AddHttpContextAccessor() | Доступ к HttpContext вне request pipeline. Не рекомендуется — нарушает DI-принципы. |
HttpClient | Через AddHttpClient<T>() | Typed/Named клиенты с политикой (Polly), base address, headers. |
4. Middleware
Middleware — компоненты, обрабатывающие каждый HTTP-запрос и ответ в порядке регистрации ("луковичная" модель). Когда middleware, а когда endpoint filter или policy pipeline — критерии и полная схема запроса: ASP.NET - фреймворк для веб-приложений — Middleware, Endpoint Filters, Policy Pipeline.
4.1. Стандартные middleware (в порядке рекомендуемой регистрации)
| Middleware | Метод расширения | Описание | Важные параметры / настройки |
|---|---|---|---|
| Exception Handler | app.UseExceptionHandler("/error") | Перехват необработанных исключений. | ExceptionHandlerOptions: ExceptionHandlingPath, AllowStatusCode404Response. |
| HTTPS Redirection | app.UseHttpsRedirection() | 307 → HTTPS (если исходный запрос HTTP). | HttpsRedirectionOptions: SslPort (по умолчанию 443). |
| Static Files | app.UseStaticFiles() | Обслуживание wwwroot. | StaticFileOptions: RequestPath, FileProvider, ServeUnknownFileTypes, OnPrepareResponse. |
| Routing | app.UseRouting() | Инициализация endpoint routing. Должен идти ДО UseAuthorization, UseEndpoints. | — |
| Authentication | app.UseAuthentication() | Выполнение схем аутентификации (устанавливает HttpContext.User). | — |
| Authorization | app.UseAuthorization() | Проверка политик доступа. Должен идти ПОСЛЕ UseAuthentication, но ДО UseEndpoints. | — |
| Session | app.UseSession() | Управление сессиями. Должен идти ПОСЛЕ UseRouting, но ДО UseAuthorization. | SessionOptions: IdleTimeout, Cookie, IOTimeout. |
| CORS | app.UseCors("PolicyName") | Кросс-доменные запросы. | CorsPolicyBuilder: WithOrigins, AllowAnyOrigin, WithMethods, WithHeaders, AllowCredentials, SetIsOriginAllowed. |
| Response Caching | app.UseResponseCaching() | Кэширование ответов (на уровне middleware). | ResponseCachingOptions: SizeLimit, UseCaseSensitivePaths. |
| Response Compression | app.UseResponseCompression() | Gzip/Brotli сжатие. | ResponseCompressionOptions: Providers, EnableForHttps, MimeTypes. |
| Endpoints | app.UseEndpoints(...) / app.Map... | Регистрация маршрутов. В minimal model — app.MapGet, app.MapControllers и т.д. | — |
✅ Правильный порядок:
UseExceptionHandler→UseHttpsRedirection→UseStaticFiles→UseRouting→UseAuthentication→UseAuthorization→UseSession→UseCors→UseResponseCaching→UseResponseCompression→UseEndpoints/Map*.
4.2. Создание кастомного middleware
Вариант 1 — Функция-делегат
app.Use(async (context, next) =>
{
// До обработки
context.Items["StartTime"] = DateTime.UtcNow;
await next(); // → следующее middleware
// После обработки
var elapsed = DateTime.UtcNow - (DateTime)context.Items["StartTime"];
context.Response.Headers["X-Elapsed"] = elapsed.TotalMilliseconds.ToString("F2");
});
Вариант 2 — Класс с InvokeAsync
Код ITЗагрузка примера кода…
⚠️ Middleware не создаётся на каждый запрос (если не transient).
InvokeAsync— scoped по контексту.
5. Маршрутизация (Endpoint Routing)
ASP.NET Core использует Endpoint Routing (начиная с 3.0): сначала определяется endpoint, затем выбирается middleware для его обработки.
5.1. Типы endpoint-ов
| Тип | Метод | Пример |
|---|---|---|
| Minimal API | app.MapGet("/api/users", ...) | app.MapPost("/items", (Item i) => Results.Created($"/items/{i.Id}", i)); |
| Controllers | app.MapControllers() | Требует AddControllers() / AddControllersWithViews(). |
| Razor Pages | app.MapRazorPages() | Требует AddRazorPages(). |
| SignalR Hubs | app.MapHub<ChatHub>("/chat") | — |
| gRPC Services | app.MapGrpcService<GreeterService>() | — |
| Health Checks | app.MapHealthChecks("/health") | — |
5.2. Атрибуты маршрутизации (для контроллеров и Razor Pages)
| Атрибут | Применяется к | Параметры | Особенности |
|---|---|---|---|
[Route("api/[controller]")] | Класс | Шаблон маршрута. [controller] → имя контроллера без Controller. | Route на классе + HttpGet → объединяются. |
[HttpGet("list")] | Метод | Относительный путь. Может включать параметры: "{id:int}". | Поддерживает HTTP-методы: HttpPost, HttpPut, HttpDelete, HttpPatch. |
[Route("[action]")] | Класс / метод | [action] → имя метода. | Редко — конфликтует с REST. |
[NonAction] | Метод | — | Метод не является endpoint'ом. |
[ApiExplorerSettings(IgnoreApi = true)] | Класс / метод | — | Скрывает из OpenAPI/Swashbuckle. |
[Consumes("application/json")] | Метод | MIME-типы | Валидация Content-Type запроса. |
[Produces("application/json")] | Метод | MIME-типы | Устанавливает Content-Type ответа. |
5.3. Параметры маршрута и ограничения (Route Constraints)
[HttpGet("users/{id:int:min(1)}")] // id — int ≥ 1
[HttpGet("files/{name:regex(^\\w+\\.txt$)}")] // имя — word chars + ".txt"
[HttpGet("posts/{date:datetime:regex(\\d{{4}}-\\d{{2}}-\\d{{2}})}")] // кастомный формат
Встроенные ограничения:
| Ограничение | Пример | Описание |
|---|---|---|
int | {id:int} | int |
bool | {active:bool} | bool |
datetime | {date:datetime} | DateTime |
decimal | {price:decimal} | decimal |
double | {lat:double} | double |
float | {temp:float} | float |
guid | {id:guid} | Guid |
long | {count:long} | long |
min(val) | {age:min(18)} | ≥ val |
max(val) | {age:max(120)} | ≤ val |
range(min,max) | {count:range(1,10)} | min ≤ x ≤ max |
alpha | {name:alpha} | только буквы |
regex(pattern) | {code:regex(^[A-Z]{3}$)} | регулярное выражение |
required | {name:required} | не null и не пустая строка |
⚠️ Если ограничение не проходит — 404 (не 400!).
5.4. Именованные маршруты и генерация URL
[HttpGet("users/{id}", Name = "GetUserById")]
public IActionResult GetUser(int id) { ... }
// Генерация в контроллере:
var url = Url.RouteUrl("GetUserById", new { id = 42 }); // → "/users/42"
// Или в Minimal API через IUrlHelperFactory:
var urlHelper = urlHelperFactory.GetUrlHelper(actionContextAccessor.ActionContext);
var url = urlHelper.RouteUrl("GetUserById", new { id = 42 });
5.5. Fallback-маршруты
app.MapFallbackToFile("index.html"); // SPA: все несуществующие пути → index.html
app.MapFallback(() => Results.Redirect("/not-found")); // кастомный fallback
MVC, Razor Pages, API-разработка, OpenAPI
1. MVC и Razor Pages — архитектура и различия
| Критерий | MVC | Razor Pages |
|---|---|---|
| Единица модульности | Контроллер + Action + View (раздельно) | PageModel + .cshtml (в одном файле .cshtml.cs) |
| Маршрутизация | Атрибуты или conventional routing ({controller=Home}/{action=Index}/{id?}) | По пути файла: /Pages/Products/Index.cshtml → /Products/Index |
| Handler-методы | public IActionResult Index() | public void OnGet(), public IActionResult OnPostCreate() и т.д. |
| Bind-параметры | public IActionResult Create([FromBody] Product p) | public IActionResult OnPost([FromForm] Product p) |
| Хранение состояния | В основном через TempData, ViewData, ViewBag | ModelState, TempData, PageContext |
| Рекомендация Microsoft | Для сложных SPA/REST API | Для page-centric приложений (CMS, админки, формы) |
✅ Оба используют один и тот же движок представлений (Razor), DI, middleware, авторизацию.
2. Контроллеры и действия (Actions)
2.1. Базовые классы и интерфейсы
| Тип | Интерфейс | Наследование | Примечание |
|---|---|---|---|
ControllerBase | IActionResult | ControllerBase | Минимальный базовый класс (для API). Не имеет View(). |
Controller | ControllerBase | Controller | Расширяет ControllerBase: добавляет View(), PartialView(), ViewData, TempData. Для MVC с представлениями. |
2.2. Типы возвращаемых значений действий
| Тип | Метод-помощник | HTTP-код | Сериализация |
|---|---|---|---|
IActionResult | Ok(), CreatedAtAction(), Unauthorized(), NotFound() | По методу | Нет (уже сформирован ответ) |
ActionResult<T> | Ok<T>(T value), CreatedAtAction<T>(...) | По методу | Нет (уже сформирован ответ) |
T (POCO) | — | 200 | Да: через OutputFormatter (JSON/XML) |
Task<T> / Task<IActionResult> | — | 200 / по методу | Да / Нет |
⚠️ Возвращая
T, вы делегируете сериализацию фреймворку. При ошибке (исключение) — 500. ИспользуйтеActionResult<T>, если нужен контроль над статус-кодом.
2.3. Атрибуты действий (Action Attributes)
| Атрибут | Применение | Параметры / Эффект |
|---|---|---|
[HttpGet], [HttpPost], [HttpPut], [HttpDelete], [HttpPatch], [HttpHead], [HttpOptions] | Метод | Name, Order, Template (относительный маршрут) |
[AcceptVerbs("MOVE", "COPY")] | Метод | Произвольные HTTP-глаголы |
[ActionName("List")] | Метод | Заменяет имя действия в маршруте и Url.Action() |
[NonAction] | Метод | Исключает из маршрутизации (вспомогательный метод) |
[Route("api/products/{id}")] | Класс / Метод | Явный маршрут (см. часть 1) |
[ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Get))] | Метод | Применяет соглашение (например, 200/404 для GET) |
2.4. Привязка параметров (Model Binding)
| Атрибут | Источник | Примечание |
|---|---|---|
[FromQuery] | ?name=value | Значения строк, коллекции (?ids=1&ids=2), объекты (?user.name=Timur) |
[FromRoute] | {id} в маршруте | Требует совпадения имени параметра и placeholder’а |
[FromBody] | Тело запроса (JSON/XML) | Только один параметр на метод. Использует InputFormatter. |
[FromForm] | application/x-www-form-urlencoded, multipart/form-data | Для HTML-форм и загрузки файлов |
[FromHeader] | Заголовки | Например, [FromHeader(Name = "X-Request-Id")] string requestId |
[FromServices] | DI-контейнер | Внедрение сервиса напрямую в параметр (не через конструктор) |
[ModelBinder(typeof(MyBinder))] | Кастомный биндер | Для нетиповых сценариев |
Пример
[HttpPost("upload")]
public IActionResult Upload(
[FromForm] IFormFile file, // файл
[FromForm] string description, // поле формы
[FromHeader("X-Api-Version")] string version, // заголовок
[FromServices] ILogger<HomeController> logger) // DI
{
// ...
}
⚠️
[FromBody]не работает сapplication/x-www-form-urlencoded— только сapplication/json,application/xml.
3. Представления (Views) и Razor-синтаксис
3.1. Основные директивы
| Директива | Назначение |
|---|---|
@page | Только в Razor Pages — делает файл страницей |
@model TypeName | Указывает тип модели представления (@Model) |
@inject ServiceType ServiceName | Внедрение сервиса в представление (не рекомендуется — нарушает разделение ответственности) |
@addTagHelper *, AssemblyName | Подключение тег-хелперов (обычно в _ViewImports.cshtml) |
@using Namespace | Импорт пространства имён |
@functions { ... } | Код C# внутри представления (устаревшее, лучше выносить в PageModel/контроллер) |
3.2. Layout и Sections
<!-- _Layout.cshtml -->
<!DOCTYPE html>
<html>
<head>
@RenderSection("Head", required: false)
</head>
<body>
<header>...</header>
<main>@RenderBody()</main>
<footer>...</footer>
@RenderSection("Scripts", required: false)
</body>
</html>
<!-- Index.cshtml -->
@{
Layout = "_Layout";
}
@section Head {
<meta name="description" content="Home page">
}
<h1>Home</h1>
@section Scripts {
<script src="/js/home.js"></script>
}
3.3. Частичные представления (Partial Views)
| Метод | Описание |
|---|---|
@await Html.PartialAsync("_Product", product) | Рендеринг частичного представления с моделью. Возвращает IHtmlContent. |
@await Html.RenderPartialAsync("_Sidebar", null) | То же, но пишет напрямую в TextWriter (чуть быстрее, но не возвращает значение). |
@{ await Html.RenderPartialAsync(...); } | При использовании RenderPartialAsync требуется await внутри блока. |
✅
_ViewStart.cshtml— выполняется перед каждым представлением (часто задаётLayout).
3.4. View Components
Аналог частичных представлений, но с логикой в классе и DI.
public class PriorityList : ViewComponent
{
private readonly IToDoItemRepository _repo;
public PriorityList(IToDoItemRepository repo) => _repo = repo;
public async Task<IViewComponentResult> InvokeAsync(int maxPriority)
{
var items = await _repo.GetItemsWithPriorityAsync(maxPriority);
return View(items); // ищет Views/Shared/Components/PriorityList/Default.cshtml
}
}
<!-- В представлении -->
@await Component.InvokeAsync("PriorityList", new { maxPriority = 2 })
<!-- Или строго типизированно: -->
@await Component.InvokeAsync<PriorityListViewComponent>(new { maxPriority = 2 })
🔹 Имена:
- Класс:
PriorityListViewComponent(илиPriorityList— фреймворк добавитViewComponentпри поиске)- Шаблон:
/Views/Shared/Components/PriorityList/Default.cshtml- Доп. шаблоны:
InvokeAsync(..., "HighPriority")→HighPriority.cshtml
4. Тег-хелперы (Tag Helpers)
Тег-хелперы — серверные компоненты, модифицирующие HTML-элементы по атрибутам/тегам.
4.1. Встроенные тег-хелперы (активируются через _ViewImports.cshtml — @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers)
| Тег-хелпер | Атрибуты | Описание |
|---|---|---|
<a> (AnchorTagHelper) | asp-controller, asp-action, asp-route-{param}, asp-fragment, asp-area, asp-protocol, asp-host | Генерирует URL через маршрутизацию. Пример: <a asp-action="List" asp-route-id="5">View</a> → <a href="/Home/List/5">View</a> |
<form> (FormTagHelper) | asp-controller, asp-action, asp-route-{param}, asp-antiforgery="true" | Генерирует <form method="post" action="..."> + <input name="__RequestVerificationToken" ...> при asp-antiforgery. |
<input> (InputTagHelper) | asp-for="Model.Property", asp-format, type | Генерирует <input> с id, name, value, type, data-val-* (для валидации). |
<label> (LabelTagHelper) | asp-for="Model.Property" | Генерирует <label for="...">ИмяСвойства</label>. |
<select> (SelectTagHelper) | asp-for, asp-items="IEnumerable<SelectListItem>" | Связывает select с моделью и коллекцией опций. |
<textarea> (TextAreaTagHelper) | asp-for | Аналогично input, но для multi-line. |
<img> (ImageTagHelper) | asp-append-version="true" | Добавляет ?v=hash к src для кэширования. |
<environment> | names="Разработка,Staging" | Условный рендеринг: <environment include="Разработка"><script src="~/js/dev.js"></script></environment> |
<cache> | expires-after, expires-on, vary-by-* | Кэширование фрагмента HTML на стороне сервера. |
<partial> | name, for, model, view-data | <partial name="_Product" model="product" /> — современная замена @Html.Partial. |
4.2. Валидация и data-val-*
При использовании asp-for генерируются атрибуты для клиентской валидации (требуется jquery.validate, jquery.validate.unobtrusive):
<input asp-for="Email" />
<!-- → -->
<input type="email" id="Email" name="Email" value=""
data-val="true"
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid e-mail address." />
✅ Валидация:
ModelState.IsValid— проверка на сервереdata-val-*+ JS — проверка на клиенте[Required],[EmailAddress],[Range],[RegularExpression]и др. — изSystem.ComponentModel.DataAnnotations.
4.3. Кастомный тег-хелпер
[HtmlTargetElement("time-ago", Attributes = "datetime")]
public class TimeAgoTagHelper : TagHelper
{
[HtmlAttributeName("datetime")]
public DateTime DateTime { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "span";
output.Attributes.SetAttribute("title", DateTime.ToString("O"));
output.Content.SetContent(DateTime.ToTimeAgo()); // "2 hours ago"
}
}
Регистрация в _ViewImports.cshtml:
@addTagHelper *, MyWebApp
Использование:
<time-ago datetime="@item.CreatedAt"></time-ago>
5. Minimal APIs
5.1. Сравнение с контроллерами
| Критерий | Minimal APIs | Контроллеры |
|---|---|---|
| Код | Меньше boilerplate | Более структурирован |
| Тестирование | Сложнее (делегаты) | Проще (классы) |
| Фильтры | Ограниченная поддержка (через AddEndpointFilter) | Полная ([Authorize], [ValidateAntiForgeryToken]) |
| Swagger/OpenAPI | Поддерживается, но требует аннотаций | Авто-документирование |
| Сложная логика | Не рекомендуется | Подходит |
5.2. Базовый синтаксис
Код ITЗагрузка примера кода…
5.3. Продвинутые сценарии
Группировка
var api = app.MapGroup("/api")
.WithTags("v1")
.RequireAuthorization();
api.MapGet("/users", () => ...);
api.MapPost("/users", (User u) => ...);
Валидация (без ModelState)
app.MapPost("/users", (User user, HttpContext ctx) =>
{
var validator = ctx.RequestServices.GetRequiredService<IValidator<User>>();
var result = validator.Validate(user);
if (!result.IsValid)
return Results.ValidationProblem(result.ToDictionary());
return Results.Created(...);
});
Endpoint filters (аналог Action Filters)
app.MapGet("/secret", () => "OK")
.AddEndpointFilter(async (efi, next) =>
{
if (efi.HttpContext.Request.Headers["X-Secret"] != "42")
return Results.Unauthorized();
return await next(efi);
});
6. Форматирование ответов (Output Formatters)
6.1. Регистрация форматтеров
builder.Services.AddControllers(options =>
{
options.OutputFormatters.Insert(0, new XmlSerializerOutputFormatter());
// или:
options.RespectBrowserAcceptHeader = true; // читать Accept: header
});
6.2. Управление форматом ответа
| Способ | Описание |
|---|---|
Accept: application/json | Лучший способ — клиент указывает предпочтения. |
?format=json / ?format=xml | Query string (требует options.ReturnHttpNotAcceptable = false). |
[Produces("application/xml")] | Принудительно для действия/контроллера. |
Content-Type: application/json в запросе | Не влияет на ответ — только на вход ([FromBody]). |
6.3. Problem Details (RFC 7807)
Стандартный формат ошибок:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-abc-def-00",
"errors": {
"Email": ["The Email field is required."]
}
}
Включается по умолчанию в AddControllers().
Настройка:
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = ctx =>
{
var problem = new ValidationProblemDetails(ctx.ModelState)
{
Status = StatusCodes.Status400BadRequest,
Type = "https://example.com/errors/validation",
Title = "Validation failed"
};
return new BadRequestObjectResult(problem);
};
});
7. Версионирование API
7.1. Способы
| Метод | Пример | Настройка |
|---|---|---|
| Query string | /api/users?api-version=2.0 | options.AssumeDefaultVersionWhenUnspecified = true; |
| Заголовок | api-version: 2.0 | options.ApiVersionReader = new HeaderApiVersionReader("api-version"); |
| URL-путь | /api/v2/users | options.ApiVersionReader = new UrlSegmentApiVersionReader(); |
7.2. Регистрация (Microsoft.AspNetCore.Mvc.Versioning)
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true; // добавляет `api-supported-versions` в заголовки
})
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'V";
options.SubstituteApiVersionInUrl = true;
});
7.3. Контроллеры с версиями
Код ITЗагрузка примера кода…
8. OpenAPI (Swagger) через Swashbuckle.AspNetCore
8.1. Установка и базовая настройка
dotnet add package Swashbuckle.AspNetCore
Код ITЗагрузка примера кода…
8.2. Middleware в pipeline
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
c.RoutePrefix = "swagger"; // доступ по /swagger
c.DisplayRequestDuration();
c.EnableTryItOutByDefault();
});
8.3. Аннотации для улучшения документации (Swashbuckle.AspNetCore.Annotations)
[SwaggerOperation(
Summary = "Creates a new user",
Description = "Creates a user and returns the created entity with ID",
OperationId = "CreateUser")]
[SwaggerResponse(StatusCodes.Status201Created, "User created", typeof(UserDto))]
[SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid input")]
public IActionResult Create([FromBody] CreateUserRequest request) { ... }
Аутентификация, авторизация, кэширование, логирование
1. Аутентификация (IAuthenticationService)
1.1. Архитектура — схемы (AuthenticationScheme)
Ключевая идея ASP.NET Core — мульти-схемная аутентификация: приложение может поддерживать несколько независимых способов аутентификации одновременно (например, JWT для API и Cookies для админки).
| Компонент | Роль |
|---|---|
AddAuthentication(string defaultScheme) | Регистрирует IAuthenticationService. defaultScheme — схема по умолчанию для Challenge/Forbid. |
AddScheme<THandler, TOptions>(string scheme, ...) | Низкоуровневая регистрация. Обычно не используется напрямую. |
AddCookie(), AddJwtBearer(), AddOAuth() и др. | Удобные методы-обёртки для популярных схем. |
Пример — две схемы
Код ITЗагрузка примера кода…
⚠️
DefaultAuthenticateSchemeопределяет, какая схема заполняетHttpContext.User.DefaultChallengeScheme— какая схема вызывается при[Authorize]безUser.Identity.IsAuthenticated.
1.2. Встроенные схемы и их параметры
1.2.1. AddCookie()
Параметр (CookieAuthenticationOptions) | Значение по умолчанию | Описание |
|---|---|---|
LoginPath | /Account/Login | Куда редиректить при Challenge. |
AccessDeniedPath | /Account/AccessDenied | Куда редиректить при Forbid. |
LogoutPath | /Account/LogOut | Обрабатывается автоматически (очистка cookie). |
Cookie.Name | .AspNetCore.Cookies | Имя cookie. |
Cookie.Domain | null | Домен (для subdomain-аутентификации: .example.com). |
Cookie.SecurePolicy | SameAsRequest | Always (только HTTPS), None — опасно. |
Cookie.HttpOnly | true | Защита от XSS. |
Cookie.SameSite | Unspecified → Lax | Strict, Lax, None (для кросс-сайтовых запросов). |
ExpireTimeSpan | 14 дней | Полный срок жизни cookie. |
SlidingExpiration | true | Продлевать при активности. |
Events.OnValidatePrincipal | null | Кастомная валидация (например, проверка отзыва токена). |
1.2.2. AddJwtBearer()
Параметр (JwtBearerOptions) | Описание |
|---|---|
Authority | URL OpenID Connect провайдера (автоматически загружает конфигурацию .well-known/openid-configuration). |
Audience | Значение aud в токене. |
TokenValidationParameters | Тонкая настройка валидации (ключ, issuer, lifetime, clock skew). |
RequireHttpsMetadata | true в продакшене (запрещает HTTP-токены). |
Events.OnTokenValidated | Доп. обработка после валидации (например, маппинг claims). |
Events.OnAuthenticationFailed | Обработка ошибок (логирование, кастомный ответ). |
1.2.3. AddOAuth() / AddOpenIdConnect()
| Параметр | Описание |
|---|---|
ClientId, ClientSecret | От провайдера (Google, GitHub и др.). |
AuthorizationEndpoint, TokenEndpoint, UserInformationEndpoint | URL’ы провайдера (часто подставляются автоматически). |
Scope | openid profile email для OIDC. |
SaveTokens | Сохранять access_token/refresh_token в AuthenticationProperties. |
ClaimActions | Фильтрация/преобразование claims (например, MapJsonKey(ClaimTypes.Name, "name")). |
Events.OnCreatingTicket | Обогащение ClaimsIdentity после получения данных. |
✅ Пример для Google:
.AddGoogle(options =>{options.ClientId = "xxx.apps.googleusercontent.com";options.ClientSecret = "GOCSPX-xxx";options.Scope.Add("email");options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");});
1.3. Управление сессией в коде
Метод (HttpContext) | Описание |
|---|---|
HttpContext.SignInAsync("Cookies", principal, properties) | Установить аутентификационную cookie. properties — AuthenticationProperties (IsPersistent, ExpiresUtc). |
HttpContext.SignOutAsync("Cookies") | Очистить cookie. |
HttpContext.AuthenticateAsync("Bearer") | Принудительно выполнить аутентификацию по схеме (например, в middleware). |
HttpContext.ChallengeAsync("oidc") | Инициировать вход (редирект на провайдера). |
HttpContext.ForbidAsync() | Вернуть 403 (или редирект на AccessDeniedPath). |
Пример контроллера входа
Код ITЗагрузка примера кода…
2. Авторизация (IAuthorizationService)
2.1. Атрибуты и базовые политики
| Атрибут | Описание |
|---|---|
[Authorize] | Требует аутентификации. |
[Authorize(Roles = "Admin,Manager")] | Требует роль (проверяет ClaimTypes.Role). |
[Authorize(Policy = "RequireAdmin")] | Требует выполнения политики. |
[AllowAnonymous] | Отключает авторизацию для действия/контроллера. |
2.2. Политики — IAuthorizationRequirement и IAuthorizationHandler
Шаг 1 — Требование
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int age) => MinimumAge = age;
}
Шаг 2 — Обработчик
Код ITЗагрузка примера кода…
Шаг 3 — Регистрация
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
Шаг 4 — Использование
[Authorize(Policy = "AtLeast21")]
public IActionResult Alcohol() => Ok("Here's your beer");
2.3. Продвинутые политики
| Метод политики | Эффект |
|---|---|
RequireAuthenticatedUser() | Аналог [Authorize]. |
RequireRole("Admin") | Аналог Roles = "Admin". |
RequireClaim("Department", "HR") | Наличие claim с конкретным значением. |
RequireAssertion(ctx => ctx.User.HasClaim(...)) | Произвольная логика. |
RequireUserName("timur") | По ClaimTypes.Name. |
AddAuthenticationSchemes("Bearer") | Ограничивает политику конкретной схемой. |
Пример — комбинированная политика
options.AddPolicy("HRManager", policy =>
{
policy.RequireRole("HR")
.RequireClaim("Level", "Manager")
.RequireAssertion(ctx => ctx.User.HasClaim("Clearance", "TopSecret"));
});
2.4. ASP.NET Core Identity
2.4.1. Базовые сервисы
| Сервис | Интерфейс | Основные методы |
|---|---|---|
| User Management | UserManager<TUser> | CreateAsync, DeleteAsync, FindByEmailAsync, AddToRoleAsync, GetRolesAsync, GetClaimsAsync, AddClaimAsync |
| Sign-in | SignInManager<TUser> | PasswordSignInAsync, SignInAsync, SignOutAsync, RefreshSignInAsync, IsSignedIn |
| Role Management | RoleManager<TRole> | CreateAsync, DeleteAsync, FindByNameAsync, AddClaimAsync |
2.4.2. Настройка Identity
Код ITЗагрузка примера кода…
2.4.3. Кастомные claim’ы после входа
// В Startup или через событие
services.ConfigureApplicationCookie(options =>
{
options.Events.OnSignedIn = async ctx =>
{
var userManager = ctx.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();
var user = await userManager.GetUserAsync(ctx.Principal);
var claims = await userManager.GetClaimsAsync(user);
var appIdentity = new ClaimsIdentity(claims, "Application");
ctx.Principal.AddIdentity(appIdentity);
};
});
3. Кэширование
3.1. ResponseCache (HTTP-кэширование)
Атрибут [ResponseCache]
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any, VaryByQueryKeys = new[] { "id" })]
public IActionResult GetProduct(int id) => Ok(...);
| Параметр | HTTP-заголовок | Описание |
|---|---|---|
Duration | Cache-Control: public, max-age=60 | Сколько кэшировать (сек). |
Location | Cache-Control: public/private/no-cache | Где: Any (CDN, браузер), Client (только браузер), None. |
NoStore | Cache-Control: no-store | Полный запрет кэширования. |
VaryByHeader | Vary: User-Agent | Разные версии кэша по заголовкам. |
VaryByQueryKeys | Vary: Accept-Encoding + внутренняя логика | По query-параметрам. |
⚠️ Не работает с авторизованными запросами по умолчанию (из-за
privateвCache-Control). Нужно явно указатьLocation = ResponseCacheLocation.Any.
3.2. IMemoryCache
Регистрация
builder.Services.AddMemoryCache(options =>
{
options.SizeLimit = 1024; // в условных единицах
});
Использование
Код ITЗагрузка примера кода…
Параметр ICacheEntry | Описание |
|---|---|
AbsoluteExpiration | Точная дата истечения. |
AbsoluteExpirationRelativeToNow | Относительно текущего времени. |
SlidingExpiration | Продлевать при доступе. |
Priority | Low, Normal, High, NeverRemove. |
Size | Для учёта SizeLimit. |
PostEvictionCallbacks | Обратный вызов при удалении (например, логирование). |
3.3. IDistributedCache (Redis, SQL Server)
Регистрация Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
options.InstanceName = "MyApp_";
});
Интерфейс
public interface IDistributedCache
{
byte[] Get(string key);
Task<byte[]> GetAsync(string key, CancellationToken token = default);
void Set(string key, byte[] value, DistributedCacheEntryOptions options);
Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default);
void Refresh(string key); // обновить sliding expiration
Task RefreshAsync(string key, CancellationToken token = default);
void Remove(string key);
Task RemoveAsync(string key, CancellationToken token = default);
}
Помощники для работы с объектами
Код ITЗагрузка примера кода…
3.4. Тег-хелпер <cache>
<cache expires-after="TimeSpan.FromMinutes(10)" vary-by-user="true">
<p>Last updated: @DateTime.Now</p>
</cache>
| Атрибут | Описание |
|---|---|
expires-on | DateTimeOffset — абсолютное время. |
expires-after | TimeSpan — относительно сейчас. |
expires-sliding | TimeSpan — sliding expiration. |
vary-by-user | Разные версии для каждого пользователя. |
vary-by-query | ?sort=asc → разные кэши. |
vary-by-route | По параметрам маршрута (id). |
vary-by-header | По заголовкам (Accept-Language). |
vary-by-cookie | По cookie (theme=dark). |
✅ Хранится в
IMemoryCacheпо умолчанию. Для распределённого кэша — кастомныйICacheTagHelperProvider.
4. Логирование (ILogger<T>)
4.1. Уровни логирования (от низкого к высокому)
| Уровень | Когда использовать |
|---|---|
Trace | Детальные данные (например, каждый шаг в алгоритме). Только в разработке. |
Debug | Отладочная информация (SQL-запросы, внутренние состояния). |
Information | Основные события ("User logged in", "Order created"). |
Warning | Нестандартная, но обработанная ситуация ("Retry #1 failed"). |
Error | Ошибка, не приводящая к падению ("Database timeout"). |
Critical | Катастрофическая ошибка ("Disk full", "Config missing"). |
4.2. Конфигурация уровней
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"MyApp.Services": "Debug"
}
}
}
Или программно:
builder.Logging.ClearProviders()
.AddConsole()
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("MyApp", LogLevel.Debug);
4.3. Использование ILogger<T>
Код ITЗагрузка примера кода…
✅ Шаблон
{PropertyName}— для структурированного логирования (Serilog, Application Insights).
4.4. Scopes
Группировка логов по контексту (например, по HTTP-запросу или ID операции).
using (_logger.BeginScope("OrderId: {OrderId}", order.Id))
{
_logger.LogInformation("Starting validation");
Validate(order);
_logger.LogInformation("Saving to DB");
Save(order);
}
// Все логи внутри будут содержать {OrderId}
Встроенные scopes:
Microsoft.AspNetCore.Hosting.Diagnostics:RequestPath,RequestIdMicrosoft.EntityFrameworkCore.Database.Command:CommandId,ConnectionId
4.5. Провайдеры логирования
| Провайдер | Пакет | Особенности |
|---|---|---|
| Console | Встроен | Цвета, JSON-режим ("Console": { "FormatterName": "json" }). |
| Debug | Встроен | Вывод в окно "Вывод" Visual Studio. |
| EventSource | Встроен | ETW-трассировка (для dotnet-trace, PerfView). |
| EventLog | Microsoft.Extensions.Logging.EventLog | Только Windows. |
| Serilog | Serilog.AspNetCore | Структурированный лог, обогащение, отправка в Seq/ELK. |
| Application Insights | Microsoft.ApplicationInsights.AspNetCore | Интеграция с Azure Monitor. |
Пример Serilog
builder.Host.UseSerilog((ctx, logger) =>
{
logger.WriteTo.Console()
.WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day)
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", "MyApp")
.ReadFrom.Configuration(ctx.Configuration);
});
SignalR, gRPC, Health Checks, Тестирование
1. SignalR
1.1. Архитектура
SignalR обеспечивает real-time двустороннюю связь между сервером и клиентами. Поддерживает fallback-транспорты:
| Транспорт | Поддержка | Характеристики |
|---|---|---|
| WebSockets | Все современные браузеры и серверы | Полный дуплекс, низкая задержка. Предпочтительный. |
| Server-Sent Events (SSE) | Все кроме IE | Только сервер → клиент. |
| Long Polling | Универсальный | Высокая задержка, высокое потребление ресурсов. |
✅ SignalR автоматически выбирает лучший доступный транспорт.
1.2. Хабы (Hubs)
Хаб — серверный класс, наследуемый от Hub или Hub<T> (строго типизированный).
Код ITЗагрузка примера кода…
Строго типизированный хаб
public interface IChatClient
{
Task ReceiveMessage(string user, string message);
Task Ack(string status);
}
public class TypedChatHub : Hub<IChatClient>
{
public async Task SendMessage(string user, string message)
{
await Clients.All.ReceiveMessage(user, message);
}
}
1.3. Регистрация и маршрутизация
builder.Services.AddSignalR(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
options.MaximumReceiveMessageSize = 32 * 1024; // 32 KB
});
var app = builder.Build();
app.MapHub<ChatHub>("/chat");
⚠️
MapHubдолжен идти послеUseRouting()и доUseEndpoints().
1.4. Авторизация
// В хабе:
[Authorize]
public class SecureChatHub : Hub { ... }
// Или на методе:
public class ChatHub : Hub
{
[Authorize(Roles = "Admin")]
public Task AdminCommand(string cmd) { ... }
}
Кастомный IUserIdProvider
public class CustomUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
}
builder.Services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
Теперь можно отправлять клиенту по ID:
await Clients.User("user123").SendAsync("PrivateMessage", message);
1.5. Масштабирование (Scale-out)
Для работы в кластере требуется backplane — Redis, Azure SignalR Service или SQL Server.
Redis backplane
dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedis
builder.Services.AddSignalR()
.AddStackExchangeRedis(options =>
{
options.Configuration = "localhost:6379";
options.ChannelPrefix = "MyApp_SignalR";
});
✅ Все серверы подписываются на один Redis-канал. Сообщения реплицируются автоматически.
1.6. Streaming
Серверный стриминг
public async IAsyncEnumerable<int> Counter(int count, int delay)
{
for (int i = 1; i <= count; i++)
{
await Task.Delay(delay);
yield return i;
}
}
Клиент (JavaScript):
const stream = connection.stream("Counter", 10, 1000);
for await (const item of stream) {
console.log(item);
}
Двусторонний стриминг
public async Task UploadStream(IAsyncEnumerable<Chunk> stream)
{
await foreach (var chunk in stream)
{
// обработка чанка
}
}
2. gRPC
2.1. Базовая настройка
.proto файл (Protos/greet.proto)
Код ITЗагрузка примера кода…
Генерация кода (в .csproj)
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>
Сервис
Код ITЗагрузка примера кода…
Регистрация
builder.Services.AddGrpc(options =>
{
options.EnableDetailedErrors = builder.Environment.IsDevelopment();
options.MaxReceiveMessageSize = 4 * 1024 * 1024; // 4 MB
options.MaxSendMessageSize = 4 * 1024 * 1024;
});
app.MapGrpcService<GreeterService>();
2.2. Клиент
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "Timur" });
Console.WriteLine(reply.Message);
2.3. Interceptor’ы
Логирование
Код ITЗагрузка примера кода…
2.4. JSON Transcoding
Позволяет вызывать gRPC-методы через HTTP/JSON (REST-like).
Шаг 1 — Аннотации в .proto
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
get: "/v1/greet/{name}"
};
}
}
Шаг 2 — Включение
builder.Services.AddGrpc()
.AddJsonTranscoding(options =>
{
options.JsonSettings = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
});
Шаг 3 — Маршрутизация
app.MapGrpcService<GreeterService>();
// Теперь GET /v1/greet/Timur → SayHello("Timur")
⚠️ Требует
Google.Api.CommonProtosиMicrosoft.AspNetCore.Grpc.JsonTranscoding.
2.5. Валидация
Protobuf не поддерживает валидацию "из коробки". Используйте:
- Атрибуты
[Required],[Range]в сгенерированных классах (ручное редактирование — неустойчиво) - Или кастомный interceptor с FluentValidation:
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(...)
{
var validator = context.GetService<IValidator<TRequest>>();
if (validator != null)
{
var result = await validator.ValidateAsync(request);
if (!result.IsValid)
throw new RpcException(new Status(StatusCode.InvalidArgument, string.Join("; ", result.Errors.Select(e => e.ErrorMessage))));
}
return await continuation(request, context);
}
3. Health Checks
3.1. Базовая настройка
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy("Service is up"))
.AddSqlServer(connectionString, name: "database")
.AddRedis("localhost:6379", name: "redis")
.AddUrlGroup(new Uri("https://api.example.com/health"), name: "external-api");
Endpoint
Код ITЗагрузка примера кода…
3.2. Кастомные проверки
Код ITЗагрузка примера кода…
3.3. Readiness и Liveness
- Liveness — "жив ли процесс?" (не завис ли).
- Readiness — "готов ли принимать трафик?" (миграции БД не завершены?).
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
// Регистрация с тегами:
services.AddHealthChecks()
.AddCheck("self", () => ..., tags: new[] { "live", "ready" })
.AddCheck("migration", () => ..., tags: new[] { "ready" });
3.4. Health Checks UI
Пакет AspNetCore.HealthChecks.UI:
builder.Services.AddHealthChecksUI()
.AddInMemoryStorage();
app.MapHealthChecksUI();
// Доступен по /healthchecks-ui
4. Тестирование
4.1. Интеграционные тесты (WebApplicationFactory<T>)
Базовый класс
Код ITЗагрузка примера кода…
Тест
Код ITЗагрузка примера кода…
4.2. Unit-тесты контроллеров
Код ITЗагрузка примера кода…
4.3. Тестирование middleware
Код ITЗагрузка примера кода…
4.4. Проверка маршрутов
[Fact]
public void ProductsController_GetProduct_HasRoute()
{
// Arrange
var action = typeof(ProductsController).GetMethod(nameof(ProductsController.GetProduct));
var attributes = action!.GetCustomAttributes(typeof(HttpGetAttribute), false);
// Assert
Assert.Single(attributes);
var route = ((HttpGetAttribute)attributes[0]).Template;
Assert.Equal("api/products/{id}", route);
}
4.5. Проверка DI-регистраций
Код ITЗагрузка примера кода…
Локализация и интернационализация, Фоновые задачи
1. Локализация и интернационализация
1.1. Основные интерфейсы и сервисы
| Компонент | Описание |
|---|---|
IStringLocalizer<T> | Локализатор строк по ключу (обычно T = класс или SharedResource). |
IStringLocalizer | Без типа — требует явного указания ресурса. |
IHtmlLocalizer<T> | Аналог IStringLocalizer, но возвращает HtmlString (без экранирования HTML). |
IViewLocalizer | Локализатор для представлений (инжектится в Razor). |
ResourceManagerStringLocalizerFactory | Реализация по умолчанию (работает с .resx-файлами). |
1.2. Ресурсы (.resx)
Структура проекта
/Resources
├── SharedResource.cs // маркер-класс (пустой)
├── SharedResource.ru.resx // русский
├── SharedResource.en.resx // английский
└── Controllers
└── HomeController.ru.resx
└── HomeController.en.resx
⚠️ Файлы должны быть:
Build Action = Embedded ResourceCustom Tool = ResXFileCodeGenerator(опционально, для типизированных классов)
Пример SharedResource.en.resx
| Name | Value |
|---|---|
| WelcomeMessage | Welcome, 0! |
| ValidationError.Required | The 0 field is required. |
1.3. Использование в коде
Код ITЗагрузка примера кода…
В представлении (Razor)
@inject IViewLocalizer Localizer
<h1>@Localizer["WelcomeMessage", User.Identity.Name]</h1>
<span class="text-danger">@Localizer["ValidationError.Required", "Email"]</span>
✅
_ViewImports.cshtml:@using Microsoft.AspNetCore.Mvc.Localization@inject IViewLocalizer Localizer
1.4. RequestLocalizationMiddleware
Контекстно-зависимое определение культуры на основе запроса.
Регистрация
Код ITЗагрузка примера кода…
Провайдеры
| Провайдер | Источник | Пример |
|---|---|---|
QueryStringRequestCultureProvider | ?Культура=ru&ui-Культура=ru | Явное указание в URL |
CookieRequestCultureProvider | Cookie ".AspNetCore.Культура" | c=ru|uic=ru |
AcceptLanguageHeaderRequestCultureProvider | Заголовок Accept-Language: ru-RU,ru;q=0.9,en;q=0.8 | Браузерный язык |
RouteDataRequestCultureProvider | Параметр маршрута {Культура=en} | /ru/products |
Кастомный провайдер (например, по домену)
Код ITЗагрузка примера кода…
Регистрация:
localizationOptions.RequestCultureProviders.Insert(0, new DomainCultureProvider());
1.5. Псевдолокализация
Для тестирования layout’а без перевода.
Код ITЗагрузка примера кода…
1.6. Pluralization
Для языков с разным количеством форм множественного числа (например, русский — 1 товар, 2 товара, 5 товаров).
Подход 1 — Через IStringLocalizer и ключи
<!-- SharedResource.ru.resx -->
Item_Count_Singular = {0} товар
Item_Count_Few = {0} товара
Item_Count_Many = {0} товаров
string GetMessage(int count)
{
return count switch
{
1 => _localizer["Item_Count_Singular", count],
int n when n % 10 is 2 or 3 or 4 && (n % 100 < 10 || n % 100 > 20) => _localizer["Item_Count_Few", count],
_ => _localizer["Item_Count_Many", count]
};
}
Подход 2 — Microsoft.Extensions.Localization.Pluralization (ограниченная поддержка)
Требует сторонней библиотеки (OrchardCore.Localization.Core или Humanizer).
Подход 3 — ICU (International Components for Unicode) через Microsoft.ICU
.NET 5+ поддерживает ICU на всех платформах. Можно использовать PluralRules:
using System.Globalization;
var rules = PluralRules.Create("ru");
var category = rules.GetCategory(5); // → PluralCategory.Many
⚠️ Пока не интегрирован "из коробки" в ASP.NET Core. Требует ручной обвязки.
2. Фоновые задачи
2.1. IHostedService и BackgroundService
Интерфейс IHostedService
public interface IHostedService
{
Task StartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
Абстрактный класс BackgroundService
public abstract class BackgroundService : IHostedService
{
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
}
Пример — периодическая задача
Код ITЗагрузка примера кода…
Регистрация
builder.Services.AddHostedService<TimerHostedService>();
✅ Всегда используйте
IServiceScopeFactory, чтобы избежать утечек scoped-зависимостей.
2.2. Отчёт о прогрессе (IProgress<T>)
Для задач с длительным выполнением и UI-обратной связью.
Код ITЗагрузка примера кода…
Использование (например, в MVC-действии с SSE или SignalR):
Код ITЗагрузка примера кода…
2.3. Quartz.NET
Для сложного планирования (cron, интервалы, распределённое выполнение).
Установка
dotnet add package Quartz
dotnet add package Quartz.Extensions.DependencyInjection
dotnet add package Quartz.Extensions.Hosting
Job
Код ITЗагрузка примера кода…
Регистрация и настройка
Код ITЗагрузка примера кода…
Расшифровка cron-выражений (Quartz-формат)
| Поле | Допустимые значения | Примеры |
|---|---|---|
| Секунды | 0–59 | 0, */10 |
| Минуты | 0–59 | 0, 30 |
| Часы | 0–23 | 2, 8-18 |
| День месяца | 1–31 | 1, L (последний), 15W (ближайший будний к 15-му) |
| Месяц | 1–12 или JAN–DEC | *, JUL |
| День недели | 1–7 или SUN–SAT | ? (не указан), MON-FRI, 1L (последний понедельник) |
| Год (опционально) | 1970–2099 | 2025 |
✅ Онлайн-валидатор: https://www.freeformatter.com/cron-expression-generator-quartz.html
2.4. Distributed Locking (для кластера)
Чтобы задача не запускалась на всех узлах одновременно.
Через Redis (Redlock.net)
Код ITЗагрузка примера кода…
Официальная документация (Microsoft Learn)
Полный навигатор с серией MVC и Docker: Документация и практика ASP.NET (Microsoft Learn). Ниже — быстрые ссылки для работы со справочником.
Хаб ASP.NET Core 10
| Раздел | URL |
|---|---|
| Документация (RU) | aspnet/core |
| Fundamentals | fundamentals |
| MVC overview | mvc/overview |
| Routing | mvc/controllers/routing |
| Razor syntax | mvc/views/razor |
| Минимальные API | fundamentals/apis |
| OpenAPI / Swagger | web-api-help-pages-using-swagger |
| Docker (Core) | host-and-deploy/docker |
| API browser | dotnet/api |
Серия Razor Pages RazorPagesMovie (практика)
Оглавление: tutorials/razor-pages — проект RazorPagesMovie, 8 частей (model, scaffold, SQL, search, validation). Рекомендуемый старт для новичков; таблица в Документация и практика ASP.NET (Microsoft Learn).
Серия MVC MvcMovie (практика)
Старт: first-mvc-app/start-mvc — проект MvcMovie, .NET 10, далее controller/views, EF, search, validation (таблица частей в Документация и практика ASP.NET (Microsoft Learn)).
Legacy — ASP.NET MVC в Docker (Framework)
Для .NET Framework + IIS, не Core: docker-aspnetmvc. Образ mcr.microsoft.com/dotnet/framework/aspnet:4.8, публикация в PublishOutput, контейнеры Windows.
Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.
В подборках
Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:
Справочники — Справочник по LINQ, Справочник по C#, Справочник по конфигурациям в C#, Справочник по C++, Справочник по F#, Справочник по PHP.