Parte 5 — Dual Auth Scheme și Async Patterns
Pasul 9 — Dual Auth Scheme (Cookie + JWT)
Ce avem acum, la nivel de Program.cs:
AddIdentity → schema default Cookies + UserManager + SignInManager + RoleManager
AddJwtBearer → adaugă schema JWT (nu înlocuiește nimic)
Rezultatul:
| Rută | Schema | Cum decide serverul |
|---|---|---|
/Articles/* (MVC) |
Cookie | [Authorize] fără argument → default scheme (Cookies) |
/api/* (API) |
JWT | [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] |
MVC Login cu cookie: rămâne din Lab 7 — AuthController.Login → PasswordSignInAsync → cookie setat → redirect.
API Login cu JWT: nou — AuthApiController.Login → CheckPasswordSignInAsync → token generat → returnat în body.
Swagger „Authorize" button
Ca să puteți testa endpoint-uri cu [Authorize] direct din Swagger UI, adăugați în AddSwaggerGen:
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "News Portal API",
Version = "v1"
});
options.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Name = "Authorization",
Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
Description = "Introduceti token-ul JWT (fara prefixul 'Bearer')."
});
options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
{
{
new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Reference = new Microsoft.OpenApi.Models.OpenApiReference
{
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
Acum, în Swagger UI apare un buton „Authorize" — lipiți token-ul, și toate cererile ulterioare îl trimit automat în header.
Pasul 10 — Advanced Async Patterns (introducere)
Task.WhenAll — paralelism între query-uri independente
Până acum, în HomeController.Index am făcut ceva de genul:
var categories = await _categoryService.GetAllAsync(ct);
var count = await _articleService.CountAsync(ct);
Cele două query-uri sunt independente. Nu au motiv să aștepte unul pe altul — dar codul de mai sus le rulează serial. Dacă fiecare durează 100ms, totalul e 200ms.
Varianta paralelă:
var categoriesTask = _categoryService.GetAllAsync(ct);
var countTask = _articleService.CountAsync(ct);
await Task.WhenAll(categoriesTask, countTask);
var categories = await categoriesTask;
var count = await countTask;
Acum ambele query-uri pleacă la BD în paralel. Totalul devine max(100, 100) = 100ms, nu 100 + 100 = 200ms. Economisim timpul celui mai lent task.
Atenție:
Task.WhenAllmerge doar dacă DbContext-ul nu e partajat între task-uri. EF Core nu permite două query-uri concurente pe acelașiDbContext— darIArticleServiceșiICategoryServicefolosesc fiecare propria instanță prin DI Scoped, deci în context de request HTTP e ok dacă folosiți context-uri separate. În caz de dubii — folosiți-l cu HttpClient, nu cu EF.
IHostedService — background tasks
Pentru operații care rulează în fundal, nu în contextul unui request HTTP — exemple: curățare fișiere temporare, trimitere emailuri din coadă, sincronizare periodică.
public class CleanupHostedService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// curățare articole vechi, sync, etc.
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
}
}
}
// în Program.cs:
builder.Services.AddHostedService<CleanupHostedService>();
Nu implementăm acum — e un concept pentru mai târziu. Dar merită știut că există și că nu trebuie să folosiți fire-and-forget:
// GREȘIT — pierdeți excepțiile, nu știți dacă a reușit:
_ = SendEmailAsync(user);
// CORECT — coadă + IHostedService, sau cel puțin await cu try/catch.
Producer-Consumer în web: requestul HTTP e producer (pune task în coadă),
IHostedServicee consumer (îl execută). În .NET —Channel<T>,BlockingCollection<T>sau mesaje externe (RabbitMQ, Azure Service Bus).