CSharpCodeProvider + MEFで実行時に生成したDLLを簡単に扱う
これは「C# Advent Calendar 2013」の18日目の記事です。
昨日は matarillo さんの「Java8とC# - 猫とC#について書くmatarilloの雑記」でした。
さいきん(やっと)、C#で手をつけてなかった async/await や、MEFなどを勉強しています。勉強している中で、CSharpCodeProviderとMEFを組み合わせたら実行時にDLLを生成してもその読み込みに苦労しないんじゃないかと思ったので、紹介しようと思います。動的コード生成ということで、yfakariyaさんとネタが被ってヒヤヒヤしました・・。
(もしかしたらこの組み合わせは普通なのかもしれませんが、個人的に感動したので記事にしちゃいます。)
もくじ
コンソールで入力した文字をそのまま返すクラスを動的に生成して、それをMEFで読み込んで実行してみようと思います。(実用性は・・・)
- 文字からコードを生成する
- 生成したコードからDLLを作成する
- MEFを使いDLLを読み込む
1. 文字からコードを生成する
CSharpCodeProvider
を使って、入力された文字からコードとDLLを生成します。
今回は、以下のインターフェースを実装したクラスを作成し、その中で入力された文字をそのまま返すコードを生成します。
public interface IMessage { string GetMessage(); }
さて、コンソールで入力された文字からコードを生成していきますが、今回はRazorEngine
を使ってみようと思います。説明は特に要りませんよね!?
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のカタログに公開する際の名前を生成しています。先頭にアンダーバーをつけているのは、クラス名は数値から始められないからです。
実行して、なにか文字を入力してみると、生成されたコードが表示されます。
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を読み込む手順は、以下のとおりになります。
- カタログを作成する
- カタログを指定してコンテナを作成する
- インターフェースと名前を指定してコンテナからインスタンスを取得する
それぞれの細かい手順については、うえで紹介した入門記事にすべてまとまっています。(丸投げ)
上記の処理を実行するクラス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); }
実行してみると、マシンの性能によりますが、コード生成からメッセージ出力までだいたい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; } }
まとめ
CSharpCodeProvider と MEFを組み合わせることで、実行時に生成したDLLを簡単に扱う方法を紹介しました。今回の例は特に役に立たないと思いますが、これを使うことでLINQPadのようなものが簡単に作れます。実際に、Fluentdを使ってMongoDBに集めたログなどのデータに対して、入力したLINQを利用して検索・集計するWebツールを作ってます。
CSharpCodeProviderではなく、いま話題(?)のRoslynなどを使えば、もっと面白いことができるかもしれないですね。まだ追いかけてませんが・・・。
ということで、CSharpCodeProvider + MEFを組み合わせる方法の紹介でした。明日は yfakariya さんです。
今回のソースコードは、以下においてあります。
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}
のように、省略可能なキャプチャー変数にデフォルト値を設定することができます。あまり使い道が浮かばない・・・。可変長キャプチャ (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でキャプチャした値を取得することができます。
リクエストを処理するために必要な値などについては、引数以外にRequest
やContext
などのプロパティが定義されています。
レスポンスを返すためにはResponse
というプロパティを使いレスポンスを生成するか、View
プロパティを使いView経由で返す、あるいな独自のレスポンスを定義することもできます。
また、ViewBagを利用することもできます。利用方法はASP.NET MVCと同じです。
まとめ
今回は、コントローラと同じ働きをするモジュール
について、ルーティングを定義する方法を中心にまとめました。基本的なルーティングの定義については、ここでまとめた内容でまかなえると思います。
これ以外にも、条件付きルーティングや、非同期処理などの機能もあります。非同期処理についてはどこかでまとえるかもしれませんが、それ以外についてはドキュメントを参照してみてください。
次回は、NancyFxで定義されているレスポンスを作成する方法についてまとめ、実際の処理を記述していこうと思います。
NancyFxチュートリアル「2. プロジェクトの細かい設定をする(+ モデルを作成する)」
この記事は、Nancy Advent Calendar 2013の3日目の記事です。
前回は、プロジェクトを作成して簡単な動作確認をしました。
今回は、前回触れなかったプロジェクトの細かい設定と、ToDoアプリのモデルクラスを作っていこうと思います。
もくじ
- 静的コンテンツを配置するディレクトリを追加する
- すべてのViewのベースとなるViewを定義する
- モデルを作成する
静的コンテンツを配置するディレクトリを追加する
NuGetを使いJavaScriptやCSSのパッケージをインストールした際、それぞれ以下のディレクトリにインストールされます。
- JavaScriptは
Scripts
ディレクトリ以下 - CSSは
Content
ディレクトリ以下
これらのディレクトリに配置されますが、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
インストール後、モデルの作成に入っていきます。
Models
ディレクトリを作成し、モデルとなるクラスTodoApp.Models.Task
とTodoApp.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)こともあり、つまづきやすいポイントとなっています。
次回からは、コントローラの実装に入っていきます。
NancyFxチュートリアル「1. プロジェクトを作成する」
この記事は、Nancy Advent Calendar 2013の2日目の記事です。
(さっそく遅れてます・・・)
今日から何日かに渡って、NancyFxのチュートリアルということで簡単なWebアプリケーションを作ってみようと思います。
完成イメージ
チュートリアルっぽく、Todoアプリを作ってみようと思います。
- タスクには、タイトルと詳細、期限日が登録できる。
- タスクには、コメントがつけられる。
- タスクは、期限日の昇順、登録順でソートされる。
チュートリアルの環境
チュートリアルは、以下の環境で進めていきます。
- Windows 8 or Windows 7
- Visual Studio 2012
- .Net Framework 4.5
- NancyFx 0.21.1
それ以外の環境でも問題なく動くと思いますが、動かない場合はコメントかTwitterで連絡ください。
プロジェクトを作成する
さっそくプロジェクトを作成します。
新しいプロジェクトのテンプレートから「Visual C#」→「Web」→「ASP.NET 空の Web アプリケーション」を選択します。
プロジェクトの名前を入力し(今回は TodoApp)、OKボタンをクリック。何もファイルが生成されていないASP.NETプロジェクトが作成されます。
NuGetで必要なパッケージをダウンロードする
NuGetを使い、今回必要となるパッケージをダウンロードします。NuGetについては@ITの記事などを参考にしてください。
VisualStudioのメニューにある「ツール」→「ライブラリ パッケージ マネージャー」→「ソリューションの NuGet パッケージの管理」をクリックします。
オンラインのパッケージソースを選択して「Nancy」で検索します。検索結果の中から以下3つのパッケージをインストールします。
Nancy
NancyFxのコアライブラリです。Nancy.Hosting.Aspnet
NancyFxのWebアプリケーションをASP.NET上で動作させるためのライブラリです。ホスティングライブラリはこれ以外にも、AzureやWCF、SelfHosting(コンソールアプリなどと一緒に動作させる場合)などがあります。Nancy.Viewengines.Razor
NancyFxのビューエンジンに「Razor」を利用するためのライブラリです。NancyFxでは、標準でSuper Simple View Engine
という、単純なテンプレートのみを実装したビューエンジンが利用できます。
The Super Simple View Engine · NancyFx/Nancy Wiki · GitHub
今回は使い慣れたRazorを利用するために、このライブラリを追加します。
今回はこれ以外に、以下のパッケージをインストールします。NancyFx以外の部分は極力ふれない予定ですので、ここは任意ということで。
最初のモジュールを作成する
NancyFxの導入ができたため、簡単な動作確認をします。文字列を返すモジュール(NancyFxでは、コントローラではなくモジュールと呼ぶようです。意味はほぼ同じです。)を作成します。
Modules
ディレクトリを作成し、その下にHomeModule
クラスを作成します。
作成後、クラスを以下の通りに実装します。
using Nancy; namespace TodoApp.Modules { public class HomeModule : NancyModule { public HomeModule() { // GET "/" Get["/"] = parameter => { return "Hello NancyFx !!"; }; } } }
デバッグ実行しブラウザでアクセスすると、以下のような画面が表示されます。ちゃんと動作していることが確認できましたね!!
最初のViewを作成する
ついでに、Viewも作ってしまいましょう。
Views/Home
ディレクトリを作成し、その下にindex.cshtml
ファイルを作成します。
作成後、cshtmlファイルの中身を編集します。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>NancyFxチュートリアル</title> <body> <h1>Hello NancyFx !!</h1> </body> </html>
HomeModule
が、文字列を返す実装になっているので、Viewを返すように変更します。
Get["/"] = parameter => { return View["index"]; };
上記のように、View["ビュー名"]
と実装すると、ビューエンジンを介したレスポンスを返すようになります。
ビューのテンプレートは、Views
/モジュール名から"Module"を抜いた名前
/指定した名前
.拡張子
のパスにあるテンプレートを利用します。今回の例ではViews/Home/index.cshtml
です。それ以外にも検索されるパスはありますが、後日別の記事としてまとめます。
修正後、再度ブラウザからアクセスすると、ちゃんとHTMLが表示されることが確認できます!!
まとめ
今回は、プロジェクトの作成から、最低限必要なパッケージの導入と、簡単な動作確認をしました。
ソースも含めて1から作成しましたが、そこまで難しくなかったと思います。今回のような感じでゆっくりと進めていこうと思います。
ソースコードについては整理してからGithubで公開します。しばしお待ちください。
NancyFxとは
この記事は、Nancy Advent Calendar 2013の1日目の記事です。
(寝落ちしてしまったので2日になってしまいましたが・・)
NancyFxの勉強をかねて、(ひとり)NancyFxアドベントカレンダーを作成しました。
NancyFxを使うひとが一人でも増えてくれたら嬉しいです。ともだちぼしゅうちゅう。
NancyFxとは
NancyFxとは、.NET Framework / Mono上で動作するように作られた軽量Webアプリケーションフレームワークで、RubyのSinatraのようなDSLを提供しています。 フルスタックなフレームワークではないので、DBアクセスなどは好きなライブラリが利用できます。
ちなみに、Nancyとは、フランク・シナトラの娘の名前だそうですw
NancyFxを知ったきっかけは、C#を使ってWebアプリを作ろうと思い、軽量(Sinatraライク)なフレームワークを求めてググったらひっかかりました。
軽量なフレームワークといいつつ、普通のWebページやREST APIなどもちゃんと実装できるよう、機能も豊富に用意されています。
どんなフレームワーク?
HTTPのGETやPOST、ルーティングを指定して、値などのレスポンスを返すだけという、ラムダ式で簡単に記述ができます。C#はライトウェイトなんだ!!
public class HomeModule : NancyModule { public HomeModule() { /* GET / */ Get["/"] = parameter => { return "Hello NancyFx!!"; }; } }
それ以外にも、数値を返すとHTTPステータスコードとして返してくれるなど、レスポンスを返す方法にもいろいろあります。
公式サイトやリソースなど
ソースはGithubにホストされています。毎週10以上のコミットがあり、活発に開発されています。
書いてから気づきましたが、11/24の週はコミット数が2ですね・・・。
NancyFx/Nancy · GitHub
Githubのプロジェクトです。Documentation · NancyFx/Nancy Wiki · GitHub
GithubのWikiページです。ドキュメントは結構しっかり作られています。Blog Posts, Video & Audio · NancyFx/Nancy Wiki · GitHub
開発者のブログや、ビデオなどがまとめられています。Sinatra ライクな .NET 用軽量 Web フレームワーク「Nancy」を使ってみた - しばやん雑記
@shibayanさんのブログポストです。WebMatrix 3: Sinatra on ASP.NET 「Nancy」 を利用する - だるろぐ
@daruyanagiさんのブログポストです。.Netの軽量Webフレームワーク「NancyFx」を試してみた - らびたろちゃんにっき
わたしがNancyFxを紹介したときのブログポストです。
おわりに
AdventCalendarのはじめは、簡単なアプリケーションを作成するチュートリアル的に進めていこうと思います。残った部分は試してみたいことやTipsを書いていこうと思います。
ひとりで完走できるようにがんばりますが、参加してくださる方がいたらぜひお願いします!
次からはNancyFxのチュートリアルです。最初は基本的なプロジェクトの作り方をまとめます。
それでは、よろしくお願いします!
NancyFxでWebAPIを作るときに必ず参照すべきライブラリ
1年以上ぶりにNancyFxネタ。日本語の情報があまりないので、色々書いて行こうと思います。
ユーザが増えてくれれば嬉しいですね。
さてNancyFxでは、デフォルトでJsonレスポンスを返すことが出来ます。
Get["/json"] = _ => { var data = new { Id = 1, Name = "rabitarochan", BlogUrl = "http://rabitarochan.hatenablog.com/" }; return Response.AsJson(data); };
ブラウザからこのURLにアクセスすると、以下のようなJsonが返ります。
{ Id: 1, Name: "rabitarochan", BlogUrl: "http://rabitarochan.hatenablog.com/" }
よくよく見てみると、キーがC#のプロパティ名と同じUpperCamelCase
となっていますね。
そのため、JavaScript側でも同じくUpperCamelCase
で扱う必要があります。
ただし、JavaScriptではほとんどの場合、lowerCamelCase
が使われていると思いますので、JavaScript側がちょっと気持ち悪い感じになってしまいます。
できればNancyFxからJsonを返す際に、キーをlowerCamelCase
に変換してくれればベストです。
NancyFxのソースを読んでみましたが、残念ながらキーとなる文字列を変換するオプション等はありませんでした。
(ちなみに、NancyFxは独自のJsonシリアライザを使っています。)
そこでNancy.Serialization.JsonNetの出番です!
リポジトリはこちら。NuGetからもインストール可能です。
https://github.com/NancyFx/Nancy.Serialization.JsonNet
このライブラリは、Jsonレスポンスを返す際のシリアライザとして、C#からJsonを扱う際に一番使われていると思われるライブラリJson.Net
を使うように変更してくれるだけのものです。
そして、Json.Net
のJsonSerializer
クラスを少し変更するだけで、キーをlowerCamelCase
に変換することが可能です!!
設定方法
設定はとても簡単で、シリアライザを継承したクラスを1つ作成することと、それをDIコンテナに登録することだけです。
シリアライザを継承したクラスを作成する
Json.Net
用のシリアライザを継承したクラスを作成します。今回と同じ目的の場合は、以下のコードをコピペでOKです。
詳細については、Json.Net
のドキュメントを参照してください。
public class CustomJsonSerializer : JsonSerializer { public CustomJsonSerializer() { this.ContractResolver = new CamelCasePropertyNamesContractResolver(); } }
DIコンテナに登録する
Bootstrapperクラスにて、DIコンテナのTinyIoc
に先ほど作成したクラスを登録します。
public class Bootstrapper : DefaultNancyBootstrapper { protected override void ConfigureApplicationContainer(Nancy.TinyIoc.TinyIoCContainer container) { base.ConfigureApplicationContainer(container); container.Register(typeof(JsonSerializer), typeof(CustomJsonSerializer)); } }
仕組み
Nancy.Serialization.JsonNet
は、JsonのシリアライザとしてJson.Net
を利用するように変更してくれるプラグインのようなものです。
NancyFxでは、コンテントタイプ毎に任意のシリアライザが登録できる仕組みになっているようです。
(その仕組みまではまだ見ていません。)
デフォルトではJson.Net
のJsonSerializer
をそのまま使用しますが、BootStrapperクラスにて今回作成したようなクラスを登録してあげることで、そちらのクラスが使用される、ということです。
最初に書いたソースはそのままで同じURLにアクセスすると、ちゃんとlowerCamelCase
に変換されていることが分かります。
{ id: 1, name: "rabitarochan", blogUrl: "http://rabitarochan.hatenablog.com/" }
デシリアライズは??
PostされたJsonをC#のクラスに変換する場合は、UpperCamelCase
、lowerCamelCase
関係なく変換してくれます。
変換には、NancyFxが提供しているModelBinding
という仕組みを利用します。
以下の例では、フォームからPostされたパラメータをクラスに変換し、そのままJsonレスポンスとして返しています。
namespace NancyJsonTest.Modules { using Nancy; using Nancy.ModelBinding; // クラスに変換するための拡張メソッド用。 public class HomeModule : NancyModule { // フォーム値用のクラス class TestForm { // input type=text name=userName public string UserName { get; set; } // input type=password name=password public string Password { get; set; } } public HomeModule() { Get["/json"] = _ => { var data = new { Id = 1, Name = "rabitarochan", BlogUrl = "http://rabitarochan.hatenablog.com/" }; return Response.AsJson(data); }; Post["/json"] = _ => { var form = this.Bind<TestForm>(); return Response.AsJson(form); }; } } }
まとめ
このライブラリを利用することで、C#とJavaScriptのプロパティ名の違いがほぼ吸収できると思います。
NancyFxでWebAPIを作成するときは、ぜひこのライブラリを利用してみてください。
プロキシサーバを指定してgit cloneできるコマンドを作りました
タイトル通りですが、jgitの勉強がてら、プロキシサーバを指定してgitリポジトリをクローンできる「jgit-proxy-clone」を作りました。名前もそのまんまです。
リポジトリはこちら。 https://github.com/rabitarochan/jgit-proxy-clone
作ったきっかけ
以下のツイートを見たのがきっかけです。
@backpaper0 ネイティブのGitだとできない気がしますが、EGitはちゃんとシステムのプロキシ設定を見て動いているように見えます。いちいち環境変数変えるのが面倒なのでクローンだけEGitでやってそのあとconfigにリポジトリ毎のプロキシ設定を追加してます。
— Naoki Takezoe (@takezoen) 2013, 8月 19
・・・実は同じようなことで悩んでいて、ちょっと前にツイートしてましたw
gitで、特定のリモートに接続する際のみ適用するproxyの設定とかってできないのかな。社内のgitサーバに対してはプロキシを経由せず、社外のgitサーバに対してはプロキシを経由する、みたいな。 #git
— Kengo Asamizu (@rabitarochan) 2013, 8月 5
会社のネットワーク環境にはプロキシサーバが設置されているため、外部のgitリポジトリを見に行く場合はそのプロキシを経由しなければいけません。
しかし、社内のgitリポジトリについてはプロキシを経由するとアクセスできなくなるため、クローンする度に.gitconfigファイルを修正していました。
今回作ったもの
jgitを使って、プロキシサーバを指定して(または、プロキシサーバを経由しないようにして)gitリポジトリをクローンするだけのコマンドを作りました。
コマンド実行時に指定したプロキシ設定については、自動的にリポジトリのconfigファイルに登録するようにしています。
そのため、クローンする時だけこのコマンドを使えばよくて、それ以降は通常のgitリポジトリとして使うことができます。
使い方
最初のリリース版を作成していますので、以下のページから「jgit-proxy-clone-0.1.jar」ダウンロードして、任意のディレクトリに保存します。
https://github.com/rabitarochan/jgit-proxy-clone/releases
jarの実行方法は以下のとおりです。
(プロキシサーバを指定する場合) > java -jar jgit-proxy-clone-0.1.jar <リモートURL> [<ディレクトリ>] -h <プロキシホスト名 or IPアドレス> -p <プロキシポート番号> (プロキシサーバを使わない場合) > java -jar jgit-proxy-clone-0.1.jar <リモートURL> [<ディレクトリ>]
上記のとおり、プロキシサーバのホスト名とポート番号を指定せず、リモートURLのみを指定して実行した場合、 .gitconfigのプロキシ設定やシステムのプロキシ設定を使わず、直接接続してクローンを実行します。
その点だけ注意してもらえれば、最低限使えるものになっています。
今後
最低限使えるものとして、とりあえずリリースした状況です。ですので、ドキュメントだったり、実行時の表示だったりがほぼ出来ていない状態となっています。
とりあえずは使い方の記述と、動作がわかるような画面表示を実装使用と思います。
それと、現在は認証が必要なリポジトリにはアクセス出来ません。これについても早めに実装しようと思っています。
まとめ
こんな状態のままリリースしてしまいましたが、最低限は動くものとなっています。
プロキシ環境が複雑でgitリポジトリのクローンに困っている方、ぜひお使いいただきフィードバック頂ければと思います。
pullreqお待ちしてます!