Monoならではの問題と対応方法

#はじめに

Mono 好きです！

初めて Mono を試したとき、Windowsで発行した ASP.NET MVC アプリが Linuxで動いたのは、魔法かと思っちゃいました。
そんなステキな Mono ですが、やっぱりちょっとだけ気をつけることがあります。
モノズキな方へのTip集。

筆者の環境は下記のとおりになっています。

* Visual Studio 2015
* .NET Framework 4.5.1
* ASP.NET MVC 5
* Mono 4.6.2
* XSP 4.2.2
* fast-cgi-mono-server 4.2.2 (0.4.0.0)
* Debian 8.6
* Nginx 1.6.2

#Machine Key
フォーム認証や暗号化のために必要な Machine Key。
普通(Windows IIS) は、Web.config に明記していなければ自動的に生成されるらしいんだけど、Mono じゃダメです。
サービスを再起動する度に値が変わってしまうため、System.Web.Security.MachineKey.Protect などでの暗号した値を decodeするときに例外を吐いてしまいます。

なのでWeb.configに明記します。
[Generate ASP.NET Machine Keys](http://www.developerfusion.com/tools/generatemachinekey/)
↑のサイトで、**Generate Key**ボタンを押下して、生成された文字列を、&lt;System.Web&gt;空間内にコピペすればOKです。

```genshi
<machineKey validationKey="E4C13FB44CF8CBCD126896B77B0A38D05D7290A47ADB1D248500A624D4A07607D096402371E15D8F981D8071986B72FEBDE40248A63DA764F48F7996F24C7871" decryptionKey="AB4996AF83FEB94C5018CC7FD003F3909C553DCEAA5FEE4AC7C6432DE3DFD701"　validation="SHA1" decryption="AES">
```

#Client Validation
Model のアノテーション設定から Validation を行ってくれる便利なアレ。
NuGet から、パッケージをちゃんと入れて、Web.config の ClientValidationEnabled も true にして、IIS環境ではちゃんと動きましたーー
でも、Monoでは、Client Validation のための HTML タグを吐いてくれない・・・そんなとき。

```html
<input data-val="true" data-val-required="メールアドレス は入力必須項目です。" id="MailAddress" name="MailAddress" type="text" value="" />

<input data-val="true" data-val-required="パスワード は入力必須項目です。" id="Passwd" name="Passwd" type="password" value="" />
```

ほんとうなら、↑こんなHTMLを出力してくれるハズが、
↓のようなプレーンがタグになっちゃう。

```html
<input id="MailAddress" name="MailAddress" type="text" value="" />

<input id="Passwd" name="Passwd" type="password" value="" />
```

これの解決方法は、次項にて。

#1発目のリクエストで例外発生
サービスを再起動したり、プログラムを入れ替えたりした後の、最初のリクエストを行うと、System.UnauthorizedAccessException を吐くことがあります。
（F5押下してリロードすると、動きだす）

![image](https://qiita-image-store.s3.amazonaws.com/0/177379/08066d0f-281c-0a51-e7d5-6333b2f37437.png)

これは、Mono 用に一時ディレクトリを作ってやれば、解決します。
ディレクトリの名前は、例外メッセージに表示されています。
このディレクトリを、所有者をサービスを実行しているユーザにして作ってやればOKです。
`Mono をパッケージからインストールしたときは、/etc/mono/registry`
`ソースから入れたときは、/usr/local/etc/mono/registry`
`fast-cgiを使っているなら、fast-cgi の実行ユーザ`
`Apache + mod_mono なら、Apache の実行ユーザ`

また、Webサーバのエラーログを見てみると、
"実行ユーザのホーム ディレクトリ/.mono" がdenidだぞ・・・
とあるので、こちらのディレクトリも同じように作成しておきます。

**こちらの設定を行うと、前項の「Client Validation」の問題も解決します**
ちゃんと validation のダグが生成されていました！


#Knockout の ViewModel を JSON にシリアライズするとき
Knockout を使った Formデータを POSTした時に `Internal error: key is null, empty or not a string.`てな例外が出たら、JSONをうまくデシリアライズできないのが原因のようです。
Windows環境では、問題なく動くのに、Mono だとダメなときがあります。

どうやら、ko.mapping を使って ViewModel 化したものを、ko.toJSON でシリアライズすると、余計なプロパティ（\_\_ko_mapping\_\_）が付いたままになり、これが付くと、Mono 上ではモデルバインダーが例外を吐いてしまうようです。

ko.mappingを使ったものは、必ず「ko.mapping.toJSON」でシリアライズするように、気をつける必要があります。




#ClosedXMLでのExcelが破損？
サーバサイドで Excelファイルを生成する処理があったので、最初は ClosedXMLを使ってました。
ところが、Mono 環境で実行すると、生成したExcelファイルを開くときに、↓のエラーが出てしまいます。
![image](https://qiita-image-store.s3.amazonaws.com/0/177379/794e5d0d-9ef7-8553-f4e7-036974a205ec.png)

![image](https://qiita-image-store.s3.amazonaws.com/0/177379/6fc6fac9-db03-66bd-ff08-37dafe4b15b3.png)

毎回、最初の問いに「はい」とすれば、Excelが開くんだけど、どうやらこれは xlsx 形式の Zip 圧縮で微妙な差異があるらしいのが原因みたいです。
※WindowsBase.dll を参照に入れたり、Web.config でアセンブリを明示してもダメでした。

仕方ないので、ClosedXML の利用を諦め、EPPlusというパッケージを試してみたら、問題なく開くことが出来ました。
EPPlus は、OpenXML SDK を使わずに Open Office XML を生成するので、単純な表なら十分だし、実効速度も速いです。


#NLog.Web 設定で aspnet 変数を書くと例外
NLog の拡張ライブラリ「NLog.Web」を入れると、設定ファイルの記述だけで ASP.NET の環境変数をログに出力できます。

```genshi:例
<!-- layout のところで、リクエストURLをログに出力しています -->
<target name="accessLog"
        xsi:type="File"
        layout="${longdate} ${aspnet-request:serverVariable=Url} - ${message} ${exception:format=tostring}"
        fileName="${logDirectory}/access.log"
        archiveEvery="Day"
        archiveFileName="${logDirectory}/access.{#}.log"
        archiveNumbering="Date"
        archiveDateFormat="yyyyMMdd"
        encoding="utf-8"
        maxArchiveFiles="30" />
```

でもこれを Mono で実行すると
`Non-web exception. Exception origin (name of application or object): System.Web.Mvc.`
てな例外が出てしまいます。

仕方ないので、出力したい情報（リクエストURL）は、ログ内容文字列に含めることにしました。