Logic Apps og langtkjørende jobber
🌰 I et nøtteskall
- Logic Apps er et low-code verktøy i Azure
- Om man kjører en HttpRequest-action har denne en timeout på 2 minutter
- For å kunne kjøre langtkjørende jobber hvor man skal bruke resultatet kan man skru på "Asynchronous Pattern" og bruke følgende asynkrone mønster
Detaljer
En langtkjørende jobb, eksempelvis en excel rapport, kan ikke hentes av en logic app dersom tiden til endepunktet man bruker overskrider 2 minutter.
Her er et eksempel på en Azure Function App som implementerer en asynkron som automatisk er støttet i Logic Apps.
Den består av tre deler:
- HTTPTrigger for å starte jobben
- Tar imot søkeparametre
- Oppretter en melding på køen (automatisk tilgjengelig på funksjonens tilhørende storage account) med en generert ID og søkeparametre fra HTTP forespørselen
- Returnerer en HTTP response med status 202 Accepted, og Location header satt til status endepunktet.
- HTTP Trigger funksjon for å sjekke status
- Sjekker om en blob er blitt generert i blob storage på storage account tilhørende funksjonen.
- Hvis nei, returner HTTP respons med status 202 Accepted og Location header satt til status endepunktet (som er seg selv)
- Hvis ja, last ned filen og returner 200 OK med filen.
- QueueTrigger for å utføre den faktiske jobben og skrive til blob storage
- Trigges når funksjon 1 legger til en ny melding på køen
- Genererer en excel rapport
- Skriver resultatet til en blob storage
Eksempel
Nuget pakker brukt i dette eksempelet
using System.IO;
using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Reporting
{
[StorageAccount("AzureWebJobsStorage")]
public class ReportingFunctions
{
[FunctionName("StartGeneratingReport")]
public IActionResult StartGeneratingReport(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "report/generate")]
HttpRequest req,
[Queue("report")] ICollector<ReportQueueMessage> queueCollector,
ILogger log
)
{
if (!req.Query.TryGetValue("dateFrom", out var dateFrom))
{
return new BadRequestObjectResult("dateFrom is required");
}
SearchQuery searchQuery;
var reqId = Guid.NewGuid();
try
{
searchQuery = GetSearchQuery(req);
queueCollector.Add(new ReportQueueMessage(reqId, searchQuery));
}
catch (BadHttpRequestException e)
{
return new BadRequestObjectResult(e.Message);
}
var location = $"{req.Scheme}://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/api/report/status/{reqId}";
return new AcceptedResult(
location,"Request accepted for Processing");
}
[FunctionName("ReportGeneratorJob")]
public async Task GeneratorJob(
[QueueTrigger("report")] ReportQueueMessage queueMessage,
[Blob("generated-reports", FileAccess.Write)]
BlobContainerClient inputBlob,
ILogger log
)
{
var fileName = $"{queueMessage.ReportId}.xlsx";
var stream = new MemoryStream();
//TODO: Generate excel file and save it to the stream
await inputBlob.CreateIfNotExistsAsync();
var blob = inputBlob.GetBlobClient(fileName);
await blob.UploadAsync(stream, new BlobHttpHeaders(){ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
}
[FunctionName("ReportStatus")]
public async Task<IActionResult> CancellationReportStatus(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "report/status/{reqId}")]
HttpRequest req,
[Blob("generated-reports/{reqId}.xlsx", FileAccess.Read)]
BlockBlobClient blob,
string reqId,
ILogger log
)
{
var exists = await blob.ExistsAsync();
if (exists)
{
MemoryStream stream = new MemoryStream();
await blob.DownloadToAsync(stream);
return FileResult(stream, $"{reqId}.xlsx");
}
var location = $"{req.Scheme}://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/api/report/status/{reqId}";
return new AcceptedResult(location,"Request is processing");
}
private static FileContentResult FileResult(MemoryStream fileStream, string fileName)
{
return new FileContentResult(fileStream.ToArray(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = fileName
};
}
private static SearchQuery GetSearchQuery(HttpRequest req)
{
if (!DateTimeOffset.TryParse(req.Query["dateFrom"], out var dateFrom))
{
throw new BadHttpRequestException("Invalid dateFrom");
}
return new SearchQuery(dateFrom);
}
public record SearchQuery(
DateTimeOffset DateFrom
);
public record ReportQueueMessage(Guid ReportId, SearchQuery query);
}
}