How to better configure C# and .NET applications for Twilio
Tags: dotnet, csharp, twilio, sms
Published: 2022-06-27 19:28:21

---
title: How to better configure C# and .NET applications for Twilio
published: true
date: 2022-04-11 00:00:00 UTC
tags: dotnet, csharp, twilio, sms
canonical_url: https://www.twilio.com/blog/better-configuration-csharp-dotnet-for-twilio
cover_image: https://swimburger.net/media/0budwzy0/how-to-better-configure-c-and-net-applications-for-twilio.png?anchor=center&mode=crop&width=1000&height=420
---

**This blog post was written for [Twilio](https://www.twilio.com/) and [originally published at the Twilio blog](https://www.twilio.com/blog/better-configuration-csharp-dotnet-for-twilio).**

There are a hundred different ways to provide configuration to your applications. For almost any programming language, you can use environment variables and _.env_ files, but configuration can also be stored in other file formats like JSON, YAML, TOML, XML, INI, and the list keeps on going. Though, in some scenarios configuration isn't pulled from files, but instead is pulled from a service like Azure Key Vault, HashiCorp Vault, or a similar vault service. It is rare that your configuration will come from a single source. Luckily, there are APIs in .NET that can help you grab configuration from multiple sources and merge them together. 

In this tutorial, you'll start with an application that sends text messages using [Twilio Programmable SMS](https://www.twilio.com/sms), where the configuration is fetched directly from the environment variables. You'll then refactor the app to

- fetch configuration from multiple sources, specifically, JSON files, user-secrets, environment variables, and command-line arguments
- bind the configuration to strongly-typed objects
- inject configuration using dependency injection following the options pattern

## Prerequisites

You will need these things to follow along with this tutorial:

- An OS that supports .NET (Windows/macOS/Linux)
- [Git CLI](https://git-scm.com/downloads)
- [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
- A code editor or IDE (I recommend [VS Code](https://code.visualstudio.com/Download) with [the C# plugin](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp), [Visual Studio](https://visualstudio.microsoft.com/), or [JetBrains Rider](https://www.jetbrains.com/rider/))
- A free or paid Twilio account (If you [register here](https://www.twilio.com/referral/OhKZOM), you'll receive $10 in Twilio credit when you upgrade to a paid account!)
- [A Twilio Phone Number](https://support.twilio.com/hc/en-us/articles/223135247-How-to-Search-for-and-Buy-a-Twilio-Phone-Number-from-Console)

You can find [the source code for this tutorial on GitHub](https://github.com/Swimburger/TwilioOptionsPattern). Use it if you run into any issues, or [submit an issue](https://github.com/Swimburger/TwilioOptionsPattern/issues) if you run into problems.

## Get Your Twilio Configuration

You'll need some configuration elements from Twilio to send text messages from the C# application. Open [the Twilio Console](https://console.twilio.com/) in your browser and **take note** of your Twilio **Account SID** , **Auth Token** , and **Twilio phone number** located at the bottom left of the page.

![Account Info box holding 3 read-only fields: Account SID field, Auth Token field, and Twilio phone number field.](https://twilio-cms-prod.s3.amazonaws.com/images/jYTRsA9PGEnPO14PJ1mEdKnPfFyt-rOfzaGWs_fFDGGNqU.width-500.png)

For any of the upcoming commands and code, replace the following placeholders like this:

- Replace `[TWILIO_ACCOUNT_SID]`, `[TWILIO_AUTH_TOKEN]`, and `[YOUR_TWILIO_PHONE_NUMBER]` with the **Account SID** , **Auth Token** , and your **Twilio Phone Number** you took note of earlier.
- Replace `[RECIPIENT_PHONE_NUMBER]` with the phone number you want to text. If you are using a trial Twilio account, you can only send text messages to Verified Caller IDs. [Verify your phone number](https://support.twilio.com/hc/en-us/articles/223180048-Adding-a-Verified-Phone-Number-or-Caller-ID-with-Twilio) or the phone number you want to message if it isn't on the list of Verified Caller IDs. 

## Get configuration directly from environment variables

To get the project up and running quickly on your machine, open your preferred shell, and use the following git command to clone the source code:

```bash
git clone https://github.com/Swimburger/TwilioOptionsPattern.git --branch Step1
```

Then navigate into the _TwilioOptionsPattern_ folder using `cd TwilioOptionsPattern`. This project is a console application with only one C# file, _Program.cs_, which has the following code:

```csharp
using Twilio;
using Twilio.Rest.Api.V2010.Account;

var twilioAccountSid = Environment.GetEnvironmentVariable("TwilioAccountSid");
var twilioAuthToken = Environment.GetEnvironmentVariable("TwilioAuthToken");
var fromPhoneNumber = Environment.GetEnvironmentVariable("FromPhoneNumber");
var toPhoneNumber = Environment.GetEnvironmentVariable("ToPhoneNumber");

TwilioClient.Init(
    username: twilioAccountSid, 
    password: twilioAuthToken
);

MessageResource.Create(
    from: fromPhoneNumber,
    to: toPhoneNumber,
    body: "Ahoy!"
);
```

The application pulls the Twilio Account SID, Auth Token, Twilio Phone Number (`FromPhoneNumber`), and the recipient phone number (`ToPhoneNumber`) from the environment variables, then uses the credentials to authenticate with the Twilio API, and then sends a text message saying "Ahoy!".

The application uses the [Twilio SDK for C# and .NET](https://www.twilio.com/docs/libraries/csharp-dotnet) to authenticate with Twilio and send SMS. The [Twilio NuGet](https://www.nuget.org/packages/Twilio) package has been added into the project as part of the source code you cloned. You can find this dependency in the csproj-file.

Before you can run this application, you'll need to configure the environment variables that the project depends on. Back in your shell, set these environment variables using the following commands. 

If you're using Bash or a similar shell:

```bash
export TwilioAccountSid=[TWILIO\_ACCOUNT\_SID]
export TwilioAuthToken=[TWILIO\_AUTH\_TOKEN]
export FromPhoneNumber=[YOUR\_TWILIO\_PHONE\_NUMBER]
export ToPhoneNumber=[RECIPIENT\_PHONE\_NUMBER]
```

If you're using PowerShell:

```powershell
$Env:TwilioAccountSid = "[TWILIO\_ACCOUNT\_SID]"
$Env:TwilioAuthToken = "[TWILIO\_AUTH\_TOKEN]"
$Env:FromPhoneNumber = "[YOUR\_TWILIO\_PHONE\_NUMBER]"
$Env:ToPhoneNumber = "[RECIPIENT\_PHONE\_NUMBER]"
```

If you're using CMD:

```bash
set "TwilioAccountSid=[TWILIO\_ACCOUNT\_SID]"
set "TwilioAuthToken=[TWILIO\_AUTH\_TOKEN]"
set "FromPhoneNumber=[YOUR\_TWILIO\_PHONE\_NUMBER]"
set "ToPhoneNumber=[RECIPIENT\_PHONE\_NUMBER]"
```

Once the environment variables have been set, you can run the project using the following .NET CLI command:

```bash
dotnet run
```

The application will send an SMS from your Twilio Phone Number to the recipient phone number saying "Ahoy!".

Pulling the configuration from the environment variables works great, but instead of using environment variables, you could've simply hardcoded all the configuration, right?  
Many of these configuration elements, especially the Auth Token, are sensitive secrets which you do not want to share with others. By pulling them from external configuration like the environment variables, you ensure that you don't accidentally check them into your public source code for everyone to see. And whenever someone else wants to run the same code, they can configure their own Twilio configuration into the environment variables and run it. 

This is why in Twilio samples, we will always use environment variables. Although it's an extra step compared to hard coding configuration, it is the fastest way to get you up and running with Twilio products.  
But in .NET, there's a better way to retrieve external configuration, using the `ConfigurationBuilder`.

## Build your Configuration with the .NET configuration builder

There are many ways to configure .NET applications and typically the configuration is composed from multiple sources. ASP.NET Core originally introduced APIs to easily load configuration from a bunch of different sources using [configuration providers](https://docs.microsoft.com/en-us/dotnet/core/extensions/configuration#configuration-providers) and merge them all together with the `ConfigurationBuilder`. These APIs were moved into the [Microsoft.Extensions.Hosting NuGet package](https://www.nuget.org/packages/Microsoft.Extensions.Hosting) so it could be used in any .NET application.

Not only can you use the new Configuration APIs on .NET (Core), you can even [use them on .NET Framework](https://swimburger.net/blog/dotnet/using-configurationproviders-from-microsoft-extensions-configuration-on-dotnet-framework). .NET Framework has its own configuration APIs, but they are less intuitive and dated compared to the new Configuration APIs.

Back in your shell, run the following .NET CLI commands to add NuGet packages for the configuration extensions and some of the configuration providers:

```bash
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.CommandLine
dotnet add package Microsoft.Extensions.Configuration.EnvironmentVariables
dotnet add package Microsoft.Extensions.Configuration.UserSecrets
```

- The [Microsoft.Extensions.Configuration](https://www.nuget.org/packages/Microsoft.Extensions.Configuration) package contains the main APIs for building configuration and also the JSON configuration provider.
- The [Microsoft.Extensions.Configuration.CommandLine](https://www.nuget.org/packages/Microsoft.Extensions.Configuration.CommandLine) package adds a configuration provider that builds configuration from command-line arguments.
- The [Microsoft.Extensions.Configuration.EnvironmentVariables](https://www.nuget.org/packages/Microsoft.Extensions.Configuration.EnvironmentVariables) package adds a configuration provider that builds configuration from environment variables.
- The [Microsoft.Extensions.Configuration.UserSecrets](https://www.nuget.org/packages/Microsoft.Extensions.Configuration.UserSecrets) package adds a configuration provider that builds configuration from the [Secret Manager](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets#enable-secret-storage).

To start using the configuration APIs, update the _Program.cs_ file with the following code:

```csharp
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Twilio;
using Twilio.Rest.Api.V2010.Account;

IConfiguration config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
    .AddUserSecrets(Assembly.GetExecutingAssembly(), optional: true, reloadOnChange: false)
    .AddEnvironmentVariables()
    .AddCommandLine(args)
    .Build();

var twilioAccountSid = config["Twilio:AccountSid"];
var twilioAuthToken = config["Twilio:AuthToken"];

var fromPhoneNumber = config["Message:From"];
var toPhoneNumber = config["Message:To"];
var messageBody = config["Message:Body"];

TwilioClient.Init(
    username: twilioAccountSid,
    password: twilioAuthToken
);

MessageResource.Create(
    from: fromPhoneNumber,
    to: toPhoneNumber,
    body: messageBody
);
```

The resulting app is the same as before but is now loading configuration from multiple sources instead of directly from the environment variables. The `ConfigurationBuilder` is a class that uses the [builder pattern](https://en.wikipedia.org/wiki/Builder_pattern) so you can chain together multiple configuration providers using the `Add…` extension methods. When configuration elements coming from different providers have the same key, the configuration added later to the chain will overwrite the configuration added earlier in the chain. This means that the order in which you add configuration providers matters! In this case:

- User Secrets overwrites configuration from **JSON**
- **Environment Variables** overwrites configuration from **User Secrets and JSON**
- **Command-Line Arguments** overwrites configuration from **User Secrets, JSON, and Environment Variables**.

The `optional` parameter specifies whether or not the application should throw an exception if the underlying source is missing. The `reloadOnChange` parameter specifies whether or not to update the configuration when the underlying source changes. For example, if there's an issue in your production application, but for performance reasons, the logging level is set to `Warning` in _appsettings.json_, you can change the logging level to `Debug`. Once you save the file, the application begins writing logs from the `Debug` level and up, without having to restart your application!

After the configuration is built, the app will grab the configuration elements from the configuration, but notice how the configuration keys now use two different prefixes: `Twilio:` and `Message:`.

You can organize your configuration into hierarchical sections by adding colons `:` as separators. So in this case, `AccountSid` and `AuthToken` are part of the `Twilio` section, and `From`, `To`, and `Body` are part of the `Message` section. How you organize your configuration will depend on your needs.

Now that the application loads configuration from multiple sources, in which source should you store configuration? It depends on the configuration, your use-case, and personal preference:

- Use JSON for configuration that does not contain secrets or other sensitive information.
- Use **User Secrets** only for local development to configure secrets and other sensitive information.
- Use **Environment Variables** for environment specific configuration including secrets or other sensitive information. However, User Secrets is preferred for local development. Environment Variables are a powerful source of configuration because all operating systems, container technology, and cloud infrastructure supports them.
- Use **Command-Line Arguments** to configure execution specific settings.

Create a new file called _appsettings.json_ and add the following JSON:

```json
{
  "Message": {
    "From": "[YOUR_TWILIO_PHONE_NUMBER]",
    "Body": "Ahoy from JSON!"
  }
}
```

The _appsettings.json_ file will provide the `Message:From` and `Message:Body` configuration, even though it would also make sense to pass this configuration in as command-line arguments.

By default, in a console app, the _appsettings.json_ file will not be copied to the output of the project. As a result the configuration provider will not find the underlying source, which means the configuration in your JSON will not be loaded. To make sure the the _appsettings.json_ file is copied to the output, add a `Content` node to the csproj-file as shown below:

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>...</PropertyGroup>
  <ItemGroup>...</ItemGroup>

  <ItemGroup>
    <Content Include="appsettings.json" CopyToOutputDirectory="Always" />
  </ItemGroup>
</Project>
```

To start using the Secret Manager, you first need to initialize it for your project using the following command:

```bash
dotnet user-secrets init
```

Now you can set the `Twilio:AccountSid` and `Twilio:AuthToken` configuration like this:

```bash
dotnet user-secrets set Twilio:AccountSid [TWILIO_ACCOUNT_SID]
dotnet user-secrets set Twilio:AuthToken [TWILIO_AUTH_TOKEN]
```

It's likely you would configure the Account SID and Auth Token as environment variables in other environments, but since you're running this locally, the Secret Manager is preferred. So, in this case, you don't need to configure any environment variables.

There's only one configuration element left to configure, `Message:To`. Instead of configuring it using one of the previous sources, run the project using the .NET CLI and pass it in as a command-line argument:

```bash
dotnet run --Message:To [RECIPIENT_PHONE_NUMBER]
```

Just like before, the recipient will receive a text message, but now saying "Ahoy from JSON!".

Run the command again, but overwrite the `Message:Body` configuration using a command-line argument:

```bash
dotnet run --Message:To [RECIPIENT_PHONE_NUMBER] --Message:Body "Ahoy from the CLI"
```

Now the recipient will receive an SMS saying "Ahoy from the CLI".

The end result may be the same, but your application is a lot more flexible now because it can be configured in many different ways. However, these are not the only configuration providers. [Microsoft has 5 more configuration providers](https://docs.microsoft.com/en-us/dotnet/core/extensions/configuration#configuration-providers), and if those don't meet your needs, you can also develop your own provider or use someone else's. 

Some operating systems and shells may not allow you to use colons. In that case you can use a double underscore `__` as a separator, and .NET will replace it with the colon for you.

## Bind configuration to strongly-typed objects

You just learned how to use the configuration builder to get configuration from multiple sources, but the amount of configuration can quickly increase. A more manageable solution to this is to bind your configuration to strongly-typed objects. 

To starting binding configuration, add the [Microsoft.Extensions.Configuration.Binder NuGet package](https://www.nuget.org/packages/Microsoft.Extensions.Configuration.Binder) with the following command:

```bash
dotnet add package Microsoft.Extensions.Configuration.Binder
```

Update the _Program.cs_ file with the code below:

```csharp
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Twilio;
using Twilio.Rest.Api.V2010.Account;

IConfiguration config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
    .AddUserSecrets(Assembly.GetExecutingAssembly(), optional: true, reloadOnChange: false)
    .AddEnvironmentVariables()
    .AddCommandLine(args)
    .Build();

var twilioOptions = config.GetSection("Twilio").Get<TwilioOptions>();
TwilioClient.Init(
    username: twilioOptions.AccountSid,
    password: twilioOptions.AuthToken
);

var messageOptions = config.GetSection("Message").Get<MessageOptions>();
MessageResource.Create(
    from: messageOptions.From,
    to: messageOptions.To,
    body: messageOptions.Body
);

public class TwilioOptions
{
    public string AccountSid { get; set; }
    public string AuthToken { get; set; }
}

public class MessageOptions
{
    public string From { get; set; }
    public string To { get; set; }
    public string Body { get; set; }
}
```

At the bottom of _Program.cs_, there are two new classes which have properties with names that match your configuration. By matching the name of the properties and the keys of the configuration, you will be able to bind the configuration to instances of said class. You can grab sections from the configuration using `config.GetSection("YourSectionName")` and then bind the section to strongly-typed objects using `.Bind<YourClass>()`. This way, the `Twilio` and `Message` sections are bound to instances of `TwilioOptions` and `MessageOptions`, which are then used to authenticate and send the text message as before.

If you run the project now, you will continue to get the same result:

```bash
dotnet run --Message:To [RECIPIENT_PHONE_NUMBER] --Message:Body "Ahoy from the CLI"
```

If `reloadOnChange` is enabled on the configuration providers, and the configuration changes in the configuration source, the configuration object will be updated, but any strongly-typed object that has already been bound will not be updated accordingly.

## Swap configuration builder with the default host builder

If you've used the ASP.NET templates, you may notice that the configuration has already been set up, without any `ConfigurationBuilder` code. That's because ASP.NET templates use the default [Web Host](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?view=aspnetcore-6.0) which sets up the default configuration, logging, dependency injection, web related functionality, and more. 

If you want the same configuration, logging, and dependency injection from the web host builder, but not the web related functionality, you can use the [Generic Host](https://docs.microsoft.com/en-us/dotnet/core/extensions/generic-host) instead of the Web Host.

Add the [Microsoft.Extensions.Hosting NuGet package](https://www.nuget.org/packages/Microsoft.Extensions.Hosting) using the .NET CLI:

```bash
dotnet add package Microsoft.Extensions.Hosting
```

In _Program.cs_, Replace the `using` statements and the `ConfigurationBuilder` code with the following code:

```csharp
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Twilio;
using Twilio.Rest.Api.V2010.Account;

using IHost host = Host.CreateDefaultBuilder(args).Build();
var config = host.Services.GetRequiredService<IConfiguration>();
```

Instead of building the configuration yourself, the project now uses the defaults that come with the Generic Host. The code then retrieves the configuration from the dependency injection container (`host.Services`), and continues using the configuration object as before.

The default Generic Host will build configuration similarly to how you did it yourself, but there are some differences. You can find an [overview of the Generic Host defaults in the Microsoft Documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-6.0#default-builder-settings).

The default Generic Host builds configuration slightly differently. More specifically, your user secrets will only be loaded if the `Environment` is configured as `Development`. There are [multiple ways to configure the environment](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments#environments), but for this tutorial, pass in the `Environment` argument when you run your project, like this:

```bash
dotnet run --Environment Development --Message:To [RECIPIENT\_PHONE\_NUMBER] --Message:Body "Ahoy from the CLI"
```

## Get configuration from dependency injection using the options pattern

Now that you're using the Generic Host, you can start using the dependency injection (DI) that comes with it, just like you can on ASP.NET applications.

Replace your existing code in _Program.cs_ with the following code:

```csharp
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Twilio;
using Twilio.Rest.Api.V2010.Account;

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        services.Configure<TwilioOptions>(context.Configuration.GetSection("Twilio"));
        services.Configure<MessageOptions>(context.Configuration.GetSection("Message"));
        services.AddTransient<MessageSender>();
    })
    .Build();

var twilioOptions = host.Services.GetRequiredService<IOptions<TwilioOptions>>().Value;
TwilioClient.Init(
    username: twilioOptions.AccountSid,
    password: twilioOptions.AuthToken
);

var messageSender = host.Services.GetRequiredService<MessageSender>();
messageSender.SendMessage();

public class MessageSender
{
    private readonly MessageOptions messageOptions;

    public MessageSender(IOptions<MessageOptions> messageOptions)
    {
        this.messageOptions = messageOptions.Value;
    }

    public void SendMessage()
    {
        MessageResource.Create(
            from: messageOptions.From,
            to: messageOptions.To,
            body: messageOptions.Body
        );
    }
}

public class TwilioOptions
{
    public string AccountSid { get; set; }
    public string AuthToken { get; set; }
}

public class MessageOptions
{
    public string From { get; set; }
    public string To { get; set; }
    public string Body { get; set; }
}
```

You can use the `ConfigureServices` method on the host builder and pass in a lambda to add more services to the DI container. In this lambda, the `Twilio` and `Message` configuration sections are added as configuration using `services.Configure<YourOptions>`. As a result, your configuration will be injected wherever DI is supported by adding a parameter of type `IOptions<YourOptions>` to your constructor or method signature. Microsoft calls this the [Options pattern](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-6.0#bind-hierarchical-configuration-data-using-the-options-pattern).

In addition to `IOptions<T>`, there's also `IOptionsSnapshot<T>`, `IOptionsMonitor<T>`, and `IOptionsFactory<TOptions>` which each have different behavior and use-cases. Learn more about the [differences between these options interfaces at Microsoft's documentation](https://docs.microsoft.com/en-us/dotnet/core/extensions/options#options-interfaces).

After configuring the options, the program will add the `MessageSender` class to the CI container. `MessageSender` is a new class that will be responsible for sending SMS.  
  
Now that the DI container has been built, you can retrieve the services from it. So now you can get the `TwilioOptions` using `host.Services.GetRequiredService<IOptions<TwilioOptions>>().Value`. You can also have your `TwilioOptions` injected anywhere that supports DI. After retrieving the `TwilioOptions` object, it is used to authenticate with Twilio.

Then, the code will retrieve an instance of `MessageSender` from the DI container, which is used to send an SMS using the `​​SendMessage` method.

When `MessageSender` is created by the DI container, the container injects an instance of `IOptions<MessageOptions>` into the constructor of `MessageSender`. `MessageSender` grabs the message options using the `.Value` property and uses it in the `SendMessage` method to send an SMS. 

You're currently using the static `TwilioClient` class to authenticate and send messages, but you could add the `TwilioRestClient` to your DI container and start using that client to make Twilio API calls. Your projects will be easier to test when you avoid using static classes and methods and also use DI.

## Better Twilio Authentication

The fastest way to get started with the Twilio API, is to use your Account SID and Auth Token, but there's a better way to authenticate. Twilio recommends creating an API Key, and using the API Key SID and Secret to authenticate. Learn how to strengthen your [Twilio Authentication by using sub accounts and API Keys in this tutorial](https://www.twilio.com/blog/better-twilio-authentication-csharp-twilio-api-keys). Once you have created an API key, update the authentication code and `TwilioOptions` in the _Program.cs_ file like this: 

```csharp
// existing code omitted for brevity

var twilioOptions = host.Services.GetRequiredService<IOptions<TwilioOptions>>().Value;
if (twilioOptions.CredentialType == CredentialType.ApiKey)
{
    TwilioClient.Init(
        username: twilioOptions.ApiKeySid,
        password: twilioOptions.ApiKeySecret,
        accountSid: twilioOptions.AccountSid
    );
}
else
{
    TwilioClient.Init(
        username: twilioOptions.AccountSid,
        password: twilioOptions.AuthToken
    );
}

// existing code omitted for brevity

public enum CredentialType
{
    AuthToken,
    ApiKey
}

public class TwilioOptions
{
    public CredentialType CredentialType { get; set; } 
        = CredentialType.AuthToken;

    public string AccountSid { get; set; }
    public string AuthToken { get; set; }
    public string ApiKeySid { get; set; }
    public string ApiKeySecret { get; set; }
}
```

Now your application supports authentication with both Auth Tokens and API Keys, and you can easily switch between the two types of credentials. You do have to configure these new options, which you can do like you've done before with the Auth Token and Account SID. To specify the `CredentialType` option, you can use any configuration source and set the configuration value to "ApiKey". For example, this is how you specify the `CredentialType` in _appsettings.json_:

```json
{
  "Twilio": {
    "CredentialType": "ApiKey"
  },
  "Message": {
    "From": "[YOUR_TWILIO_PHONE_NUMBER]",
    "Body": "Ahoy from JSON!"
  }
}
```

After configuring the `ApiKeySid`, `ApiKeySecret`, and `CredentialType`, the application should continue to work as before using the recommended API Key credentials.

## Why so complicated?

You went from approximately 6 straightforward lines of code, to approximately 25 lines of code with classes, interfaces, lambda expressions, generic types, and more. (Count excludes usings, brackets, whitespace, and API Key code.)  
Why complicate the code so much? 

For a small sample where you want to go from point A to point B as fast as possible, you can use environment variables and be done with it, but for real-world applications, although it requires extra setup, the techniques from this tutorial will decrease the amount of code you need to write and increase the flexibility and maintainability of your solution.

## Better .NET configuration

After following this tutorial, you have a learned how to use .NET's configuration APIs to:

- securely configure secrets and sensitive information
- retrieve configuration from multiple sources
- override configuration from one source with another source
- add configuration to the dependency inject container
- inject configuration into your classes

Congratulations on making it to the end of a long post about configuration! 👏  
You can start including these techniques for Twilio, SendGrid, or really any .NET applications! If you run into any problems, you can refer to the [source code for this tutorial on GitHub](https://github.com/Swimburger/TwilioOptionsPattern), or you can submit an issue on the GitHub repository. In addition to the end result, you can find each step in a separate branch ([Step 1](https://github.com/Swimburger/TwilioOptionsPattern/tree/Step1), [2](https://github.com/Swimburger/TwilioOptionsPattern/tree/Step2), [3](https://github.com/Swimburger/TwilioOptionsPattern/tree/Step3), [4](https://github.com/Swimburger/TwilioOptionsPattern/tree/Step5), [5](https://github.com/Swimburger/TwilioOptionsPattern/tree/Step5), and [6](https://github.com/Swimburger/TwilioOptionsPattern/tree/Step6))

Let me know what you're working on. I can't wait to see what you build!