しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Core から SendGrid の Template Engine を使ってメールを送信する

最近は仕事で ASP.NET Core MVC を使ったアプリケーションを書いてきて、メール送信が必要になったので SendGrid を例によって使うわけですが、今回は Template Engine を使って送信することにしました。

使うために調べは済んでましたが、これまでに仕事で使ったことはありませんでした。

大抵の場合は高度なカスタマイズが必要になり、これまでは Template Engine を見送ってきました。でも今回は時間的にも Template Engine を使うのが最適だったので選びました。

画面は多少変わっていますが、管理画面から Template を作成して ID をコピーしてくるのは変わりません。

f:id:shiba-yan:20171130234640p:plain

メールテンプレートを作る部分は省略して、実際に ASP.NET Core のアプリケーションから送信する部分に進んでいきます。今回は公式のクライアントを使うことにしました。

SendGrid 公式クライアントは .NET Core でもいい感じに使えるようになっています。

一応 Core MVC のお作法?にのっとって Options と Service を作って利用することにします。

項目としては API キーと送信元となるメールアドレス、そしてテンプレートの ID を用意しました。

public class MailOptions
{
    public string ApiKey { get; set; }

    public string From { get; set; }

    public string SampleTemplateId { get; set; }
}

設定値は appsettings.json で切り替えが出来るようになるので、ステージングと本番を SendGrid のサブユーザーで切り替えていても問題なく扱えます。

そして適当にサービスクラスを用意します。テンプレートで置換する値はクラスとして定義したいのと、テンプレート ID も直接触りたくはなかったので、メールの種類ごとにメソッドを用意することにしました。

public class MailService
{
    public MailService(IOptions<MailOptions> options)
    {
        _options = options.Value;
    }

    private readonly MailOptions _options;

    public Task SampleMailAsync(string to, SampleMailModel model)
    {
        return SendMailAsync(_options.SampleTemplateId, to, model);
    }

    private async Task SendMailAsync(string templateId, string to, object model)
    {
        var sendGrid = new SendGridClient(_options.ApiKey);

        var message = new SendGridMessage
        {
            TemplateId = templateId,
            From = new EmailAddress(_options.From),
            Subject = " ",
            PlainTextContent = " ",
            HtmlContent = " "
        };

        message.AddTo(to);

        foreach (var propertyInfo in model.GetType().GetProperties())
        {
            message.AddSubstitution($":{propertyInfo.Name}:", propertyInfo.GetValue(model).ToString());
        }

        await sendGrid.SendEmailAsync(message);
    }
}

Template Engine によって置換される値は、リフレクションを使って設定すれば楽です。

例によって Subject / PlainTextContent / HtmlContent が空文字列の場合には API がエラーを返すので、適当に何か文字列を入れておきます。空白とかで良いと思います。

最後にコントローラからサービスを呼び出します。特に説明は必要ないと思います。

public class MailController : Controller
{
    public MailController(MailService mailService)
    {
        _mailService = mailService;
    }

    private readonly MailService _mailService;

    public IActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Index(IFormCollection collection)
    {
        var model = new Models.SampleMailModel
        {
            Owner = "かずあき",
            Name = "Surface Book",
            Memory = "8GB"
        };

        await _mailService.SampleMailAsync("***@gmail.com", model);

        return View();
    }
}

最後に忘れずに DI に作成したクラスを追加します。大体追加を忘れていてエラーにしてしまうので、もっと良い方法がないのかと最近は考えています。

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MailOptions>(Configuration.GetSection("Mail"));

    services.AddSingleton<MailService>();

    services.AddMvc();
}

これで確認のメールを送信すると、ちゃんとテンプレートにモデルに入れた値が埋め込まれています。

f:id:shiba-yan:20171130234543p:plain

Template Engine を使うことで綺麗にモデルとビューを分けることが出来るので、メールの調整も大体はテンプレートの管理画面から修正するだけで終わります。非常に運用が楽になりました。

そしてメールの送信にかかる工数が非常に少なく済みました。1 時間もあれば余裕で作り終わってしまうコード量なので、締め切りギリギリのタイミングで非常に助けられました。実は感謝エントリです。