08

Lab 08 - Web API, DTOs, Swagger si JWT Authentication

Partea 5 din 6

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.LoginPasswordSignInAsync → cookie setat → redirect.

API Login cu JWT: nou — AuthApiController.LoginCheckPasswordSignInAsync → 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.WhenAll merge doar dacă DbContext-ul nu e partajat între task-uri. EF Core nu permite două query-uri concurente pe același DbContext — dar IArticleService și ICategoryService folosesc 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ă), IHostedService e consumer (îl execută). În .NET — Channel<T>, BlockingCollection<T> sau mesaje externe (RabbitMQ, Azure Service Bus).