前回写経した書籍「Instant Nancy」の中に、「Separating applications and hosting (Advanced)」という章があり、1つのNancyアプリでASP.NETとSelfHostingでホスティングするという章がありました。
ただ、写経時点ではいくつか例外が出ており、また、その例外がNancy以外にも関係するものだったため、例外を解決するよりもNancy全体の理解を優先して先に進めました。
前回で写経も一段落したので、復習を兼ねて1つのNancyアプリで構成してみました。
なお、OWINでのホスティングは次のエントリで試しています。
C# + Nancyを使って、1つのアプリでHerokuとAzure Websitesの両方へデプロイできるように構成する - メモ的な思考的な
環境
- Nancy 1.0.0
- .NET Framework 4.5
- VisualStudio 2013 Community
具体的な流れは以下の通りです。
ソリューション・プロジェクトの作成
ソリューションの構成は以下とします。
プロジェクト名 | プロジェクトテンプレート名 | 内容 | プロジェクトの参照 |
---|---|---|---|
NancyApp | クラスライブラリ | NancyのViewやBootstrapperなど、メインのNancyアプリ | - |
AspNetHosting | ASP.NET 空のWebアプリケーション | ASP.NET (IIS) でのホスティング設定 | NancyAppを参照に追加 |
SelfHosting | コンソールアプリケーション | セルフホスティング設定 | NancyAppを参照に追加 |
また、それぞれのプロジェクトに含むNuGetパッケージは以下の通りです。なお、今回はViewEngineとしてSSVEとRazorの2つを試します。
パッケージ名 | バージョン | NancyApp | AspNetHosting | SelfHosting |
---|---|---|---|---|
Nancy | 1.0.0 | o | o | o |
Nancy.Hosting.AspNet | 1.0.0 | - | o | - |
Nancy.Hosting.Self | 1.0.0 | - | - | o |
Nancy.ViewEngines.Razor | 1.0.0 | o | - | - |
Microsoft.AspNet.Razor | 2.0.30506.0 | o | - | - |
Viewを使わずに「hello world」を表示
NancyAppプロジェクトにHomeModule
クラスを追加
/
というパスで文字列を表示できるようにします。
public class HomeModule : Nancy.NancyModule { public HomeModule() { Get["/"] = _ => "hello world"; } }
SelfHostingプロジェクトのProgram.cs
を実装
ApsNetHostingプロジェクトは上記だけで動作しますが、SelfHostingプロジェクトの場合はProgram.cs
の実装が必要になります。
class Program { static void Main(string[] args) { var port = 8765; var nancyHost = new Nancy.Hosting.Self.NancyHost(new Uri("http://localhost:" + port)); Console.WriteLine("Running port: {0}", port); nancyHost.Start(); Console.ReadKey(); nancyHost.Stop(); } }
また、ポート8765
を利用できるよう、名前空間予約の構成を追加します。
netsh http add urlacl url=http://+:8765/ user=Everyone
なお、不要になったら以下で削除します。
netsh http delete urlacl url=http://+:8765/
実行
AspNetHostingとSelfHosting、それぞれスタートアッププロジェクトにして実行すると、「hello world」が表示されます。
ここまでのコミットです。
thinkAmi-sandbox/NancyMultiHosting-sample at 4f3c4185a7372a16f7cd0ffe5dc8adc12b2ae3b7
ViewEngineを使わない、静的HTMLを表示
NancyAppプロジェクトに、HTMLファイルの追加
- NancyAppプロジェクトに新しいフォルダとして
Views
を追加 - その中に
static.html
というHTMLファイルを追加 - HTMLの中身は、bodyタグに「hello static file」を記述
- HTMLファイルのプロパティを変更
- ビルドアクション:
コンテンツ
- 出力ディレクトリにコピー:
常にコピーする
- ビルドアクション:
HomeModuleの編集
/static
というパスで静的HTMLを表示できるようにします。
public class HomeModule : NancyModule { public HomeModule() { Get["/static"] = _ => View["static"]; } }
実行して確認
SelfHostingでは動作するものの、AspNetHostingでは以下のエラーが出て動作できません。
Nancy.RequestExecutionException: Oh noes! ---> Nancy.ViewEngines.ViewNotFoundException: Unable to locate view 'static' Currently available view engine extensions: sshtml,html,htm,cshtml,vbhtml Locations inspected: views/Home/static-ja,views/Home/static,Home/static-ja,Home/static,views/static-ja,views/static,static-ja,static Root path: path\to\project\AspNetHosting\
エラーよりAspNetHostingではHTMLのあるフォルダを認識できていないようなので、HomeModuleにstatic.html
のあるフォルダのパスを追加します。
Get["/static"] = _ => View["bin/Content/static"];
しかし、AspNetHostingは動作するようになりましたが、今度はSelfHostingの方がエラーで動作しなくなりました。
Nancy.RequestExecutionException: Oh noes! ---> Nancy.ViewEngines.ViewNotFoundException: Unable to locate view 'bin/Views/static' Currently available view engine extensions: sshtml,html,htm Locations inspected: views/Home/bin/Views/static-ja,views/Home/bin/Views/static,Home/bin/Views/static-ja,Home/bin/Views/static,views/bin/Views/static-ja,views/bin/Views/static,bin/Views/static-ja,bin/Views/static Root path: D:\Sandbox\NancyMultiHosting-sample\SelfHosting\bin\Debug
Bootstrapperの追加
そこで、両方でViewを認識させるためにNancyAppプロジェクトにBootstrapper
クラスを用意し、静的HTMLのあるフォルダをViewのフォルダとして追加します。
View location conventions · NancyFx/Nancy Wiki · GitHub
public class BootStrapper : Nancy.DefaultNancyBootstrapper { protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines) { base.ApplicationStartup(container, pipelines); this.Conventions.ViewLocationConventions.Add((viewName, model, context) => { return string.Concat("bin/Views/", viewName); }); } }
これでAspNetHostingでもフォルダを認識できるようになったため、HomeModuleから不要なパスは削除しておきます。
Get["/static"] = _ => View["static"];
ここまでのコミットです。
thinkAmi-sandbox/NancyMultiHosting-sample at 00ff4cbacfd22978af6a8af3d71ac26914e9ba40
SuperSimpleViewEngine(SSVE)のViewを試す
まずはデフォルトのViewEngineである、SSVEを試します。
Viewの追加
よく使いそうなMasterページを含むViewを作ります。
- NancyAppプロジェクトの
Views
フォルダの下に、main.sshtml
とssve.sshtml
を作成'- HTMLファイルテンプレートを使ったあと、拡張子を
sshtml
へと変換
- HTMLファイルテンプレートを使ったあと、拡張子を
- 2つのsshtmlファイルとも、プロパティを変更
- ビルドアクション:
コンテンツ
- 出力ディレクトリにコピー:
常にコピーする
- ビルドアクション:
master.sshtml
は、bodyタグにhello ssve
を追加ssve.sshtml
は、すべてのタグを削除し、@Master['master.sshtml']
のみを記載
HomeModuleへの追加
/ssve
というパスでSSVEのViewを表示できるようにします。
Get["/ssve"] = _ => View["ssve"];
以上により、SSVEを使ったAspNetHostingとSelfHostingの両方が動作しました。
ここまでのコミットです。
thinkAmi-sandbox/NancyMultiHosting-sample at f1b7e518ca03c2a6f862abf55595cc8ce3acf438
Razor ViewEngine のViewを試す
NancyでRazorを使う場合、NuGetパッケージを追加しただけではRazorのViewを表示できません。
Razor View Engine · NancyFx/Nancy Wiki · GitHub
上記にはいくつか方法が記載されていますが、動かすのが簡単と書かれているIRazorConfiguration
を使う方法で試してみます。
NancyAppプロジェクトに、インタフェースIRazorConfiguration
を実装したクラスを作成
NancyAppプロジェクトに RazorConfig
クラスを作成します。なお、Razor ViewEngineを使うのはNancyAppプロジェクト内だけなので、指定するアセンブリや名前空間はNancyApp
だけです。
public class RazorConfig : Nancy.ViewEngines.Razor.IRazorConfiguration { public IEnumerable<string> GetAssemblyNames() { yield return "NancyApp"; } public IEnumerable<string> GetDefaultNamespaces() { yield return "NancyApp"; } public bool AutoIncludeModelNamespace { get { return true; } } }
これ以外には追加する必要はなく、このままでRazor ViewEngineが動作します。
Viewの追加
- NancyAppプロジェクトの
Views
フォルダの下に、layout.sshtml
とrazor.sshtml
を作成- HTMLファイルテンプレートを使ったあと、拡張子を
cshtml
へと変換
- HTMLファイルテンプレートを使ったあと、拡張子を
- 2つのcshtmlファイルとも、プロパティを変更
- ビルドアクション:
コンテンツ
- 出力ディレクトリにコピー:
常にコピーする
- ビルドアクション:
- それぞれのファイル内容は以下の通り
layout.cshtml
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8" /> <title></title> @RenderSection("js", required:false) </head> <body> @RenderBody() </body> </html>
razor.cshtml
@{ Layout = "layout.cshtml"; } <h1>hello razor</h1> @section js{ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> }
HomeModuleの追加
/razor
というパスでRazorのViewを表示できるようにします。
Get["/razor"] = _ => View["razor"];
AspNetHostingとSelfHostingとも、エラーが出ることなくRazorで表示でき、HTMLソースにもjavascriptタグが追加されています。
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8" /> <title></title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> </head> <body> <h1>hello razor</h1> </body> </html>
ソースコード
Razor ViewEngineまでを含めた最終的なものはこちらです。
thinkAmi-sandbox/NancyMultiHosting-sample
Azure WebsitesとHerokuへのデプロイ (2015/2/5追記)
上記のソースコードを使った場合、Azure Websitesはデプロイしても動作しますが、Herokuは以下の例外で動作しませんでした。
... [ERROR] FATAL UNHANDLED EXCEPTION: System.TypeInitializationException: An exception was thrown by the type initializer for Nancy.Bootstrapper.AppDomainAssemblyTypeScanner ---> System.Reflection.ReflectionTypeLoadException: The classes in the module cannot be loaded. at System.Reflection.Assembly.GetExportedTypes () [0x00000] in <filename unknown>:0 ...
同じようなエラーはstackoverflowにありました。
c# - The type initializer for 'Nancy.Bootstrapper.AppDomainAssemblyTypeScanner' threw an exception - Stack Overflow
ただ、バージョン1.0.0のNancyアプリを最初から作成していることと、次のエントリによりHerokuはOWINを使えば動作することが分かったため、今回はこれ以上追求しません。