I mange moderne applikationer spiller dataadgang en central rolle, og effektiv håndtering af data er afgørende for både udviklerens produktivitet og applikationens ydeevne. Entity Framework Core (EF Core) og Micro ORMs, såsom Dapper, er to populære teknologier, der kan hjælpe med at forenkle og optimere dataadgangen. Begge tilgange tilbyder forskellige fordele, men de har også deres udfordringer, som det er vigtigt at forstå for at vælge den rette løsning til din applikation.

EF Core, som er en fuld ORM (Object-Relational Mapping) teknologi, er blevet populær på grund af sin evne til at automatisere mange af de opgaver, der normalt kræver manuel SQL-kodning. Ved hjælp af EF Core kan udviklere arbejde med objekter, uden at skulle bekymre sig om de underliggende databaseoperationer, såsom SELECT, INSERT eller UPDATE. Når man definerer en DbContext-klasse, kan man mappe databaseobjekter direkte til C#-klasser ved hjælp af DbSet-egenskaber. For eksempel vil følgende kode hente alle konti fra databasen:

csharp
public async Task<List<Account>> GetAllAccountsAsync() {
return await _context.Accounts.ToListAsync(); }

Denne metode gør det muligt at hente data fra databasen uden at skrive nogen SQL-forespørgsler direkte, hvilket giver en meget højere abstraktion. EF Core genererer automatisk de nødvendige SQL-kommandoer baseret på den model, der er defineret i applikationen. Denne tilgang reducerer boilerplate-kode og gør det lettere at vedligeholde og ændre dataadgangslogik i fremtiden.

Selv om EF Core bringer flere fordele, er der også nogle ulemper, som udviklere bør overveje. Den største ulempe er ydeevnen. EF Core kan være langsommere end håndskrevne SQL-kommandoer i komplekse scenarier med mange relationer mellem objekter. Desuden kan det være svært at optimere de genererede SQL-forespørgsler, hvilket kan føre til ineffektiv databasetilgang i visse tilfælde. Derfor kan det være nødvendigt at benytte en anden løsning, såsom Micro ORMs, når ydeevnen er afgørende.

Micro ORMs som Dapper er blevet populære, da de tilbyder en lettere og hurtigere måde at interagere med databasen på. Dapper, som er et open-source projekt, er designet til at være hurtigere og mindre kompleks end EF Core. Dapper giver udviklere mere kontrol over SQL-forespørgslerne og kræver mindre overhead i forhold til større ORMs. Dapper er ideelt, når ydeevnen er vigtig, og når der er behov for at skrive meget specifikke SQL-forespørgsler, som EF Core ikke nødvendigvis genererer optimalt.

Der er flere forskelle mellem en traditionel ORM som EF Core og en Micro ORM som Dapper, som kan påvirke valget af teknologi afhængigt af applikationens behov. Micro ORMs har en meget mindre kodebase og færre afhængigheder, hvilket betyder, at de er hurtigere at implementere og kører med lavere overhead. De tilbyder dog ikke de samme funktioner som EF Core, såsom automatisk ændringssporing, migrationshåndtering eller kompleks objektrelateret administration. Dette kan være en ulempe i store applikationer, hvor det er nødvendigt at håndtere komplekse datarelationer.

Dapper tillader udviklere at have større kontrol over SQL-forespørgslerne, hvilket kan resultere i bedre ydeevne i scenarier med stor datamængde eller komplekse forespørgsler. På den anden side tilbyder EF Core en højere abstraktion, der gør det lettere at arbejde med objekter og databaser, hvilket kan være fordelagtigt, når man arbejder med standardiserede applikationer med mindre fokus på ydeevne.

Når man arbejder med EF Core eller Dapper, er det vigtigt at vælge den rette teknologi afhængigt af applikationens krav til ydeevne, kompleksitet og funktionalitet. Begge tilgange kan coexistere i samme applikation, hvor EF Core håndterer de mere standardiserede dataoperationer og Dapper bruges til de mere performance-kritiske dele af applikationen.

Udviklere skal også være opmærksomme på, at begge teknologier kræver, at de forståelse af databasedesign og optimering. ORM-teknologier som EF Core kan skjule de underliggende SQL-kommandoer, hvilket gør det lettere at arbejde med objekter, men det kan også gøre det sværere at forstå og optimere de genererede SQL-forespørgsler. På den anden side giver Micro ORMs som Dapper udviklere mere kontrol, men kræver samtidig en højere grad af ekspertise i SQL.

Det er vigtigt at bemærke, at ORM og Micro ORM-teknologier ikke er den eneste tilgang til dataadgang. Der findes stadig tilfælde, hvor manuel SQL-kodning eller brug af ADO.NET kan være den bedste løsning. Selvom ORM-teknologier tilbyder mange fordele, er de ikke altid den bedste løsning i alle scenarier. At vælge den rette teknologi kræver en grundig forståelse af både applikationens behov og de underliggende databaseteknologier.

Hvordan man beskytter API-ruter med autentificering og autorisation i ASP.NET Core

Når vi arbejder med API’er i ASP.NET Core, er det afgørende at beskytte de ruter, der håndterer følsomme data, for kun at tillade anerkendte og autoriserede brugere at tilgå dem. For at gøre dette skal vi sikre, at hver rute kræver autentificering, så kun brugere med de rette rettigheder kan få adgang. Når en bruger forsøger at tilgå en beskyttet rute uden at være korrekt autentificeret, skal API’en returnere en HTTP 401 statuskode, som informerer brugeren om, at autentificering er påkrævet.

I denne kontekst lærer vi, hvordan vi kan beskytte ruter og forhindre uautoriserede anmodninger i vores API. Det er vigtigt at forstå, at selvom alle autentificering og autorisation indstillinger er konfigureret korrekt i applikationen, skal vi præcist definere, hvilke ruter der skal beskyttes, og hvilke der kan tilgås uden krav om autentificering.

En af de mest praktiske måder at sikre, at en given API-metode er beskyttet af autentificering og autorisation, er ved at tilføje en eksplicit konfiguration til ruterne. I et projekt, hvor API-ruterne er implementeret som udvidelsesmetoder i separate filer – en god praksis i ASP.NET Core – kan vi gøre en ændring i filen AccountHandler.cs, som findes i applikationens RouteHandler-mappe. Her kan vi konfigurere ruten /accounts, så den kun accepterer anmodninger, hvis brugeren er autentificeret. Dette opnås ved at anvende metoden RequireAuthorization() i ruten.

Eksemplet på ændringen ser således ud:

csharp
app.MapGet("/accounts", async (BankingDbContext dbContext) => { var accounts = await dbContext.Accounts.ToListAsync(); return Results.Ok(accounts); }).RequireAuthorization();

I koden ovenfor ser vi, hvordan RequireAuthorization() metoden sikrer, at ruten kun kan tilgås af brugere, der er autentificerede. Dette er et vigtigt skridt, da det betyder, at hvis brugeren ikke er autentificeret, vil anmodningen blive afvist med en HTTP 401 statuskode, som indikerer, at autentificering er påkrævet.

Når ruten er beskyttet, skal vi teste, hvordan systemet reagerer på en anmodning uden autentificering. Hvis vi sender en GET-anmodning til /accounts uden at være logget ind, vil vi modtage en HTTP 401 statuskode, hvilket betyder, at anmodningen ikke blev autoriseret. For at kunne tilgå ruten skal vi først logge ind og autentificere os som en gyldig bruger.

For at oprette en bruger i systemet kan vi bruge Postman, et populært værktøj til API-testning. Først opretter vi en POST-anmodning til registreringsruten (som typisk er /register) og sender brugerens data i JSON-format. Når brugeren er oprettet, kan vi logge ind ved at sende en POST-anmodning til login-ruten, for eksempel /login, hvor vi inkluderer brugerens e-mail og adgangskode.

Når login-anmodningen er blevet behandlet, modtager vi en JSON-respons, som indeholder et access-token. Dette token er den nøgle, vi skal bruge til at autorisere fremtidige anmodninger til beskyttede ruter. Access-tokenet skal inkluderes som en del af anmodningens HTTP-header under "Authorization" som en Bearer Token.

Når vi nu sender en GET-anmodning til /accounts ruten og inkluderer vores access-token i headeren, vil anmodningen blive behandlet korrekt, og vi vil få en svar med HTTP 200 statuskode og den ønskede data – i dette tilfælde en liste over bankkonti. Hvis der ikke er nogen konti registreret, vil vi få en tom liste, men anmodningen vil stadig blive behandlet korrekt, og statuskoden vil være 200.

Det er også vigtigt at forstå, hvordan HTTP-headere fungerer. Når vi sender en anmodning med et token, tilføjer Postman automatisk en header med nøglen "Authorization" og værdien af vores access-token. Dette headerfelt er den mekanisme, autentificeringsmiddleware bruger til at validere brugeren. Når anmodningen modtages, læser autentificeringsmiddleware det inkluderede token og verificerer, om det er gyldigt og ikke er udløbet.

Det er også værd at nævne, at ASP.NET Core Identity tilbyder flere autentificeringsmuligheder, herunder JWT, cookies og andre konfigurationer. I denne gennemgang brugte vi en token-baseret tilgang, som, selvom den ikke involverer JWT, følger en lignende konfiguration som JWT-baseret autentificering. Hvis du ønsker at ændre den nuværende token-konfiguration til JWT eller en anden form for token, kan du gøre dette ved at justere indstillingerne i applikationens konfigurationsfiler.

Når det kommer til tokenets opbygning, er det vigtigt at forstå, at det ikke nødvendigvis følger de konventioner, som f.eks. JWT-token gør. I denne version af ASP.NET Core Identity er tokenet genereret på en proprietær måde, men det er muligt at ændre dette, hvis du ønsker at bruge JWT eller andre typer token i din applikation.

Endelig skal du være opmærksom på, at autorisation og autentificering er to forskellige begreber. Autentificering handler om at identificere brugeren, mens autorisation handler om at bekræfte, om en bruger har de rette rettigheder til at tilgå bestemte ressourcer. Begge processer er nødvendige for at sikre, at din API kun kan tilgås af de rette brugere og for at forhindre uautoriserede anmodninger.

Hvordan Håndtering af Hemmeligheder Forbedrer Applikationssikkerhed i ASP.NET Core

For at beskytte følsomme data og infrastrukturen i din applikation er det vigtigt at implementere korrekt håndtering af hemmeligheder. En vigtig praksis er at sikre, at oplysninger som API-nøgler og forbindelsesstrenge ikke er hardkodet i kildekoden, men derimod gemmes og tilgås sikkert. I .NET Core SDK er værktøjet "Secret Manager" inkluderet, hvilket gør det muligt for udviklere at administrere hemmeligheder uden at skulle installere yderligere værktøjer, hvis SDK'et allerede er installeret.

For at komme i gang med Secret Manager skal du initialisere det i dit projekt. Naviger til din arbejdsmappe for projektet, og åbn din kommandoprompt eller terminal. Hvis du har en .csproj-fil til dit projekt, kan du køre kommandoen dotnet user-secrets init. Denne kommando tilføjer en UserSecretsId i din .csproj-fil, som entydigt identificerer hemmelighederne for dit projekt. Du kan verificere, at UserSecretsId er blevet tilføjet ved at åbne din .csproj-fil i en kodeeditor.

For at konfigurere forbindelsen til en SQL Server-database skal du bruge en kommando som denne: dotnet user-secrets set "ConnectionStrings:BankingDbContext" "DIN DATABASEFORBINDELSESSTRØNG". Her benyttes kolonnotationen, som er almindelig til at adskille forskellige niveauer i en hierarkisk struktur af hemmeligheder. I dette tilfælde repræsenterer ConnectionStrings den øverste kategori, og BankingDbContext er den konkrete hemmelighed, som indeholder forbindelsesstrengen til databasen. Denne konvention er ikke kun nyttig i Secret Manager, men stemmer også overens med, hvordan ASP.NET Core konfigurationer hentes fra forskellige kilder som f.eks. appsettings.json, miljøvariabler og Secret Manager.

ASP.NET Core’s konfigurationsteam håndterer automatisk opbygningen af nøgler og opretter forbindelser mellem kildedata og konfigurationen, uden at koden i selve applikationen skal ændres. Når miljøvariabler anvendes, er der en lille forskel på kolonsyntaksen, da nogle operativsystemer ikke tillader kolon i variabelnavne. I stedet bruges dobbelt-understregninger, f.eks. ConnectionStrings__BankingDbContext. Denne struktur gør det muligt for ASP.NET Core at genopbygge hierarkiet af hemmeligheder og arbejde med dem på samme måde som når de er defineret i Secret Manager eller appsettings.json.

Du kan administrere hemmeligheder på din lokale maskine med kommandoen dotnet user-secrets list, som viser de hemmeligheder, der er tilgængelige. Hvis du ønsker at fjerne en bestemt hemmelighed, kan du bruge kommandoen dotnet user-secrets remove "ConnectionStrings:BankingDbContext". For at slette alle hemmeligheder på din maskine kan du bruge dotnet user-secrets clear. Det er vigtigt at understrege, at hemmelighederne kun er tilgængelige på den lokale maskine og ikke deles med eksterne repositories, hvilket betyder, at de ikke bliver eksponeret under versionering eller i koden.

Secret Manager er kun beregnet til udviklingsformål. Når du arbejder med produktionsmiljøer, er det anbefalet at bruge en mere sikker løsning som Azure Key Vault eller AWS Secrets Manager for at håndtere følsomme data. Dette giver et højere niveau af sikkerhed og kontrol i produktionsmiljøer. I næste kapitel vil vi uddybe, hvordan du kan implementere sikre konfigurationer i applikationen og hvordan disse integreres med skybaserede løsninger.

Et andet vigtigt aspekt ved at sikre applikationer er brugen af HTTPS og håndtering af CORS (Cross-Origin Resource Sharing). HTTPS er afgørende for at sikre krypteret kommunikation mellem klienter og servere, og ASP.NET Core giver indbygget middleware, der kan tvinge HTTPS-forbindelser. For at konfigurere din applikation til at bruge HTTPS kan du tilføje middleware i din Program.cs-fil med koden: app.UseHttpsRedirection();. Dette sikrer, at al trafik automatisk bliver omdirigeret til HTTPS.

For at opnå maksimal sikkerhed skal du også konfigurere din webserver korrekt til at håndtere HTTPS-forbindelser. Det betyder, at du skal have et gyldigt SSL/TLS-certifikat fra en betroet certifikatudbyder. Når HTTPS er implementeret, kan du kombinere det med CORS, som begrænser, hvilke webapplikationer der må få adgang til dine ressourcer på tværs af forskellige domæner. Dette er især vigtigt i Single Page Applications (SPA), hvor JavaScript ofte bruges til at lave HTTP-anmodninger på tværs af forskellige servere.

I ASP.NET Core 9 kan du nemt konfigurere CORS-politikker i din applikation ved at tilføje en CORS-konfiguration til Program.cs-filen. Her kan du definere, hvilke oprindelser der er tilladt at få adgang til applikationens ressourcer, hvilket giver dig kontrol over, hvilke domæner der kan sende anmodninger til din server. En simpel CORS-konfiguration kunne se sådan ud:

csharp
builder.Services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin", builder => { builder.WithOrigins("https://myapp.com") .AllowAnyHeader() .AllowAnyMethod(); }); });

I denne konfiguration tillader vi kun anmodninger fra https://myapp.com at få adgang til serverens ressourcer, hvilket giver en præcis kontrol over, hvem der kan interagere med applikationen.

Endvidere er det vigtigt at forstå, at CORS-implementering ikke kun beskytter mod uautoriserede anmodninger, men også er med til at forhindre, at skadelig kode på en anden server kan få adgang til ressourcerne i din applikation. CORS-politikkerne bør derfor tilpasses applikationens sikkerhedsbehov og de specifikke krav til de domæner, der skal have adgang.

Hvordan håndteres dynamisk konfiguration i ASP.NET Core med Azure App Configuration?

Ved integration af Azure App Configuration i en ASP.NET Core-applikation skabes der mulighed for at adskille konfigurationsstyringen fra selve applikationskoden. Dette betyder, at ændringer i indstillinger kan foretages direkte i cloudmiljøet uden behov for at genudrulle eller genstarte applikationen.

Gennem brugen af en connection string forbindes applikationen til den instans, der tidligere er oprettet i Azure App Configuration. Ved at bruge .Select("Dynamic Configuration:*", LabelFilter.Null) udvælges alle nøgler med præfikset DynamicConfiguration: – kun de, der ikke er mærket med en label, hentes. Dette tillader en standardkonfiguration, som senere kan suppleres eller overskrives med labels efter behov.

Metoden .ConfigureRefresh registrerer hvilke nøgler, der skal overvåges for ændringer. I eksemplet registreres nøglen DynamicConfiguration:Sentinel, der fungerer som en "sentinel key". Når værdien på denne sentinel ændres, udløses en opdatering af alle relaterede konfigurationer. Dette er langt mere effektivt end at inspicere hver konfiguration individuelt for ændringer.

Integrationen med ASP.NET Core foretages gennem builder.Services.Configure(...), hvor applikationen binds til en konfigurationssektion, f.eks. DynamicConfiguration:GlobalOptions. Dette tillader at benytte Options Pattern, hvor konfigurationsværdierne indsprøjtes direkte i klasser ved brug af dependency injection containeren (DIC).

Når middleware UseAzureAppConfiguration tilføjes i pipeline, muliggøres automatisk opdatering af applikationens konfiguration uden manuel indgriben.

SDK’en til Azure App Configuration benytter caching for at undgå konstante kald til Azure og dermed forbedre performance. Som standard opdateres konfigurationerne hver 30. sekund, men denne interval kan tilpasses via refreshOptions.SetCacheExpiration.

Labels spiller en central rolle i konfigurationsadskillelse mellem miljøer såsom development, staging og production. Ved at anvende labels opnås isolation og fleksibilitet, hvilket tillader præcis styring af hvilke konfigurationer, der gælder i hvilke kontekster. Konfigurationer uden labels fungerer som standardindstillinger og anvendes når ingen label matcher.

Oprettelse og redigering af konfigurationer foregår direkte i Azure Portal. F.eks. kan man oprette en nøgle DynamicConfiguration:GlobalOptions:Title med værdien "ASP.NET Core 9", og opdatere sentinel-nøglen DynamicConfiguration:Sentinel fra "1" til "2" for at udløse en opdatering. Ændringen slår igennem i applikationen i realtid, og brugeren kan opleve effekten ved blot at opdatere siden – uden at serveren genstartes.

Dette realtidsbaserede adfærdsskifte er kernen i moderne cloud-native applikationer og understøttes af principperne i The Twelve-Factor App-metoden. Et af faktorerne handler specifikt om konfigurationsstyring og foreskriver, at konfigurationer skal være adskilt fra koden og let kunne ændres mellem udrulninger. Azure App Configuration efterlever dette ved at tilbyde centraliseret, dynamisk og kontekstsensitiv konfigurationsstyring.

I applikationer med krav til hurtig reaktionstid, sikkerhedsadskillelse mellem miljøer og CI/CD-processer, bliver denne tilgang ikke blot en god praksis – men en nødvendighed. Samtidig bliver muligheden for at ændre applikationsadfærd i realtid et stærkt redskab i brugeroplevelsesoptimering, A/B-testning og nedetidshåndtering.

Denne arkitektur fremmer robusthed, fleksibilitet og kontrol – tre grundpiller i moderne softwareudvikling i skyen.

Hvordan aktiverer man funktioner dynamisk i ASP.NET Core 9 med feature toggles?

At kunne aktivere eller deaktivere funktionalitet i en applikation i realtid uden at skulle genudrulle kode er ikke blot et teknisk trick, men en fundamental tilgang til moderne softwareudvikling. Dette muliggøres ved brug af feature toggles, også kaldet feature flags, som introducerer en beslutningsmekanisme i applikationens kode. Når en bestemt funktion skal testes, justeres eller gradvist frigives, kan toggles anvendes til at styre adfærden, ofte uden at brugeren bemærker det – men med fuld kontrol og transparens for udviklingsteamet.

Den reelle styrke i denne teknik ligger i dens fleksibilitet. Feature toggles giver mulighed for kontrollerede udrulninger, hvor funktioner gradvist introduceres til et begrænset antal brugere. Det bliver dermed muligt at overvåge performance og indsamle brugerfeedback uden at påvirke den brede brugerbase. Ligeledes bliver A/B tests mere præcise og hurtigere at gennemføre, idet forskellige implementeringer kan vises for forskellige segmenter. Og skulle noget gå galt, kan funktionaliteten omgående slås fra – uden ny deployment.

I ASP.NET Core 9 bliver dette især kraftfuldt, når toggles integreres med Azure App Configuration. Her håndteres feature flags centralt og kan opdateres direkte gennem Azure-portalen. SDK’en fra Microsoft gør det muligt at bruge toggles via IFeatureManager, attributter som FeatureGate, eller tag helpers direkte i Razor views. Applikationen kobles til Azure App Configuration ved hjælp af builder.Services.AddAzureAppConfiguration() og builder.Services.AddFeatureManagement() samt opsætningen i Program.cs, hvor blandt andet cache-intervallet for feature flags kan konfigureres.

Fra et arkitektonisk perspektiv skal toggles dog ikke introduceres vilkårligt. For at undgå uhåndterlig kompleksitet skal udvikleren følge Single Responsibility Principle (SRP). Det betyder, at hver klasse eller komponent i applikationen skal have ét klart afgrænset ansvar. Feature toggles må ikke føre til, at gamle og nye implementeringer blandes i samme service. I stedet bør alternative implementeringer pakkes ind i egne klasser, og afhængigheder injiceres via DI og factory-metoder, så applikationen forbliver vedligeholdelsesvenlig og skalerbar.

Når toggles anvendes i visningslaget via tag helpers, bliver det muligt at vise eller skjule dele af brugergrænsefladen afhængigt af hvilke funktioner der er aktiveret. Dette integreres ved at tilføje tag helper-funktionalitet i _ViewImports.cshtml og anvende betingede elementer i f.eks. Index.cshtml. Resultatet er en dynamisk UI-adfærd styret direkte fra konfigurationslaget.

Vigtige elementer i denne tilgang er ikke kun de tekniske, men også de organisatoriske. Feature toggles er lige så meget et redskab for produktledelse og DevOps som for udviklere. Rigtigt anvendt muliggør teknikken kontinuerlig levering, hvor kode altid er klar til produktion, men funktionalitet aktiveres målrettet og taktisk. Det reducerer risiko, fremmer eksperimentation og adskiller udrulning fra aktivering.

Det er dog afgørende at forstå den potentielle tekniske gæld ved overdreven eller forkert brug af toggles. Hvis toggles ikke fjernes, når de ikke længere er nødvendige, ophobes teknisk støj og kompleksitet. Det er vigtigt med klare processer for opfølgning og oprydning. Feature toggles bør derfor betragtes som midlertidige værktøjer, ikke permanente arkitekturmekanismer.

Endvidere kræver implementering af toggles en bevidsthed om versionsstyring, teststrategier og sikkerhed. Fordi toggles potentielt ændrer applikationens adfærd i realtid, skal de inkluderes i test- og valideringsscenarier. Desuden skal adgangen til at ændre toggles kontrolleres, især i systemer med følsomme eller kritiske funktioner.

Feature toggles repræsenterer ikke blot et teknisk værktøj, men en filosofi i moderne udvikling – hvor fleksibilitet, risikoaflastning og kontinuerlig forbedring står i centrum. Det kræver disciplin at implementere korrekt og en forståelse for både designprincipper og driftsmodeller.