Når vi arbejder med ASP.NET Core, er det afgørende at konfigurere DbContext-klassen korrekt i afhængighedsinjektionskonteksten. I vores eksempel tilføjer vi BankingDbContext-klassen til afhængighedsinjektionen og konfigurerer brugen af SQL Server, hvor forbindelsen oprettes med ConnectionString-værdien, som vi sender som parameter til UseSqlServer-metoden. Denne ConnectionString hentes fra applikationens indstillinger og findes typisk i filen appsettings.json. På dette tidspunkt har vi alle de nødvendige konfigurationer for at kunne implementere ASP.NET Core Identity korrekt i datalaget.

Efter at have sat denne konfiguration på plads er det tid til at opdatere databasen, så vi kan tilføje de nødvendige tabeller til identitetsstyring. I kapitel 5 oprettede vi en API, der simulerer digitale bankoperationer og forbinder sig til en database ved hjælp af Entity Framework Core. For dette eksempel benytter vi den samme database, dbBanking, som har følgende datamodel:

dbBanking-databasen består af fire tabeller, hvoraf tre af dem er en del af applikationens kontekst: dbo.Accounts, dbo.Customers og dbo.Movements. Den fjerde tabel, dbo.EFMigrationsHistory, har ansvaret for at styre ændringer i databasen, der sker via migrationer. Tabellen dbo.EFMigrationsHistory indeholder en historik over de første entiteter, der blev oprettet for bank-API'et. Denne historik kan vi se i applikationens mappestruktur under Migrations-mappen, som automatisk oprettes af Entity Framework Core's kommandolinjeværktøjer.

Efter at have lavet ændringer i DbContext-klassen og tilføjet ASP.NET Core Identity-modeller, skal databasen opdateres. Dette kan gøres ved at oprette en ny migration. For at oprette denne migration åbner vi en terminal og kører kommandoen:

csharp
dotnet ef migrations add IdentityModels

Denne kommando bruger Entity Framework Core's ef-værktøj og tilføjer en migration kaldet IdentityModels. Når vi åbner Migrations-mappen, kan vi se, hvilke nye klasser der er blevet oprettet. I dette tilfælde skal vi nu opdatere databasen, så den indeholder Identity-tabellerne. Dette gøres med følgende kommando i terminalen:

pgsql
dotnet ef database update

Denne kommando læser de tilgængelige migrationer i projektet, analyserer migrationhistorikken i dbo.EFMigrationsHistory-tabellen i databasen og anvender opdateringerne, som i dette tilfælde involverer oprettelsen af de nødvendige tabeller til, at ASP.NET Core Identity kan fungere korrekt.

Med dette er de grundlæggende ASP.NET Core Identity-konfigurationer relateret til datamodellen blevet tilføjet succesfuldt. Dog mangler der stadig nogle konfigurationer, før applikationen kan håndtere autorisation og autentifikation. I næste afsnit vil vi tilføje ASP.NET Core Identity-tjenesterne til applikationens afhængighedsinjektionskontekst.

ASP.NET Core Identity indeholder de nødvendige abstraktioner til at arbejde med autorisation og autentifikation, herunder tjenester til autentifikation og token-generering. For at aktivere disse abstraktioner skal vi eksplicit tilføje nogle linjer kode i applikationen. Dette gøres ved at åbne Program.cs-filen og tilføje følgende:

  1. Tilføj den nødvendige Identity-namespace: using Microsoft.AspNetCore.Identity;.

  2. Tilføj autentifikationstjenesterne til afhængighedsinjektionskonteksten, som er ansvarlige for at bestemme brugerens identitet, samt autentifikationsmetoden. I dette tilfælde bruger vi en bearer token: builder.Services.AddAuthentication().AddBearerToken();.

  3. Tilføj autorisationstjenesterne til afhængighedsinjektionskonteksten med builder.Services.AddAuthorization();.

  4. Tilføj Identity API'er og konfigurer dataadgang via Entity Framework Core med builder.Services.AddIdentityApiEndpoints().AddEntityFrameworkStores();.

  5. Kortlæg Identity-endepunkterne ved at bruge app.MapIdentityApi();.

  6. Tilføj autentifikationsmiddleware til applikationens anmodningsbehandlingspipeline med app.UseAuthentication();.

  7. Tilføj middleware, der kontrollerer autorisationspolitikker mod den autentificerede brugers identitet for at bestemme, om brugeren har tilladelse til at fortsætte med den aktuelle anmodning: app.UseAuthorization();.

Efter disse ændringer vil Program.cs-filen se således ud:

csharp
using Dapper; using Microsoft.AspNetCore.Identity; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.SqlServer; using WorkingWithIdentity.Context; using WorkingWithIdentity.Model; using WorkingWithIdentity.RouteHandler; var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication().AddBearerToken(); builder.Services.AddAuthorization(); builder.Services.AddIdentityApiEndpoints().AddEntityFrameworkStores(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddDbContext<BankingDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("BankingDbContext"))); builder.Services.AddScoped(_ => new SqlConnection(builder.Configuration.GetConnectionString("BankingDbContext"))); var app = builder.Build(); app.MapIdentityApi(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.RegisterAccountRoutes(); app.RegisterCustomerRoutes(); app.MapGet("GetAllCustomersUsingDapper", async (SqlConnection connection) => { var customers = await connection.QueryAsync("SELECT Id, Name FROM Customers ORDER BY Name"); return Results.Ok(customers); }); app.MapGet("GetCustomerByIdUsingDapper", async (int id, SqlConnection connection) => { var customer = await connection.QueryFirstOrDefaultAsync( "SELECT Id, Name FROM Customers WHERE Id = @id", new { id });
if (customer is null) return Results.NotFound();
return Results.Ok(customer); }); app.UseAuthentication(); app.UseAuthorization(); app.Run();

Med disse ændringer har vi nu oprettet den nødvendige infrastruktur for at håndtere både autentifikation og autorisation i vores applikation ved hjælp af ASP.NET Core Identity.

Endtext

Hvordan håndtere fejl og logge anmodninger effektivt i ASP.NET Core 9

I enhver applikation er korrekt håndtering af fejl afgørende for at undgå, at undtagelser forårsager funktionsfejl. En god praksis til at opnå dette er at anvende et globalt middleware for fejlhåndtering, der gør det muligt at styre applikationens undtagelsesforløb centralt. Dette kan også udvides med funktionalitet til at tilføje logfiler i forskellige overvågningsværktøjer, som er essentielle til fejlretning. Et sådant middleware kan hurtigt blive et grundlæggende element i din applikation, da det både beskytter mod utilsigtede nedbrud og gør det lettere at rette fejl effektivt.

For at implementere dette, kan vi oprette en fil kaldet ErrorHandlingMiddleware.cs i den tidligere oprettede Middlewares-mappe. I denne fil kan vi skrive følgende kode:

csharp
public class ErrorHandlingMiddleware { private readonly RequestDelegate _next; public ErrorHandlingMiddleware(RequestDelegate next) { _next = next; }
public async Task InvokeAsync(HttpContext context)
{
try { await _next(context); } catch (Exception ex) { await HandleExceptionAsync(context, ex); } } private Task HandleExceptionAsync(HttpContext context, Exception exception) { context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; return context.Response.WriteAsync(new ErrorDetails() { StatusCode = context.Response.StatusCode, Message = "Internal Server Error from the custom middleware." }.ToString()); } } public class ErrorDetails { public int StatusCode { get; set; } public string Message { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this); } }

I ovenstående kode kan vi se den almindelige struktur for et middleware. Den store funktionalitet ved dette globale fejlhåndteringsmiddleware er brugen af try/catch blokken i InvokeAsync metoden. Kommandoen await _next(context) bliver udført i en try blok, således at hvis der opstår en undtagelse i applikationen, bliver den håndteret centralt. Fejlhåndteringen sker gennem HandleExceptionAsync metoden, som bliver kaldt i catch blokken. Denne metode ændrer anmodningens svar ved at sætte StatusCode-egenskaben til Internal Server Error, HTTP statuskode 500, og returnerer et objekt i anmodningens krop. Dette objekt repræsenteres af ErrorDetails klassen, som har egenskaberne StatusCode og Message. På den måde garanteres det, at enhver undtagelse bliver håndteret, samtidig med at der returneres et standardiseret svar, som kan anvendes til at håndtere fejlen korrekt i UI'et og dermed forbedre brugeroplevelsen.

ASP.NET Core 9 tilbyder også en forbedret måde at håndtere fejl på gennem "Problem Details" formatet, som er baseret på RFC 7807. Dette format giver udviklere mulighed for at inkorporere en Trace ID i fejlsvaret, hvilket væsentligt forbedrer fejlfinding og sporing af fejl. Et "Problem Details" svar med en Trace ID kunne se ud som følgende:

json
{ "type": "https://example.com/probs/server-error", "title": "An unexpected error occurred.", "status": 500, "detail": "The system encountered an issue.", "instance": "/example-path", "traceId": "00-abcdef1234567890abcdef1234567890-1234567890abcdef-01" }

For at implementere dette i dit middleware, kan du ændre HandleExceptionAsync metoden som følger:

csharp
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{ var traceId = Activity.Current?.Id ?? context.TraceIdentifier; var problemDetails = new ProblemDetails { Type = "server-error", Title = "An unexpected error occurred.", Status = StatusCodes.Status500InternalServerError, Detail = "Internal Server Error from the custom middleware.", Instance = context.Request.Path }; // Tilføj Trace ID til Problem Details. problemDetails.Extensions["traceId"] = traceId; context.Response.ContentType = "application/problem+json"; context.Response.StatusCode = StatusCodes.Status500InternalServerError; return context.Response.WriteAsJsonAsync(problemDetails); }

I denne kode bliver traceId værdi indsamlet fra Activity.Current?.Id eller, hvis ikke tilgængelig, fra context.TraceIdentifier. Dette ID tilføjes derefter til objektet ProblemDetails. At inkludere traceId i loggen er yderst nyttigt for fejlfinding, da det muliggør korrelation af fejlmeddelelser i loggene, hvilket forenkler fejlretning. Denne tilføjelse forbedrer diagnosticering, så udviklere kan få hurtigere indsigt i, hvad der præcist gik galt og hvor.

En anden vigtig del af middleware i moderne applikationer er logning af anmodninger. Det er ikke kun vigtigt at håndtere fejl, men også at logge information om de anmodninger, der behandles i applikationen. Dette gør det muligt for udviklere at følge anmodningernes forløb, forstå applikationens præstation og spore brugerinteraktioner. Her er et eksempel på, hvordan du kan logge anmodningstidspunktet og behandlingsforløbet ved at oprette et middleware kaldet PerformanceLoggingMiddleware.cs:

csharp
public class PerformanceLoggingMiddleware { private readonly RequestDelegate _next; public PerformanceLoggingMiddleware(RequestDelegate next) { _next = next; }
public async Task InvokeAsync(HttpContext context, ILogger logger)
{
var timestamp = Stopwatch.GetTimestamp(); await _next(context); var elapsedMilliseconds = Stopwatch.GetElapsedTime(timestamp).TotalMilliseconds; logger.LogInformation("Request {Method} {Path} took {ElapsedMilliseconds} ms", context.Request.Method, context.Request.Path, elapsedMilliseconds); } }

Denne middleware registrerer den tid, det tager at behandle en anmodning, og logger det i applikationens logfiler. Denne type logning giver udviklere indsigt i applikationens ydeevne og hjælper med at identificere flaskehalse i behandlingen af anmodninger. Det er en vigtig komponent i at optimere applikationens præstation og forbedre brugeroplevelsen.

Gennem korrekt brug af global fejlhåndtering og logning af anmodninger kan applikationer blive langt mere robuste, lettere at vedligeholde og hurtigt kunne diagnosticeres, når der opstår problemer.

Hvordan administrativ proces og cloud-native principper påvirker applikationsudvikling

I udviklingen af moderne applikationer spiller databasen en væsentlig rolle som en central komponent i løsningen. Men det er vigtigt at forstå, at opgaver som migrationer og scripts til initialisering af grundlæggende informationer ikke er applikationens ansvar. Den administrative proces er derfor adskilt fra applikationen og bør udføres i et isoleret miljø, hvor det er muligt at overvåge ændringer. Opgaver som CI/CD (Continuous Integration/Continuous Delivery) er også udenfor applikationens umiddelbare kontekst. Under udførelsen af en pipeline og CI kan opgaver som generering af database-migrationscripts blive udført og derefter deles med CD-pipelines, som kan have forskellige krav for at anvende ændringerne.

Eksemplet i figur 11.18 viser en pipeline-flow, hvor to distinkte opgaver bliver udført under CD-processen, hvilket sikrer, at applikationen er korrekt forberedt til udførelse i et givet miljø. Udførelsen af én gangs administrative opgaver hjælper med at opretholde applikationens tilstand og sikrer, at eventuelle ændringer, der foretages under disse opgaver, hurtigt bliver afspejlet i produktionsmiljøet. Dette reducerer risikoen for fejl og uoverensstemmelser, som kunne opstå, hvis disse opgaver ikke blev gennemført korrekt.

Denne administrative proces er en vigtig faktor i applikationens livscyklus og bør betragtes som en central komponent sammen med de andre faktorer, der er beskrevet i den velkendte "12-factor app"-metodologi. Denne metodologi blev udviklet for at hjælpe udviklere med at skabe moderne, skalerbare og vedligeholdelsesvenlige applikationer, der er godt rustet til at udnytte cloud-native miljøer. Principperne i 12-factor app-metodologien understøtter ikke kun mikroservice-arkitektur og containerisering, men også de kontinuerlige integrations- og leveringsprocesser, som er essentielle for moderne applikationsudvikling.

Når man anvender disse principper sammen med andre metoder og værktøjer som CAF (Cloud Adoption Framework), WAF (Web Application Firewall) og teknologier fra CNCF (Cloud Native Computing Foundation), får softwareudviklere et stærkt fundament for at bygge applikationer, der er både fleksible og effektive. Med værktøjerne i ASP.NET Core 9 er det nemt at implementere disse principper, da de giver et solidt grundlag for at udvikle cloud-native applikationer.

Forståelse af cloud-arkitekturprincipper

Moderne cloud-arkitekturer er fundamentet for at skabe skalerbare, modstandsdygtige og højtilgængelige applikationer. I denne sammenhæng er det vigtigt for softwareudviklere at forstå, hvordan de kan kombinere deres arbejde med cloud-native principper og optimere deres løsninger ved hjælp af ASP.NET Core 9. I en cloud-baseret løsning er det ikke nok blot at have adgang til ressourcer – det er nødvendigt at tilpasse applikationen, så den kan håndtere brugerkrav og samtidig sikre en optimal brugeroplevelse.

I dagens teknologiske landskab, hvor data og tjenester bliver mere og mere essentielle for virksomheders succes, er det nødvendigt at tænke på arkitektur på et højere niveau end blot kildekode og applikationslag. Virksomheder skal være i stand til at håndtere store datamængder, levere API’er, implementere kunstig intelligens og muliggøre sømløse integrationer mellem forskellige systemer.

En applikation, som for eksempel en onlinebutik under Black Friday, bør kunne tilpasse sig brugernes krav og effektivt håndtere væksten i betalingsanmodninger, uden at systemet bliver nede, selv for en kort periode. For at opnå dette er det nødvendigt at arbejde med event-drevet arkitektur og asynkron behandling, som giver systemet mulighed for at reagere på ændringer i realtid og opretholde høj tilgængelighed.

Event-drevet arkitektur

En af de centrale principper i moderne cloud-native løsninger er event-drevet arkitektur, som giver applikationer mulighed for at behandle informationer asynkront. Dette skaber en mere fleksibel og responsiv applikation, der kan reagere på begivenheder, såsom ændringer i tilstanden af en ordre i en onlinebutik. Event-drevet arkitektur gør det muligt at opretholde høj konsistens i forretningskritiske processer som betalingstransaktioner.

I en event-drevet arkitektur fungerer event broker-teknologier som Azure Event Grid som et kommunikationsled, der modtager og videresender events til de relevante modtagere, som kan opdatere lagre, sende notifikationer til brugerne eller udføre yderligere behandlinger. Dette giver ikke kun skalerbarhed, men øger også robustheden og vedligeholdelsesevnen af applikationen, da komponenter kan afkobles og udvikles uafhængigt af hinanden.

ASP.NET Core 9 understøtter event-drevne systemer og integreres nemt med teknologier som Azure Event Hubs og Apache Kafka, som muliggør implementering af event sourcing. Denne metode gør det muligt at opbygge en komplet log over alle ændringer i systemet, hvilket skaber transparens og sporbarhed.

Skalering og resiliens i applikationer

En anden vigtig egenskab ved event-drevet arkitektur er muligheden for at opbygge systemer, der kan skaleres i takt med efterspørgslen. Dette er essentielt i en verden, hvor applikationer skal håndtere store datamængder og kunne tilpasse sig dynamiske brugerbehov. Event-drevet arkitektur er ideel til applikationer, der skal reagere hurtigt og pålideligt på ændringer i miljøet. Når en applikation er bygget med event-drevet design, kan den håndtere høje mængder af samtidige anmodninger uden at belaste systemet, hvilket øger både systemets skalerbarhed og modstandsdygtighed.

En nøglefunktion ved denne arkitektur er event sourcing, hvor alle ændringer til applikationens tilstand bliver registreret som events, der kan afspilles igen senere, hvilket giver en komplet historik og muligheden for at "spole tilbage" i applikationen. Det sikrer både konsistens og en bedre mulighed for at fejlsøge eller genskabe systemet i tilfælde af problemer.

I en sådan arkitektur er det nødvendigt at tage højde for flere faktorer som sikkerhed, omkostninger, netværk, og ikke mindst brugervenlighed. For at sikre at systemet forbliver effektivt og økonomisk rentabelt, skal udviklere tage disse elementer i betragtning, når de skaber løsninger, der er både robuste og fleksible.