SendGrid を使ってメールを送信する

MailKit 意味が分からないエラーたくさん出てきて困る。オフィシャルのパッケージがあるようなのでそれ SendGrid.NetCore を使ってメールを送信してみます。

public sealed class Email
{
    public string Address { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }

    public override string ToString ()
    {
        return $"{{Address: {Address}, Subject: {Subject}, Body: {Body}}}";
    }
}

public interface IEmailSender
{
    Task SendAsync (Email email);
}

public sealed class SendGridEmailSender : IEmailSender
{
    #region HIDDEN

    readonly ILogger _Logger;
    readonly Web _TransportWeb;
    readonly MailAddress _Sender;

    #endregion

    public SendGridEmailSender (IOptions<SendGridEmailSenderOptions> options, ILoggerFactory loggerFactory)
    {
        _Logger = loggerFactory.CreateLogger<SendGridEmailSender> ();
        _TransportWeb = new Web (new NetworkCredential (options.Value.UserName, options.Value.Password));
        _Sender = new MailAddress (options.Value.Sender, options.Value.SenderDisplay);
    }
    public Task SendAsync (Email email)
    {
        var message = new SendGridMessage ();
        message.AddTo (email.Address);
        message.Subject = email.Subject;
        message.Text = email.Body;
        message.From = _Sender;
        return _TransportWeb
            .DeliverAsync (message)
            .ContinueWith (_ => _Logger.LogInformation ("Email sent: {0}", email));
    }
}

public static class ServiceCollectionExtensions
{
    public static void AddSendGridEmailSender (this IServiceCollection services, Action<SendGridEmailSenderOptions> configureOptions)
    {
        services.AddScoped<IEmailSender, SendGridEmailSender> ();
        services.Configure (configureOptions);
    }
}

リファレンス更新してください、マジで。

Entity Framework Core でエンティティクラスを別のプロジェクトに分ける方法

運濃すぎる。分けたらマイグレーションできなくなった。一週間前これで退社できず昼夜逆転しまだ直りません。

Targeting class library projects is not supported

ここで書かれてるようなイケてるエラーは出なかったけどな。早く fix して。対処方法しか見つからなかったのですが、マイグレーションできないそのプロジェクトを実行可能にしろってことみたいです。

エンティティクラスをまとめた (あと DB コンテキスト) プロジェクトの project.json は以下のようになりました。ASP.NET Core Identity 使ってるので関係無いものも含まれてます。

{
    "dependencies": {
        "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.1.0-preview1-final",
        "Microsoft.EntityFrameworkCore.SqlServer": "1.1.0-preview1-final",
        "Microsoft.EntityFrameworkCore.SqlServer.Design": {
            "version": "1.1.0-preview1-final",
            "type": "build"
        },
        "Microsoft.EntityFrameworkCore.Design": {
            "version": "1.0.0-preview2-final",
            "type": "build"
        },
        "Microsoft.EntityFrameworkCore.Tools": {
            "version": "1.0.0-preview3-final",
            "type": "build"
        }
    },
    "tools": {
        "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview3-final"
    },
    "frameworks": {
        "net462": {}
    },
    "buildOptions": {
        "emitEntryPoint": true
    }
}

"emitEntryPoint": true が味噌らしいです。あとはパッケージ周りのエラーを見ながら必要なパッケージを追加します。で、エントリポイント発行のため空のエントリポイントを追加します。

public static class Program
{
    public static void Main (string[] args)
    {
        // 実行しないので書く必要が無い
    }
}

最後に IDbContextFactory{TDbContext} を実装したクラスをどこかに置いておきます。IdentityDbContext{TUser} を継承しているのでこのように対処しました。

public sealed class DbContextFactory : IDbContextFactory<DbContext>
{
    public DbContext Create (DbContextFactoryOptions options)
    {
        var builder = new DbContextOptionsBuilder<DbContext> ();

        //
        // ...
        //
        // DB コンテキストを初期化します。
        // builder は DbContext{T}.OnConfiguring の引数と同じ。
        //

        return new DbContext (builder.Options);
    }
}

Add-Migration で素っ気ないエラーが出る場合 (一行だけの Built error. みたいな奴だったと思うけど忘れた)、僕の場合はコードにバグがあるかパッケージ不足が原因でした。コードとパッケージが問題なく、リレーションなどの不具合が原因ならマイグレーション追加時にちゃんと報告してくれます。

TagHelper に指定された属性をエンコードする

HttpUtility があるけど古の腐ったタレっぽい気がするので、何か新しいやり方があるんじゃないかと。だが調べても出てこなかった。

結論から書くと TagHelper でも DI 使えるので、IHtmlGenerator を引数に取って使います。書いといてくださいよ…。

[HtmlTargetElement ("image")]
public sealed class ImageTagHelper
{
    #region HIDDEN

    reaedonly IHtmlGenerator _Generator;

    #endregion

    public string Url { get; set; }
    public string Description { get; set; }

    public ImageTagHelper (IHtmlGenerator generator)
    {
        _Generator = generator;
    }
    public override void Process (TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "img";
        output.Attributes.SetAttribute ("src", _Generator.Encode (Url));
        output.Attributes.SetAttribute ("alt", _Generator.Encode (Description));
    }
}

これで、

@{
    var url = " /><p>Hello, World!</p>"; // ユーザー入力から来る値
    <image url="@url" description="気持ちよくさせねーよ" />
}

こういう悪戯に対処できます。