static
, so you don't have to use DI, but sometimes you might want to. For example, you might want to create a custom TwilioRestClient
that uses the HttpClientFactory
features of .NET Core 2.1. In this post, I describe how to create a custom ITwilioRestClient
, register it with the ASP.NET Core DI container, and inject it into a web API controller, so you can use it to send an SMS.TwilioClient.Init(accountSid, authToken); var message = MessageResource.Create( to: new PhoneNumber("+15558675309"), from: new PhoneNumber("+15017250604"), body: "Hello from C#");
TwilioRestClient
using the Twilio credentials you pass to the Init()
method. The MessageResource.Create()
method uses this client by default.TwilioRestClient
uses the standard HttpClient
class (in the System.Net.Http
namespace) to call the REST API. By customizing the HttpClient
you can control all the requests a TwilioRestClient
makes. In .NET Core 2.1, the best way to customize an HttpClient
is to use the HttpClientFactory
feature.HttpClient
has been around for a long time, and is a common tool for .NET developers, but it's easy to misuse. The class implements IDisposable
, so some developers use HttpClient
with a using
statement. Unfortunately, this can lead to "socket exhaustion".HttpClient
as a static
or singleton object. But this leads to another problem—a static HttpClient
doesn't respect DNS changes.HttpClientFactory
. Instead of having to micro-manage your HttpClient
instances, HttpClientFactory
takes care of managing the lifecycle of HttpClient
instances for you.HttpClientFactory
allows you to easily customize each HttpClient
, for example by adding automatic transient-fault handling using the Polly library.dotnet new webapi --no-https
.<ItemGroup>
section of the project file should look like this (version numbers may be higher):<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.App" /> <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" /> <PackageReference Include="Twilio" Version="5.22.0" /> </ItemGroup>
HttpClientFactory
-managed HttpClient
by injecting it into the constructor of any of your classes. You're then free to customize the headers or make any other necessary changes before using it to make requests.ITwilioRestClient
using an HttpClient
injected into the constructor and adds a custom header to all outgoing requests.using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Twilio.Clients; using Twilio.Http; namespace CustomTwilioRestClientDemo { public class CustomTwilioClient : ITwilioRestClient { private readonly ITwilioRestClient _innerClient; public CustomTwilioClient(IConfiguration config, System.Net.Http.HttpClient httpClient) { // customize the underlying HttpClient httpClient.DefaultRequestHeaders.Add("X-Custom-Header", "CustomTwilioRestClient-Demo"); _innerClient = new TwilioRestClient( config["Twilio:AccountSid"], config["Twilio:AuthToken"], httpClient: new SystemNetHttpClient(httpClient)); } public Response Request(Request request) => _innerClient.Request(request); public Task<Response> RequestAsync(Request request) => _innerClient.RequestAsync(request); public string AccountSid => _innerClient.AccountSid; public string Region => _innerClient.Region; public Twilio.Http.HttpClient HttpClient => _innerClient.HttpClient; } }
HttpClient
into the constructor and customize it by adding a default request header, X-Custom-Header
. We pass this HttpClient
into the new TwilioRestClient
, so all outgoing requests to the Twilio REST API using _innerClient
will contain this header.public CustomTwilioClient(IConfiguration config, System.Net.Http.HttpClient httpClient) { httpClient.DefaultRequestHeaders.Add("X-Custom-Header", "CustomTwilioRestClient-Demo"); _innerClient = new TwilioRestClient( config["Twilio:AccountSid"], config["Twilio:AuthToken"], httpClient: new SystemNetHttpClient(httpClient)); }
ITwilioRestClient
interface by delegating to the private TwilioRestClient _innerClient
. For example, all of the following methods and properties call the same property on the _innerClient
and return the result.public Response Request(Request request) => _innerClient.Request(request); public Task<Response> RequestAsync(Request request) => _innerClient.RequestAsync(request); public string AccountSid => _innerClient.AccountSid; public string Region => _innerClient.Region; public Twilio.Http.HttpClient HttpClient => _innerClient.HttpClient;
IConfiguration config
. This includes your Twilio Account Sid and Auth Token (found in the Twilio Dashboard), which can be placed in a file like appsettings.json below or, better yet, using the Secrets Manager tool. You can learn how in another Twilio blog post: User Secrets in a .NET Core Web App. For this demo, add the following section to the appsettings.json file and insert your Twilio SID and Auth Token in place of the placeholders:"Twilio": { "AccountSID": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "AuthToken": "your_auth_token" }
IConfiguration
object directly in your classes is generally not the best approach. Instead, you should consider using strongly typed settings with the Options pattern. I've used the configuration option here purely for convenience.Startup.ConfigureServices()
. This class and method is created by default when you create a new ASP.NET Core project, and is where you configure your DI container. You need to register any framework services you require as well as your custom classes. Once a class is registered with the DI container you can inject it into the constructor of other classes.HttpClientFactory
to create the HttpClient
required by our CustomTwilioClient
so we can use the AddHttpClient<,>()
convenience method to register our class as a “typed client”. Add the directive using Twilio.Clients;
to Startup.cs, then update the ConfigureServices
method to the following:public void ConfigureServices(IServiceCollection services) { services.AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddHttpClient<ITwilioRestClient, CustomTwilioClient>(); }
AddMvc()
is added by default when you create an ASP.NET Core application, and adds all the required framework services for using MVC in your app.CustomTwilioClient
as an implementation of the ITwilioRestClient
service, and instructs the HttpClientFactory
to inject an HttpClient
at runtime.services.AddHttpClient<ITwilioRestClient, CustomTwilioClient>();
HttpClient
that will be injected into your CustomTwilioClient
here. For example, instead of adding custom headers inside your CustomTwilioClient
constructor, you could configure it here instead:services.AddHttpClient<ITwilioRestClient, CustomTwilioClient>(client => client.DefaultRequestHeaders.Add("X-Custom-Header", "HttpClientFactory-Sample"));
AddHttpClient<,>()
returns an IHttpClientBuilder
instance that you can use for further customization, for example to add transient HTTP fault handling.ITwilioRestClient
into any class and the DI container will create a CustomTwilioClient
for you. You can use this client to make calls using Twilio helper library methods, such as MessageResource.Create()
. Create MessageController.cs and add the following code. This shows an API controller in which we've injected an ITwilioRestClient
, and used it to send a message when it receives an HTTP POST.using Microsoft.AspNetCore.Mvc; using Twilio.Clients; using Twilio.Rest.Api.V2010.Account; using Twilio.Types; namespace CustomTwilioRestClientDemo { [ApiController] public class MessageController : ControllerBase { private readonly ITwilioRestClient _client; public MessageController(ITwilioRestClient client) { _client = client; } [HttpPost("api/send-sms")] public IActionResult SendSms(MessageModel model) { var message = MessageResource.Create( to: new PhoneNumber(model.To), from: new PhoneNumber(model.From), body: model.Message, client: _client); // pass in the custom client return Ok(message.Sid); } public class MessageModel { public string To { get; set; } public string From { get; set; } public string Message { get; set; } } } }
MessageController
constructor, and as long as you've registered the service with the DI container in Startup.ConfigureServices()
the framework will inject it for you. In this case we're injecting an ITwilioRestClient
and saving it in a readonly
field for use by the action method.private readonly ITwilioRestClient _client; public MessageController(ITwilioRestClient client) { _client = client; }
SendSms
builds a MessageModel
view model from POST requests, which we use to set the to
, from
, and body
, parameters of the MessageResource.Create()
call. We also pass in the custom ITwilioRestClient
so that the helper library uses this instead of the default TwilioRestClient
.[HttpPost("api/send-sms")] public IActionResult SendSms(MessageModel model) { var message = MessageResource.Create( to: new PhoneNumber(model.To), from: new PhoneNumber(model.From), body: model.Message, client: _client); // pass in the custom client return Ok(message.Sid); }
[ApiController]
attribute, so we don't need to worry about specifying the [FromBody]
parameter to bind JSON, or explicitly checking ModelState.IsValid
. See the documentation for further details on the [ApiController]
attribute.to
, from
, and body
, parametersfrom
parameter with your Twilio phone numberto
parameter must be a phone number you’ve registered with Twilio, typically the number you used when creating your account, and it must be able to receive SMS{ "from": "+441123456789", "to": "+441123456789", "message": "Hello from CustomTwilioClient!" }
MessageController
then returns a 200 OK response containing the Sid
of the message created:TwilioRestClient
and wanted to test your MessageController
then you'd be sending a new message with every request. By injecting an ITwilioRestClient
instead, you could inject a mock or stub implementation for your tests.CustomTwilioClient
further by using the HttpClientFactory
features to make it resistant to transient failures using the Polly library, use a proxy server, or add caching, for example. Once your CustomTwilioClient
is registered with the DI container, you can inject it anywhere—into API controllers, as in this article, but also into Razor pages, or your own custom services.ITwilioRestClient
, and use HttpClientFactory
to inject a custom HttpClient
into the CustomTwilioClient
constructor. We used this custom HttpClient
to add extra headers to all outgoing requests to the Twilio REST API. By registering the ITwilioRestClient
with the DI container you were able to create a simple Web API controller for sending SMSs with Twilio using the CustomTwilioClient
. Finally, you saw how to test your controller using Postman by sending a JSON request.HttpClientFactory
I recommend Steve Gordon’s excellent blog post series. For more posts on ASP.NET Core and running applications with Docker follow me on Twitter, or on my blog.