ScalaでC#のasync, awaitを実現するライブラリ「async」の紹介

この記事は Iwate Advent Calendar 2014 の22日目の記事です。昨日はnana4gontaさんのFirefox Developer Editionを使って他ブラウザをリモートデバッグする - Qiitaでした。明日はayokuraさんです。

岩手関連の記事を書こうと思い、仕事で使おうとしてるD3.jsを使って岩手県を書こうとしたらズバリそのままのサイトを発見して挫折しました。

岩手県ぬりえ
http://acuerdo.m18u.net/iwate_nurie/

結局思いつかなかったので全然関係ない話題を書きます。

続きを読む

型パラメータ付きメソッドを持つトレイトとコンパニオンオブジェクト

タイトルはなんだか難しいですが、要は以下のコードのコンパイルを通したいということです。

trait Test {
  def apply[A](value: A): String
}

object Test {
  def apply[A](f: A => String): Test = new Test {
    def apply[A](value: A): String = f(value)
  }
}

上記のソースだと、Testオブジェクト#applyの型パラメータAとTestトレイト#applyの型パラメータが別々のものなので、コンパイルが通りません。

error: type mismatch;
found   : value.type (with underlying type A)
required: A
        def apply[A](value: A): String = f(value)
                                           ^

どうにかする方法はないでしょうか・・。

Play2.3でSecureSocialを使う(2014年6月時点)

※(2014年6月27日)この情報はすでに古く、Play2.3に対応した公式モジュールがリリースされている可能性があります。公式サイトやリポジトリをご確認ください。

PlayFrameworkに認証/認可機能を追加できるSecureSocialというモジュールがあります。

SecureSocialの特徴は以下のとおりで、必要とされている機能はひと通り用意されており、認証機能を手軽に構築できるモジュールです。(ほぼ上記ページの内容ですが。)

  • ScalaだけでなくJavaでも使える。
  • Play2だけでなくPlay1でも使える。
  • ユーザ/パスワード認証だけでなく、OAuth1,2やOpenIDにも対応。また、TwitterFacebookなど向けのモジュールも用意されている。
  • 既存アプリへの組込みも簡単。
  • 拡張性があり、新たな認証にも対応できる。
  • ユーザ登録時やパスワードリセット時などにメールを送信する機能が使える。

さて、このモジュールですが残念ながら現在(2014/06/27)時点でPlay2.3向けのモジュールが提供されていません。 現在の状況としては、PRは投げられていますがメイン開発者が他の機能に注力しているため、レビュー・取り込みされていません。

ですが、PR元のプロジェクトをsbtで参照することで、Play2.3に対応したSecureSocialが使えますので、今回はその手順をまとめてみました。

  1. Playプロジェクトを作成する
  2. Play2.3対応版SecureSocialをプロジェクト参照する

1. Playプロジェクトを作成する

まずは、空のプロジェクトを作成してきます。Play2.3からはplayコマンドではなく、Typesafe Activatorを利用してプロジェクトを作成するように変更されていますのでご注意ください。 今回は、play23-securesocialというプロジェクト名でplay-scalaというテンプレートを利用して作成します。

> activator new play23-securesocial play-scala

Typesafe Activatorとは?

Typesafe Activatorとは、Scalaプロジェクトをあらかじめ用意されたひな形を利用して作成できるツールです。実際には、Webからソースコードを編集するUIなども用意されているようですが、ここでは説明しません。

Typesafe Activatorをインストールしていない方は、公式サイトZIPファイルをダウンロードし、任意の場所に展開します。 その後、実行ファイルにPATHを通せばactivatorコマンドが利用できるようになります。

Typesafe Activatorをインストールしたくない!

Typesafe Activatorをインストールしたくない方は、@xuwei_kさんがズバリのブログ記事を公開していますので、そちらを参考にしましょう。

ただし、この記事ではTypesafe Activatorを利用して作成したプロジェクトを元に話を進めます。build.sbtの中身がTypesafe Activatorを利用した場合と@xuwei_kさんの記事とで若干異なりますのでご注意ください。

2. Play2.3対応版SecureSocialをプロジェクト参照する

プロジェクトが作成できましたので、Play2.3に対応したSecureSocialを参照するように設定します。

といっても、冒頭に記載したとおりPlay2.3に対応したモジュールはまだリリースされていませんので、libraryDependenciesでは参照できません。そのため、GitHubリポジトリを直接参照するように、build.sbtを書いていきます。

//build.sbtの一部
lazy val root = (project in file(".")).enablePlugins(PlayScala)
  .dependsOn(ProjectRef(uri("https://github.com/ewiner/securesocial.git#play-2.3"), "mainModule"))

dependsOn以降を追加することにより、uriで指定した、Play2.3対応SecureSocialのGitプロジェクトが参照されます。

まとめ

Play2.3対応のSecureSocialについて調査していたところ、上記の方法で使えるという情報があったのでまとめてみました。 それより、sbtにGitプロジェクトを直接参照する機能があることを知らず感動しました。知らないことはまだまだいっぱいありますね・・。

認証/認可に必要な機能はひと通り揃っているモジュールですので、興味のある方はSecureSocialを使ってみてください。

FluentdのWindowsブランチに対してテストを流してみた

Rubyはほとんど触ったことありません。

FluentdのWindowsブランチに結構手が入ってるようでしたので、テストを流してみました。

結果はこちら。

FluentdをWindows環境でテストしてみた。

実行環境

2013-12-19現在のWindowsブランチのHEADに対してテストを実施しました。

  • OS: Windows 8 x64
  • Ruby: ruby 1.9.3p362 (2012-12-25) [i386-mingw32]
  • Bundler: Bundler version 1.2.3
  • Rake: rake, version 10.1.0

実行方法

何も考えずにbundle exec rake testを実行すると、すぐエラーが発生してしまうので、以下のバッチファイルを作成して1ファイルずつテストを流しました。

@echo off

@call bundle install

for %%f in ("test\*.rb") do (
  echo [Test start: %%f]
  @call bundle exec rake --trace test TEST='"%%f"' >test_log\%%f.log 2>&1
)

for %%f in ("test\plugin\*.rb") do (
  echo [Test start: %%f]
  @call bundle exec rake --trace test TEST='"%%f"' >test_log\%%f.log 2>&1
)

pause
exit /b 0

結果

プラグインで結構失敗が多いですね。中にはRuby側?なのか、[BUG] win32_mutex_lock: WAIT_ABANDONEDと出力されていました。

in_tail.rbのテストで発生したエラーを追いかけてみると、shutdownメソッドの@thread.joinを実行した際に上記のエラーが発生していました。Cool.io絡みなのかとおもいつつ、それを使っているプラグインにはテストが成功しているものもあるので、一概には言えない感じですね・・。Unixにひもづく処理だったり、ファイルを扱う処理なのでしょうか。

あと、そのままテストを流すと、おそらく$platformwinがうまく設定されていない感じだったので、それも原因の一つかもしれないです。Issueに、Windows版の場合はソースを分けるというものが上がっていたので、その対応次第では通るテストも増えるかもしれません。

まとめ

やはりWindows対応は大変そうだなーという印象でした。もう少しRuby力をあげて、直せそうなところから手をつけていきたいです。

CSharpCodeProvider + MEFで実行時に生成したDLLを簡単に扱う

これは「C# Advent Calendar 2013」の18日目の記事です。

昨日は matarillo さんの「Java8とC# - 猫とC#について書くmatarilloの雑記」でした。

さいきん(やっと)、C#で手をつけてなかった async/await や、MEFなどを勉強しています。勉強している中で、CSharpCodeProviderとMEFを組み合わせたら実行時にDLLを生成してもその読み込みに苦労しないんじゃないかと思ったので、紹介しようと思います。動的コード生成ということで、yfakariyaさんとネタが被ってヒヤヒヤしました・・。

(もしかしたらこの組み合わせは普通なのかもしれませんが、個人的に感動したので記事にしちゃいます。)

もくじ

コンソールで入力した文字をそのまま返すクラスを動的に生成して、それをMEFで読み込んで実行してみようと思います。(実用性は・・・)

  1. 文字からコードを生成する
  2. 生成したコードからDLLを作成する
  3. MEFを使いDLLを読み込む

1. 文字からコードを生成する

CSharpCodeProviderを使って、入力された文字からコードとDLLを生成します。

今回は、以下のインターフェースを実装したクラスを作成し、その中で入力された文字をそのまま返すコードを生成します。

public interface IMessage
{
    string GetMessage();
}

さて、コンソールで入力された文字からコードを生成していきますが、今回はRazorEngineを使ってみようと思います。説明は特に要りませんよね!?

Antaris/RazorEngine · GitHub

PM> Install-Package RazorEngine

App_Dataディレクトリを作成し、その中にテンプレートIMessageTemplate.razorを作成します。

using System.ComponentModel.Composition;
using CSharpCodeProviderAndMEF;

[Export("@(Model.Id)", typeof(IMessage))]
public class @(Model.Id)Message : IMessage
{
    public string GetMessage()
    {
        return @@"@(Model.Message)";
    }
}

ポイントは、クラスにつける属性ExportAttributeです。この属性をつけることで、MEFのカタログに公開するクラスであることを指定しています。また、ExportAttributeのプロパティに名前をつけることで、生成した後に名前を指定してインスタンスを取得できるようにしています。

Razorに渡すモデルとして、以下のクラスも作成します。

public class MessageModel
{
    public string Id { get; set; }
    public string Message { get; set; }
}

準備は整ったので、Razorを使ってコードを生成するクラスCodeFactoryを作成します。

public class CodeFactory
{
    private const string TemplatePath = @"App_Data\IMessageTemplate.razor";
    private const string CacheName = "message";

    public CodeFactory()
    {
        Initialize();
    }

    public string CreateCode(string id, string message)
    {
        var model = new MessageModel { Id = id, Message = message };
        var code = RazorEngine.Razor.Run(CacheName, model);
        return code;
    }


    // private

    private void Initialize()
    {
        var template = File.ReadAllText(TemplatePath);
        RazorEngine.Razor.Compile<MessageModel>(template, CacheName);
    }
}

このクラスを使ってコードを生成してみます。

class Program
{
    static void Main()
    {
        var codeFactory = new CodeFactory();

        while (true) {
            Console.Write("文字を入力してください。> ");
            var message = Console.ReadLine();

            if (string.IsNullOrEmpty(message)) break;

            var id = CreateId();
            var code = codeFactory.CreateCode(id, message);

            Console.WriteLine("==== {0} ====", id);
            Console.WriteLine(code);
        }
    }

    static string CreateId()
    {
        var id = Guid.NewGuid().ToString("N");
        return "_" + id;
    }
}

CteateIdメソッドでは、Guidからクラス名とMEFのカタログに公開する際の名前を生成しています。先頭にアンダーバーをつけているのは、クラス名は数値から始められないからです。

実行して、なにか文字を入力してみると、生成されたコードが表示されます。

f:id:rabitarochan:20131218125237p:plain

2. 生成したコードからDLLを作成する

ソースコードの生成ができましたので、次はDLLを作成していきます。DLLの作成にはCSharpCodeProviderを利用します。

CSharpCodeProvider クラス (Microsoft.CSharp)

@IT:.NET TIPS プログラムからソース・コードをコンパイルするには? - C# VB.NET

DLLを生成する処理については、ほぼ@ITの記事そのままですので、特に説明はしません。注意する点としては、参照するDLLを指定する際に、自分自身のexeファイルを指定する必要があることです。

class DllFactory
{
    public const string ExtensionDirectoryPath = @"App_Data\Extensions";

    private CSharpCodeProvider csc;

    public DllFactory()
    {
        Initialize();
    }

    public CompilerResults Compile(string id, string code)
    {
        var parameter = CreateParameter(id);
        var result = csc.CompileAssemblyFromSource(parameter, code);

        return result;
    }


    // private

    private void Initialize()
    {
        if (!Directory.Exists(ExtensionDirectoryPath)) {
            Directory.CreateDirectory(ExtensionDirectoryPath);
        }

        csc = new CSharpCodeProvider(new Dictionary<string, string> {
            { "CompilerVersion", "v4.0" }
        });
    }

    private CompilerParameters CreateParameter(string id)
    {
        var dllPath = Path.Combine(ExtensionDirectoryPath, id + ".dll");

        var parameter = new CompilerParameters(new[] {
            "mscorlib.dll",
            "System.dll",
            "System.Core.dll",
            "System.ComponentModel.Composition.dll",
            "CSharpCodeProviderAndMEF.exe" // 自分を含めるのを忘れずに!!
        }, dllPath);

        return parameter;
    }
}

このクラスを使って、DLLを生成するように、Mainメソッドを書き換えます。

static void Main()
{
    var codeFactory = new CodeFactory();
    var dllFactory = new DllFactory();

    while (true) {
        Console.Write("文字を入力してください。> ");
        var message = Console.ReadLine();

        if (string.IsNullOrEmpty(message)) break;

        var id = CreateId();
        var code = codeFactory.CreateCode(id, message);

        var compileResult = dllFactory.Compile(id, code);

        Console.WriteLine("CompileResult: {0}", compileResult.NativeCompilerReturnValue);
        if (compileResult.NativeCompilerReturnValue != 0) {
            Console.WriteLine(string.Concat(compileResult.Output.Cast<string>()));
        }
    }
}

実行してみると、文字を入力するたびにApp_Data\Extensionsディレクトリ以下にDLLがポンポンと作成されていきますw

3. MEFを使いDLLを読み込む

DLLが作成できるようになったので、MEF (Managed Extensibility Framework)を使い作成したDLLを読み込むクラスを作成していきます。

Managed Extensibility Framework (MEF)

MEFについては、okazukiさんのブログに入門記事がまとまっています。とても分かりやすいです。

Managed Extensibility Framework入門 まとめ - かずきのBlog@hatena

DLLを読み込む手順は、以下のとおりになります。

  1. カタログを作成する
  2. カタログを指定してコンテナを作成する
  3. インターフェースと名前を指定してコンテナからインスタンスを取得する

それぞれの細かい手順については、うえで紹介した入門記事にすべてまとまっています。(丸投げ)

上記の処理を実行するクラスMessageContainerを作成します。

public class MessageContainer
{
    private DirectoryCatalog dirCatalog;
    private CompositionContainer container;

    public MessageContainer()
    {
        Initialize();
    }

    public IMessage Resolve(string id)
    {
        var instance = container.GetExportedValue<IMessage>(id);
        return instance;
    }

    public void Refresh()
    {
        // ディレクトリ以下の最新ファイルでカタログを更新する
        dirCatalog.Refresh();
    }

    // private

    private void Initialize()
    {
        var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
        dirCatalog = new DirectoryCatalog(DllFactory.ExtensionDirectoryPath);
        var catalog = new AggregateCatalog(asmCatalog, dirCatalog);

        container = new CompositionContainer(catalog);
    }
}

ポイントは、Refreshメソッドです。RefreshメソッドではDirectoryCatalog#Refreshを呼び出しています。このメソッドを呼び出すことで、コンストラクタで指定したディレクトリ以下に新しいDLLがあれば、それを含むようにカタログを更新してくれます。DLLを生成後にRefreshメソッドを呼び出すことで、毎回1からコンテナを作成する必要がありません。

Resolveメソッドに、コード生成とDLL作成時に指定したIDを指定することで、生成したクラスのインスタンスが取得できます。そのインスタンスのメソッドGetMessageを実行することで、入力した文字が取得できるようになります。

Mainメソッドを次のように書き換えます。ついでに時間も測っておきます。

static void Main()
{
    var codeFactory = new CodeFactory();
    var dllFactory = new DllFactory();
    var container = new MessageContainer();

    while (true) {
        Console.Write("文字を入力してください。> ");
        var message = Console.ReadLine();

        if (string.IsNullOrEmpty(message)) break;

        var sw = Stopwatch.StartNew();

        var id = CreateId();
        var code = codeFactory.CreateCode(id, message);

        var compileResult = dllFactory.Compile(id, code);

        if (compileResult.NativeCompilerReturnValue != 0) {
            Console.Error.WriteLine(string.Concat(compileResult.Output.Cast<string>()));
            continue;
        }

        container.Refresh();
        var instance = container.Resolve(id);
        Console.WriteLine(
            "from MEF. [Message: {0}, Time: {1}]",
            instance.GetMessage(),
            sw.Elapsed);
    }

f:id:rabitarochan:20131218140211p:plain

実行してみると、マシンの性能によりますが、コード生成からメッセージ出力までだいたい200ミリ秒くらいでできます。

また、一度生成したDLLについてはMEFのコンテナでキャッシュされていますので、時間がかかりません。IDをキャッシュして、コンソールからの入力がなかった場合は前回のメッセージを出力するように修正してみます。

static void Main()
{
    var codeFactory = new CodeFactory();
    var dllFactory = new DllFactory();
    var container = new MessageContainer();

    string preId = null;

    while (true) {
        Console.Write("文字を入力してください。> ");
        var message = Console.ReadLine();

        var sw = Stopwatch.StartNew();

        if (string.IsNullOrEmpty(message)) {
            var preInstance = container.Resolve(preId);
            Console.WriteLine(
                "cached MEF. [Message: {0}, Time: {1}]",
                preInstance.GetMessage(),
                sw.Elapsed);
            continue;
        }

        var id = CreateId();
        var code = codeFactory.CreateCode(id, message);

        var compileResult = dllFactory.Compile(id, code);

        if (compileResult.NativeCompilerReturnValue != 0) {
            Console.Error.WriteLine(string.Concat(compileResult.Output.Cast<string>()));
            continue;
        }

        container.Refresh();
        var instance = container.Resolve(id);
        Console.WriteLine(
            "from MEF. [Message: {0}, Time: {1}]",
            instance.GetMessage(),
            sw.Elapsed);

        preId = id;
    }
}

f:id:rabitarochan:20131218140216p:plain

まとめ

CSharpCodeProvider と MEFを組み合わせることで、実行時に生成したDLLを簡単に扱う方法を紹介しました。今回の例は特に役に立たないと思いますが、これを使うことでLINQPadのようなものが簡単に作れます。実際に、Fluentdを使ってMongoDBに集めたログなどのデータに対して、入力したLINQを利用して検索・集計するWebツールを作ってます。

CSharpCodeProviderではなく、いま話題(?)のRoslynなどを使えば、もっと面白いことができるかもしれないですね。まだ追いかけてませんが・・・。

ということで、CSharpCodeProvider + MEFを組み合わせる方法の紹介でした。明日は yfakariya さんです。

今回のソースコードは、以下においてあります。

https://github.com/rabitarochan/CSharpCodeProviderAndMEF

NancyFxチュートリアル「3.1. モジュール(コントローラ)を作成する (ルーティング編)」

この記事は、Nancy Advent Calendar 2013の8日目の記事です。

書けない日の分を書き続けるのもアレですので、かけていない日の分を空けます(^^;

前回は、プロジェクトの細かい設定と、モデルの作成をしました。

今回からモジュール(コントローラ)を作成していこうと思います。モジュールについては少し細かく書いていこうと思います。

もくじ

  • ルーティングの構文
  • HTTPメソッド
  • パターン
  • 複数のパターンが重複した場合
  • アクション

ルーティングの構文

ドキュメントは以下です。

Defining routes · NancyFx/Nancy Wiki · GitHub

ルーティングについては、AC2日目の記事で動作確認をした際に、簡単にご紹介しました。

public class HomeModule : NancyModule
{
    public HomeModule()
    {
        // GET "/"
        Get["/"] = parameter => {
            return "Hello NancyFx !!";
        };
    }
}

このように、ルーティングをはモジュールクラスのコンストラクタで指定します。指定する構文は、HTTPメソッド[パターン] = Func<dynamic, dynamic>です。

それぞれの項目に指定できる値をみてみます。

HTTPメソッド

HTTPメソッドには、GET POST PUT DELETE と、OPTIONS HEAD PATCHが指定できます。HEAD以外はそれぞれの名前のインデクサーが定義されています。HEADについては、GETのインデクサーに定義したものが使われるようです。

パターン

パターンは、ルートからの相対URLを指定します。パターン構文はNancyFxである程度用意されており、それで十分こと足りると思いますが、独自に定義することも可能です。

また、パターンにはスコアによる優先順位があります。同じようなルーティングが定義されている場合は、カッコ内の数値が高い順に優先されます。

NancyFxで用意されているルーティングは以下のとおりです。

  • リテラル (10,000) - /some/literal/segument のように、URLに完全一致する場合に指定します。一番優先順位が高いです。

  • キャプチャー (1,000) - /{name} のように、変数にキャプチャーできるパターンです。/blog/{date}/comments/{id}のように、リテラルと組み合わせて使用します。

  • キャプチャー(Optional) (1,000) - /{name?}のように、キャプチャー名に?をつけることで、そのURLを省略可能にできます。

  • キャプチャー(Optional + デフォルト値) (1,000) - /{name?unnamed}のように、省略可能なキャプチャー変数にデフォルト値を設定することができます。あまり使い道が浮かばない・・・。

  • 正規表現 (1,000) - /(?<age>[\d]{1,2})のように、正規表現を利用して値をキャプチャーします。

  • 可変長キャプチャ (0) - /{name*}のように、キャプチャー変数名に*をつけることで、スラッシュ以降のセグメントを可変長引数のようにキャプチャーします。

  • 可変長正規表現キャプチャ (100) - ^(?<name>[a-z]{3,10}(?:/{1})(?<action>[a-z]{5,10}))$のように、スラッシュ以降のパスを全て正規表現でキャプチャーします。難しい・・・。

  • マルチキャプチャー (100) - /{file}.{extension}/{file}.xmlのように、キャプチャーとリテラルを組み合わせる方法です。jsonやxmlのように拡張子を指定したREST APIなどを実装する際に使えます。

複数のパターンが重複した場合

さきほど説明したとおり、複数のパターンが重複した場合はスコアによる順位付けがなされます。たとえば、以下のようなルーティングを定義したとします。

Get["/{category}"] = _ => {
    return "My category is " + _.category;
};

Get["/sayhello"] = _ => {
    return "Hello from Nancy";
};

/{category}/sayhelloのルーティングを定義しましたが、ブラウザから/sayhelloとアクセスした場合に重複してますね。このようにルーティングが重複した場合はスコアの大きいほうが優先されますので、スコア10,000の/sayhelloが適用されます。結果は実際にブラウザからアクセスして試してみてください。

アクション

ルーティングアクションには、リクエストが指定したルーティングに一致した場合に呼び出される動作を指定します。

引数にはdynamic型インスタンスが渡され、URLでキャプチャした値を取得することができます。

リクエストを処理するために必要な値などについては、引数以外にRequestContextなどのプロパティが定義されています。

レスポンスを返すためにはResponseというプロパティを使いレスポンスを生成するか、Viewプロパティを使いView経由で返す、あるいな独自のレスポンスを定義することもできます。

また、ViewBagを利用することもできます。利用方法はASP.NET MVCと同じです。

まとめ

今回は、コントローラと同じ働きをするモジュールについて、ルーティングを定義する方法を中心にまとめました。基本的なルーティングの定義については、ここでまとめた内容でまかなえると思います。

これ以外にも、条件付きルーティングや、非同期処理などの機能もあります。非同期処理についてはどこかでまとえるかもしれませんが、それ以外についてはドキュメントを参照してみてください。

次回は、NancyFxで定義されているレスポンスを作成する方法についてまとめ、実際の処理を記述していこうと思います。

NancyFxチュートリアル「2. プロジェクトの細かい設定をする(+ モデルを作成する)」

この記事は、Nancy Advent Calendar 2013の3日目の記事です。

前回は、プロジェクトを作成して簡単な動作確認をしました。

今回は、前回触れなかったプロジェクトの細かい設定と、ToDoアプリのモデルクラスを作っていこうと思います。

もくじ

  • 静的コンテンツを配置するディレクトリを追加する
  • すべてのViewのベースとなるViewを定義する
  • モデルを作成する

静的コンテンツを配置するディレクトリを追加する

NuGetを使いJavaScriptCSSのパッケージをインストールした際、それぞれ以下のディレクトリにインストールされます。

  • JavaScriptScriptsディレクトリ以下
  • CSSContentディレクトリ以下

これらのディレクトリに配置されますが、NancyFxではデフォルトでContentディレクトリ以下のファイルのみを静的コンテンツとして配信します。そのため、JavaScriptを配信するためには、Contentディレクトリ以下に移動するか、静的コンテンツを配信するディレクトリを追加する必要があります。

ここでは、静的コンテンツを配信するディレクトリを追加する方法について説明します。

Managing static content · NancyFx/Nancy Wiki · GitHub

NancyFxでは、フレームワークの設定を変更するためのクラスNancy.DefaultNancyBootstrapperを用意しています。このクラスを継承することで、アプリケーションの動作はもちろん、フレームワーク自身の動作もほぼ全てカスタマイズすることができます。このクラスに、静的コンテンツなどを登録できるメソッドvoid ConfigureConventions(NancyConventions)があるため、それをオーバーライドして登録します。

Bootstrapper · NancyFx/Nancy Wiki · GitHub

プロジェクトのルートに、クラスTodoApp.TodoAppBootstrapperを作成し、クラスNancy.DefaultNancyBootstrapperを継承して以下のとおり実装します。

using Nancy;
using Nancy.Conventions;

namespace TodoApp
{
    public class TodoAppBootstrapper : DefaultNancyBootstrapper
    {
        protected override void ConfigureConventions(NancyConventions nancyConventions)
        {
            base.ConfigureConventions(nancyConventions);

            // Scriptsディレクトリ以下のファイルを静的コンテンツとして扱うようにする。
            nancyConventions.StaticContentsConventions.Add(
                StaticContentConventionBuilder.AddDirectory("Scripts")    
            );
        }
    }
}

void ConfigureConventions(NancyConventions)をオーバーライドし、上記の通りScriptsディレクトリ以下のファイルを静的コンテンツとして配信するように設定しています。

この設定をすることで、Scriptsディレクトリ以下にあるJavaScriptファイルが配信されるようになりました。jQueryなどのJavaScriptファイルを参照して動作確認してみてください。

Bootstrapperクラスではこれ以外にも、アプリケーション起動時の処理を記述するメソッドvoid ApplicationStartup(TContainer, IPipelines)や、リクエスト開始時の処理を記述するメソッドvoid RequestStartup(TContainer, IPipelines, NancyContext)などがオーバーライドできます。

これらのメソッドや引数はユーザ認証の設定をする際などに登場しますので、ここでは説明を省略します。

すべてのViewのベースとなるViewを定義する

ASP.NET MVCでは、適用するレイアウトを定義する際に_ViewStart.cshtml_ViewStart.vbhtmlといったファイルを作成して指定します。この指定方法はNancyFxでも利用することができますが、少し注意が必要です。

レイアウトを指定するため、Views/_ViewStart.cshtmlファイルとViews/Shared/_Layout.cshtmlファイルを作成します。途中のSharedディレクトリも一緒に作成してください。

Views/Shared/_Layout.cshtmlには、他のテンプレートのベースとなるレイアウトを指定することができます。この詳細については、ASP.NET MVC 3の記事ですが以下のページが参考になります。

第1回 Controller−View開発のキモを押さえる − @IT

上の記事でも、レイアウトはViews/_ViewStart.cshtmlファイルで指定するとありますが、NancyFxでは指定の方法が若干異なります。

ASP.NET MVCでは、Layoutを~/Views/...のように指定しますが、NancyFxではその部分を省略して、Views以下のディレクトリから指定します。

@{
    // ASP.NET MVC は "~/Views/Shared/_Layout.cshtml" と指定する。
    Layout = "Shared/_Layout.cshtml";
}

この件については、ドキュメントを見つけられませんでした。

モデルを作成する

モデルについては、NancyFxでのサポートがありませんので、自由に選択することができます。今回はEntityFrameworkを使ってモデルファースト開発をしてみようと思います。

EntityFrameworkは、@shibayanさんのASP.NET MVC 3 開発入門を参考にしました。

ASP.NET MVC 3 開発入門 (3) - モデルをコードファーストで作成 - しばやん雑記

まずはNuGetで、EntityFramework 6 と、データベースにSQLServer CEを使うためのパッケージをインストールします。

NuGetのダイアログを開き、EntityFrameworkで検索をして以下のパッケージをインストールします。どちらも2013年12月3日時点ではバージョン6.0.1でした。

  • EntityFramework
  • EntityFramework.SqlServerCompact

f:id:rabitarochan:20131204012238p:plain

インストール後、モデルの作成に入っていきます。

Modelsディレクトリを作成し、モデルとなるクラスTodoApp.Models.TaskTodoApp.Models.Commentを追加します。クラスの定義は以下のとおりです。命名規則などは参考にした@shibayanさんの記事を参考にしてください。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace TodoApp.Models
{
    public class Task
    {
        public int TaskId { get; set; }

        [Required]
        [StringLength(128)]
        [DisplayName("タイトル")]
        public string Title { get; set; }

        [StringLength(1024)]
        [DisplayName("詳細")]
        public string Detail { get; set; }

        [DisplayName("期限日")]
        public DateTime DueDate { get; set; }

        public virtual ICollection<Comment> Comments { get; set; } 
    }
}
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace TodoApp.Models
{
    public class Comment
    {
        public int CommentId { get; set; }

        [Required]
        [DisplayName("コメント")]
        public string Message { get; set; }

        public DateTime CreatedAt { get; set; }
    }
}

モデルクラスができたので、それらを操作するためのデータコンテキストクラスTodoApp.Models.TodoAppContextと、Web.configファイルに接続文字列の設定をします。

using System.Data.Entity;

namespace TodoApp.Models
{
    public class TodoAppContext : DbContext
    {
        public DbSet<Task> Tasks { get; set; }
        public DbSet<Comment> Comments { get; set; } 
    }
}
<!-- Web.config の一部 -->
<connectionStrings>
  <add name="TodoAppContext"
       connectionString="Data Source=|DataDirectory|\TodoApp.sdf"
       providerName="System.Data.SqlServerCe.4.0" />
</connectionStrings>

さいごに、データベースの自動生成をするために、TodoAppBootstrapperクラスを修正します。

アプリケーション起動時の処理を記述するメソッドvoid ApplicationStartup(TContainer, IPipelines)をオーバーライドし、以下のように実装します。

// TodoAppBootstrapper の一部
protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines)
{
    base.ApplicationStartup(container, pipelines);

    // EntityFramework のコードファースト設定。モデルに変更がある場合にDBを作り直す。
    Database.SetInitializer(new DropCreateDatabaseIfModelChanges<TodoAppContext>());
}

これでモデルを利用するための準備が整いました。これらのクラスを利用する際は、テストなどを考慮してリポジトリパターンを利用してアクセスすることになります。リポジトリパターンについては、これも@shibayanさんのブログが参考になります。

ASP.NET MVC 3 開発入門 (4) - リポジトリパターンを適用する - しばやん雑記

まとめ

今回は、プロジェクトの細かい設定と、モデルクラスを作成しました。

プロジェクトの設定の中でも特に_ViewStart.cshtmlについては、ドキュメントや情報が少なく、間違った指定をした際のエラーがよく分からない(NullReferenceException)こともあり、つまづきやすいポイントとなっています。

次回からは、コントローラの実装に入っていきます。