I en webapplikation er en anmodning en kompleks enhed, der indeholder flere aspekter, som for eksempel en anmodningskrop (body), URL, forespørgselsstrenge og parametre sendt via formularer. I ASP.NET Core abstraheres anmodningen som et objekt af typen HttpRequest, som giver nem adgang til alle anmodningens egenskaber via Request-egenskaben i ControllerBase-klassen. Ved at implementere et controller-baseret API kan man effektivt få adgang til disse aspekter og håndtere dem korrekt.

En af de mest grundlæggende opgaver i forbindelse med at arbejde med anmodninger er at hente værdier fra forespørgselsstrenge. Dette kan gøres på flere måder. For eksempel kan følgende kode hente en værdi fra forespørgselsstrengen:

csharp
string fullname1 = Request.QueryString["fullname"];
string fullname2 = Request["fullname"];

Men en mere elegant måde at gøre dette på i ASP.NET Core er ved at benytte FromQuery-attributten, som binder forespørgselsstrengen direkte til en metodeparameter:

csharp
[HttpGet]
public IActionResult GetTasks([FromQuery]bool isCompleted = false)
{
// .. }

I dette tilfælde vil ASP.NET Core automatisk binde værdien af isCompleted til en parameter, der er angivet i forespørgselsstrengen. Hvis parameterens navn i forespørgselsstrengen er anderledes end parameterens navn i metoden, kan man bruge en overbelastning af attributten for at definere et alternativt navn:

csharp
public IActionResult GetTasks([FromQuery("completed")] bool isCompleted = false)
{ // .. }

Der findes flere andre attributter, som kan bruges til at binde data fra forskellige kilder:

  • FromBody: Bruges til at binde data fra anmodningens krop, typisk ved POST- eller PUT-anmodninger.

  • FromForm: Bruges til at binde data fra formularfelter (nøgle-værdi-par) i POST-anmodninger.

  • FromServices: Bruges til at injicere tjenester direkte i metodeparametre.

  • FromHeader: Bruges til at hente data fra HTTP-overskrifter, eksempelvis til API-versionering eller tokens.

  • FromRoute: Bruges til at binde parameterne fra URL-segmenter, f.eks. når der arbejdes med RESTful API'er.

Hver af disse metoder giver mulighed for at tilpasse hvordan data bindes til controllerens handlinger. Når standardbindemodellen ikke er tilstrækkelig til applikationens behov, kan man implementere brugerdefinerede bindemodeller. Dette er dog uden for omfanget af denne diskussion, men det er et emne, der kan undersøges nærmere på den officielle Microsoft-dokumentation.

Modelbinding i ASP.NET Core abstraherer meget af den implementeringskompleksitet, som ellers kunne være forbundet med at udfylde værdier i en controllerhandling. Dog er der ingen garanti for, at de bundne værdier er korrekte i forhold til applikationens forretningslogik. For at sikre datavaliditet er det nødvendigt at udføre valideringer, og ASP.NET Core tilbyder et kraftfuldt valideringssystem gennem ModelState.

Validering af modeller

Modelvalidering er en af de grundlæggende byggesten i opbygningen af robuste API'er. I ASP.NET Core kan dette gøres effektivt ved hjælp af ModelState, som fungerer som en slags "grænsevagt" for at kontrollere og validere data, før de når applikationens kerne.

Et simpelt eksempel på en produktregistrerings-API kunne være:

csharp
[HttpPost]
public IActionResult Post(Product product) { if (product == null) return BadRequest(); if (!ModelState.IsValid) return BadRequest(ModelState); // .. }

I dette eksempel bliver ModelState.IsValid tjekket for at sikre, at dataene er valide. Hvis det er falsk, returneres en HTTP 400-fejl (bad request) med en detaljeret fejlmeddelelse. ModelState er et dictionary, som når det serialiseres i JSON-format, repræsenteres som et objekt, hvor hver egenskab af objektet svarer til en validere ejendom i modellen. Værdien af hver egenskab er et array af strenge, der indeholder valideringsresultaterne.

For at sikre, at ModelState korrekt validerer en model, skal man annotere modelens egenskaber med valideringsattributter. For eksempel kan en Product-model være defineret som:

csharp
public class Product { public int Id { get; set; } [Required(ErrorMessage = "The field Name is required")] [MinLength(3, ErrorMessage = "The Name field must have at least 3 characters.")] public string Name { get; set; } public decimal Price { get; set; } }

I dette eksempel er Name-egenskaben både obligatorisk og skal have mindst tre tegn. Valideringsattributterne kan kombineres for at opnå den nødvendige validering. Hvis validering fejler, kan man også manuelt tilføje fejl til ModelState med AddModelError-metoden:

csharp
if (product.Price < 0) ModelState.AddModelError("Price", "The Price field cannot have a value less than zero."); if (!ModelState.IsValid) return BadRequest(ModelState);

Validering er en essentiel del af API-design, og ASP.NET Core giver et fleksibelt system til at validere modeller, både ved hjælp af attributter og gennem manuelle fejlhåndteringer.

Yderligere muligheder for validering og dokumentation

ASP.NET Core tilbyder et væld af indbyggede valideringsattributter, som kan anvendes for at sikre dataintegriteten. For mere avancerede scenarier kan man implementere brugerdefinerede valideringslogikker. Desuden er det vigtigt at dokumentere API'ens adfærd. API-dokumentation er et kritisk aspekt ved at sikre, at forbrugerne ved, hvordan de interagerer med API'en, hvilke parametre der skal bruges, og hvilke forventede resultater der er.

Med indbyggede værktøjer som Swashbuckle kan man generere automatisk API-dokumentation baseret på controllerens handlinger og modeller, hvilket gør det lettere for udviklere at forstå og bruge API'en korrekt.

Hvordan Implementering af Resilience Strategier Forbedrer Applikationers Stabilitet i .NET

I moderne applikationsudvikling er det essentielt at sikre systemernes modstandsdygtighed overfor fejl og forstyrrelser, især når applikationer interagerer med eksterne tjenester eller netværk. En central tilgang til dette problem er anvendelsen af forskellige fejlbehandlingsstrategier, som kan hjælpe med at sikre, at applikationen fortsætter med at fungere, selv når uventede problemer opstår. To af de mest anvendte strategier til dette formål er Circuit Breaker og Retry, som begge er vigtige værktøjer til at håndtere midlertidige fejl og forhindre systemet i at blive overbelastet med gentagne fejlslagne operationer.

Polly, en bibliotek fra .NET Foundation, giver en simpel og effektiv måde at implementere disse strategier i applikationer, hvilket gør det muligt for udviklere at tilføje resiliensfunktioner til deres systemer. Polly er et populært valg på grund af dets konstante opdatering af open source-fællesskabet og den brede anvendelse i produktionsmiljøer.

Retry-strategi

Retry-strategien er en mekanisme, der forsøger at gentage en operation flere gange, hvis den oprindelige anmodning fejler. Dette er særlig nyttigt, når der er tale om midlertidige fejl, som kan løses ved en ny forsøg. Polly giver udviklere mulighed for nemt at implementere en retry-strategi som set i følgende kodeeksempel:

csharp
var retryPolicy = Policy.Handle<Exception>().RetryAsync(3);
public async Task GetDataWithRetryAsync() { return await retryPolicy.ExecuteAsync(async () => { var data = await _dataService.GetDataAsync(); return Ok(data); }); }

I denne kode er retry-politikken konfigureret til at forsøge tre gange, før den opgiver. Denne strategi er ideel til situationer, hvor der er lejlighedsvise problemer med eksterne API'er, f.eks. netværksproblemer eller midlertidig utilgængelighed af tjenester. Retry-strategien giver systemet tid til at "komme sig" og forsøge at hente data igen, hvilket minimerer chancen for at en transaktion fejler permanent på grund af midlertidige udfordringer.

Circuit Breaker-strategi

Circuit Breaker-strategien adskiller sig fra retry-strategien ved at fokusere på at forhindre et system i at blive overbelastet af gentagne fejlslagne operationer. Når antallet af successive fejl overstiger et bestemt antal, vil circuit breaker åbne "kredsløbet" og forhindre yderligere forsøg på at kontakte den fejlagtige service. Dette giver systemet mulighed for at komme sig og forhindrer, at fejlen spreder sig til andre dele af applikationen. Et eksempel på implementeringen af en circuit breaker-strategi kunne være:

csharp
var circuitBreakerPolicy = Policy.Handle<Exception>() .CircuitBreakerAsync(3, TimeSpan.FromMinutes(1)); public async Task GetDataWithCircuitBreakerAsync() { return await circuitBreakerPolicy.ExecuteAsync(async () => { var data = await _dataService.GetDataAsync(); return Ok(data); }); }

Her åbner circuit breaker'en, hvis der opstår tre consecutive fejl, og det forhindrer yderligere forsøg på at oprette forbindelse til tjenesten i ét minut. Dette forhindrer systemet i at blive overbelastet af konstante fejlslag og giver tid til, at den underliggende tjeneste kan komme sig. Efter den specificerede tid vil circuit breaker'en gå i "halv-åben" tilstand, hvilket tillader et begrænset antal testkald for at afgøre, om problemet er blevet løst. Hvis disse forsøg lykkes, går systemet tilbage til den "lukkede" tilstand.

Sammenligning af Circuit Breaker og Retry

Selvom både circuit breaker og retry-strategier sigter mod at forbedre systemets resiliens, har de forskellige formål og adfærd. Mens retry-strategien forsøger at gentage operationen et antal gange ved midlertidige fejl, stopper circuit breaker-strategien anmodningerne efter et bestemt antal fejlslag og åbner kredsløbet for at forhindre yderligere belastning på systemet.

Begge strategier har deres anvendelsesområder, og de kan ofte anvendes sammen. For eksempel kan retry-strategien bruges til at håndtere midlertidige fejl, mens circuit breaker-strategien kan træde til, hvis fejlene bliver ved med at opstå, og det er nødvendigt at beskytte systemet mod overbelastning.

AspektCircuit BreakerRetry
FormålForhindrer overbelastning af et system ved gentagne fejlHåndterer midlertidige fejl ved at prøve operationen igen
AdfærdStopper anmodninger efter et bestemt antal fejl og åbner kredsløbetGentager operationen et specifikt antal gange med mellemrum
TilstandeLukked, Åben, Halv-åbenIngen tilstande, kun forsøg
FejlbehandlingFejl hurtigt, når kredsløbet er åbentForsøger flere gange før fejlfinding
Hvornår man brugerNår man ønsker at forhindre systemet i at blive overbelastet ved gentagne fejlNår midlertidige fejl forventes at blive løst ved genforsøg
KompleksitetHøj, med tilstandsovergangLav, med simpel retry-logik
BrugerfeedbackØjeblikkelig fejlinformation når kredsløbet er åbentForsinket feedback efter alle forsøg fejler

I praksis bruges disse strategier ofte sammen for at skabe en robust fejlbehandlingsmekanisme. Først kan retry-strategien forsøge at håndtere kortvarige fejl, og hvis fejlerne fortsætter, kan circuit breaker-strategien træde til for at beskytte systemet.

Vigtigheden af Logning og Overvågning

Selvom det er afgørende at implementere mekanismer, der forhindrer systemfejl, er det mindst lige så vigtigt at kunne opdage og forstå fejl, når de opstår. Her kommer logning og overvågning ind i billedet. Det er nødvendigt at registrere relevante hændelser og fejlinformationer for at kunne analysere og løse problemer hurtigt. Effektiv logning giver udviklere indsigt i systemets adfærd og kan hjælpe med at identificere årsagen til fejl, mens overvågning giver et realtidsbillede af applikationens sundhed.

I ASP.NET Core 9 er der indbygget et robust logningssystem, der gør det muligt at opsamle og analysere logs for at sikre, at applikationen fungerer korrekt og effektivt kan håndtere fejl. Det er afgørende, at man implementerer både logning og overvågning for at kunne reagere hurtigt, når noget går galt.