Data Points - Calling Azure Functions from the Universal Windows Platform

Data Points - Calling Azure Functions from the Universal Windows Platform

By Julie Lerman | March 2018 | Get the Code

Read the entire EF Core 2 and UWP App Data series:

Using EF Core 2 and Azure Functions to Store UWP App Data Locally and Globally, Part 1
Using EF Core 2 and Azure Functions to Store UWP App Data Locally and Globally, Part 2
Using EF Core 2 and Azure Functions to Store UWP App Data Locally and Globally, Part 3
Using EF Core 2 and Azure Functions to Store UWP App Data Locally and Globally, Part 4

Julie LermanThis is the final installment of my series on building a Universal Windows Platform (UWP) app that stores data locally and in the cloud. In the first installment, I built the UWP Cookie­Binge game, which uses Entity Framework Core 2 (EF Core 2) to store game scores onto the device on which the game is being played. In the next two installments, I showed you how to build Azure Functions in the cloud to store the game scores to and retrieve them from a Microsoft Azure Cosmos DB database. Finally, in this column, you’ll see how to make requests from the UWP app to those Azure Functions to send scores, as well as receive and display top scores for the player across all of her devices in addition to top scores for all players around the globe.

This solution also enables users to register in the cloud and tie any of their devices to that registration. While I’ll leverage that registration information to send and retrieve scores from the Azure Functions, I won’t be describing that part of the application. However, you can see that code in the download, including code for the new Azure Functions I created for registration.

Where We Left Off

A little refresher will help reacquaint you with the UWP app and the Azure Functions I’ll be connecting.

In the CookieBinge game, when a user completes a binge, they have two buttons to choose from to tell the app that they’re done. One is the “Worth it” button to indicate that they’re finished and are happy about all the cookies they scarfed down. The other is the “Not worth it” button. In response to these click events, the logic leads to the BingeServices.RecordBinge method, which uses EF Core 2 to store the data to the local database.

The app also has a feature that displays the top five scores from the local database.

There are three Azure Functions that I built in my last few columns. The first receives the game score, along with the player’s UserId (assigned by the regis­tration feature) and the name of the device on which the game was played. The function then stores this data into the Cosmos DB database. The other two functions respond to requests for data from the Cosmos DB database. One receives the player’s UserId and returns their top five scores sent from all of the devices on which they play. The other simply returns the top five scores stored in the database regardless of the player.

So now the task is to integrate the Azure Functions into the game. First, at the time the game score is stored into the local database, the app should also send the score and other relevant data to the StoreScore function. Second, at the time the app reads the score history from the local database, it should also send requests to the functions that return the scores and display the results of those requests, as shown in Figure 1.

Scores from the Azure Cosmos DB Database Retrieved from Azure Functions
Figure 1 Scores from the Azure Cosmos DB Database Retrieved from Azure Functions

Communicating with the Web from the UWP

The UWP framework uses a special set of APIs for making Web requests and receiving responses. In fact, there are two namespaces to choose from and I recommend reading the MSDN blog post: “Demystifying HttpClient APIs in the Universal Windows Platform” at bit.ly/2rxZu3f. I’ll be working with the APIs from the Windows.Web.Http namespace. These APIs have a very specific requirement for how data is sent along with any requests, and that means a little extra effort. For cases where I need to send some JSON along with my request, I’ve leveraged a helper class, HttpJsonContent, that will combine the JSON content together with the header content and perform some additional logic. HttpJsonContent requires that I send my JSON in the format of a Windows.Data.Json.JsonValue. So you’ll see where I’ve taken those steps. Alternatively, I could explicitly set the header content on the HttpRequest and then post as a String using the StringContent method, as demonstrated at bit.ly/2BBjFNE.

I encapsulated all of the logic for interacting with the Azure Functions into a class called CloudService.cs.

Once I figured out the pattern for using the UWP request methods and how to create JsonValue objects, I created a method called CallCookieBingeFunctionAsync that encapsulates most of that logic. The method takes two parameters: the name of the Azure Function to call and a JsonValue object. I also created an overload of this method that doesn’t require the JsonValue object parameter.

Here’s the signature for that method:

C#
Copy
private async Task<T> CallCookieBingeFunctionAsync<T>(string apiMethod,   JsonValue jsonValue)

As there are three different Azure Functions I need to call, let’s start with the simplest—GetTop5GlobalUserScores. This function doesn’t take any parameters or other content and returns results as JSON.

The GetTopGlobalScores method in the CloudService class calls my new CallCookieBingeFunctionAsync method, passing in the name of the function, and then returns the results contained in the function’s response.

C#
Copy
public async Task<List<ScoreViewModel>> GetTopGlobalScores() {   var results= await CallCookieBingeFunctionAsync<List<ScoreViewModel>>     ("GetTop5GlobalUserScores");   return results; }

Notice that I’m not passing a second parameter to the method. That means the overload I created that doesn’t require a JsonValue will be called:

C#
Copy
private async Task<T> CallCookieBingeFunctionAsync<T>(string apiMethod) {   return await CallCookieBingeFunctionAsync<T>(apiMethod, null); }

This in turn calls the other version of the method and simply passes a null where the JsonValue is expected. Here’s the full listing for the CallCookieBingeFunctionAsync method (which definitely needs an explanation):

C#
Copy
private async Task<T> CallCookieBingeFunctionAsync<T>(string apiMethod, JsonValue jsonValue) {   var httpClient = new HttpClient();   var uri = new Uri("https://cookiebinge.azurewebsites.net/api/" + apiMethod);   var httpContent = jsonValue != null ? new HttpJsonContent(jsonValue): null;   var cts = new CancellationTokenSource();   HttpResponseMessage response = await httpClient.PostAsync(uri,     httpContent).AsTask(cts.Token);  string body = await response.Content.ReadAsStringAsync();   T deserializedBody = JsonConvert.DeserializeObject<T>(body);   return deserializedBody; }

In the first step, the method creates an instance of Windows.Web.Http.HttpClient. Then the method constructs the information needed to make the request from the HttpClient, beginning with the Web address of the function to be called. All of my functions will begin with https://cookiebinge.azurewebsites.net/­api/, so I’ve hardcoded that value into the method and then append the function name passed into the method.

Next, I have to define the headers and any content passed along to the function. As I explained earlier, I’ve chosen to use the helper class, HttpJsonContent, for this step. I copied this class from the JSON section of the official Windows Universal samples (bit.ly/2ry7mBP), which provided me with a means to transform a JsonValue object into an object that implements IHttpContent. (You can see the full class that I copied in the download.) If no JsonValue is passed into the method, as is the case for the GetTop5GlobalUserScores function, the httpContent variable will be null.

The next step in the method defines a CancellationTokenSource in the variable cts. While I won’t be handling a cancellation in my code, I wanted to be sure you were aware of this pattern so I’ve included the token nevertheless.

With all of my pieces now constructed—the URI, the httpContent and the CancellationTokenSource, I can finally make the call to my Azure Function using the HttpClient.PostAsync method. The response comes back as JSON. My code reads that and uses JSON.Net’s JsonConvert method to deserialize the response into whatever object was specified by the calling method.

If you look back at the code for GetTopGlobalScores, you’ll see I specified that the results should be a List<ScoreViewModel>. ScoreViewModel is a type I created to match the schema of the score data returned by two of the Azure Functions. The class also has some additional properties that format the data based on how I want to display it in the UWP app. Given that the Score­ViewModel class is a long listing, I’ll let you inspect its code in the download sample.

Calling an Azure Function That Takes a Parameter

There are still two more Azure Functions to explore. Let’s look now at the other one that returns score data. This function needs the UserId passed in, whereas the previous function took no input. But in this case, I’m still not required to build up the HttpJsonContent because, if you recall the function as it was described in last month’s article, it expects the UserId value to be passed in as part of the URI. The simple example from last month’s article just uses the string 54321 as the UserId in this URI: https://cookiebinge.azurewebsites.net/­api/GetUserScores/54321. With the addition of the identity management feature to the app, the UserId will now be a GUID.

I won’t go deeply into the code for how a user’s identity is managed, but here’s a quick look. You will be able to see all of this code in the download. I created a new pair of Azure Functions for user management. When a user chooses to register to the cloud for score tracking, one of these functions creates a new GUID for the UserId, stores it in a separate collection in the CookieBinge Cosmos DB database and returns the GUID to the UWP app. The UWP app then uses EF Core 2 to store that UserId into a new table in the local database. Part of that GUID is displayed to users on their account page, as shown in Figure 1. When a user plays the CookieBinge game on another device, they can acquire the full GUID by sending the partial GUID that’s available on any other device they’ve already registered to another Azure Function. That function returns the full GUID and the app will then store that UserId on the current device. In this way, the user can post scores from any of their devices to the cloud, always using the same UserId. Additionally, the application can use that same UserId to retrieve the scores from all of their devices from the cloud. An AccountService.cs class has functionality for the local interactions related to the UserId, including storing and retrieving the UserId from the local database. I came up with this pattern on my own and patted myself on the back, feeling so clever, even though I could probably have leveraged an existing framework.

GetUserTopScores is the method in CloudServices that calls out to the GetUserScores function. Like the previous method, it calls the CallCookieBingeFunctionAsync method, again expecting the returned type to be a list of ScoreViewModel objects. I’m again passing in just a single parameter, which is not only the name of the function, but the full string that should get appended to the base URL. I’m using string interpolation to combine the function name with the results of the AccountService.AccountId property:

C#
Copy
public async Task<List<ScoreViewModel>> GetUserTopScores() {   var results = await CallCookie­Binge­Function­Async<List<ScoreViewModel>>     ($"GetUserScores\\­{AccountService.AccountId}");   return results; }
Calling an Azure Function That Expects JSON Content in the Request

The final Azure Function, StoreScores, gives me the opportunity to show you how to append JSON to an HttpRequest. StoreScores takes a JSON object and stores its data into the Cosmos DB database. Figure 2 serves as a reminder of how I tested the function in the Azure Portal by sending in a JSON object that follows the expected schema.

The Azure Portal View of the StoreScores Function Being Tested with a JSON Request Body
Figure 2 The Azure Portal View of the StoreScores Function Being Tested with a JSON Request Body

To match that schema in the UWP app I created a data transfer object (DTO) struct named StoreScoreDto, which helps me create the JSON body for the request. Here’s the CloudService.SendBingeToCloudAsync method, which takes the Binge data resulting from game play and sends it to the Azure Function with the aid of the same CallCookieBingeFunctionAsync method I used to call the other two functions:

C#
Copy
public async void SendBingeToCloudAsync(int count, bool worthIt,   DateTime timeOccurred) {   var storeScore = new StoreScoreDto(AccountService.AccountId,                                      "Julie", AccountService.DeviceName,                                      timeOccurred, count, worthIt);   var jsonScore = JsonConvert.SerializeObject(storeScore);   var jsonValueScore = JsonValue.Parse(jsonScore);   var results = await CallCookieBingeFunctionAsync<string>("StoreScores",     jsonValueScore); }

SendBingeToCloudAsync begins by taking in the relevant data about the Binge to be stored—the count of cookies consumed, whether or not the binge was worth it and when it occurred. I then create a StoreScoreDto object from that data and use JsonConvert again, this time to serialize the StoreScoreDto into a JSON object. The next step is to create a JsonValue, as I explained earlier, a special type in the Windows.Json.Data namespace. I do that using the JsonValue.Parse method, passing in the JSON object represented by jsonScore. The resulting JsonValue is the format required to send the JSON object along with the HTTP request. Now that I’ve got the properly formatted JsonValue, I can send it to the CallCookeBingeFunctionAsync method along with the name of the function, StoreScores. Notice that the type I expect to be returned is a string, which will be a notification from the StoreScores Azure Function of the function’s success or failure.

Wiring up the UI to the CloudService

With the CloudService methods in place, I can finally make sure the UI interacts with them. Recall that when a Binge is saved, the code in MainPage.xaml.cs calls a method in BingeService that stores that data to the local database. That same method, shown in Figure 3, now also sends the binge data to the CloudService to store it in the cloud via the StoreScores Azure Function.

Figure 3 The Pre-Existing RecordBinge Method Now Sends the Binge to the Cloud
C#
Copy
public static  void RecordBinge(int count, bool worthIt) {   var binge = new CookieBinge{HowMany = count, WorthIt = worthIt,                               TimeOccurred = DateTime.Now};   using (var context = new BingeContext(options))   {     context.Binges.Add(binge);     context.SaveChanges();   }   using (var cloudService = new BingeCloudService())   {     cloudService.SendBingeToCloudAsync(count, worthIt, binge.TimeOccurred);   } }

Both of the other methods that interact with the Azure Functions return lists of ScoreViewModel objects.

To display the scores stored in the cloud, as shown in Figure 1, I added a method to MainWindow.xaml.cs that calls the CloudService methods to retrieve the scores and then binds them to the relevant ListViews on the page. I named this method ReloadScores because it’s also called by the refresh button on the same page:

C#
Copy
private async Task ReloadScores() {   using (var cloudService = new BingeCloudService())   {     YourScoresList.ItemsSource = await cloudService.GetUserTopScores();     GlobalScoresList.ItemsSource =       await cloudService.GetTopGlobalScores();   } }

The UI then displays the score data based on the templates defined for each list on the page. For example, Figure 4 shows XAML for displaying the GlobalScores in the UI.

Figure 4 XAML Data Binding the Score Data Returned from an Azure Function
HTML/XHTML
Copy
<ListView  x:Name="GlobalScoresList"    >   <ListView.ItemTemplate>     <DataTemplate >       <StackPanel Orientation="Horizontal">         <TextBlock FontSize="24" Text="{Binding score}"                    VerticalAlignment="Center"/>         <TextBlock FontSize="16" Text="{Binding displayGlobalScore}"                    VerticalAlignment="Center" />       </StackPanel>     </DataTemplate>   </ListView.ItemTemplate></ListView>
Wrapping Up This Four-Part Series

What began as an exercise to try out the latest version of EF Core 2 on Windows-based mobile devices turned into quite an adventure for me, and I hope it was a fun, interesting and educational journey for you, as well. Working with the new .NET Standard 2.0-based UWP, especially in its early pre-release days, was certainly challenging for this back-end developer. But I loved the idea of being able to store data both locally and in the cloud and gaining new skills along the way.

The second and third column in the series were my very first experiences working with Azure Functions and I’m so happy I had an excuse to do so because I’m now a huge fan of this technology and have done much more with it since those first steps. I certainly hope you’ve been equally inspired!

As you saw in this article, interacting with those functions from the UWP app isn’t as straightforward as my earlier experience making Web calls from other platforms. I personally got great satisfaction figuring out the workflow.

If you check out the download, you’ll see the other addition I made to the application—all of the logic for registering to cloud storage, saving the Azure-generated UserId, as well as a name for the device, and registering additional devices and then accessing the UserId and device name for use in the StoreScores and Get­UserScores methods. I’ve downloaded the entire Azure Function App into a .NET project so you can see and interact with all of the functions that support the app. I spent a surprising amount of time puzzling through the identity workflow and became somewhat obsessed with the entertainment of working it out. Perhaps I’ll write about that someday, as well.

Julie Lerman is a Microsoft Regional Director, Microsoft MVP, software team coach and consultant who lives in the hills of Vermont. You can find her presenting on data access and other topics at user groups and conferences around the world. She blogs at the thedatafarm.com/blog and is the author of “Programming Entity Framework,” as well as a Code First and a DbContext edition, all from O’Reilly Media. Follow her on Twitter: @julielerman and see her Pluralsight courses at juliel.me/PS-Videos.

Thanks to the following technical expert for reviewing this article: Ginny Caughey (Carolina Software Inc.)
Ginny Caughey is President of Carolina Software, Inc., providing software and services to the solid waste industry throughout the US and Canada. In her spare time she"s also author of Password Padlock for Windows and Windows phone. She"s active on Twitter (@gcaughey) and a Windows Development MVP.

Nguồn: msdn.microsoft.com