最近は仕事で ASP.NET Core MVC を使ったアプリケーションを書いてきて、メール送信が必要になったので SendGrid を例によって使うわけですが、今回は Template Engine を使って送信することにしました。
使うために調べは済んでましたが、これまでに仕事で使ったことはありませんでした。
大抵の場合は高度なカスタマイズが必要になり、これまでは Template Engine を見送ってきました。でも今回は時間的にも Template Engine を使うのが最適だったので選びました。
画面は多少変わっていますが、管理画面から Template を作成して ID をコピーしてくるのは変わりません。
メールテンプレートを作る部分は省略して、実際に 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(); }
これで確認のメールを送信すると、ちゃんとテンプレートにモデルに入れた値が埋め込まれています。
Template Engine を使うことで綺麗にモデルとビューを分けることが出来るので、メールの調整も大体はテンプレートの管理画面から修正するだけで終わります。非常に運用が楽になりました。
そしてメールの送信にかかる工数が非常に少なく済みました。1 時間もあれば余裕で作り終わってしまうコード量なので、締め切りギリギリのタイミングで非常に助けられました。実は感謝エントリです。