Rainy with a chance of Azure cloud
Tags: serverless, dotnet, tutorial, webdev
Published: 2019-09-30 13:39:07

> This article is part of [#ServerlessSeptember](https://dev.to/azure/serverless-september-content-collection-2fhb). You'll find other helpful articles, detailed tutorials, and videos in this all-things-Serverless content collection. New articles are published every day — that's right, every day — from community members and cloud advocates in the month of September.

> Find out more about how Microsoft Azure enables your Serverless functions at [https://docs.microsoft.com/azure/azure-functions/](https://docs.microsoft.com/azure/azure-functions/?WT.mc_id=servsept_devto-blog-cxa).

We all have good days and bad days. On a good day, we may be filled with positive thoughts and on a bad day, we may forget that anything was ever positive!

![alt gif of cartoon animal crying in the rain](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/original_images/vTeT5o3zl2dU45-38Zg611iR7AK_9tdjsGg9FVPHMQ70XUulqO6_jtFJLjmdIMS9tkPJrMe0_UxkVU "Bad days")

In this post, we will use Twilio to text your positive thoughts and affirmations to an Azure Function and save them in Azure Table Storage. We will then create a timer-based function that will send a random happy thought from the table storage via SMS to your friends.

This post assumes some basic knowledge of C# and RESTful APIs.

To get started, we will need:
* This [GitHub](https://github.com/Layla-P/DailyAffirmations) project cloned
* A [Twilio account](https://www.twilio.com/try-twilio) and an SMS enabled [Twilio number](https://www.twilio.com/console/phone-numbers/search)
* An Azure Account ([sign up for free here](https://azure.microsoft.com/en-us/free/))
* The latest .NET Core 2.2 SDK ([download here](https://dotnet.microsoft.com/download/dotnet-core/2.2))
* [Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#v2) version 2.6.666 or later
* [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) version 2.0 or later
* [Postman](https://www.getpostman.com/) for testing our functions locally
* An IDE or code editor with C# support

### Create an Azure resource group and required resources
Once logged into your [Azure portal](https://portal.azure.com), we will create a Resource Group, Functions App Service and Azure Function.

To create the Resource Group, go to "Resource groups" and then click the "plus" button to add a new resource. You will be taken to the "Create a new resource group" page where you can enter a name for your resource group, such as "RainyDays", choose a suitable region and then click "Review + create". Your resource will take a few moments to deploy.

![alt azure portal create a resource group page image](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/i0d3DEVlr0Uc45ik5D11006S8fyFhICClfL_NA7y8QHXTW.width-500.png "Create a resource group")

Once deployed, we can create a new "Function App" resource. Click the "Create a resource" button on the top left of the side menu. In the search bar, search for and select  "Function App", then click "Create".

![alt azure portal create a function app image](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/r-_tnUgyAMB6IZcr5FgbLaTOd2ATtXSWVAphXWv8N6sjKo.width-500.png "Add a resource")

A "Create Function App” dialogue will come up and you will need to give the app a unique name. Add it to the Resource Group that we just created. Then set the "Hosting Plan" to "Consumption Plan", choose an appropriate location, set "Runtime stack" to ".NET Core". There are a few other tabs on this wizard; "Hosting" and "Monitoring" being the more important ones. You can leave everything as the default. Finally, click the "Review and create" button at the bottom of the form.
After a few moments, you should have a new Function App Service, which you can then navigate to. In the centre of the screen will be a button called "+ New function". Click on this.

![alt azure portal create a function image](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/V0r6kGGTDLLy9CZ6RtZcnIs_nNOjijvFepSVjD0MH3LnWy.width-500.png "Create a function")

The next screen will give several options, choose the IDE that you are using and if it's not there just choose the "Any editor + Core Tools" option.

![alt azure portal choose a dev environment image](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/8X4-SBONyEMAYI7DZhs0cTdMbXJ7HHm1qmKEHsKmc4i3Ti.width-500.png "Choose dev environment")

You should then be shown a very helpful "cheat sheet" with all the commands that you need to work with your project locally, using the CLI. Especially useful is the "Deploy your code to Azure" command, so you may want to make a note of that for later.
Now that we have the Azure side of everything set up, it's time to look at the code.

### The Function App
Open up the solution that we cloned at the beginning of this post in your editor of choice. We will work on the `master` branch, the completed code is on the `complete` branch if you would rather see the finished solution.

We will focus on the code in `src/Functions` for this post.

Within this directory, we have two additional directories, `Models` and `TableServices`.
The  `Models` directory contains `TableConfiguration.cs` and its interface for setting up the table storage dependency injection. It also has `QuoteEntity.cs` and `Message.cs`.

### Table Storage structure and the "QuoteEntity" model
Table storage is a way to store NoSQL data in a structured way, with a schemaless design and a key/attribute store. For more information on how the data is structured, check out the docs [here](https://docs.microsoft.com/en-us/azure/storage/tables/table-storage-overview).

The model, `QuoteEntity` provided in the source code already, inherits from `TableEntity`. The overloaded constructor, `public QuoteEntity(string phoneNumber, string quote)`, creates an object with all the properties needed to store it in a table store.

```csharp
//"src/Functions/Models/QuoteEntity.cs"
public class QuoteEntity : TableEntity
{
   public QuoteEntity() {  }
   public QuoteEntity(string phoneNumber, string quote)
   {
       PartitionKey = "Quotes";
       RowKey = Guid.NewGuid().ToString();
       UserPhoneNumber = phoneNumber;
       Quote = quote;
   }
   public string UserPhoneNumber { get; set; }
   public string Quote { get; set; }
}

```


### Writing to an Azure table store
To avoid duplicating code, we will use `TableDbContext` to talk to the Azure Table Store. This code is in the "TableServices" directory and already provided. We will use dependency injection, recently added to Azure Functions natively, to inject the context into the constructor of both our functions.  This DbContext has already been registered to the IoT container in the `Startup.cs` file as seen below:

```csharp
//"src/Functions/Startup.cs"
ITableConfiguration tableConfig = new TableConfiguration
{
   ConnectionString = Environment.GetEnvironmentVariable("TableStorage-ConnectionString"),
   TableName = Environment.GetEnvironmentVariable("TableStorage-TableName")
};

builder.Services.AddSingleton(tableConfig);
builder.Services.AddSingleton<ITableDbContext, TableDbContext>();
```

`TableDbContext` will handle all interactions with the table storage. It implements the `ITableDbContext` and has two private methods, `CreateStorageAccountFromConnectionString`, which ensures the table is created and `GetRandom`, which generates a random integer based upon the number of rows in a given collection.

Both the `ILoggerFactory` and the `ITableConfiguration` dependencies are injected into the class.

Let's implement the `CreateTableAsync` method first. We will use the private method `CreateStorageAccountFromConnectionString()` to validate the connection string and return a `CloudStorageAccount` object. We can then use this object to create a `CloudTableClient` object called `tableClient` which we can then use to create a table object, assuming that one doesn't exist already, and assign that to the `CloudTable` object called `_table`.

```csharp
//"src/Functions/TableServices/TableDbContext.cs"
private CloudTable _table;
...
public async Task CreateTableAsync()
{
   // Retrieve storage account information from connection string.
   CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString();

   // Create a table client for interacting with the table service
   CloudTableClient tableClient = storageAccount.CreateCloudTableClient(new TableClientConfiguration());

   // Create the table
   _table = tableClient.GetTableReference(_tableConfiguration.TableName);
  
   if (await _table.CreateIfNotExistsAsync())
   {
       _log.LogInformation($"Created Table named: {_tableConfiguration.TableName}");
   }
   else
   {
       _log.LogInformation($"Table {_tableConfiguration.TableName} already exists");
   }
}
```

Next, let's implement the method that will write data to the table, `InsertOrMergeEntityAsync`. 

First, we must check if the table already exists, and if not, call the `CreateTableAsync` method that we previously created. Next, we check if the incoming `TableEntity` argument `entity` is null and throw an error if it is. 

```csharp
public  async Task<QuoteEntity> InsertOrMergeEntityAsync(TableEntity entity)
{
   if (_table is null)
   {
       await CreateTableAsync();
   }
   if (entity == null)
   {
       throw new ArgumentNullException(nameof(entity));
   }
try
{
}
 catch (Exception e)
   {
       _log.LogInformation(e.Message);
       throw;
}

}
```

 If `entity` is not null, then we move immediately into a "try/catch" statement. Add the following code to the `try{}`:

```csharp
try
{

       // Create the InsertOrMerge table operation
       TableOperation insertOrMergeOperation = TableOperation.InsertOrMerge(entity);

       // Execute the operation.
       TableResult result = await _table.ExecuteAsync(insertOrMergeOperation);
       var insertedQuote = result.Result as QuoteEntity;

       // Get the request units consumed by the current operation. RequestCharge of a TableResult is only applied to Azure CosmoS DB
       if (result.RequestCharge.HasValue)
       {
           _log.LogInformation($"Request Charge of InsertOrMerge Operation:{ result.RequestCharge}");
       }

       return insertedQuote;
   }
  }
```
In the above code, we attempt to use the `TableOperation.InsertOrMerge(TableEntity entity)` method provided in 
 `Microsoft.Azure.Cosmos.Table` library to create a table operation. We then execute the operation using `_table.ExecuteAsync(TableOperation insertOrMergeOperation)` method from the same library.

If all goes well, we are returned the created entity. If not we log any errors in the "catch".

### The Inbound Function
Our first function, `InboundFunction`, is found in the "Functions" directory.  It is an HTTP triggered function that we will invoke using a POST request from Twilio. The `TableDbContext` is injected into the constructor, so it is immediately available for us to use.

First, we will need to read the body of the request and write it to a string, so add the following line to the `Run` method:

```csharp
//"src/Functions/InboundFunction.cs"
[FunctionName("InboundFunction")]
public async Task Run(
   [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]
   HttpRequest req, ILogger log)
{
   string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
}

```
We will need to extract the "From" number and the "Body" from the incoming request and map it to a `Message` model object. `Message` has a constructor available that will take the incoming request body as a string and map it for us.
```csharp
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var message = new Message(requestBody);
```
Now we can create a new `QuoteEntity`, using the overloaded constructor and the `From` and `Body` properties of the `Message` object. This can then be passed to the `InsertOrMergeEntityAsync` method on our DB context, `_tableContext` which will create and save our new quote:

```csharp
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var message = new Message(requestBody);
var quoteEntity = new QuoteEntity(message.From, message.Body);
var newQuote = await _tableContext.InsertOrMergeEntityAsync(quoteEntity) as QuoteEntity;
```
I would recommend adding some logging to the above code. If you would like to see how that's done, then check out the `complete` branch of the repo.

### Writing to an Azure table store
We can now write data to our table store, but we also need to read it back out. Let's return to the `TableDbContext` and implement the remaining methods.

We want to be able to pick a random quote from our store and send it to all our friends. To do this we will need to bring back all the quotes as an array, create a random number based upon the length of our array, and then use that number to select a quote via its index.

But first, we need to check if the table exists!

```csharp
//"src/Functions/TableServices/TableDbContext"
public async Task<QuoteEntity> GetRandomEntityAsync()
{
    if (_table is null)
    {
        await CreateTableAsync();
    }
    try { }
    catch (Exception e)
    {
        _log.LogInformation(e.Message);
        throw;
    }

}
```

Once we are sure that we have a table created, we can then, within a "try/catch" statement, create a new table query `TableQuery<TableEntity>`, execute that query, and then create an array from the result.
As a precaution, if the array is empty, we return a default quote.
If it is populated, we then use the private `GetRandom` method to return a random number based upon the length of the array.
This random number will then be used to select a quote via its index, casting the result to a `QuoteEntity` and returning that value. If something goes wrong, we log it in the "catch".

```csharp
try{
   var query = new TableQuery<TableEntity>();
   var quotes = _table.ExecuteQuery(query).ToArray();
   if (quotes.Length == 0)
   {
       return new QuoteEntity
       {
           Quote = "You look lovely today, text in and say something nice for your friends!"
       };
   }
   var random = GetRandom(quotes);
   var randomQuote = quotes[random] as QuoteEntity;
   return randomQuote;
}

```

In addition to a random quote, we will also want a list of all the numbers that have texted a quote in. We can then use this list to send the random quote to all our friends, assuming your friends are sending in their happy thoughts to your app, too!

```csharp
public async Task<IEnumerable<string>> ListNumbersAsync()
{
   if (_table is null)
   {
       await CreateTableAsync();
   }
}
```

First, we again check if our table is created.
If it is, we can create our query from within a "try/catch" statement. We then create an empty list of type string, ready to populate with phone numbers.

```csharp
try
{
   var list = new List<string>();
}
catch (Exception e)
{
   _log.LogInformation(e.Message);
   throw;
}
```

Next, we will need to create a `TableQuery` object and execute this. Only this time, instead of creating an array, we will group by phone number, to ensure we only get each number once.

```csharp
var list = new List<string>();
var query = new TableQuery<QuoteEntity>();
var quotes = _table.ExecuteQuery(query).GroupBy(e=>e.UserPhoneNumber);
```

We can then iterate over our collection, adding the phone numbers to our newly created list:

```csharp
var list = new List<string>();
var query = new TableQuery<QuoteEntity>();
var quotes = _table.ExecuteQuery(query).GroupBy(e=>e.UserPhoneNumber);
foreach (var quote in quotes)
{
   list.Add(quote.Key);
}

return list;
```


### The Outbound Function
Our second function, `OutboundFunction`, canalso found in the "Functions" directory.  It is a function that is time-triggered based upon a schedule described by a [cron expression](https://en.wikipedia.org/wiki/Cron). The `TableDbContext` is injected into the constructor and is available for us to use as a private attribute throughout the class. 

We also fetch the Twilio phone number from settings, which we will set up soon, and assign it to the private attribute `_phoneNumber`, within the constructor.

Our Function cron expression is set to `0 0 10 * * *`, which means it will fire at 10 am every day. If you would like it to fire differently, then this handy [cheat sheet](https://codehollow.com/2017/02/azure-functions-time-trigger-cron-cheat-sheet/) for cron expressions can help you.

We will be using Twilio to send our SMS messages. We will need to initialise the Twilio client within the `Run` method. The Twilio Account SID and Auth token will also be taken from settings.

```csharp
//"src/Functions/OutboundFunction.cs"
public async Task Run([TimerTrigger("0 0 10 * * *")] TimerInfo myTimer, ILogger log)
{
   TwilioClient.Init(
       Environment.GetEnvironmentVariable("TWILIO_ACCOUNT_SID"),
       Environment.GetEnvironmentVariable("TWILIO_AUTH_TOKEN")
   );
}
```

We can now use our `_tableContext` to first fetch the list of phone numbers and then the random quote we wish to send.
```csharp
//collect all the telephone numbers of our users from the table storage
var numbers = await _tableContext.ListNumbersAsync();
//get a random quote from the database
var quoteEntity = await _tableContext.GetRandomEntityAsync();
```
The final piece of this code, is to iterate over our number collection and send the quote to each user phone number.

```csharp
foreach (var number in numbers)
{
       var message =  MessageResource.Create(
       from: new PhoneNumber(_phoneNumber),
       to: new PhoneNumber(number),
       body: quoteEntity.Quote );
}
```
### Setting the configuration
When developing locally, Azure functions rely on having all configuration within the `local.settings.json` file. You won't have this in your copy that you cloned from GitHub, as the `.gitignore` that comes with a function app excludes it. This is to ensure sensitive data isn't accidentally pushed to repositories. As a workaround, there should be a `local.settings.demo.json` file in the GitHub copy, so just rename that to be `local.settings.json`.

It is in this `local.settings.json` file where we will need to add all of the connection strings and secrets.

You can find the Table Storage connection string in the Azure portal.
Navigate to the Resource Group that we made earlier and click into the "storage account" which is used for our Table Storage. On the left-hand menu, click into "Access keys" and copy the "Connection string" listed against "key1".

This connection string will be the value applied to the following three keys in `local.settings.json`; "StorageConnectionSettings", "AzureWebJobsStorage" and "TableStorage-ConnectionString".

Your Twilio Account SID and Auth token can be found on the Twilio console. Grab your Twilio number and set it as a value against the setting "PhoneNumber".

We are now ready to test our functions!

### Getting up and running
Start your functions from your IDE or from within the CLI with the following command from the following location "~/DailyAffirmations/src/Functions":
```bash
func start
```
Your function will start and you will receive the following message in the console telling you about the HTTP trigger endpoints.

```
Http Functions:
	InboundFunction: [POST] http://localhost:7071/api/InboundFunction
```
We will want to wire the Twilio number to this endpoint using ngrok.

### Setting up ngrok
[ngrok creates a public-facing](https://www.twilio.com/blog/2013/10/test-your-webhooks-locally-with-ngrok.html) URL that tunnels to our project running locally so we can test our endpoint without deploying to a server. Follow the [instructions on the ngrok site to download and install it](https://ngrok.com/download).

Once installed, run the following on your command line to start ngrok:

```bash
> ngrok http 7071 -host-header="localhost:7071"
```

If your function isn't running on port 7071, then just update the above command with your port number.

You will then see an output similar to below.  

![alt ngrok in the console image](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/D3XLian-zic0S-QgfpnnnvWze8OTitTNrh98u-bzG7kv5L.width-500.png "ngrok in the console")

Copy the public-facing URL which should be along the lines of `https://12345.ngrok.io`.

To enable Twilio to make the initial request that fires off our string of events, we need to set up the webhook.

![alt image of twilio console and where to set up webhook](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/1zrMcMi_gxz-UPey9laAMlw9DaFyS8RaB8y5gttt2IRTkx.width-500.png "Twilio webhook page")

Go to the [Twilio console](https://www.twilio.com/console) and find the number that you added for this project.  Next, add the API endpoint `https://<NGROK_NUMBER>.ngrok.io/api/Inbound Function` from ngrok and paste into the A MESSAGE COMES IN section, then save.
Now you can send an SMS with your inspiring thought as the message and it will be written to the table store.

![alt gif with inspirational affirmations](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/original_images/qnsMGCQj_1DjDjGOh_OHBBcWL5jRQ_5_63iOzwHd0JxrzgGdv62F4dLKMzvA06_A994B4sRJfpWnuf "Affirmations gif")

### Testing the timer trigger
We don't want to wait for our trigger to fire in order to test it… that only happens once a day! It's much more convenient to test it with an HTTP POST request.
For the purpose of testing, we’ll use Postman to create a new POST request to the following URL "http://localhost:7071/admin/functions/OutboundFunction", with "Content-Type" set to "application/json" and the body set to an empty JSON object `{}`.
Once you send this post you will receive an SMS with a happy thought!


Publishing to Azure
An easy way to publish your app is by using the Azure CLI. First, make sure you are logged into your Azure account with the command `az login`, which will open a browser and enable you to log in to Azure.
Then, from within "src/Functions" run the command we made a note of at the beginning:

```bash
func azure functionapp publish <FUNCTION_APP_NAME>
```

Don't forget to add your app settings and update your Twilio endpoint to your Function app's URL.

### What next?
We've seen how we can quickly receive an incoming SMS message and send out an email.  A great update to the above code would be to add further logging and error handling. You could also add a third function that enables you to add your friend's numbers without them having to text a quote in. 

Let me know what other cool ideas you have for Twilio SMS and Azure Functions. I can't wait to see what you build!
* Email: lporter@twilio.com
* Twitter: [@LaylaCodesIt](https://github.com/Layla-P)
* GitHub: [layla-p](https://github.com/Layla-P)