How to better configure C# and .NET applications for SendGrid

Niels Swimburger.NET 🍔 - Jun 27 '22 - - Dev Community

This blog post was written for Twilio and originally published at the Twilio blog.

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. However, in some scenarios configuration isn't pulled from files, but instead is pulled from 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 emails using Twilio SendGrid, 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:

You can find the source code for this tutorial on GitHub. Use it if you run into any issues, or submit an issue if you run into problems.

Configure your SendGrid account to send emails

There are two things you need to configure before you can send emails. First, you’ll need to set up Sender Authentication. This will verify that you own the email address or domain that you will send emails from. Second, you’ll need to create a SendGrid API Key with permission to send emails.

Sender Authentication

To quickly get started, you will use Single Sender Verification for this tutorial. This verifies that you own the email address that the application will send emails from. Single Sender Verification is great for testing purposes , but it is not recommended for production.

Twilio recommends Domain Authentication for production environments. An authenticated domain proves to email providers that you own the domain, and removes the "via sendgrid.net" text that inbox providers would otherwise append to your from address.

To set up Single Sender Verification, click the Settings tab in the left menu. Once the settings tab opens, click Sender Authentication.

Side-navigation with a Settings group and multiple links in the group. The link "Sender Authentication" is highlighted.

Then, click Get Started under the Single Sender Verification section.

A button saying "Get Started" to verify an individual email address.

This opens a form in the right-side panel. Fill out the form with your information and email address.

A form to create a sender. The form asks for contact information including the email address that will be used to send emails from.

Click Create after filling out the form. Another panel will appear on the right, asking you to confirm your email address in your email inbox.Go to your personal email inbox, open the email from SendGrid, and click Verify Single Sender.

An email from SendGrid with a button saying "Verify Single Sender".

Your email address has been verified. You can now use it to send emails!

Create a SendGrid API Key to send emails

Back on the SendGrid website, click API Keys under the Settings tab , then click Create API Key in the top right-hand corner. This opens another form in the right-side panel.

Give your API Key a meaningful name. You can assign different permissions to the API Key. For optimal security, you should only give the minimum amount of permissions that you need.

Next, click Restricted Access.

A form to create an API Key. The form asks for a name for the key and what permissions to grant to the key.

Scroll down to the Mail Send accordion item and click on it to reveal the permissions underneath. Drag the slider to the right for the Mail Send permission.

A list of permissions that you can control using a slider bar next to each permission. The "Mail Send" accordion item is expanded to reveal the "Mail Send" permission underneath.

Scroll to the bottom of the form and click Create & View. The API key will now be displayed on your screen.

You will not be able to retrieve the API key again once you leave this screen, so make sure you copy the API Key somewhere safe.

API key is displayed to copy and store elsewhere. Key will not be shown again.

With the sender verified and the API Key created, you’re ready to write some code!

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

  • Replace [SENDGRID_API_KEY] with the API Key you just took note of
  • Replace [FROM_EMAIL_ADDRESS] and [FROM_NAME] with the email address you verified earlier and the name you want your recipients to see.
  • Replace [TO_EMAIL_ADDRESS] and [TO_NAME] with the email address and name of the desired recipient of the email.

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:

git clone https://github.com/Swimburger/SendGridOptionsPattern.git --branch Step1
Enter fullscreen mode Exit fullscreen mode

Then navigate into the SendGridOptionsPattern folder using cd SendGridOptionsPattern. This project is a console application with only one C# file, Program.cs, which has the following code:

using SendGrid;
using SendGrid.Helpers.Mail;

var apiKey = Environment.GetEnvironmentVariable("SendGridApiKey");
var fromEmailAddress = Environment.GetEnvironmentVariable("FromEmailAddress");
var fromName = Environment.GetEnvironmentVariable("FromName");
var toEmailAddress = Environment.GetEnvironmentVariable("ToEmailAddress");
var toName = Environment.GetEnvironmentVariable("ToName");

var client = new SendGridClient(apiKey);
var message = new SendGridMessage
{
    From = new EmailAddress(fromEmailAddress, fromName),
    Subject = "Sending with Twilio SendGrid is Fun",
    PlainTextContent = "and easy to do anywhere, especially with C#"
};
message.AddTo(new EmailAddress(toEmailAddress, toName));
var response = await client.SendEmailAsync(message);

// A success status code means SendGrid received the email request and will process it.
// Errors can still occur when SendGrid tries to send the email. 
// If email is not received, use this URL to debug: https://app.sendgrid.com/email_activity 
Console.WriteLine(response.IsSuccessStatusCode ? "Email queued successfully!" : "Something went wrong!");
Enter fullscreen mode Exit fullscreen mode

The application pulls the SendGrid API Key, as well as the email address and name for the sender and recipient from the environment variables. The code then creates a new SendGridClient with the API key, creates an email message using SendGridMessage, and then sends the email.

The application uses the SendGrid library for C# and .NET to authenticate with and send emails through the SendGrid API. The SendGrid NuGet 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:

export SendGridApiKey=[SENDGRID_API_KEY]
export FromEmailAddress=[FROM_EMAIL_ADDRESS]
export FromName="[FROM_NAME]"
export ToEmailAddress=[TO_EMAIL_ADDRESS]
export ToName="[TO_NAME]"
Enter fullscreen mode Exit fullscreen mode

If you're using PowerShell:

$Env:SendGridApiKey = "[SENDGRID_API_KEY]"
$Env:FromEmailAddress = "[FROM_EMAIL_ADDRESS]"
$Env:FromName = "[FROM_NAME]"
$Env:ToEmailAddress = "TO_EMAIL_ADDRESS]"
$Env:ToName = "[TO_NAME]"
Enter fullscreen mode Exit fullscreen mode

If you're using CMD:

set "SendGridApiKey=[SENDGRID_API_KEY]"
set "FromEmailAddress=[FROM_EMAIL_ADDRESS]"
set "FromName=[FROM_NAME]"
set "ToEmailAddress=[TO_EMAIL_ADDRESS]"
set "ToName=[TO_NAME]"
Enter fullscreen mode Exit fullscreen mode

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

dotnet run
Enter fullscreen mode Exit fullscreen mode

The application will send an email from your verified email address to the recipient.

An email with title "Sending with Twilio SendGrid is Fun" and body "and easy to do anywhere, especially with C#

When the SendGrid API responds with a successful HTTP status code, that does not necessarily mean that the email was successfully received by the recipient. It does however mean that the SendGrid API has accepted your request and will send the email. If the recipient does not receive the email, you can use the Email Activity Feed to find out why.

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 API Key, 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 API Key configuration into the environment variables and run it.

This is why in SendGrid 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 SendGrid without compromising your API Key by accident.

However, 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 and merge them all together with the ConfigurationBuilder. These APIs were moved into the Microsoft.Extensions.Hosting NuGet package 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. .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:

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
Enter fullscreen mode Exit fullscreen mode

To start using the configuration APIs, update the Program.cs file with the following code:

using System.Reflection;
using Microsoft.Extensions.Configuration;
using SendGrid;
using SendGrid.Helpers.Mail;

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

var apiKey = config["SendGrid:ApiKey"];
var fromEmailAddress = config["Email:From:Email"];
var fromName = config["Email:From:Name"];
var toEmailAddress = config["Email:To:Email"];
var toName = config["Email:To:Name"];
var emailSubject = config["Email:Subject"];
var emailBody = config["Email:Body"];

var client = new SendGridClient(apiKey);
var message = new SendGridMessage
{
    From = new EmailAddress(fromEmailAddress, fromName),
    Subject = emailSubject,
    PlainTextContent = emailBody
};
message.AddTo(new EmailAddress(toEmailAddress, toName));
var response = await client.SendEmailAsync(message);

// A success status code means SendGrid received the email request and will process it.
// Errors can still occur when SendGrid tries to send the email. 
// If email is not received, use this URL to debug: https://app.sendgrid.com/email_activity 
Console.WriteLine(response.IsSuccessStatusCode ? "Email queued successfully!" : "Something went wrong!");
Enter fullscreen mode Exit fullscreen mode

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 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: SendGrid: and Email:.

You can organize your configuration into hierarchical sections by adding colons : as separators. So in this case, ApiKey is part of the SendGrid section, and all the other configuration is part of the Email section. You can also nest sections as shown in the Email section. The Email section contains child sections From and To which hold on to the email address and name of the sender and recipient. There are a lot of different ways you could structure your configuration, but how you organize your configuration all depends 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:

{
  "Email": {
    "From": {
      "Email": "[FROM_EMAIL_ADDRESS]",
      "Name": "[FROM_NAME]"
    },
    "Subject": "Ahoy from JSON!",
    "Body": "Thank you for signing up to our website. See you around!"
  }
}
Enter fullscreen mode Exit fullscreen mode

The appsettings.json file will provide the FromEmailAddress, FromName, Subject, and 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:

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

  <ItemGroup>
    <Content Include="appsettings.json" CopyToOutputDirectory="Always" />
  </ItemGroup>
</Project>
Enter fullscreen mode Exit fullscreen mode

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

dotnet user-secrets init
Enter fullscreen mode Exit fullscreen mode

Now you can set the SendGrid:ApiKey configuration like this:

dotnet user-secrets set SendGrid:ApiKey [SENDGRID_API_KEY]
Enter fullscreen mode Exit fullscreen mode

It's likely you would configure the API Key as an environment variable 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.

The application now has configuration for who the email will be sent from, but not who the email will be sent 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:

dotnet run --Email:To:Email [TO_EMAIL_ADDRESS] --Email:To:Name "[TO_NAME]"
Enter fullscreen mode Exit fullscreen mode

Just like before, the recipient will receive an email, but now the subject will be "Ahoy from JSON!".

Run the command again, but overwrite the Email:Subject configuration using a command-line argument:

dotnet run --Email:To:Email [TO_EMAIL_ADDRESS] --Email:To:Name "[TO_NAME]" --Email:Subject "Ahoy from the CLI"
Enter fullscreen mode Exit fullscreen mode

Now the recipient will receive an email with the subject "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, 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 get out of hand. 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 with the following command:

dotnet add package Microsoft.Extensions.Configuration.Binder
Enter fullscreen mode Exit fullscreen mode

Update the Program.cs file with the code below:

using System.Reflection;
using Microsoft.Extensions.Configuration;
using SendGrid;
using SendGrid.Helpers.Mail;

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

var apiKey = config["SendGrid:ApiKey"];

var emailOptions = config.GetSection("Email").Get<EmailOptions>();

var client = new SendGridClient(apiKey);
var message = new SendGridMessage
{
    From = emailOptions.From,
    Subject = emailOptions.Subject,
    PlainTextContent = emailOptions.Body
};
message.AddTo(emailOptions.To);
var response = await client.SendEmailAsync(message);

// A success status code means SendGrid received the email request and will process it.
// Errors can still occur when SendGrid tries to send the email. 
// If email is not received, use this URL to debug: https://app.sendgrid.com/email_activity 
Console.WriteLine(response.IsSuccessStatusCode ? "Email queued successfully!" : "Something went wrong!");

public class EmailOptions
{
    public EmailAddress From { get; set; }
    public EmailAddress To { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

At the bottom of Program.cs, there is a new class which has 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 .Get<YourClass>(). This way, the Email section is bound to instances of EmailOptions which is then used to send the email as before.

For the EmailOptions class, the From and To properties are defined using the EmailAddress class provided by the SendGrid library. The EmailAddress class contains two properties: Email and Name. Because these properties names match the names of your configuration, even these nested objects will be bound.

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

dotnet run --Email:To:Email [TO_EMAIL_ADDRESS] --Email:To:Name "[TO_NAME]" --Email:Subject "Ahoy from the CLI"

Enter fullscreen mode Exit fullscreen mode

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 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 instead of the Web Host.

Add the Microsoft.Extensions.Hosting NuGet package using the .NET CLI:

dotnet add package Microsoft.Extensions.Hosting
Enter fullscreen mode Exit fullscreen mode

In Program.cs, Replace the using statements and the ConfigurationBuilder code with the following code:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SendGrid;
using SendGrid.Helpers.Mail;

using IHost host = Host.CreateDefaultBuilder(args).Build();
var config = host.Services.GetRequiredService<IConfiguration>();
Enter fullscreen mode Exit fullscreen mode

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.

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, but for this tutorial, pass in the Environment argument when you run your project, like this:

dotnet run --Environment Development  --Email:To:Email [TO_EMAIL_ADDRESS] --Email:To:Name "[TO_NAME]" --Email:Subject "Ahoy from the CLI"
Enter fullscreen mode Exit fullscreen mode

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:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        services.Configure<EmailOptions>(context.Configuration.GetSection("Email"));
        services.AddTransient<EmailSender>();
    })
    .Build();

var config = host.Services.GetRequiredService<IConfiguration>();

var apiKey = config["SendGrid:ApiKey"];
var client = new SendGridClient(apiKey);

var emailSender = host.Services.GetRequiredService<EmailSender>();
await emailSender.SendEmail(client);

public class EmailSender
{
    private readonly EmailOptions emailOptions;

    public EmailSender(IOptions<EmailOptions> emailOptions)
    {
        this.emailOptions = emailOptions.Value;
    }

    public async Task SendEmail(ISendGridClient client)
    {
        var message = new SendGridMessage
        {
            From = emailOptions.From,
            Subject = emailOptions.Subject,
            PlainTextContent = emailOptions.Body
        };
        message.AddTo(emailOptions.To);
        var response = await client.SendEmailAsync(message);

        // A success status code means SendGrid received the email request and will process it.
        // Errors can still occur when SendGrid tries to send the email. 
        // If email is not received, use this URL to debug: https://app.sendgrid.com/email_activity 
        Console.WriteLine(response.IsSuccessStatusCode ? "Email queued successfully!" : "Something went wrong!");
    }
}

public class EmailOptions
{
    public EmailAddress From { get; set; }
    public EmailAddress To { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

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 Email configuration section is 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.

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.

After configuring the options, the program will add the EmailSender class to the CI container. EmailSender is a new class that will be responsible for sending Emails. Now that the DI container has been built, you can retrieve the services from it. So now you have your EmailOptions injected anywhere that supports DI.

Now, the code will retrieve an instance of EmailSender from the DI container, which is used to send an email using the ​​SendEmail method.

When EmailSender is created by the DI container, the container injects an instance of IOptions<EmailOptions> into the constructor of EmailSender. EmailSender grabs the message options using the .Value property and uses it in the SendEmail method to send an email.

Add SendGridClient to the Dependency Injection Container

You've made your application more flexible and configurable, but you can also further integrate SendGrid into your application. The SendGrid SDK for .NET has an extra NuGet package that will add the SendGrid client to the dependency injection container.

Run the following command to add the SendGrid DI package:

dotnet add package SendGrid.Extensions.DependencyInjection
Enter fullscreen mode Exit fullscreen mode

Then update the Program.cs file like this:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Extensions.DependencyInjection;
using SendGrid.Helpers.Mail;

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        services.AddSendGrid(options =>
            options.ApiKey = context.Configuration["SendGrid:ApiKey"]
        );
        services.Configure<EmailOptions>(context.Configuration.GetSection("Email"));
        services.AddTransient<EmailSender>();
    })
    .Build();

var emailSender = host.Services.GetRequiredService<EmailSender>();
await emailSender.SendEmail();

public class EmailSender
{
    private readonly ISendGridClient sendGridClient;
    private readonly EmailOptions emailOptions;

    public EmailSender(IOptions<EmailOptions> emailOptions, ISendGridClient sendGridClient)
    {
        this.sendGridClient = sendGridClient;
        this.emailOptions = emailOptions.Value;
    }

    public async Task SendEmail()
    {
        var message = new SendGridMessage
        {
            From = emailOptions.From,
            Subject = emailOptions.Subject,
            PlainTextContent = emailOptions.Body
        };
        message.AddTo(emailOptions.To);
        var response = await sendGridClient.SendEmailAsync(message);

        // A success status code means SendGrid received the email request and will process it.
        // Errors can still occur when SendGrid tries to send the email. 
        // If email is not received, use this URL to debug: https://app.sendgrid.com/email_activity 
        Console.WriteLine(response.IsSuccessStatusCode ? "Email queued successfully!" : "Something went wrong!");
    }
}

public class EmailOptions
{
    public EmailAddress From { get; set; }
    public EmailAddress To { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

The application adds the SendGrid client to the DI container using the services.AddSendGrid method.

To configure the options of SendGrid, another lambda is used and passed into AddSendGrid. The lambda-inception can be tricky, so make sure to verify all your parentheses match the code above.

Instead of constructing the SendGridClient as you did before, the ISendGridClient will now be injected into the constructor of the EmailSender class, and then used to send an email as before.

Why so complicated?

You went from approximately 12 straightforward lines of code, to approximately 26 lines of code with classes, interfaces, lambda expressions, generic types, and more.

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 up-front, 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 SendGrid, Twilio, or really any .NET applications! If you run into any problems, you can refer to the source code for this tutorial on GitHub, 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, 2, 3, 4, 5, and 6)

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .