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
↑のサイトで、Generate Keyボタンを押下して、生成された文字列を、<System.Web>空間内にコピペすればOKです。

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

Client Validation

Model のアノテーション設定から Validation を行ってくれる便利なアレ。
NuGet から、パッケージをちゃんと入れて、Web.config の ClientValidationEnabled も true にして、IIS環境ではちゃんと動きましたーー
でも、Monoでは、Client Validation のための 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を出力してくれるハズが、
↓のようなプレーンがタグになっちゃう。

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

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

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

1発目のリクエストで例外発生

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

これは、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ファイルを開くときに、↓のエラーが出てしまいます。

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

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

NLog.Web 設定で aspnet 変数を書くと例外

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

<!-- 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)は、ログ内容文字列に含めることにしました。