As I mentioned in a recent post, I have been spending some time getting to know Azure Functions lately. A friend and I are taking the opportunity to learn about Azure Functions and build something that will help us with activities related to the community conferences we organize. As always, this is more of a breadcrumb trail for me, but leave a comment if this helps you! I'd love to hear from you.
Why Do We Need Shared CSX Code?
To help us focus on a real world problem, we are using Azure Functions to automate the management of some social media tasks for some conferences we organize. When we accept a speaker session or confirm a sponsor, we want to schedule tweets to notify attendees to sessions and thank speakers and sponsors for supporting the event.
Our initial goal is to have a function that accepts an HttpRequest and queues the information for a Speaker, Sponsor or Session. We then want a QueueTrigger to process the speaker, sponsor or session data and schedule a tweet. The Speaker, Sponsor and Session objects will all be strongly typed POCO objects, but we don't want to reproduce the class definitions across functions in our Function App.
Loading Shared CSX Files in Azure Functions
For this post, we'll just focus on a simple example of the Http POST to receive a Speaker and then the QueueTrigger code to deserialize the Speaker object from the queue. The following code is an HttpTrigger in C# that simply takes an HttpRequestMessage and adds the content to a Azure Storage Queue.
Notice the #load "..\Shared\Speaker.csx"
line at the top of the code. This line references the Speaker.csx file that contains our Speaker class definition that we want to serialize into the queue.
#load "..\Shared\Speaker.csx"
#r "Newtonsoft.Json"
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Diagnostics;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, IAsyncCollector<string> speakersOut, TraceWriter log)
{
dynamic data = await req.Content.ReadAsAsync<object>();
HttpResponseMessage res = null;
string twitterHandle = data?.twitterHandle;
if (!string.IsNullOrEmpty(twitterHandle))
{
var speaker = new Speaker(){FirstName = data?.firstName, LastName = data?.lastName, TwitterHandle = data?.twitterHandle };
await speakersOut.AddAsync(JsonConvert.SerializeObject(speaker));
res = new HttpResponseMessage(HttpStatusCode.OK);
}
else
{
res = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent("Please pass a valid speaker.")
};
}
return res;
}
Once the await speakersOut.AddAsync(JsonConvert.SerializeObject(speaker));
completes, the speaker message is now on the queue. Time to deserialize. The ProcessSpeaker function receives the message containing the Speaker object and deserializes to process the Speaker. The var speaker = JsonConvert.DeserializeObject<Speaker>(queuedSpeaker);
line deserializes the speaker. I am simply using the object to call a function on the Speaker instance and write out the twitter handle to the logs in this sample, the tweeting comes later.
#load "..\Shared\Speaker.csx"
#r "Newtonsoft.Json"
using System;
using Newtonsoft.Json;
public static void Run(string queuedSpeaker, TraceWriter log)
{
log.Info($"C# Queue trigger function processed: {queuedSpeaker}");
var speaker = JsonConvert.DeserializeObject<Speaker>(queuedSpeaker);
log.Info($"{speaker.FullName()} tweets from {speaker.TwitterHandle}");
}
Finally, the Speaker.csx file that has the properties and methods we want shared across our functions is very simple for this example, but there is real power here. For example, our class that is responsible for Tweets might be a shared class that we can consume across multiple functions. Below is a screen shot of the Speaker class being edited in the Kudu console.
Global Configuration for Azure Functions
What if some of your code changes, how will your functions reload and use the new changes? At the root of a Function App's directory structure there is a host.json
file, which is mapped to the WebJobs SDK JobHostConfiguration settings class. If you are composing your functions within the Azure Portal, you can get to this file from the Kudu console for your Function App as shown above.
If you have configured continuous integration, you can access this file at the root of you project.
The key setting we are concerned with the "watchDirectories": ["Shared"],
line. Adding this property and the "Shared" folder value to our host.json
file indicates that files in the array of folders listed should be watched for changes by functions within your Function App. If there is a change to your code, the function app is restarted, recompiled and and errors are logged. For instance, while writing this post, I changed the TwitterHandle
property on the Speaker class to be Twitterhandle
and the UpsertFunction
immediately failed with an error when attempting to create the Speaker instance:
'Speaker' does not contain a definition for 'TwitterHandle'
What's Next
Azure Functions continues to capture my interest. The integration with Flow, LogicApps, simple API creation, the development model, the benefits to business, and much more make Azure Functions a compelling tool for your tool belt. Check out the Azure Functions C# developer reference for more details.
The recent release of the Visual Studio Tools for Azure Functions and the Azure Functions CLI are starting to ease the learning curve of Azure Function development, but the development experience still has some rough edges. Specifically, the local development and debugging story is a bit murky right now but getting better. I think this may be the topic of the next post!
As I understand and learn more of the functionality (see what I did there...) of Azure Functions I'll keep posting.
HTH - As always, let me know if you have a comment or suggestions.