NCRONTAB-biblioteket er ikke i sig selv en scheduler, men et værktøj til at parse cron-udtryk. Dette gør det muligt for udviklere at arbejde med tidsplaner og eksekvere handlinger baseret på tid, men det kræver et separat program, der håndterer kørsel og planlægning. I denne sammenhæng viser vi, hvordan man kan bruge NCRONTAB-biblioteket til at definere tidsintervaller og finde specifikke begivenheder inden for en given periode.

For at komme i gang skal man først bygge NCrontab.Console-projektet. Åbn filen Program.cs, fjern de eksisterende statements, og indsæt nye, der definerer et datointerval for året 2023. Du kan derefter bruge NCRONTAB-udtrykket til at generere en plan for de næste 40 begivenheder, som ville finde sted i 2023. Her er et eksempel på, hvordan koden kan se ud:

csharp
using NCrontab; DateTime start = new(2023, 1, 1); DateTime end = start.AddYears(1); WriteLine($"Start at: {start:ddd, dd MMM yyyy HH:mm:ss}"); WriteLine($"End at: {end:ddd, dd MMM yyyy HH:mm:ss}"); WriteLine(); string sec = "0,30"; string min = "*"; string hour = "*"; string dayOfMonth = "*"; string month = "*"; string dayOfWeek = "*";
string expression = string.Format("{0,-3} {1,-3} {2,-3} {3,-3} {4,-3} {5,-3}", sec, min, hour, dayOfMonth, month, dayOfWeek);
WriteLine(
$"Expression: {expression}"); WriteLine(@" \ / \ / \ / \ / \ / \ /"); WriteLine($" - - - - - -"); WriteLine($" | | | | | |"); WriteLine($" | | | | | +--- day of week (0 - 6) (Sunday=0)"); WriteLine($" | | | | +------- month (1 - 12)"); WriteLine($" | | | +----------- day of month (1 - 31)"); WriteLine($" | | +--------------- hour (0 - 23)"); WriteLine($" | +------------------- min (0 - 59)"); WriteLine($" +----------------------- sec (0 - 59)"); CrontabSchedule schedule = CrontabSchedule.Parse(expression, new CrontabSchedule.ParseOptions { IncludingSeconds = true }); IEnumerable<DateTime> occurrences = schedule.GetNextOccurrences(start, end); // Output de første 40 begivenheder foreach (DateTime occurrence in occurrences.Take(40)) { WriteLine($"{occurrence:ddd, dd MMM yyyy HH:mm:ss}"); }

I dette eksempel er tidsintervallet defineret som starten af 2023 og slutningen af året. Udtrykket "0,30 * * * * *" betyder, at der vil være en begivenhed hvert minut ved henholdsvis 0 og 30 sekunder, hver time, hver dag, hver uge og hver måned. Udtrykket bruges derefter til at generere de næste 40 begivenheder i perioden.

Det er vigtigt at bemærke, at udtrykket inkluderer sekunder, hvilket kræver, at parsing-indstillingen for sekunder også er angivet. CrontabSchedule.Parse bruges til at oprette tidsplanen, og GetNextOccurrences metoden returnerer en sekvens af alle de beregnede begivenheder. Det er muligt at justere udtrykket for at opnå specifikke tidsintervaller, f.eks. at udføre handlinger hver fjerde time ved hjælp af et udtryk som "0 0 */4 * * *", som vil give begivenheder hver dag på bestemte tidspunkter, der gentages hver 4. time.

Når du ændrer komponenterne i udtrykket, kan du eksperimentere med forskellige tidsplaner og se, hvordan de påvirker de returnerede begivenheder. Dette kan hjælpe dig med at forstå, hvordan NCRONTAB udtryk fungerer, og hvordan du kan skræddersy dem til dine behov.

NCRONTAB er kun en del af den større kontekst af automatiserede processer og serverless funktioner, som f.eks. Azure Functions. Azure Functions gør det muligt at implementere serverless applikationer, som kan håndtere tidsbestemte opgaver ved hjælp af sådanne cron-lignende udtryk. For at køre funktionerne effektivt, skal man vælge en passende hosting-model.

Azure Functions tilbyder to hosting-modeller: in-process og isolated. I in-process modellen kører funktionerne i samme proces som runtime-hosten, og funktionerne skal bruge LTS (Long-Term Support) versioner af .NET, f.eks. .NET 6. Den isolated model giver mulighed for at køre funktionerne i en separat proces, og her kan man benytte STS (Standard-Term Support) versioner af .NET, som f.eks. .NET 7.0. Den anbefalede model til langvarig brug er den in-process hosting-model, som sikrer langsigtet support og stabilitet.

Azure Functions understøtter også flere programmeringssprog, herunder C#, JavaScript, Python, Java og PowerShell, og det er muligt at udvide med brugerdefinerede håndteringsfunktioner, der tillader brug af andre sprog.

Når du arbejder med funktioner og tidsplanlægning i en serverless arkitektur, er det vigtigt at overveje både det tekniske fundament og den løbende vedligeholdelse af de valgte værktøjer og versioner. Ved at vælge den rette version af .NET og den rette hosting-model kan man sikre, at funktionerne fungerer effektivt og bliver understøttet i lang tid fremover.

Hvordan fungerer Azure Functions runtime, hostingplaner og udviklingsmiljøer i .NET?

Azure Functions runtime understøtter kun Long-Term Support (LTS) versioner af .NET, og det betyder, at hver version af Azure Functions er bundet til én specifik LTS-udgivelse for den in-process hostingmodel. For eksempel kræver Azure Functions version 3, at man anvender .NET Core 3.1, mens version 4 kræver .NET 6.0. Hvis man vælger at udvikle isolerede funktioner, er man derimod ikke begrænset til en bestemt .NET-version og kan bruge enhver version, hvilket giver større fleksibilitet, men ofte på bekostning af ydelse. For at opnå den bedste performance og skalerbarhed anbefales det at benytte in-process hostingmodellen.

Når udviklingen er testet lokalt, skal Azure Functions-projektet deployeres til en Azure hostingplan, hvor tre hovedtyper findes: Consumption, Premium og Dedicated. Consumption-planen er tæt på det ægte serverløse koncept, hvor instanser automatisk tilføjes eller fjernes baseret på belastning, og man betaler kun for den tid, funktionerne kører. Denne model skalerer automatisk og har mulighed for at sætte en timeout på eksekveringstiden, hvilket forhindrer uendelige kørselstilfælde.

Premium-planen tilbyder elastisk skalering, hvor instanser altid er varme for at undgå kolde starter, ubegrænset eksekveringsvarighed og muligheden for multicore-instanser. Den har mere forudsigelige omkostninger, men kræver altid mindst én aktiv instans, hvilket betyder en minimumsomkostning. Dedicated-planen kører på en Azure App Service-plan, der fungerer som en dedikeret serverfarm, hvilket kan være en fordel, hvis man allerede bruger App Service til andre webprojekter, da man kan samle ressourcerne. Her betaler man for App Service-planen, uanset hvor mange funktioner eller webapps der hostes.

Det er vigtigt at være opmærksom på, at både Premium- og Dedicated-planer kører på App Service-planer, og valget af den rette plan skal matche den tilsigtede hostingplan. En Elastic Premium plan som EP1 passer til Premium hosting, mens en App Service plan som P1V1 ikke skalerer elastisk og dermed er dedikeret.

Azure Functions kræver et Azure Storage-konto til håndtering af bindingsdata og triggers. Her anvendes Azure Files til lagring og kørsel af kode i Consumption og Premium planer, Blob Storage til tilstand og funktionsnøgler, Queue Storage til fejl- og retryhåndtering, og Table Storage til Durable Functions’ task hubs, der kombinerer disse lagertyper.

Lokalt kan man teste funktioner med Azurite, en open source-emulator for Azure Storage, som understøtter Blob, Queue og Table Storage. Azurite er platformuafhængig og kan installeres via Visual Studio, Visual Studio Code eller via kommandolinjen med Node.js. Efter lokal testning kan man nemt skifte til en rigtig Azure Storage-konto i skyen.

Azure Functions har tre niveauer af autorisation for adgang til funktioner: Anonym (ingen nøgle), Function (funktionsspecifik nøgle) og Admin (masternøgle). Disse API-nøgler administreres gennem Azure-portalen og sikrer fleksibel adgangskontrol.

Afhængighedsinjektion i Azure Functions bygger på standard .NET-mekanismer, men implementeringen afhænger af hostingmodellen. For at registrere services skaber man en klasse, der nedarver fra FunctionsStartup og overskriver Configure-metoden, hvor man tilføjer services til IFunctionsHostBuilder. Da funktionerne normalt er statiske metoder, kræver brug af konstruktørinjektion, at funktionerne implementeres i instansklasser.

Azure Functions Core Tools giver et lokalt udviklingsmiljø og runtime til Windows, macOS og Linux, og inkluderer skabeloner til hurtig oprettelse af funktioner. Det er med i Visual Studio 2022’s udviklingspakke, men kan også installeres via npm, MSI eller andre pakkehåndteringssystemer. Core Tools muliggør lokal kørsel og debugging før deployment til skyen.

Ved oprettelse af et Azure Functions-projekt anbefales det at starte lokalt for bedre kontrol og fejlfinding, før man deployerer til Azure. I Visual Studio 2022 kan man vælge projektet "Azure Functions", angive .NET-version, trigger-type, autorisationsniveau og om man vil bruge Azurite til lokal lagring. Visual Studio Code kræver installation af Azure Functions-extension og dens afhængigheder, derefter kan man oprette projektmapper og arbejdsmiljøer til udvikling.

Det er vigtigt at forstå, at valget af runtime-version, hostingplan og lokal udviklingsopsætning har direkte konsekvenser for funktionernes ydeevne, skalerbarhed og omkostningsstruktur. Desuden er korrekt brug af Azure Storage og autorisationsniveauer afgørende for både funktionalitet og sikkerhed. Kendskab til disse elementer sikrer en solid og effektiv udviklingsproces i Azure Functions-økosystemet.

Hvordan man Tilføjer Et Produkt via GraphQL Mutation i .NET

I denne sektion dykker vi ned i, hvordan man kan tilføje et produkt til en database ved hjælp af GraphQL mutation i en .NET-applikation. Dette er en proces, der involverer flere trin, fra oprettelse af inputmodellen til selve mutationens implementering og udførelse af en GraphQL-anmodning. Vi ser på, hvordan man bygger og tester en mutation, som effektivt kan tilføje nye produkter i et system som f.eks. Northwind-databasen.

I den første fase defineres en AddProductInput model, som specificerer de nødvendige inputparametre, der skal til for at oprette et nyt produkt. Denne model inkluderer feltet ProductName, som er navnet på produktet, samt valgfrie felter som SupplierId, CategoryId, QuantityPerUnit, og UnitPrice. Felterne UnitsInStock, UnitsOnOrder, ReorderLevel og Discontinued beskriver produktets lagerstatus og salgsbetingelser. Dette input vil blive brugt i mutationens metode, som er ansvarlig for at tilføje produktet til databasen.

csharp
public record AddProductInput(
string ProductName, int? SupplierId, int? CategoryId, string QuantityPerUnit, decimal? UnitPrice, short? UnitsInStock, short? UnitsOnOrder, short? ReorderLevel, bool Discontinued);

I næste trin defineres en AddProductPayload klasse, som omfatter den oprettede produktmodel, der bliver returneret som resultat af mutationens udførelse. Dette er nyttigt for klienten, da den kan få adgang til den oprettede produktpost efter anmodningen er blevet udført.

csharp
public class AddProductPayload
{ public AddProductPayload(Product product) { Product = product; } public Product Product { get; } }

Mutationens metode AddProductAsync tager et AddProductInput objekt som parameter og bruger det til at oprette et nyt produkt i databasen. Produktet tilføjes derefter til Products-tabellen i en NorthwindContext-instans. Efter produktet er blevet tilføjet, gemmes ændringerne til databasen, og et AddProductPayload-objekt returneres med det nyligt oprettede produkt.

csharp
public async Task AddProductAsync(AddProductInput input, NorthwindContext db) { Product product = new() { ProductName = input.ProductName, SupplierId = input.SupplierId, CategoryId = input.CategoryId, QuantityPerUnit = input.QuantityPerUnit, UnitPrice = input.UnitPrice, UnitsInStock = input.UnitsInStock, UnitsOnOrder = input.UnitsOnOrder, ReorderLevel = input.ReorderLevel, Discontinued = input.Discontinued }; db.Products.Add(product); int affectedRows = await db.SaveChangesAsync(); return new AddProductPayload(product); }

For at kunne bruge mutationstypen i GraphQL-tjenesten, skal vi i Program.cs sikre os, at den er registreret korrekt. Dette gøres ved at tilføje en opkald til metoden AddMutationType som vist her:

csharp
builder.Services
.AddGraphQLServer() .RegisterDbContext() .AddQueryType() .AddMutationType();

Når tjenesten er startet, kan vi bruge et GraphQL-klientværktøj som Banana Cake Pop til at interagere med GraphQL-schemaet. I dette tilfælde kan vi bruge en mutation til at tilføje et nyt produkt, f.eks. et produkt kaldet "Tasty Burgers". I Banana Cake Pop kan du derefter udføre mutationerne ved hjælp af følgende GraphQL-forespørgsel:

graphql
mutation AddProduct {
addProduct( input: { productName: "Tasty Burgers" supplierId: 1 categoryId: 2 quantityPerUnit: "6 per box" unitPrice: 40 unitsInStock: 0 unitsOnOrder: 0 reorderLevel: 0 discontinued: false } ) { product { productId productName } } }

Når mutationens anmodning er sendt, og den er blevet korrekt behandlet, kan resultatet bekræftes via følgende svar:

json
{ "data": { "addProduct": { "product": { "productId": 79, "productName": "Tasty Burgers" } } } }

Denne proces demonstrerer, hvordan GraphQL-mutationer fungerer i praksis og giver udvikleren mulighed for hurtigt og effektivt at interagere med et GraphQL API. For at sikre, at mutationerne fungerer korrekt, er det vigtigt at forstå både de tekniske aspekter af GraphQL og de forretningsregler, der skal implementeres i mutationerne.

Når du arbejder med GraphQL, skal du huske på, at mutationer er designet til at ændre tilstanden på serveren, f.eks. ved at tilføje, opdatere eller slette data. Det er vigtigt at forstå forskellen på en mutation og en query: mens queries bruges til at hente data, er mutationer beregnet til at ændre data på serveren. Det er også afgørende at håndtere fejl korrekt under mutationer, da der kan opstå problemer som følge af forkerte input eller serverfejl.

Endelig bør du være opmærksom på, at testning og validering af inputdata er vigtige skridt i udviklingen af robuste og pålidelige GraphQL-applikationer.