Today’s tale is the narrative retelling of what became a bit of an adventure. The primary purpose is to share the unorderly process by which one developer — me — learned and implemented a new technology, including the occasional journey down paths that culminated in undesired destinations. Secondarily, today’s entry might also provide to my fellow developers some insight into how to successfully implement an Azure Function.
So, what is an Azure Function? 🔍
Per Anna Fitzgerald at HubSpot, a serverless function is “a programmatic function written by a software developer for a single purpose. It’s then hosted and maintained on infrastructure by cloud computing companies. …”
While true, “single” is just one adjective that we might use to describe our function’s purpose. But also…
Our function’s glorious purpose is to serve as the email mechanism for a contact form I am building for the DjBogoodski site still in development. The function will be triggered by an HTTP request, containing the form-data provided by the user. The function will be integrated with the SendGrid API to handle the output, transmitting by email the user-provided data to a specified inbox. Our “cloud computing company”, in this case, will be Microsoft via its Azure platform, due only to my own familiarity.
The Azure Function provides convenience because, currently, this email service is the only custom server-side functionality intended for the DjBogoodski website. As an otherwise static React single page application, the website provides no impetus for building out a more robust server implementation.
The only obstacle is that … I’ve never actually built a serverless function before. But, I’m game. So, let’s go.
We approached this journey with little preparation. That is to say that I sauntered up to the starting gate with general conceptual awareness of Azure Functions but no practical knowledge regarding their actual implementation. So, we are going to learn together, embarking on discovery of the importance of “bindings” in Azure Functions, other learning, and an unintended detour provoked by a tricky software malfunction.
Implementing an Azure Function
There appears to be two general ways to create an Azure Function. The options include logging into the Azure Portal and spinning up a function utilizing the portal’s integrated code editor. Another is to open Visual Studio and work from a provided Azure Function template. A template featuring an integration with SendGrid was among those available.
I initially chose the first option, working inside the Azure Portal to set up a preconfigured Azure Function integrated with the SendGrid API. I was surprised to discover that my new function appeared to be basically operational right out of the box.
The Azure Portal provided testing functionality, allowing me to submit an HTTP POST request to my Azure Function, passing a JSON object. Executing this test, I received from the server status code 200, success, and found an email successfully transmitted to the intended inbox.
But, this revealed the question that would soon bedevil us: How do we interact with our Azure Function outside of the Azure Portal environment? For that, I turned to the Postman app that I use for API testing.
Some context before diving into Postman: Our preconfigured Azure Function was set to be triggered whenever a message was received by a queue in Azure storage. I didn’t implement this configuration; it was a default setting when I spun up the function template. Digging through my account in the Azure Portal, I couldn’t locate any storage options where I had activated a queue and couldn’t otherwise understand how it was intended for me to engage with this queue. So, you know…
… Confusion abounds.
I wrestled with this for a while before settling on the realization that perhaps I had embarked down the wrong fork when initially departing on our path toward function implementation. When first spinning up our function, I chose the template that came integrated with SendGrid. The temptation of the SendGrid integration compelled me to overlook other function templates, including one described as an “HTTP Trigger”.
Realizing my potential mistake, I returned to the starting line; this time with the wisdom of experience driving me toward the HTTP Trigger template. Instead of the template integrated with SendGrid.
I also decided this time to attempt to build our function in Visual Studio, rather than inside the Azure Portal. Which led to the first potential hiccup that I would like to help other developers avoid: When building an Azure Function within the Azure Portal, you are likely writing a C# script. When creating the function via template in Visual Studio, you are building out more of a C# project or solution. The script has some minor syntax differences from the C# I am used to writing for my .NET projects and, importantly, collects the function bindings — which we will address later — in a function.json file. In Visual Studio, these bindings are written directly into the method signature, not in a separate JSON file.
In the image above, from my Visual Studio, the HttpTrigger is configured via an attribute. When building out the function script within the Azure Portal, the HTTP Trigger is configured in the function.json file.
I published my new HTTP Trigger function via Visual Studio to Azure. Important note about what happened here “behind the scenes”, because it may affect your own Azure Function development: Visual Studio zipped the function and basically FTP’ed the zipped file to Azure. Once transferred, Azure ran the function from the zipped file. Which, consequently, restricted me from being able to edit the function from within the Azure Portal.
Okay, back to Postman.
I apologize for the momentary diversion from discussion of Postman. But consider it metaphorical because Postman, we are about to find, is going to introduce a distraction of its own.
The Azure Function screen within the Azure Portal displayed a “Get Function URL” button that provided the endpoint for engaging the function. Using this URL, along with the value for my function key (for authentication), I submitted a POST request via Postman, attempting to pass the same JSON object that had tested successfully within the Azure Portal.
Status Code: 400. Bad Request — Invalid Hostname.
Much googling, I performed.
With great trepidation, I submitted my question like raw meat to the carnivores at StackOverflow.
I even took my issue to social media, feeling a bit like a town crier in some digital city square.
This research did not lead to anything that resolved the issue at hand. It did, however, teach me more about how Azure Functions … function. For example, the aforementioned bindings? They are essential.
Per the bindings in the image above, my function required that a parameter named “req” be passed to it. In the template “req” was of type HttpRequest. The docs address this:
The trigger input type is declared as either
HttpRequest
or a custom type. If you chooseHttpRequest
, you get full access to the request object. For a custom type, the runtime tries to parse the JSON request body to set the object properties.
The second object in the bindings array configures the Azure Function output. That output, in this case, was handled via integration with SendGrid. SendGrid, as I understand it, requires the Azure Function to return type SendGridMessage.
All of that leads us to the function itself…
Here’s the thing. I don’t know what you consider a “long time” to work on resolving a bug. But, in this case, “all my free time after my work shift” felt like exactly that. And that is at least how long I tried unsuccessfully to get Postman to pass a POST request to my Azure Function that returned status code 200 and an email in the appropriate inbox. Among the things I tried:
- I embedded my function key value as a query string in the URL;
- I passed the function key as the value to the “x-functions-key” in the request headers;
- I tried sending the request to an alternate URL that conformed to a format described in the documentation for non-HTTP Trigger Azure Functions:
- I removed all the properties that Postman added by default to the request headers.
“I did everything I could.” 😭
Here’s where the plot twists:
I googled something like “test API” and was provided a link to https://reqbin.com/.
I copied the URL that embedded the function key as a query string (again, this URL was available to me via the “Get Function URL” button in the Azure Portal). I pasted the URL in the appropriate text field on ReqBin and entered the same JSON payload that had successfully tested via the Azure Portal. Clicked “Send” on the POST request and, what do you know? Success. Status code: 200. Email transmitted to the specified inbox.
The issue was Postman all along.
Like, I still don’t know what exactly was Postman’s issue. And maybe will publish an addendum here when I discover its source. But, for now, I’m done with you, sir.
My Azure Function is operational. Awesome. Still have work to do. I have to add exception handling and other rigor to the method’s logic. And the form on the DjBogoodski site is not yet complete. So, there is that to build. But I’m declaring this segment of the race “complete”.
What did we learn?
- Azure Functions can be written in the IDE via C# or in the Azure Portal with C# script;
- The SendGrid Azure Function template is triggered by receipt of messages to a queue, not necessarily by HTTP request;
- Understanding bindings is critical to successfully debugging Azure Functions;
- Configuration for Azure Functions when using C# script in the Azure Portal is managed in JSON files, including function.json for bindings. Configuration can be written directly in the method when using C# in Visual Studio.
- If the “authLevel” configuration is set to anything other than “anonymous”, a valid key must be passed with the POST request;
- Postman owes us. Bigly.
That’s really all I have for you today.
I hope you enjoyed venturing on this path of discovery with me. Here is a glimpse of the DjBogoodski site, still in works, as a departure gift and token of my appreciation for your dedicated reading. :) Thank you.