Overview
In my previous Function App article (How to Build a REST API with Azure Function Apps) I talked about how Microsoft provides sample code for building a Azure Function HTTP Trigger when you create a Function App in Visual Studio Code. With the provided code you can then go ahead and modify it to your needs. In this article we are going to explore how we can modify our Azure Function to build a simple Environment Canada weather API or HTTP triggered Function App. We will build a GET HTTP trigger function, which will get the weather for a Canadian city.
Getting started
The first thing you will want to do is install Postman. If you aren’t familiar with Postman, it allows you to make REST API calls, which we will be using for testing our shiny and fancy API. With Postman we will be able to call our Azure Function locally before we deploy it to Azure.
Parsing the weather
The first thing we will need to do is parse the weather data (which is returned as XML) and deserialize it as an object. Instead of having to write out the entire weather class, I’ve added the class as a GitHub Gist for you to easily include in your project. We won’t really go through the code here, but basically there is a method in the Weather class called GetCityWeather and it takes three parameters which is the province code, the city code, and the language code.
The province code is the short form for a province, so for example British Columbia would have a province code of “bc”. The city code is a designation assigned by Environment Canada. This code is retrieved by the method GetSiteList, which gets the list of all Canadian cities with available weather information. And the final code, language code is the language that you want your results returned as. In this particular example, I am only supporting English, but you can modify the code to also support French and allow the user to pass the language code into the API key to return the weather in both languages.
Lets get to work
Building off what we learnt in part 1 of this Azure Function series, lets take a look at how we can integrate the new weather class to fetch the latest weather. First thing you will want to do is fire up your part 1 project and add the Weather.cs file (provided in the Gist link) into your project. Once you’ve added the file, you might want to rename the namespace of the file to match your namespace or add it as a reference in your main function app file.
Before we start clicking and clacking that keyboard, we will need to configure two environment variables that we will use to retrieve the weather from Environment Canada’s weather service. Before we do that I should mention that you can certainly hard code these strings into your function app, but by adding them as environment variables it allows for easier configuration outside of code changes. This is best practice, so I highly recommend you do this in your regular Function App development going forward. It also means that if you want to point to different paths for local dev vs Azure dev vs Azure production, you can do it easily with environment variables. Read more about environment variables here.
For local testing we will add the variables to “local.settings.json” file which is auto-generated when you create your HTTP trigger function app. To add an environment variable to the file, simply add values to the “Values” property. Below is a copy and paste of my local settings, with the two environment variables that we will need: “SiteListURL” and “CityBaseURL”.
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"SiteListURL":"https://dd.weather.gc.ca/citypage_weather/xml/siteList.xml",
"CityBaseURL":"https://dd.weather.gc.ca/citypage_weather/xml/"
}
}
Create the get city weather GET request method
Lets build our “GetCityWeather” HTTP trigger. Instead of deleting the current method from part 1, we will modify it. The first thing we will want to do is rename the function name to “GetCityWeather”. We will also need to remove the “post” portion from the method, leaving just the “get” request method. You might notice that the authorization level is set to anonymous, which we will leave as is right now for ease of testing. Please note that you will need to change this in most production scenarios unless you are happy with anyone publicly using your API, free of charge.
[FunctionName("GetCityWeather")]
public async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,ILogger log)
Query parameter
So obviously when a user makes a GET request to our API we want them to pass us the city that they want their weather for. We will allow them to pass a query parameter with a key “cityName” to our API. Luckily, the Azure Function app SDK provides a simple way of retrieving a query parameter from a call.
string cityName = req.Query["cityName"];
Requiring query parameters
Now that we have that query, we will need to check to see if the string is null or empty. Basically if a user doesn’t supply us with a city name, we need to return an error to them to let them know that the city name is required.
if(string.IsNullOrEmpty(cityName))
{
return new HttpResponseMessage(HttpStatusCode.NotAcceptable) {
Content = new StringContent("Please provide a city name.")
};
}
Logging information
A great feature that you can use to help troubleshoot your function app is adding logs. Logs are very similar to writing to console, but can provide more control over what logs you would like to see. The ILogger interfaces provides 6 different log levels which you can configure in your Function App depending on if you want to see debug logs or more critical errors. Read more about the logs here.
In my case, I’m going to log the city name for debugging purposes.
log.LogInformation($"Get City Weather trigger function processed a request for the city: {cityName}.");
Lets get some weather!
The next thing you will want to do is instantiate the weather class and call the GetCityWeather method by passing in the city name as a parameter. Make sure you are referencing the Weather.cs class, otherwise your code won’t be able to instantiate the class.
Weather weather = new Weather();
var city = weather.GetCityWeather(cityName);
What if that city variable is BLANK!?
So what do we do if we don’t get a result back from Environment Canada. There might be various reasons for not getting a result back, but to keep it simple for now, we will assume that if we don’t get any weather back, the city that the user entered wasn’t recognized.
if(city == null)
{
return new HttpResponseMessage(HttpStatusCode.NotFound) {
Content = new StringContent("City name not recognized.")
};
}
Serialization is the name of the game
Now that we have our city weather, we need to serialize as a JSON object to return to our user.
string jsonToReturn = JsonConvert.SerializeObject(city);
Return the final result
And if we are happy with everything, we can return the final result.
return new HttpResponseMessage(HttpStatusCode.OK) {
Content = new StringContent(jsonToReturn, Encoding.UTF8, "application/json")
};
Bring it together
Lets bring it all together. The only difference between my code and yours should be the try catch statement wrapped around my code for general error handling.
[FunctionName("GetCityWeather")]
public async Task<HttpResponseMessage> CityWeather(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("Get City Weather processing request...");
try
{
string cityName = req.Query["cityName"];
if(string.IsNullOrEmpty(cityName))
{
return new HttpResponseMessage(HttpStatusCode.NotAcceptable) {
Content = new StringContent("Please provide a city name.")
};
}
log.LogInformation($"Get City Weather trigger function processed a request for the city: {cityName}.");
Weather weather = new Weather();
var city = weather.GetCityWeather(cityName);
if(city == null)
{
return new HttpResponseMessage(HttpStatusCode.NotFound) {
Content = new StringContent("City name not recognized.")
};
}
string jsonToReturn = JsonConvert.SerializeObject(city);
return new HttpResponseMessage(HttpStatusCode.OK) {
Content = new StringContent(jsonToReturn, Encoding.UTF8, "application/json")
};
}
catch (Exception ex)
{
return new HttpResponseMessage(HttpStatusCode.BadRequest) {
Content = new StringContent($"{ex.Message}")
};
}
}
Test time
First off, we need to run our Function App up in debug mode from VS Code. Once it’s attached and running, copy the GET URL shown in terminal. It should look something like this: “http://localhost:7071/api/GetCityWeather”.
Postman, here we come
Next, lets fire up the Postman app (if you haven’t already, make sure you download it). Create a new Get call by pasting in the URL for your local Function app and make sure “GET” is selected. Under the “Params” sections, add a key “cityName” and in the value field type a valid Canadian city such as “Vancouver”. Now you can simple click the “Send” button. You should get a response back that looks like the JSON object below.
{
"Temperature": {
"Value": 5.7,
"Units": "C"
},
"RelativeHumidty": {
"Value": 95.0,
"Units": "%"
},
"Condition": "Cloudy",
"Visibility": {
"Value": 24.1,
"Units": "km"
},
"UtcObservationDateTime": {
"DateTime": "2022-03-21T03:00:00",
"TimeZone": "UTC",
"UTCOffset": 0
},
"LocalObservationDateTime": {
"DateTime": "2022-03-20T20:00:00",
"TimeZone": "PDT",
"UTCOffset": -7
},
"Dewpoint": {
"Value": 5.0,
"Units": "C"
},
"Pressure": {
"Value": 102.4,
"Units": "kPa",
"Tendency": "falling"
},
"Wind": {
"WindSpeed": {
"Value": 18.0,
"Units": "km/h"
},
"WindGust": {
"Value": -1.0,
"Units": "km/h"
},
"Direction": "ENE",
"Bearing": {
"Value": 71.0,
"Units": "degrees"
}
}
}
Till next time!
As I’ve shown, creating a simple Environment Canada weather API using Azure Function Apps is pretty straightforward and is a quick and simple way to deploy APIs. So where do we go from here? There are certainly are some areas that can be improved upon and areas to explore. Next time, we will take a look at how to secure our Function App API and how we can use Azure API Management to manage our APIs. We will also look at how to deploy our Function App to Azure so that we can share our APIs with others!
Until next time! ✌️