On with the show. Today I will show you a simple process that I have automated with the Azure Event Grid and Logic Apps, sending an invoice PDF to the Azure Event Grid. Since we have events inside Business Central, all we have to do is subscribe to an event in an extension and send it to the Azure Event Grid. How hard can it be?
Related reading A monumental event, part 1
Of course we first need to create an Event Grid Topic. The Event Grid is completely integrated into Azure. You can subscribe to any number of built in events, like a notification that a blob has been created on the Azure Blob Storage. For applications that have no built in Event Grid events you can also create a custom topic. Once that is up and running you can send events to it. The Event Grid works with a Push-Subscribe method. We send events with a push message, an HTTP POST, and receive events by subscribing to a topic, a webhook. For this blog I will only discuss the push, subscribing to events will be discussed in blog post 3 in this series.
For an in depth exploration of the event grid this would be a good start. For the purpose of this blog I have created a new event grid topic and called it red-blog. From this event we need the event URL and the access key.
To send a BC event to the event grid we need to subscribe to a Business Central event and create a call to the Event Grid REST API, it is really simple. the Event Grid message format has space for your own message JSON so you can send all the data you may need. In my case I need to send some invoice metadata and the PDF file as a base64 string. You can also choose to just send an API URL for your resource so any application that receives the event can fetch the necessary data. Personally I prefer to send as much data as I can so Business Central does not get burdened with any unnecessary web service requests. The message format of the Event Grid message body is this. In this JSON message we find a data object. In this data object we can nest our payload, in any JSON format we want.
[ { "subject": string, "id": string, "eventType": string, "eventTime": string, "data":{ object-unique-to-each-publisher }, "dataVersion": string, "metadataVersion": string } ]
In my Business Central Code I create an event subscription to the event I need. When my event gets triggered the first thing I do is to create the Event Grid JSON with the JsonObject datatype.
local procedure CreateBody(JobQueueEntry: Record "Job Queue Entry") message: JsonArray var RecRef: RecordRef; body: JsonObject; begin RecRef.Get(JobQueueEntry."Record ID to Process"); RecRef.SetRecFilter(); body.Add('id', JobQueueEntry.ID); body.add('eventType', JobQueueEntry.Description); body.add('subject', StrSubstNo('%1 %2', RecRef.Name, RecRef.GetFilters())); body.Add('eventTime', CurrentDateTime()); body.Add('data', GetRecData(RecRef)); message.Add(body); end; local procedure GetRecData(RecRef: RecordRef) data: JsonObject var Base64: Text; FileType: Text; begin data.Add('table', RecRef.Name); data.Add('company', CompanyName); data.Add('bcId', GetId(RecRef)); data.Add('bcData', GetProperties(RecRef)); GetPdf(RecRef, Base64, FileType); data.Add('file', Base64); data.Add('filetype', FileType); end; local procedure GetPdf(RecRef: RecordRef; var Base64: Text; var FileType: Text); var SalesInvoiceHeader: Record "Sales Invoice Header"; ReportSelections: Record "Report Selections"; Base64Convert: Codeunit "Base64 Convert"; TempBlob: Codeunit "Temp Blob"; FileManagement: Codeunit "File Management"; Instr: InStream; RecVar: Variant; CustomerNo: Code[20]; begin case RecRef.Number of Database::"Sales Invoice Header": begin RecRef.SetTable(SalesInvoiceHeader); RecVar := SalesInvoiceHeader; CustomerNo := SalesInvoiceHeader."Bill-to Customer No."; end; else exit; end; ReportSelections.GetPdfReportForCust(TempBlob, ReportSelections.Usage::"S.Invoice", RecVar, CustomerNo); TempBlob.CreateInStream(Instr); Base64 := Base64Convert.ToBase64(Instr); FileType := 'pdf'; end;
In the above code you will find three functions. The first created the Event Grid JSON body. For the body object the GetRecData function is called. You will notice I have used my own JSON format for the data object. We will get to that when we get to using the event in Azure.
To send the event to the Event Grid all we need to do is send the JSON body we created to the Event Grid Topic we created earlier. To do so we need to add a request header with the API key. We will send the request to the URL we copied from the Event Grid Topic
local procedure SendMessage(message: JsonArray) Result: text var Client: HttpClient; Response: HttpResponseMessage; Content: HttpContent; ContentHeaders: HttpHeaders; begin Client.DefaultRequestHeaders.Add( 'aeg-sas-key', 'W+rdBTon0hNgMMv8pYiPlDRpqM31AlGwPKVeJ9CSgiM='); Content.WriteFrom(Format(message)); Content.GetHeaders(ContentHeaders); ContentHeaders.Clear(); ContentHeaders.Add('Content-Type', 'application/json'); if not Client.Post( 'https://red-blog.westeurope-1.eventgrid.azure.net/api/events', Content, Response) then Error(CannotConnectErr); if not Response.IsSuccessStatusCode then Error(WebServiceErr, Response.HttpStatusCode, response.ReasonPhrase); Response.Content.ReadAs(Result); end;
A second design decision I made was to make all Event Grid web service calls run asynchronous by sending them with the job queue. The advantage of that is of course that my primary process is not delayed and I am sure that I can see an error message when the message does not get delivered.
The full demo code can be found here.
When that is done I can create a simple application to subscribe to my Event Grid events and do something with the data it receives. You can use any kind of Azure application to subscribe to your event, like an Azure Function or a Logic App, or you can choose to write an application that can subscribe to the Event Grid webhook. I generally use Logic Apps to process my BC events as they are easy to create for non developers. For this example I have created a Logic App that is subscribed to my Event Grid Topic webhook.
When I run my code this is what the Logic App receives.
{ "id": "{1A49F23D-CADA-40BD-9A6B-57D8450DEB6C}", "eventType": "Red-Salesdocposted", "subject": "Sales Invoice Header No.: 103222", "data": { "table": "Sales Invoice Header", "company": "CRONUS NL", "bcId": "{00000000-0000-0000-0000-000000000000}", "bcData": { "no.": "103222", "bill-to Customer No.": "40000", "external Document No.": "", "your Reference": "OPEN", "shortcut Dimension 1 Code": "", "shortcut Dimension 2 Code": "KLEIN" }, "file": "BASE64 STRING CONTAINING THE REPORT", "filetype": "pdf" }, "dataVersion": "", "metadataVersion": "1", "eventTime": "2021-06-22T19:08:39.549Z", "topic": "/subscriptions/..." }
The main advantage here of course is scalability and ease of development. I can easily create another subscriber to my Topic that can email my customer, or store the PDF file in OneDrive or the Azure Blob storage. The list is endless. The Azure Event Grid will scale to match you requirements. Unlike your Business Central service I might add.
Photo by Pablo Heimplatz on Unsplash