Перейти к основному содержимому

Справочник по 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}.jsone.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 Handlerapp.UseExceptionHandler("/error")Перехват необработанных исключений.ExceptionHandlerOptions: ExceptionHandlingPath, AllowStatusCode404Response.
HTTPS Redirectionapp.UseHttpsRedirection()307 → HTTPS (если исходный запрос HTTP).HttpsRedirectionOptions: SslPort (по умолчанию 443).
Static Filesapp.UseStaticFiles()Обслуживание wwwroot.StaticFileOptions: RequestPath, FileProvider, ServeUnknownFileTypes, OnPrepareResponse.
Routingapp.UseRouting()Инициализация endpoint routing. Должен идти ДО UseAuthorization, UseEndpoints.
Authenticationapp.UseAuthentication()Выполнение схем аутентификации (устанавливает HttpContext.User).
Authorizationapp.UseAuthorization()Проверка политик доступа. Должен идти ПОСЛЕ UseAuthentication, но ДО UseEndpoints.
Sessionapp.UseSession()Управление сессиями. Должен идти ПОСЛЕ UseRouting, но ДО UseAuthorization.SessionOptions: IdleTimeout, Cookie, IOTimeout.
CORSapp.UseCors("PolicyName")Кросс-доменные запросы.CorsPolicyBuilder: WithOrigins, AllowAnyOrigin, WithMethods, WithHeaders, AllowCredentials, SetIsOriginAllowed.
Response Cachingapp.UseResponseCaching()Кэширование ответов (на уровне middleware).ResponseCachingOptions: SizeLimit, UseCaseSensitivePaths.
Response Compressionapp.UseResponseCompression()Gzip/Brotli сжатие.ResponseCompressionOptions: Providers, EnableForHttps, MimeTypes.
Endpointsapp.UseEndpoints(...) / app.Map...Регистрация маршрутов. В minimal model — app.MapGet, app.MapControllers и т.д.

✅ Правильный порядок:
UseExceptionHandlerUseHttpsRedirectionUseStaticFilesUseRoutingUseAuthenticationUseAuthorizationUseSessionUseCorsUseResponseCachingUseResponseCompressionUseEndpoints / 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 APIapp.MapGet("/api/users", ...)app.MapPost("/items", (Item i) => Results.Created($"/items/{i.Id}", i));
Controllersapp.MapControllers()Требует AddControllers() / AddControllersWithViews().
Razor Pagesapp.MapRazorPages()Требует AddRazorPages().
SignalR Hubsapp.MapHub<ChatHub>("/chat")
gRPC Servicesapp.MapGrpcService<GreeterService>()
Health Checksapp.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 — архитектура и различия

КритерийMVCRazor 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, ViewBagModelState, TempData, PageContext
Рекомендация MicrosoftДля сложных SPA/REST APIДля page-centric приложений (CMS, админки, формы)

✅ Оба используют один и тот же движок представлений (Razor), DI, middleware, авторизацию.


2. Контроллеры и действия (Actions)

2.1. Базовые классы и интерфейсы
ТипИнтерфейсНаследованиеПримечание
ControllerBaseIActionResultControllerBaseМинимальный базовый класс (для API). Не имеет View().
ControllerControllerBaseControllerРасширяет ControllerBase: добавляет View(), PartialView(), ViewData, TempData. Для MVC с представлениями.

2.2. Типы возвращаемых значений действий
ТипМетод-помощникHTTP-кодСериализация
IActionResultOk(), 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=xmlQuery 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.0options.AssumeDefaultVersionWhenUnspecified = true;
Заголовокapi-version: 2.0options.ApiVersionReader = new HeaderApiVersionReader("api-version");
URL-путь/api/v2/usersoptions.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.DomainnullДомен (для subdomain-аутентификации: .example.com).
Cookie.SecurePolicySameAsRequestAlways (только HTTPS), None — опасно.
Cookie.HttpOnlytrueЗащита от XSS.
Cookie.SameSiteUnspecifiedLaxStrict, Lax, None (для кросс-сайтовых запросов).
ExpireTimeSpan14 днейПолный срок жизни cookie.
SlidingExpirationtrueПродлевать при активности.
Events.OnValidatePrincipalnullКастомная валидация (например, проверка отзыва токена).

1.2.2. AddJwtBearer()
Параметр (JwtBearerOptions)Описание
AuthorityURL OpenID Connect провайдера (автоматически загружает конфигурацию .well-known/openid-configuration).
AudienceЗначение aud в токене.
TokenValidationParametersТонкая настройка валидации (ключ, issuer, lifetime, clock skew).
RequireHttpsMetadatatrue в продакшене (запрещает HTTP-токены).
Events.OnTokenValidatedДоп. обработка после валидации (например, маппинг claims).
Events.OnAuthenticationFailedОбработка ошибок (логирование, кастомный ответ).

1.2.3. AddOAuth() / AddOpenIdConnect()
ПараметрОписание
ClientId, ClientSecretОт провайдера (Google, GitHub и др.).
AuthorizationEndpoint, TokenEndpoint, UserInformationEndpointURL’ы провайдера (часто подставляются автоматически).
Scopeopenid 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. propertiesAuthenticationProperties (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 ManagementUserManager<TUser>CreateAsync, DeleteAsync, FindByEmailAsync, AddToRoleAsync, GetRolesAsync, GetClaimsAsync, AddClaimAsync
Sign-inSignInManager<TUser>PasswordSignInAsync, SignInAsync, SignOutAsync, RefreshSignInAsync, IsSignedIn
Role ManagementRoleManager<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-заголовокОписание
DurationCache-Control: public, max-age=60Сколько кэшировать (сек).
LocationCache-Control: public/private/no-cacheГде: Any (CDN, браузер), Client (только браузер), None.
NoStoreCache-Control: no-storeПолный запрет кэширования.
VaryByHeaderVary: User-AgentРазные версии кэша по заголовкам.
VaryByQueryKeysVary: Accept-Encoding + внутренняя логикаПо query-параметрам.

⚠️ Не работает с авторизованными запросами по умолчанию (из-за private в Cache-Control). Нужно явно указать Location = ResponseCacheLocation.Any.


3.2. IMemoryCache
Регистрация
builder.Services.AddMemoryCache(options =>
{
options.SizeLimit = 1024; // в условных единицах
});

Использование

Код ITЗагрузка примера кода…

Параметр ICacheEntryОписание
AbsoluteExpirationТочная дата истечения.
AbsoluteExpirationRelativeToNowОтносительно текущего времени.
SlidingExpirationПродлевать при доступе.
PriorityLow, 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-onDateTimeOffset — абсолютное время.
expires-afterTimeSpan — относительно сейчас.
expires-slidingTimeSpan — 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, RequestId
  • Microsoft.EntityFrameworkCore.Database.Command: CommandId, ConnectionId

4.5. Провайдеры логирования
ПровайдерПакетОсобенности
ConsoleВстроенЦвета, JSON-режим ("Console": { "FormatterName": "json" }).
DebugВстроенВывод в окно "Вывод" Visual Studio.
EventSourceВстроенETW-трассировка (для dotnet-trace, PerfView).
EventLogMicrosoft.Extensions.Logging.EventLogТолько Windows.
SerilogSerilog.AspNetCoreСтруктурированный лог, обогащение, отправка в Seq/ELK.
Application InsightsMicrosoft.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 Resource
  • Custom Tool = ResXFileCodeGenerator (опционально, для типизированных классов)

Пример SharedResource.en.resx
NameValue
WelcomeMessageWelcome, 0!
ValidationError.RequiredThe 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
CookieRequestCultureProviderCookie ".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–590, */10
Минуты0–590, 30
Часы0–232, 8-18
День месяца1–311, L (последний), 15W (ближайший будний к 15-му)
Месяц1–12 или JAN–DEC*, JUL
День недели1–7 или SUN–SAT? (не указан), MON-FRI, 1L (последний понедельник)
Год (опционально)1970–20992025

✅ Онлайн-валидатор: 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
Fundamentalsfundamentals
MVC overviewmvc/overview
Routingmvc/controllers/routing
Razor syntaxmvc/views/razor
Минимальные APIfundamentals/apis
OpenAPI / Swaggerweb-api-help-pages-using-swagger
Docker (Core)host-and-deploy/docker
API browserdotnet/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.