読者です 読者をやめる 読者になる 読者になる

しばやん雑記

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

IIS Express TestKit で使う設定を XML Document Transform を使って操作するようにしてみた

IIS

ぼちぼち改善している IIS Express で URL Rewrite のテストを書くパッケージですが、設定周りに柔軟性が無くかなりイマイチだと思っていたので XML Document Transform を使って改善しました。

NuGet に公開している最新版では XDT が使えるようになっています。

これまでのバージョンでは system.webServer の rewrite 部分のみ変更可能で、定義したファイルには rewrite 以下の設定を 1 つにまとめる必要がありました。

IIS Express TestKit ではデフォルトで以下のような Web.config を生成するので、発想としては Visual Studio の発行時の処理と同じです。この定義に対して XDT で追加を行うわけです。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="EchoHttpHandler" verb="*" path="*" type="IisExpressTestKit.EchoHttpHandler, IisExpressTestKit" resourceType="Unspecified" />
    </handlers>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="EchoHttpModule" type="IisExpressTestKit.EchoHttpModule, IisExpressTestKit" />
    </modules>
  </system.webServer>
</configuration>

変換を定義した XDT は Transform.config という名前で保存すると、IIS Express の起動前に Web.config への反映が自動的に行われます。

例を挙げると、以下のような内容で Transform.config を作成する形になります。

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.webServer>
    <rewrite xdt:Transform="Insert">
      <rules>
        <rule name="Redirect" stopProcessing="true">
          <match url="^redirect" />
          <action type="Redirect" url="http://www.google.co.jp" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

実行時にはリダイレクトのルールがちゃんと追加された形で IIS Express が起動されるので、あとはこれまで通りテストケースを書いていくだけです。

configSource を使って別ファイルを読み込む

Web.config などは configSource 属性を使うことで、別ファイルに定義を分割することが出来るので、今回 XDT の追加と同じタイミングで configSource の処理を行うように実装しました。

先ほどと同じ定義を configSource を使って書き換えてみます。Transform.config は以下のように修正します。

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.webServer>
    <rewrite xdt:Transform="Insert">
      <rules configSource="..\..\Rewrite.config" />
    </rewrite>
  </system.webServer>
</configuration>

作業としては rules の中身を削除し、configSource をパスを追加します。そして Rewrite.config という名前で rules の中身をそっくりコピーして保存しました。

<rules>
  <rule name="Redirect" stopProcessing="true">
    <match url="^redirect" />
    <action type="Redirect" url="http://www.google.co.jp" />
  </rule>
</rules>

これだけの修正で Rewrite のルールを別ファイルに分離して、テスト時には読み込んで実行することが出来るようになります。XDT を使うことでわかりやすく、そして柔軟に設定が出来るようになったと思います。

実際のテストプロジェクトでは、ファイル構造は以下のようになります。

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

IIS Express TestKit のリポジトリには動作するテストコードも置いてあるので、使い始める場合には参考にしてみてください。Outbound Rule のサンプルテストも書いてあります。

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

最近は AppVeyor を使って URL Rewrite を含むテストを実行するように設定しているので、こちらも併せて参考にしてもらえると良いかもしれません。

IIS Express なので CI サーバーでも簡単に URL Rewrite のテストを実行できています。

MADOSMA Q601 をヨドバシの深夜受け取りを利用して買ってみた話

Windows

今日 28 日は MADOSMA Q601 の発売日です。初代 MADOSMA も結局発売日に買っていた私ですが、今回の Q601 はちょっとタイミングをずらそうかなと思っていました。自転車盗まれたし。

しかし、ふとヨドバシの商品詳細ページを見ると、アキヨドでは MADOSMA Q601 を 30 分以内に用意してくれるみたいだったので、無理だろと思いつつ注文してみたら買えてしまった話です。

注文してから 7 分後には商品の準備が出来たので取りに来いメールが届いたので、タクシーを使ってアキヨドまで出向いて購入してきました。

シャッターを都度開けるとか、地味に凄い手間かかってるなと思いました。

経緯は割と謎ですが、TL 最速で MADOSMA Q601 を購入できたようです。アキバに近いとこういう芸当が出来るのが結構面白いですね、多分もう二度とやらないとは思いますが。

開封してみた

当然ながら Q501 より箱は全体的に大きいですが、それ以外はあまり変わっていない感じです。

f:id:shiba-yan:20160728022540j:plain

開けると、さっそく本体のお目見えです。実機は触ってましたが、それでもやっぱり 6 型はかなり大きく見えます。自分の手は割と大きい方ですが、割とギリギリです。

f:id:shiba-yan:20160728022603j:plain

今回は液晶保護シートが付いていない気がします。Gorilla Glass 3 だからでしょうか。

電源を入れてみた

起動画面は新しい mouse のロゴになっていて、ちょっとかわいいです。

f:id:shiba-yan:20160728022707j:plain

解像度が Full HD なので広々としてます。設定を弄るとタイルを 4 つ横並びに出来るらしいですが、今のところは 3 つで良いかなという気がしています。

f:id:shiba-yan:20160728024939p:plain:w450

そして事前情報の通り、すぐに新しいビルドが落ちてくるので、早々にアップデートを行っておくのが良さそうです。いつも通りですが、やっぱり結構時間がかかる感じです。

f:id:shiba-yan:20160728025013p:plain:w450

この記事を書いている間、ずっとアップデートの歯車が回っているので、朝まで放置する予定です。

Continuum を Android TV で試してみた

折角なので Continuum を手持ちの 4K BRAVIA のディスプレイミラーリング機能を使って試してみました。Continuum アプリを起動して無線接続すると、すぐにテレビが見つかりました。

f:id:shiba-yan:20160728025049p:plain:w450

昔は Miracast の接続が全く安定していなかったのですが、テレビ側のアップデートでじわじわと改善されてきたのかもしれないです。地味に喜ばしいことです。

しかし、予想通りレスポンスはあまりよくなく、解像度もなんだか狂っているようです。

f:id:shiba-yan:20160728024517j:plain

明らかに縦方向に潰れているように見えます。解像度の設定を Continuum アプリ側で出来るのかなと思いましたが、特にそういう設定もなかったので相性なのかもしれないです。

ちゃんと Windows 10 の Miracast 拡張に対応した機器を買ったほうが良さそうです。

Android TV が Amazon ビデオに対応した関係上、HDMI が一つ空いたので、そこに Microsoft の Miracast アダプタを刺そうかと考えています。

ASP.NET Core MVC で大きく変わったフィルタについて調べた

ASP.NET Core MVC

仙台に行ったとき、ぼんぷろおじさんに ActionFilter で実行時にオプションを扱う場合にどうすればいいのか聞かれて、フィルタ周りまとめないといけないことを思い出したので書きます。

思いのほか長くなってしまったので、久し振りに目次記法を使うことにします。

パイプラインの整理

ASP.NET Core MVC ではフィルタパイプラインが再実装されて、分かりやすくシンプルになりました。パイプラインに関しては公式ドキュメントの図が分かりやすいです。

Filters — ASP.NET documentation

新しく Resource Filter が追加されて、キャッシュなどパフォーマンスの改善といった処理を書きやすくなっています。モデルバインディングの前に実行されるのが特徴です。

抽象クラスを利用する方法

ASP.NET MVC 5 までと同じように扱える抽象クラスが Core MVC でも用意されているので、以下の抽象クラスを継承したクラスを作成して、利用したいメソッドをオーバーライドするだけで実装できます。

  • ActionFilterAttribute
  • ResultFilterAttribute
  • ExceptionFilterAttribute

機能としてはクラス名の通りなので説明は省略します。

これまでと同じ Executing / Executed メソッドの他に、Task を返す非同期メソッドが用意されています。async を使うことで簡単に処理を前後に追加することが出来ます。

public class MyActionFilterAttribute : ActionFilterAttribute
{
    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // OnActionExecuting に相当する処理
        Debug.WriteLine("OnActionExecuting");

        await next();

        // OnActionExecuted に相当する処理
        Debug.WriteLine("OnActionExecuted");
    }
}

ActionExecutionDelegate が実際にアクションを実行するためのデリゲートなので、ちゃんと呼び出しておかないと後続の処理がスキップされます。

抽象クラスを使っている場合は、これまでと同じように簡単に実装できますが、Core MVC では抽象クラスは互換性のためのような機能です。このままでは DI が使えないので、実装できる処理が限られます。

インターフェースを実装する方法

MVC 5 までも一部のインターフェースは用意されていましたが、Core MVC では IResourceFilter が追加されて全部で 5 種類になりました。

  • IActionFilter
  • IAuthorizationFilter
  • IExceptionFilter
  • IResourceFilter
  • IResultFilter

そして Core MVC ではさらに非同期版が追加されて、インターフェースは 10 種類が利用出来ます。

  • IAsyncActionFilter
  • IAsyncAuthorizationFilter
  • IAsyncExceptionFilter
  • IAsyncResourceFilter
  • IAsyncResultFilter

更にフィルタの実行順序を指定する必要がある場合には IOrderedFilter を実装します。

使い方はインターフェースを実装したクラスを作成するだけなので、特に説明も要らない気がします。一番簡単な実装は以下のようなクラスになるかと思います。

public class MyActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        await next();
    }
}

ActionFilterAttribute と同じ処理を実装する場合は、IAsyncActionFilter と IAsyncResultFilter を実装するだけです。インターフェースを実装する方法の方が、属性よりも DI との親和性が高いです。

public class MyCustomFilter : IAsyncActionFilter, IAsyncResultFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        await next();
    }

    public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
    {
        await next();
    }
}

ちなみに ActionFilterAttribute などもインターフェースを実装しているだけの、単純な属性なので別に使わなくても問題はないです。必要なインターフェースだけ実装するのが良さそうです。

DI を利用する

属性として実装していないフィルタをコントローラ単位で使うには、以下の属性を使う必要があります。

  • TypeFilter 属性
  • ServiceFilter 属性

両方ともコンストラクタでフィルタの型を渡すだけなので簡単ですが、挙動が微妙に異なっているので注意して使い分けたいです。TypeFilter はコンストラクタへのインジェクションを行います。

以下の場合、MyActionFilter のコンストラクタに対して DI からサービスを引っ張ってきてくれます。

[TypeFilter(typeof(MyActionFilter))]
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

それに対して ServiceFilter の場合は、指定されたフィルタ自体を DI から参照するので、以下の場合は ConfigureServices で DI に追加しておく必要があります。見つからない場合には例外が投げられます。

// ConfigureServices で AddScoped<MyActionFilter>() などしておく
[ServiceFilter(typeof(MyActionFilter))]
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

基本的には TypeFilter を使うことが多くなるのではないかと思っています。

グローバルで利用する

MVC 5 で言うところのグローバルフィルタは Core MVC の場合はオプションで設定を行います。

パラメータとして IFilterMetadata が大体要求されますが、上で紹介したインターフェースは全て継承しているので気にしなくても大丈夫です。MVC 5 の時のようにインスタンスをコレクションに追加すれば、全体で利用されるようになります。

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc(options =>
    {
        options.Filters.Add(new MyActionFilter());
    });
}

それ以外にも Type を受け取るオーバーロードがあるので、こっちを使って登録する方法もあります。

インスタンスを追加する方法との違いとして、こちらは DI を使ってコンストラクタへのインジェクションを行う点が挙げられます。MVC 5 まではフィルタ内で DB を触ったりし難かったですが、Core MVC だと DI でサービスを参照できるので簡単です。

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(MyActionFilter));
    });
}

この場合、MyActionFilter のコンストラクタに対して、DI からサービスをインジェクションしてくれます。

更に Core MVC では ServiceFilter を使ってフィルタ自体を DI を使って解決出来るようになりましたが、同じことをグローバルでも AddService メソッドを使って行えます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<MyActionFilter>();

    // Add framework services.
    services.AddMvc(options =>
    {
        options.Filters.AddService(typeof(MyActionFilter));
    });
}

AddService を使う場合には、当然ながらフィルタの実体を DI に追加しておく必要があります。

IFilterFactory を実装した属性を用意する

少し特殊なパターンとして IFilterFactory を実装した属性を使って、フィルタ自体をインスタンス化して返す方法があります。CreateInstance メソッドに IServiceProvider が渡されるので、DI を使ってフィルタ自体を引っ張ってくることも出来ます。

public class MyFilterFactoryAttribute : Attribute, IFilterFactory
{
    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return serviceProvider.GetRequiredService<MyActionFilter>();
    }

    public bool IsReusable => true;
}

Core MVC の FormatFilter がこの方法を使っています。コントローラでの指定のしやすさと、DI を使って既存の実装を簡単に入れ替えられるので、拡張性も高い実装です。

今回のまとめ

長くなったので、今回のポイントを最後に簡単にまとめました。全体的に Core MVC ではフィルタの表現力が上がったという感じです。

  • DI が必要ない場合
    • 属性を実装する (抽象クラスをオーバーライド、必要なインターフェースを実装)
    • Filters.Add メソッドを使ってインスタンスを追加する
  • DI が必要な場合
    • TypeFilter 属性を使う
    • Filters.Add メソッドを使って型を追加する
  • フィルタ自体を DI で解決したい場合
    • ServiceFilter 属性を使う
    • Filters.AddService メソッドを使って型を追加する
  • フィルタのインスタンス化のタイミングで処理が必要な場合
    • IFilterFactory を実装した属性を使う

実際に使ってみて、それぞれに適した利用方法を探っていきたいですね。特に ServiceFilter 周りはパッと思いつかないので、いい感じの利用方法を見つけたいです。