What's new in the Twilio helper library for ASP.NET (v7.0.0 - November 2022)

November 18, 2022
Written by
Reviewed by

What's new in the Twilio helper library for ASP.NET (v7.0.0 - November 2022)

The Twilio helper library for ASP.NET (Twilio.AspNet) is a community-driven open-source project to make integrating Twilio with ASP.NET easier, for both ASP.NET Core and ASP.NET MVC on .NET Framework. The library helps you achieve common use cases and with the release of version 7, we're expanding the library's capabilities and improving existing features.

Wondering what was previously introduced? You can read about v6 and prior releases here.

What's new in Twilio.AspNet v7.0.0

V7.0.0 is a major release of the Twilio.AspNet library because it contains breaking changes.

Here's an overview of the changes:

🎉 NEW FEATURES  

  • You can now use the ValidateTwilioRequestFilter to validate that HTTP requests originate from Twilio. This adds validation to endpoints including Minimal APIs.
  • You can also use the new ValidateTwilioRequestMiddleware to validate Twilio requests for when you can't use the [ValidateRequest] attribute or the ValidateTwilioRequestFilter.
  • There are new overloads for the TwiMLResult constructor and for the TwiML methods that let you specify how to format the TwiML in the HTTP response body. This lets you remove whitespace to minimize the payload size.
  • The VoiceResponse and MessagingResponse class has a new extension method ToTwiMLResult() that will create a TwiMLResult for you.

🙌 ENHANCEMENTS

⚠️ BREAKING CHANGES

  • You can no longer pass in a string or XDocument into the TwiMLResult constructor. You can find guidance on how to update your code in this GitHub Gist.
  • The public properties on TwiMLResult have been removed.
  • The HttpRequest.IsLocal() extension method has been removed.
  • The HttpStatusCodeResult class has been removed in favor of StatusCodeResult provided by ASP.NET Core MVC and HttpStatusCodeResult by ASP.NET MVC on .NET Framework.
  • The Twilio.AspNet.Core.MinimalApi namespace has been removed. Types from the namespace have moved to the Twilio.AspNet.Core namespace.
  • The RequestValidationHelper class is now static. You'll need to change your code to not instantiate this class and call its methods in a static manner instead.

Validate Twilio requests in endpoints and minimal APIs

To integrate with Twilio webhooks, your webhook handlers have to be publicly accessible on the internet. That  means everyone else can access your webhook handlers – including malicious actors. This is why you have to validate that the incoming HTTP request originates from Twilio.

The helper library for ASP.NET has multiple ways to perform this validation. First of all, you need to configure Twilio request validation by calling .AddTwilioRequestValidation() during startup:

using Twilio.AspNet.Core;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTwilioRequestValidation();

Then you need to use .NET configuration to configure the request validation. Here's the JSON representation of the configuration:

{
  "Twilio": {
    "RequestValidation": {
      "AuthToken": "[YOUR_AUTH_TOKEN]",
      "AllowLocal": true,
      "BaseUrlOverride": "https://??????.ngrok.io"
    }
  }
}

While the example above shows the JSON representation of the configuration, you can use any configuration provider plugged into your application. The AuthToken in particular should not be stored in your appsettings.json file. Instead, use user-secrets, a vault service, or environment variables instead.

Now that request validation is configured, you can apply the [ValidateRequest] attribute to your controllers and actions in ASP.NET Core MVC. However, until now, there was no way to validate requests outside of MVC. In v7 there are two new ways to validate requests outside of MVC, using the ValidateTwilioRequestFilter for endpoints and Minimal APIs, and using the ValidateTwilioRequestMiddleware which is discussed in more detail later.

ValidateTwilioRequestFilter relies on the new endpoint filters introduced in ASP.NET Core 7, which means this feature is only available when you install the library into an ASP.NET Core 7 project.

Here's how you can add request validation using the new  ValidateTwilioRequestFilter:

using Twilio.AspNet.Core;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTwilioRequestValidation();

var app = builder.Build();

app.MapPost("/sms", () => ...)
    .ValidateTwilioRequest();

app.Run();

Before the /sms endpoint is executed, the ValidateTwilioRequestFilter will validate that the HTTP requests originate from Twilio.

ValidateTwilioRequest() is a convenience extension method and is the equivalent of the following code:

app.MapPost("/sms", () => ...)
    .AddEndpointFilter<ValidateTwilioRequestFilter>();

ASP.NET Core 7 also introduced endpoint groups, which you can also apply the request validation to:

var twilioGroup = app.MapGroup("/twilio");
twilioGroup.ValidateTwilioRequest();
{
    twilioGroup.MapPost("/sms", () => ...);
    twilioGroup.MapPost("/voice", () => ...);
}

Now HTTP POST requests going to /twilio/sms and /twilio/voice will first be validated by the ValidateTwilioRequestFilter.

You can specify an empty path when creating an endpoint group. This can be helpful if you want to apply request validation to all endpoints in a group, but you don't want to prefix the paths of your endpoints.

Validate Twilio requests using middleware

If you can't use the [ValidateRequest] attribute or the new ValidateTwilioRequestFilter, you can now plug the ValidateTwilioRequestMiddleware into your request pipeline. Keep in mind that the middleware will run for all HTTP requests unless you conditionally apply the middleware using MapWhen or UseWhen.

The ValidateTwilioRequestMiddleware is particularly useful for serving static media files that only Twilio should have access to, because middleware is the only way to hook into the HTTP requests for static files. Here's how you would serve static files with Twilio request validation:

using System.Net;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using Twilio.AspNet.Core;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTwilioRequestValidation();

var app = builder.Build();

app.UseWhen(
    context => context.Request.Path.StartsWithSegments("/twilio-media", StringComparison.OrdinalIgnoreCase),
    app => app.UseTwilioRequestValidation()
);

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, "TwilioMedia")),
    RequestPath = "/twilio-media"
});

app.Run();

The ValidateTwilioRequestMiddleware is added by the app.UseTwilioRequestValidation() extension method. However, this middleware only runs when the path of the incoming HTTP request starts with /twilio-media because it is conditionally added using app.UseWhen.

The media files are stored in a subfolder called TwilioMedia and served at the path /twilio-media. When those media files are requested, the ValidateTwilioRequestMiddleware validates the HTTP request before returning the file. However, other HTTP requests are unaffected.

Remove whitespace from your TwiML response

There are now overloads for the TwiML methods and the TwiMLResult constructor that let you specify the formatting of the TwiML using the System.Xml.Linq.SaveOptions enum. By specifying SaveOptions.DisableFormatting, there will be no formatting for the TwiML written to the HTTP response body. This will remove the whitespace and indentation from the resulting XML.

Here's an example of this using Minimal APIs:

using System.Xml.Linq;
using Twilio.AspNet.Core;
using Twilio.TwiML;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/message", () =>
{
    var response = new MessagingResponse();
    response.Message("Ahoy!");
    return Results.Extensions.TwiML(response, SaveOptions.DisableFormatting);
});

app.Run();

The response body would normally look like this:

<?xml version="1.0" encoding="utf-8"?>
<Response>
  <Message>Ahoy!</Message>
</Response>

However, with formatting removed, the response body looks like this:

<?xml version="1.0" encoding="utf-8"?><Response><Message>Ahoy!</Message></Response>

The ToTwiMLResult() extension method

The ToTwiMLResult() method extends the MessagingResponse and VoiceResponse classes, so you can more fluently create a TwiMLResult.

Here's how you'd create a TwiMLResult in Minimal API without ToTwiMLResult():

app.MapPost("/message", () =>
{
    var response = new MessagingResponse()
        .Message("Ahoy!");
    return Results.Extensions.TwiML(response);
});

This is the same code using the ToTwiMLResult extension method:

app.MapPost("/message", () => new MessagingResponse()
    .Message("Ahoy!")
    .ToTwiMLResult()
);

The ToTwiMLResult() also works in ASP.NET Core MVC.

Go use the shiny new bits

You can take advantage of these new features and enhancements now by installing the latest version of the Twilio helper library for ASP.NET. You can find the installation instructions in the readme of the Twilio.AspNet GitHub repository. If you like this library, consider giving it a star on the GitHub repo. Also you are welcome to submit an issue if you run into problems.

We can't wait to see what you'll build with Twilio.AspNet. Let us know on social media and don't forget to mention @TwilioDevs and @RealSwimburger on Twitter or LinkedIn.

Niels Swimberghe is a Belgian American software engineer and technical content creator at Twilio, and a Microsoft MVP in Developer Technologies. Get in touch with Niels on Twitter @RealSwimburger and follow Niels’ personal blog on .NET, Azure, and web development at swimburger.net.