Az API implementálását a végpontok és visszatérési kódok meghatározása után az adatok definiálásával szeretem folytatni, hogy melyik végponton milyen adatok lesznek elérhetÅ‘ek. Ezekhez adatátviteli objektumokat definiálunk. Ezek az objektumok lesznek majd JSON-be alakÃtva az átküldéshez, hogy a rendszerünk többi részével együttműködjenek. Közvetlenül azért nem a domain modell osztályainkat rakjuk ki a végpontra, hogy függetlenek maradjunk az üzleti logikától. Az üzleti logika metódusainak nem kell a HTTP-re reflektálna, mivel az adatátvitel és a működés két külön dolog.
Ha külön adatátviteli objektumokat, úgynevezett DTO-kat definiáltunk, akkor egy nagyobb objektumot visszaadó publikus API kiszolgálása történhet több üzleti logika metódusból és tÃpusból, amit függetlenül módosÃthatunk késÅ‘bb anélkül, hogy a külvilág számára eltörnénk bármit is. Ezeket az osztályokat egy külön névtérben szokás tárolni.
namespace UrlShortner.Web.Dto;
public class UrlResponseDto
{
public required string ShortUrl { get; init; }
public required string LongUrl { get; init; }
public required DateTime? ValidTill { get; init; }
public required DateTime CreatedAt { get; init; }
public required long ClickCount { get; init; }
}
public class CreateUrlDto
{
public required string LongUrl { get; set; }
public DateTime? ValidTill { get; set; }
}
public class UpdateUrlDto
{
public DateTime? ValidTill { get; set; }
}
Mappelés
Ezen DTO objektumok és az üzleti logika tÃpusai között kelleni fog egy konverziós réteg, egy mapper. Ezt megÃrhatjuk kézzel is. Jelen API alkalmazásban teljesen helytálló lenne, mivel három osztályról beszélünk, amibÅ‘l kettÅ‘t (CreateUrlDto és UpdateUrlDto) nem is nagyon kell konvertálgatnunk, mivel az üzleti logikánknak csak a bennük tárolt tulajdonságokra van szüksége. Az egyetlenegy tÃpus, ahol foglalkoznunk kell konverzióval, az a UrlResponseDto és UrlModel esete.
A kézi megÃráson kÃvül alkalmazhatunk valami automatikus konverziós megoldást. Ez implementációtól függÅ‘en történhet reflection vagy kód generálás segÃtségével. A reflection alapú megoldások közül az egyik legnépszerűbb az AutoMapper. Ez jó pár éve létezik és gyakorlatilag nem nincs olyan szituáció, amit ne támogatna. A forráskód generálás alapú megoldások közül a legnépszerűbb a Mapperly. Ez, mivel nem létezik annyira régóta, mint az AutoMapper, kevésbé elterjedtnek számÃt.
Azt, hogy melyiket érdemes használni, ismételten csak szituációfüggő. Jelen alkalmazásban az AutoMapper is bőven jó lenne, mivel az NFR-ekben nem szerepelnek bődületesen nagy számok. Viszont a jövőbe mutatás jegyében a mappelésre a Mapperly-t választottam.
A mapper megvalósÃtása:
using Riok.Mapperly.Abstractions;
using UrlShortner.Core.Models;
using UrlShortner.Web.Dto;
namespace UrlShortner.Web;
[Mapper]
public partial class DtoMappers
{
[MapPropertyFromSource(nameof(UrlResponseDto.LongUrl), Use = nameof(ConvertUri))]
[MapPropertyFromSource(nameof(UrlResponseDto.ShortUrl), Use = nameof(ConvertShortUrl))]
public static partial UrlResponseDto MapToDto(UrlModel model);
private static string ConvertShortUrl(UrlModel urlModel)
=> urlModel.ShortUri;
private static string ConvertUri(UrlModel urlModel)
=> urlModel.LongUri.ToString();
}
A Mapperly kódgenerálás közben Mapper attribútummal megjelölt osztályokat keres, amelyekben a partial metódusokhoz készÃti el a generálást. Jelen esetben ez a MapToDto metódus lesz. Az efelett elhelyezkedÅ‘ MapPropertyFromSource attribútumok egyedi mappelést konfigurálnak. Ha a név és tÃpus egyezik a két osztályban, akkor ezekre semmi szükség, de ahol tÃpuskonverzió vagy néveltérés van, ott kézzel kell megadnunk, hogy mit mire és hogyan konvertáljon. Ezen célt szolgálják a ConvertShortUrl és a ConvertUri metódusok.
Validáció és kiegészÃtések
A FluentValidation a fluent builder tervezési mintára épül. A központi osztálya a AbstractValidator<T>, aminél a T tÃpus az az osztály, amit validálni szeretnénk. Jelen esetben a CreateUrlDto osztályt fogjuk validálni, hogy megfelelÅ‘ paraméterekkel lett-e feltöltve. A validációs szabályokat az osztály konstruktorában kell megadnunk és a tényleges validációt majd az örökölt ValidateAsync vagy Validate metódus meghÃvásával tudjuk elvégezni. A szabályok között rengeteg beépÃtettet találunk, de a Must metódussal lényegében bármilyen komplex logikát le tudunk implementálni.
using FluentValidation;
using UrlShortner.Web.Dto;
namespace UrlShortner.Web.Validation;
public class CreateUrlValidator : AbstractValidator<CreateUrlDto>
{
public CreateUrlValidator()
{
RuleFor(x => x.LongUrl)
.Must(LongUrl => Uri.TryCreate(LongUrl, UriKind.Absolute, out _))
.WithMessage("Please provide a valid url");
RuleFor(x => x.ValidTill)
.Must(date => date == null || date > DateTime.UtcNow)
.WithMessage("Valid till date must be in the future or null.");
}
}
public class UpdateUrlValidator : AbstractValidator<UpdateUrlDto>
{
public UpdateUrlValidator()
{
RuleFor(x => x.ValidTill)
.Must(date => date == null || date > DateTime.UtcNow)
.WithMessage("Valid till date must be in the future or null.");
}
}
A FluentValidation könyvtár dokumentációját a https://docs.fluentvalidation.net/en/latest/ cÃmen találjuk meg.
A végpontok implementálásához készÃtettem egy pár extension metódust, ami az életünket megkönnyÃti. A ToListAsync és a SelectAsync LINQ metódusok, amik IAsyncEnumerable támogatással rendelkeznek. Ezek egyenlÅ‘re (.NET 9 alatt) nem részei a .NET keretrendszernek, Ãgy ezeket nekünk kell megvalósÃtanunk. A GetProblems metódus pedig a FluentValidation könyvtár ValidationResult eredményébÅ‘l csoportosÃtja tulajdonságonként a problémákat, hogy majd hiba esetén könnyen vissza tudjuk Å‘ket adni. A TryGetUserName metódus majd azon API végpontok esetén lesz hasznos, ahol a felhasználónévre szükségünk van.
using System.Diagnostics.CodeAnalysis;
using FluentValidation.Results;
namespace UrlShortner.Web;
internal static class Extensions
{
public static async Task<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> asyncEnumerable)
{
List<T> list = new();
await foreach (T item in asyncEnumerable)
{
list.Add(item);
}
return list;
}
public static async IAsyncEnumerable<TOut> SelectAsync<TOut, TIn>(this IAsyncEnumerable<TIn> source, Func<TIn, TOut> selector)
{
await foreach (TIn item in source)
{
yield return selector(item);
}
}
public static IEnumerable<KeyValuePair<string, string[]>> GetProblems(this ValidationResult validationResult)
{
return validationResult.Errors
.GroupBy(x => x.PropertyName)
.ToDictionary(x => x.Key, x => x.Select(x => x.ErrorMessage).ToArray());
}
public static bool TryGetUserName(this HttpContext context, [NotNullWhen(true)] out string? username)
{
username = context.User?.Identity?.Name;
return !string.IsNullOrEmpty(username);
}
}
Végpontok
A végpontok implementálása történhetne akár a fÅ‘programban lambda metódusokkal is, de hogy valami struktúra legyen az alkalmazásunkban, érdemes Å‘ket szétbontani. Egy bontási stratégia lehet például, hogy ha URL-ekkel és felhasználókkal dolgozik az API, akkor egy URL API műveletekhez tartozó osztályban helyezzük el az URL végpontok implementálását, mÃg a felhasználókhoz tartozó dolgokat egy másikban.
Jelen esetben azonban egy teljesen más megoldást választottam. A műveleteim implementációi egyenként egy külön Handler osztályban kaptak helyet, mégpedig azért, hogy az egyes műveletek implementációi önálló egységet alkossanak és több száz sor kód egyben átlátása helyett legyen lehetőség a hozzátartozó magyarázat olvasására.
A legegyszerűbb API végpontunk a /v1/{shortcode} útvonalhoz tatozik.
Ez az implementációjában a RedirectUrl nevet kapta, mivel átirányÃtás fog történni. A metódus bemeneti paraméterei között a HttpContext tÃpusú context paraméter a kérés és a válasz kontextus objektuma, amibÅ‘l rengeteg információt ki tudunk nyerni, például a beléptetett felhasználó adatait, a kliens IP cÃmét és még sorolhatnám.
Ugyanennek a context objektumnak a megfelelő metódusaival közvetlenül válaszolhatunk a kérésekre, de ezt megtehetjük kulturáltabb módon egy IResult visszaadásával. Az IResult egy HTTP API választ reprezentál. A gyakran használt válaszkódok a Results nevű factory osztályból érhetőek el.
A metódus egyes paramétereinél szerepel a FromServices attribútum. Ez az ASP.NET-et arra utasÃtja, hogy az adott paramétert a DI konténerbÅ‘l helyettesÃtse be. A FromRoute attribútum pedig az URL útvonal egy darabját helyettesÃti be.
A logoláshoz egy ILoggerFactory kerül beinjektálásra, amibÅ‘l egy konkrét logger kikérhetÅ‘. Erre azért van Ãgy szükség, mert a logger példányosÃtásakor megadható egy kategória név, egyfajta szkóp, ami alapján majd késÅ‘bb könnyen kereshetünk a hibaüzenetekben.
A metódusban két hibás eset van, amit le kell kezelnünk. Ha az URL üres vagy csupa szóköz, akkor az egy 400-as Bad request, ha pedig az URL nem található az adatbázisban, akkor egy 404-es hibakódot kell visszaadnunk. Ezeket a Results.BadRequest és a Results.NotFound metódusok megfelelÅ‘ eseti meghÃvásával tudjuk lekezelni. Ha pedig minden jól ment, akkor a Results.Redirect hÃvással tudunk egy 307-es választ produkálni, amennyiben paraméterben megadjuk, hogy az nem egy permanens átirányÃtás.
using Microsoft.AspNetCore.Mvc;
using UrlShortner.Core;
namespace UrlShortner.Web.Endpoints;
public static class RedirectUrlHandler
{
public static async Task<IResult> RedirectUrl(HttpContext context,
[FromServices] IUrlShortner urlShortner,
[FromServices] ILoggerFactory loggerFactory,
[FromRoute] string shortcode)
{
var logger = loggerFactory.CreateLogger("UrlShortner.Web.Endpoints.RedirectUrl");
if (string.IsNullOrWhiteSpace(shortcode))
{
logger.LogWarning("RedirectUrl called with empty shortcode by {connectionid}", context.Connection.Id);
return Results.BadRequest("Shortcode cannot be empty.");
}
Uri? redirectUri = await urlShortner.GetRedirectUriAsync(shortcode);
if (redirectUri is null)
{
logger.LogWarning("RedirectUri not found for shortcode {shortcode} by {connectionid}", shortcode, context.Connection.Id);
return Results.NotFound();
}
return Results.Redirect(redirectUri.ToString(), permanent: false);
}
}
A következÅ‘ legegyszerűbb kiszolgáló metódus a /v1/urls lekérdezéséhez tartozó. Ennek a logikája egyszerűbbnek tűnik, de itt már szükségünk van a felhasználónévre. Ezt a TryGetUserName extension metódussal teszem meg, hogy ne kelljen minden egyes alkalommal a context.User?.Identity?.Name tagokat kiÃrnom és megfelelÅ‘en null ellenÅ‘riznem. A SelectAsync és ToListAsync extension metódusokal pedig az üzleti logika modelljeit át tudom konvertálni egy DTO listára, amit eredményként visszaad a metódus, becsomagolva egy HTTP 200-as válaszkódba.
using Microsoft.AspNetCore.Mvc;
using UrlShortner.Core;
using UrlShortner.Web.Dto;
namespace UrlShortner.Web.Endpoints;
public static class GetUrlHandler
{
public static async Task<IResult> GetUrls(HttpContext context,
[FromServices] IUrlShortner urlShortner,
[FromServices] ILoggerFactory loggerFactory)
{
var logger = loggerFactory.CreateLogger("UrlShortner.Web.Endpoints.GetUrls");
if (!context.TryGetUserName(out string? username))
{
logger.LogError("Unauthorized access attempt from: {ip}", context.Connection.LocalIpAddress);
return Results.Unauthorized();
}
List<UrlResponseDto> urls = await urlShortner
.GetUrlModelsAsync(username)
.SelectAsync(DtoMappers.MapToDto)
.ToListAsync();
return Results.Ok(urls);
}
}
A CreateUrl metódus a /v1/urls POST hÃvással történÅ‘ kiszolgálásáért felelÅ‘s. Ez egy CreateUrlDto formában várja a szükséges adatokat. Itt a FromBody attribútum jelzi a keretrendszernek, hogy ezt az objektumot a POST kérés törzsébÅ‘l szedje ki. A felhasználónév ellenÅ‘rzése és az objektum validálása után az üzleti logika eredménye alapján vagy 200-as OK választ adunk vissza a létrejött URL-el, vagy egy 400-as Bad Request hibát.
Ha a validáció nem sikerült, akkor a Results.ValidationProblem segÃtségével adunk vissza egy 400-as Bad Request hibát. A különbség a ValidationProblem és a BadRequest hÃvásban a néven kÃvül a visszaadott adat. A ValidationProblem egy speciális JSON dokumentumot állÃt elÅ‘, amit Problem detailsnek hÃvnak. Ez egy RFC (https://datatracker.ietf.org/doc/html/rfc7807) dokumentált adatformátum, amit specifikusan arra találtak ki, hogy a HTTP API-k egységes, könnyen feldolgozható formátumban tudjanak jelezni problémákat.
using Microsoft.AspNetCore.Mvc;
using UrlShortner.Core;
using UrlShortner.Core.Models;
using UrlShortner.Web.Dto;
using UrlShortner.Web.Validation;
namespace UrlShortner.Web.Endpoints;
public static class CreateUrlHandler
{
public static async Task<IResult> CreateUrl(HttpContext context,
[FromServices] IUrlShortner urlShortner,
[FromServices] ILoggerFactory loggerFactory,
[FromBody] CreateUrlDto dto)
{
var logger = loggerFactory.CreateLogger("UrlShortner.Web.Endpoints.CreateUrl");
if (!context.TryGetUserName(out string? username))
{
logger.LogError("Unauthorized access attempt from: {ip}", context.Connection.LocalIpAddress);
return Results.Unauthorized();
}
CreateUrlValidator validator = new();
var validationResult = await validator.ValidateAsync(dto);
if (!validationResult.IsValid)
{
return Results.ValidationProblem(validationResult.GetProblems());
}
UrlModel? createdUrl = await urlShortner.CreateAsync(dto.LongUrl, username, dto.ValidTill);
return createdUrl is not null
? Results.Ok(createdUrl)
: Results.BadRequest("Failed to create URL.");
}
}
A /v1/{shortcode} DELETE hÃvásához a DeleteUrl metódus tartozik és igazából semmi érdekes nincs benne, amirÅ‘l eddig nem volt szó.
using Microsoft.AspNetCore.Mvc;
using UrlShortner.Core;
namespace UrlShortner.Web.Endpoints;
public static class DeleteUrlHandler
{
public static async Task<IResult> DeleteUrl(HttpContext context,
[FromServices] IUrlShortner urlShortner,
[FromServices] ILoggerFactory loggerFactory,
[FromRoute] string shortcode)
{
var logger = loggerFactory.CreateLogger("UrlShortner.Web.Endpoints.DeleteUrl");
if (!context.TryGetUserName(out string? username))
{
logger.LogError("Unauthorized access attempt from: {ip}", context.Connection.LocalIpAddress);
return Results.Unauthorized();
}
if (string.IsNullOrWhiteSpace(shortcode))
{
logger.LogWarning("DeleteUrl called with empty shortcode by {connectionid}", context.Connection.Id);
return Results.BadRequest("Shortcode cannot be empty.");
}
await urlShortner.DeleteByShortUriAsync(shortcode, username);
return Results.NoContent();
}
}
A frissÃtésért felelÅ‘s UpdateUrl metódusról ugyanez mondható el:
using Microsoft.AspNetCore.Mvc;
using UrlShortner.Core;
using UrlShortner.Web.Dto;
using UrlShortner.Web.Validation;
namespace UrlShortner.Web.Endpoints;
public static class UpdateUrlHandler
{
public static async Task<IResult> UpdateUrl(HttpContext context,
[FromServices] IUrlShortner urlShortner,
[FromServices] ILoggerFactory loggerFactory,
[FromRoute] string shortcode,
[FromBody] UpdateUrlDto dto)
{
var logger = loggerFactory.CreateLogger("UrlShortner.Web.Endpoints.UpdateUrl");
if (!context.TryGetUserName(out string? username))
{
logger.LogError("Unauthorized access attempt from: {ip}", context.Connection.LocalIpAddress);
return Results.Unauthorized();
}
if (string.IsNullOrWhiteSpace(shortcode))
{
logger.LogWarning("UpdateUrl called with empty shortcode by {connectionid}", context.Connection.Id);
return Results.BadRequest("Shortcode cannot be empty.");
}
UpdateUrlValidator validator = new();
var validationResult = await validator.ValidateAsync(dto);
if (!validationResult.IsValid)
{
logger.LogError("Validation failed for UpdateUrl with shortcode {shortcode}: {errors}", shortcode, validationResult.Errors);
return Results.ValidationProblem(validationResult.GetProblems());
}
await urlShortner.UpdateAsync(shortcode, username, dto.ValidTill);
return Results.NoContent();
}
}
Lényegében ennyi az endpoint-ok implementációja. Ezt követően nincs más dolgunk, mint összerakni ezeket a Program.cs állományban.
A main metódusunk
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using UrlShortner.Core;
using UrlShortner.Core.Services;
using UrlShortner.Database;
using UrlShortner.Web.Endpoints;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddOpenApi();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication().AddBearerToken();
builder.Services.AddAuthorization();
builder.Services.AddLogging();
builder.Services.AddScoped(x => TimeProvider.System);
builder.Services.AddScoped<IUrlRepository, UrlRespository>();
builder.Services.AddScoped<IUrlShortner, UrlShortner.Core.UrlShortner>();
builder.Services.AddScoped<IShortNameGenerator, ShortNameGenerator>();
builder.Services.AddDbContext<UrlShortnerDbContext>(options => options.UseSqlite(connectionString, b => b.MigrationsAssembly("UrlShortner.Database")));
builder.Services.AddIdentityApiEndpoints<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedEmail = false;
options.SignIn.RequireConfirmedAccount = false;
options.SignIn.RequireConfirmedPhoneNumber = false;
options.Lockout.MaxFailedAccessAttempts = 5;
}).AddEntityFrameworkStores<UrlShortnerDbContext>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseSwaggerUI();
}
//https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-9.0
app.MapIdentityApi<IdentityUser>();
app.UseHttpsRedirection();
app.UseSwagger();
app.UseAuthentication();
app.UseAuthorization();
app.MapPost("/v1/urls", CreateUrlHandler.CreateUrl).RequireAuthorization();
app.MapGet("/v1/urls", GetUrlHandler.GetUrls).RequireAuthorization();
app.MapPut("/v1/{shortcode}", UpdateUrlHandler.UpdateUrl).RequireAuthorization();
app.MapDelete("/v1/{shortcode}", DeleteUrlHandler.DeleteUrl).RequireAuthorization();
app.MapGet("/v1/{shortcode}", RedirectUrlHandler.RedirectUrl);
app.Run();
Az ASP.NET alkalmazásunk egy HTTP/HTTPS szerverrel együtt egy WebApplication osztályban fog futni. Ehhez tartozik egy Builder, amivel a különböző aspektusait tudjuk konfigurálni.
ElsÅ‘ körben a szolgáltatásokat konfiguráljuk. Ezek a builder.Services objektuma alatt érhetÅ‘ek el és az Extensions.DependencyInjection Add metódusain kÃvül lehetÅ‘ségünk van beszédes nevű extension metódusok segÃtségével szolgáltatásokat könnyen konfigurálni. Az AddOpenApi és a AddSwaggerGen az API dokumentálásáért felelÅ‘s szolgáltatásokat húzza magával.
Az AddAuthentication és a AddAuthorization beszédes nevükkel az autentikációt és authorizációt húzzák magukkal. Itt felhÃvnám a figyelmet az AddBearerToken hÃvásra, ami bearer tÃpusú azonosÃtást konfigurál. Ha JWT-t akarnánk használni, akkor ezt a hÃvást kellene lecserélni. Az AddLogging hÃvás után pedig a saját szolgáltatásaink következnek.
Mivel a szolgáltatásaink mindegyike az adatbázisra épül, fontos, hogy az AddScoped hÃvással konfiguráljuk Å‘ket. Ha nem Ãgy történne, akkor az elsÅ‘ feloldás esetén kapnánk egy kivételt, mivel a függÅ‘ségek megfelelÅ‘ regisztrációját validálja a DI konténer.
A DbContext regisztrálására az AddDbContext metódus alkalmazható. Itt szükségünk lesz egy ConnectionString-re, ami a kapcsolat beállÃtásait specifikálja. Ezt az app.config-ból a GetConnectionString metódussal tudjuk felolvasni. Mivel a DbContext implementációnk nem a fÅ‘ futtatható állományunkban van, ezért itt konfigurálnunk kell a migrációkhoz használt szerelvény nevét is.
Ezt követÅ‘en a bejelentkezéshez és a felhasználó menedzsmenthez szükséges szolgáltatások az AddIdentityApiEndpoints hÃvással beállÃthatóak. Itt meg kell adnunk a felhasználó tÃpusunkat, aminek egy IdentityUser leszármazottnak kell lennie. Mivel mi extra tulajdonságokat, adatokat nem rendeltünk hozzá, ezért szimplán az IdentityUser tÃpust adtam meg. Ugyanitt számos opciót konfigurálhatunk az azonosÃtást illetÅ‘en. Ezek közül a legfontosabbak a megerÅ‘sÃtésre vonatkozóak. Alapértelmezetten a felhasználónak meg kellene erÅ‘sÃtenie az adatait egy visszaigazoló mail-ben kiküldött linkre kattintással. Éles környezetben ez mindenképp hasznos, de mivel belsÅ‘ hálózaton lesz csak elérhetÅ‘ a termék, ezért ettÅ‘l a megrendelÅ‘ kérésére eltekintettünk.
Itt megjegyzem, hogy az ajánlott biztonsági beállÃtások áthágásáról, ha ügyfél kérésre történnek, akkor mindenképpen legyen Ãrásos bizonyÃték, a késÅ‘bbi esetleges nézeteltérések és felelÅ‘sségek tisztázása érdekében.
A konfigurálás után meg kell adni egy DBContext tÃpust, ami majd a felhasználói adatok elérését tárolja.
A builder.Build() hÃvás után az alkalmazásunk konfigurált szolgáltatások tekintetében, de nem jelenti még azt, hogy futásra kész, mivel ezt követÅ‘en történhet meg a HTTP kérés feldolgozások konfigurálása.
A MapIdentityApi hÃvás adja hozzá az alkalmazásunkhoz a bejelentkezéshez és regisztrációhoz szükséges végpontokat. A UseAuthentication és a UseAuthorization pedig használatba veszi a korábban konfigurált autentikációs és authorizációs szolgáltatásokat. A UseSwagger a dokumentáció publikálásért felelÅ‘s, a UseHttpsRedirection pedig a sima HTTP kérésre érkezÅ‘ hÃvásokat HTTPS-re irányÃtja át.
A saját útvonalaink a különbözÅ‘ Map hÃvásokkal konfigurálhatóak. Ezek közül némelyiknél szerepel még a RequireAuthorization is. Ez kikényszerÃti, hogy a felhasználó be legyen jelentkeztetve.
De hogyan is kényszerÃti ezt ki? ASP.NET esetén a HTTP kérések feldolgozása egy pipeline-on keresztül történik. Ebben a pipeline-ban middleware-nek nevezett osztályok sokasága szerepel, amelyek ha nem tudják feldolgozni a HTTP kérést, akkor továbbadják azt a sorban utánuk következÅ‘nek. Az autentikáció és az autorizáció is ilyen middleware, ami ha azt érzékeli, hogy nincs érvényes felhasználó egy konfigurált URL-hez, akkor saját hatáskörben lerendezi a hÃvást és a vezérlés a tényleges feldolgozó metódushoz elvileg nem is kerülhet át. EbbÅ‘l adódóan feleslegesnek tűnhet a HTTP végpontokhoz társÃtott metódusokban ellenÅ‘rizni, hogy van-e felhasználó, mivel kellene lennie. Azonban ha biztonságról van szó, sosem lehetünk elég alaposak, illetve egyfajta szándékot is kifejez a kódunk, amit késÅ‘bb olvasva tisztán látszik, hogy adott metódust csak bejelentkezett felhasználó tudja meghÃvni.
Swagger
A swaggerrÅ‘l nem esett még szó. Ez dokumentáció mellett a /swagger cÃmen egy felhasználói felületet kÃnál, ahol könnyen áttekinthetjük az API végpontokat és az egyes végpontokhoz tartozó URL sémákat. Ez azonban csak akkor áll rendelkezésre, ha az alkalmazásunkban szerepel a UseSwaggerUI metódushÃvás.
Ezen felül tesztelési lehetÅ‘ségeket is biztosÃt a felület. Éppen ezért a UseSwaggerUI biztonsági okokból csak fejlesztÅ‘i környezetben legyen kipublikálva. Ezt a kódban az app.Environment.IsDevelopment() hÃvással tudjuk ellenÅ‘rizni.