WindowsアプリにOWINホストWEBアプリをオンしてみた

Webフレームワークには[Nancy](http://nancyfx.org/)を使いました。

※SignalRが含まれていますがまだ手を付けていません。


## プロジェクト構成

FormとWebページが混在しています。分けたいです。

![20141112_proj_tree.png](https://qiita-image-store.s3.amazonaws.com/0/7736/8d9e8ccc-98aa-3e02-d9ba-5c2235fe2769.png)


## package.config

```xml
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="jQuery" version="1.6.4" targetFramework="net451" />
  <package id="jquery.TypeScript.DefinitelyTyped" version="1.4.0" targetFramework="net451" />
  <package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net451" />
  <package id="Microsoft.AspNet.SignalR" version="2.1.2" targetFramework="net451" />
  <package id="Microsoft.AspNet.SignalR.Core" version="2.1.2" targetFramework="net451" />
  <package id="Microsoft.AspNet.SignalR.JS" version="2.1.2" targetFramework="net451" />
  <package id="Microsoft.AspNet.SignalR.SystemWeb" version="2.1.2" targetFramework="net451" />
  <package id="Microsoft.Owin" version="3.0.0" targetFramework="net451" />
  <package id="Microsoft.Owin.Host.HttpListener" version="3.0.0" targetFramework="net451" />
  <package id="Microsoft.Owin.Host.SystemWeb" version="2.0.1" targetFramework="net451" />
  <package id="Microsoft.Owin.Hosting" version="3.0.0" targetFramework="net451" />
  <package id="Microsoft.Owin.Security" version="3.0.0" targetFramework="net451" />
  <package id="Nancy" version="0.23.2" targetFramework="net451" />
  <package id="Nancy.Authentication.Forms" version="0.23.2" targetFramework="net451" />
  <package id="Nancy.Owin" version="0.23.2" targetFramework="net451" />
  <package id="Nancy.Viewengines.Razor" version="0.23.2" targetFramework="net451" />
  <package id="Newtonsoft.Json" version="6.0.4" targetFramework="net451" />
  <package id="Owin" version="1.0" targetFramework="net451" />
</packages>
```


## Bootstrapperクラス

Windowsアプリでホストする都合上、RootPathProviderを独自に用意する必要があります。

今回はデバッグビルドの出力先が bin\Debug に生成されていることを前提とした、SelfHostRootProviderクラスを実装しました。 (リリースビルドのときはexeとリソースフォルダが同じ場所に配置されると思うので、気にしていません。)

あとは、静的リソース場所の設定とフォーム認証とセッションの有効化を行っています。

```csharp
namespace SelfHostApp2
{
    public class MyBootstrapper : DefaultNancyBootstrapper
    {
        protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines)
        {
            base.ApplicationStartup(container, pipelines);
#if DEBUG
            container.Register<IRootPathProvider>(new SelfHostRootProvider());
#endif

            CookieBasedSessions.Enable(pipelines);
        }

        protected override void ConfigureConventions(NancyConventions nancyConventions)
        {
            base.ConfigureConventions(nancyConventions);

            this.Conventions.StaticContentsConventions.Clear();
            this.Conventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("/Content"));
            this.Conventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("/Scripts"));
        }

        protected override void ConfigureRequestContainer(Nancy.TinyIoc.TinyIoCContainer container, NancyContext context)
        {
            base.ConfigureRequestContainer(container, context);

            container.Register<IUserMapper, UserDatabase>(); //今回は掲載していないが、認証用のUserDatabaseクラスが別途存在する。
        }

        protected override void RequestStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines, NancyContext context)
        {
            base.RequestStartup(container, pipelines, context);


            var formsAuthConfiguration = new FormsAuthenticationConfiguration()
            {
                RedirectUrl = "/Auth", //認証失敗時のリダイレクト先
                UserMapper = container.Resolve<IUserMapper>()
            };
            FormsAuthentication.Enable(pipelines, formsAuthConfiguration);  //フォーム認証の有効化
        }
    }

#if DEBUG
    public class SelfHostRootProvider : IRootPathProvider
    {
        public string GetRootPath()
        {
            var assembly = Assembly.GetEntryAssembly();

            return assembly != null ?
                GetProjectDirectory(assembly.Location) :
                GetProjectDirectory(Assembly.GetExecutingAssembly().Location);
        }

        private string GetProjectDirectory(string asmPath)
        {
            var di = new DirectoryInfo(Path.GetDirectoryName(asmPath));
            return di.Parent.Parent.FullName;
        }
    }
#endif
}
```

## Razorテンプレート

Razorテンプレートからhtmlが生成され、静的リソースの参照が可能ことを確認しました。

ここに載せてはいませんが、フォーム認証機能とセッション機能も有効に動きました。

```html
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<dynamic>

<!DOCTYPE html>

<html lang="jp" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>@Model.Detail</title>
    <link rel="stylesheet" href="/Content/style/index.css" />
</head>
<body>
    <div class="container">
        <p>24時間まえの時間は @DateTime.Now.AddHours(-24.0d) です。</p>
        <p>現在の時間は @DateTime.Now です。</p>
        <p>24時間後の時間は @DateTime.Now.AddHours(24.0d) です。</p>
        <img class="fax-image" src="/Content/img/faxfax.jpg" />
    </div>
    <script src="/Scripts/jquery-1.6.4.min.js"></script>
    <script src="/Scripts/Hoge.js"></script>
</body>
</html>
```

## TypeScript

プロジェクトがWindowsアプリケーションのため、TypeScriptファイルを編集保存してもjavascriptへの自動変換は行われません。

Webアプリケーションのプロジェクトファイルと比較して差異を確認したところ、下記2箇所を追加することで自動変換されるようになりました。

### 1. /Project 要素

以下のImport要素を追加。

```
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props')" />

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets')" /> 
```

### 2. /Project/PropertyGroup 要素

先頭のPropertyGroup要素に以下を追加。

```
<TypeScriptToolsVersion>1.0</TypeScriptToolsVersion> 
```

## フォーム

```csharp
using Microsoft.Owin.Hosting;

namespace SelfHostApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            btnStart.Enabled = true;
            btnStop.Enabled = false;
        }

        private IDisposable webapp = null;

        private void button1_Click(object sender, EventArgs e)
        {
            var url = "http://+:" + txtPort.Text;
            this.webapp = WebApp.Start(url);
            btnStart.Enabled = false;
            btnStop.Enabled = true;
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            StopWebApp();
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            StopWebApp();
            btnStart.Enabled = true;
            btnStop.Enabled = false;
        }

        private void StopWebApp()
        {
            if (webapp != null)
                webapp.Dispose();
        }
    }
}
```