Introduction
While at Microsoft Ignite in September I started playing around with some of the Azure cloud offerings such as Function Apps, Event Hub, and Cosmos DB. I was able to quickly connect the resources together to create a sample solution and wanted to document the process.
The sample solution will consist of a Xamarin application which allows a person to start a new work period by calling a Function App. The Xamarin application will also send location data to an Event Hub. We will create a Function App which subscribes to the Event Hub as a processor and stores the location in Cosmos DB. We will also update our Function App to send an email whenever it starts a new work period. Here is a diagram that shows the high-level architecture.
Everything we are going to build can be done with a free Azure account which you can sign-up for today. It includes $200 to spend on any resources you would like. In addition you get access to over 25 products for free including 5GB of Cosmos DB, 1,000,000 Function requests per month, and 8,000 messages per day in the IOT Hub. The Xamarin application we are going to build can be created with Visual Studio Community addition which is free and runs on both Windows and macOS.
Xamarin Application
Once you have installed Visual Studio go ahead and run it. From the file menu select File -> New -> Project. Select Cross-Platform and then select the Cross Platform App (Xamarin) project template. If you do not see Cross-Platform as an option please run the Visual Studio installer and make sure you have the Mobile development with .NET workload
installed. Now give your project a name and click OK. Select the Blank App template. Make sure that Xamarin.Forms and Shared Project are selected as shown below and then click OK.
When prompted for which version of Windows SDK you can leave it the default or change it to the specific version you wish to target. Here is the documentation to help choose a SDK version.
Since I am an Android phone user I am going to be running the application on a real Android device. You can choose to run the application in one of the bundled emulators for Android or iOS or just run the UWP application directly on your development machine. One recommendation I have if you are doing Android development is to use Vysor to mirror your device’s screen on your development machine. It saves time not having to pick-up the device and interact with it physically. It also allows me to leave an Android device plugged into my development machine and remote into it and continue developing without having to be physically sitting at the machine.
If you run the application now you see a Welcome to Xamarin.Forms screen displayed.
Let’s add a button to the screen. There should be 4 projects in the solution [AppName].Android, [AppName].iOS, [AppName].UWP, and one just [AppName]. To keep from referring to the solution as [AppName] we will assume our app is named Demo01. Not a huge improvement, but at least it is a name. The project Demo01 is our Xamarin.Forms shared library which will contain all of our changes for this sample. Open MainPage.xaml and copy the following code just below the existing label control.
<Button x:Name="StartWorkPeriodButton" Clicked="StartWorkPeriod_Clicked" Text="Start Work Period" VerticalOptions="StartAndExpand" HorizontalOptions="CenterAndExpand" />
We will use this button to submit an HTTP request to a Function App. Now open the MainPage.xaml.cs file. Copy the code below.
private const string StartWorkPeriodUri = "[FUNCTION_APP_URL_GOES_HERE]"; private static readonly HttpClient client = new HttpClient(); private void StartWorkPeriod_Clicked(object sender, EventArgs e) { Console.WriteLine("Start WorkPeriod Clicked"); StartWorkPeriod(); } public static async Task StartWorkPeriod() { var workPeriod = new { PersonId = "1", PersonName = "John Doe", WorkPeriodStartDateTime = DateTime.UtcNow.ToString("o") }; var workPeriodSerialized = JsonConvert.SerializeObject(workPeriod); // Wrap our JSON inside a StringContent which then can be used by the HttpClient class var httpContent = new StringContent(workPeriodSerialized, Encoding.UTF8, "application/json"); var response = await client.PostAsync(StartWorkPeriodUri, httpContent); var responseString = await response.Content.ReadAsStringAsync(); }
You will need to add the following using statements.
using Newtonsoft.Json; using System.Net.Http;
You will need to add a System.Net.Http reference to the Demo01.Android project as well as add the Newtonsoft.Json Nuget package. Now we should be able to compile and run and see our new button. If we click the button our application throws an exception because we haven’t yet created our Function App.
Function App
Let’s go ahead and create our first Function App. I am assuming you have already created your Azure account if not go do that right now. Once you have logged into the Azure Portal click New and search for Function App.
Select Function App from the list and click create. Give your Function App a name. You can leave the rest of the options alone if you want. Note that the default option is to create a new Resource Group named the same as your Function App. A Resource Group is a container that holds related resources for an Azure solution. The resource group can include all the resources for the solution, or only those resources that you want to manage as a group. You decide how you want to allocate resources to resource groups based on what makes the most sense for your organization. Here is a link to the Azure Resource Manager documentation.
Click create and give it a couple minutes to create the resources. Once it is complete you should be presented with a Function App page like below. Click the [+] icon next to Functions to create our first Function. We will be creating an API using the CSharp language so select those options if they are not the default and click Create this function.
The default function code is essentially a hello world which looks for a name parameter in the query parameters or request body and replies back hello. You can click the run button to make a test call to the API using the test settings on the right hand pane. There is a log of the Function activity at the bottom and a link to display the function URL in the top middle as shown below.
We are going to be performing two actions in our function. First we will store the WorkPeriod data passed to our API in a Cosmos DB document database. Then we will send an email using SendGrid. To get started we click Integrate
then + New Output
button. Select Azure Cosmos DB and click Select.
Document parameter name should be outputDocument. Database name should be outDatabase. Collection Name should be MyCollection. Select the checkbox to create a new Cosmos DB database. Enter /PartitionKey in the Partition Key field. Click new
to create a new Azure Cosmos DB account. You will click Create New then provide a unique account ID. From the API dropdown select SQL which is the old Document DB API. Use your existing resource group that we created earlier and click Ok.
It will take a little while to create the account and database, but once it is done your Azure Cosmos DB account connection will be filled in and you can click save.
Now let’s add the code to write to Cosmos DB. Replace the Function code with the code below.
#r "Newtonsoft.Json" using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; public static void Run(string req, out object outputDocument, TraceWriter log) { log.Info($"C# Queue trigger function processed: {req}"); dynamic data = JObject.Parse(req); string personId = data?.PersonId; string personName = data?.PersonName; string workPeriodStartDateTime = data?.WorkPeriodStartDateTime; string workPeriodId = Guid.NewGuid().ToString(); outputDocument = new { PartitionKey = "workperiod", id = workPeriodId, personName = personName, personId = personId, workPeriodStartDateTime = workPeriodStartDateTime }; }
The #r pulls in the Nuget package for Newtonsoft.Json. The parameters are req
which is a json string representing our WorkPeriod, outputDocument
which is our object that gets stored in Cosmos DB, and log
which allows our function to write to the log stream. To test our function copy the JSON below into the test window Request Body and click Run.
{ "PersonId": "1", "PersonName": "John Doe", "WorkPeriodStartDateTime": "2017-11-05T19:27:42.627Z" }
This will call our Function and should write a new document to Cosmos DB. To check if our document made it into Cosmos DB select Resource Groups from the left navigation. Then select your resource group you created the Cosmos DB in. Click the Cosmos DB resource.
Now select Data Explorer -> MyCollection -> Documents which should list one document. When you click on that document you should see the data in that document.
Now that we have created our Function App and tested it let’s configure our Xamarin application to call it. Naviagate back to our Function App by clicking Resource Groups -> Your Resource Group -> Function App. Then click the Get Function URL
link and copy the URL.
Now paste that URL into your MainPage.xaml.cs file replacing the text “[FUNCTION_APP_URL_GOES_HERE]”.
private const string StartWorkPeriodUri = "[FUNCTION_APP_URL_GOES_HERE]";
Now run your application and click the Start Work Period button. If you refresh the Cosmos DB data explorer you should see another document now. It will be best to open the Cosmos DB Data Explorer in another tab as we will be referring back here several times.
Now lets add another button to our Xamarin application that will send location data to Azure Event Hub. We could send our location data directly to a new Function App API, but Event Hub is excellent at handling a large volume of events and provides a built-in pub/sub model. Copy the following code to MainPage.xaml.
<Button x:Name="SendLocationButton" Clicked="SendLocationButton_Clicked" Text="Send Location" VerticalOptions="StartAndExpand" HorizontalOptions="CenterAndExpand" />
Copy the following code to MainPage.xaml.cs.
private static EventHubClient eventHubClient; private const string EhConnectionString = "[EVENTHUB_URL_GOES_HERE]"; private const string EhEntityPath = "location"; private void SendLocationButton_Clicked(object sender, EventArgs e) { Console.WriteLine("Send Location Clicked"); SendLocationEvent(); } public static async Task SendLocationEvent() { // Creates an EventHubsConnectionStringBuilder object from the connection string, and sets the EntityPath. // Typically, the connection string should have the entity path in it, but for the sake of this simple scenario // we are using the connection string from the namespace. var connectionStringBuilder = new EventHubsConnectionStringBuilder(EhConnectionString) { EntityPath = EhEntityPath }; eventHubClient = EventHubClient.CreateFromConnectionString(connectionStringBuilder.ToString()); var location = await GetCurrentLocation(); if (location == null) return; try { var message = $@"{{""Latitude"":""{location.Latitude}"",""Longitude"":""{location.Longitude}""}}"; Console.WriteLine($"Sending message: {message}"); await eventHubClient.SendAsync(new EventData(Encoding.UTF8.GetBytes(message))); } catch (Exception exception) { Console.WriteLine($"{DateTime.Now} > Exception: {exception.Message}"); } await eventHubClient.CloseAsync(); } public static async Task<Position> GetCurrentLocation() { Position position = null; try { var locator = CrossGeolocator.Current; locator.DesiredAccuracy = 100; position = await locator.GetLastKnownLocationAsync(); if (position != null) { //got a cahched position, so let's use it. return position; } if (!locator.IsGeolocationAvailable || !locator.IsGeolocationEnabled) { //not available or enabled return null; } position = await locator.GetPositionAsync(TimeSpan.FromSeconds(20), null, true); } catch (Exception ex) { //Display error as we have timed out or can't get location. } if (position == null) return null; var output = string.Format("Time: {0} \nLat: {1} \nLong: {2} \nAltitude: {3} \nAltitude Accuracy: {4} \nAccuracy: {5} \nHeading: {6} \nSpeed: {7}", position.Timestamp, position.Latitude, position.Longitude, position.Altitude, position.AltitudeAccuracy, position.Accuracy, position.Heading, position.Speed); Debug.WriteLine(output); return position; }
Now we will install two Nuget packages Microsoft.Azure.EventHubs
and Xam.Plugin.Geolocator
. The Geolocator package provides cross-platform access to the location APIs.
Azure Event Hub
To create our Event Hub in the Azure Portal click New and search for Event Hub and click Create. Give the Event Hub a name, select the Basic pricing tier, and use your existing resource group. Click create.
Now we need to create an Event Hub end point. Click Event Hubs and + Event Hubs
to add a new end point. Give the Event Hub the name location
and click create.
Now we will get the Event Hub connection string by clicking the Connection Strings
link on the overview screen.
Select the RootManageSharedAccessKey and copy the Connection string-primary key.
Back in our Xamarin application replace the text [EVENTHUB_URL_GOES_HERE]
in MainPage.xaml.cs with your connection string. Now run the application and click the Send Location
button a few times. If we go to the metrics dashboard in Azure for our Event Hub we should see those messages being received.
We have our location data making it into the Event Hub, but nothing interesting is happening with those messages. Let’s create a new Function App that will subscribe to our Event Hub and write those locations to Cosmos DB. In the Azure portal navigate to your existing Function App. Click the +
icon. Select EventHubTrigger - C#
. Click new
for Event Hub connection, which will pop up a modal. Select the Event Hub we just created and click Select. Enter location
for the Event Hub Name and click Create.
All the function does currently is write the Event Hub message it receives to the log. If you run your Xamarin application and click the Send Location button now you should see your location get written to the log console of your Function App.
So we have our Xamarin application sending the location to Event Hub and our Function App receiving it. Now we just need to store that location in Cosmos DB. Copy the code below and replace the code for your Function.
#r "Newtonsoft.Json" using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; public static void Run(string myEventHubMessage, out object outputDocument, TraceWriter log) { log.Info($"C# Event Hub trigger function processed a message: {myEventHubMessage}"); dynamic data = JObject.Parse(myEventHubMessage); string personId = data?.PersonId; string latitude = data?.Latitude; string longitude = data?.Longitude; outputDocument = new { PartitionKey = "location", PersonId = personId, Latitude = latitude, Longitude = longitude, }; }
Now lets connect to our Cosmos DB.
- Click the
Integrate
link -
Click
+ New Output
-
Select Azure Cosmos DB
-
Click Select
-
Enter
/PartitionKey
in the Partition Key field -
Click Save
Let’s test our code now. Copy the following into the test window request body.
{
"PersonId": "1",
"Latitude": "38.7634182",
"Longitude": "-84.381"
}
Click Run
and if everything goes well you should be able to open the Data Explorer for our Cosmos DB and see our new location event.
Now click the Send Location
button in the Xamarin application to send a few more location events to Cosmos DB. Refresh your Data Explorer query to see the new documents.
The last feature we will add is to have our Function App API send an email notifying that a new Work Period is being started. We will use SendGrid to handle the sending of emails. They offer a free account that can send up to 40,000 messages. Once you have created your account and logged into to the portal the first thing we need to do is create an API Key for our Function App. From the portal click Settings
-> API Keys
-> Create API Key
- Enter a name for the key
-
Grant the permission to
Send Mail
- Click
Create & View
- Copy the API Key
Now in another browser tab go to your Function App in the Azure Portal. Click the Application Settings
link from the Overview page.
Add a new setting with the name SENDGRID_KEY
and paste in your SendGrid API key for the value. Click save.
Click the HttpTriggerSharp1 function and let’s add a new file to the function called project.json.
Copy the following code into the file. This will pull the in the Sendgrid library from Nuget.
{ "frameworks": { "net46":{ "dependencies": { "Sendgrid": "9.9.0" } } } }
Now select the run.csx
file and replace the code with what’s below. Replace the To address placeholder [REPLACE_WITH_YOUR_EMAIL]
with your email address if you wish to receive the email.
#r "Newtonsoft.Json" #r "SendGrid" using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SendGrid; using SendGrid.Helpers.Mail; using System; public static void Run(string req, out object outputDocument, TraceWriter log) { log.Info($"C# Queue trigger function processed: {req}"); dynamic data = JObject.Parse(req); string personId = data?.PersonId; string personName = data?.PersonName; string workPeriodStartDateTime = data?.WorkPeriodStartDateTime; string workPeriodId = Guid.NewGuid().ToString(); outputDocument = new { PartitionKey = "workperiod", id = workPeriodId, personName = personName, personId = personId, workPeriodStartDateTime = workPeriodStartDateTime }; var apiKey = Environment.GetEnvironmentVariable("SENDGRID_KEY"); var client = new SendGridClient(apiKey); var from = new EmailAddress("test@example.com", "Field Service App"); var subject = $"{personName} has Started a New Work Period"; var to = new EmailAddress("[REPLACE_WITH_YOUR_EMAIL]", "Jane Doe"); var plainTextContent = $"{req}"; var htmlContent = $"{req}"; var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent); var response = client.SendEmailAsync(msg); }
We are ready to click Save and Run
which will call our function and send the email. We can verify that SendGrid received our request by looking at the dashboard.
Summary
We have built a Xamarin mobile application that calls an Azure Function App API. The Function App stores a document in Cosmos DB and sends an email using the SendGrid service. The Xamarin app also uses the devices location services to get the devices current location and sends a message to Azure Event Hubs. We built a Function App that subscribes to the Event Hub and takes that location and stores it in Cosmos DB.