2013年7月20日土曜日

IE10 の互換モードで jquery.validate.unobtrusive を使うとエラー

最近ハマったのでメモ。解決策は以下の記事を参考にしました。

エラーの内容

jquery.validate.unobtrusive を IE10の互換モードで利用するとこんなエラーが出ることがあります。

行: 2699
エラー: メンバーが見つかりません。

どうやらページ内に form があるだけで起こるようで、jQuery その他のスクリプトが全滅して全く動かなくなります。怖い。

なんとなく IE10 の互換モードのバグっぽい雰囲気ですが、時間がなかったためあまり詳しく調べることができませんでした。(いいわけ)

jQuery の以下の set メソッド呼び出しで、novalidate という属性を設定しようとしてエラーが発生しているようです。

nodeHook = jQuery.valHooks.button = {
  get: function( elem, name ) {
   var ret;
   ret = elem.getAttributeNode( name );
   return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
    ret.nodeValue :
    undefined;
  },
  set: function( elem, value, name ) {
   // Set the existing or create a new attribute node
   var ret = elem.getAttributeNode( name );
   if ( !ret ) {
    ret = document.createAttribute( name );
    elem.setAttributeNode( ret );
   }
   return ( ret.nodeValue = value + "" );
  }
 };

解決策

いろいろな記事を見て回ると、jQuery の中身を書き換えたりする方法もありましたが、ちょっと怖いので互換モードの指定で逃げました。

以下のように X-UA-Compatible で IE が利用可能な最新のレンダリングモードを指定します。

<meta http-equiv="X-UA-Compatible" content="IE=edge" />

おわりに

いろいろなところでこの問題がおきてテンション下がりました。

jquery.validate.unobtrusive は ASP.NET MVC のテンプレートに入っているし、form がページ内にあるだけでエラーが起こるので、結構影響大きいのではと思うのですが、全然日本語の記事がなかったので書いてみました。

2013年6月16日日曜日

値オブジェクトのためのカスタムモデルバインダー

値オブジェクト

単一の値を表すオブジェクトのことで、いわいる不変オブジェクトのことです。(イミュータブルってやつですね)
例えばこんな感じのヤツですね。

バージョン情報 (e.g: "1.2", "11.101") を単一の値として扱う
public class Version
{
    public Version(int major, int minor)
    {
        Major = major;
        Minor = minor;
    }

    public int Major { get; private set; }

    public int Minor { get; private set; }

    public override string ToString()
    {
        return string.Format("{0}.{1}", Major, Minor);
    }
}

Major, Minor といったプリミティブ値を Version という型にまとめ、状態を変えれないようにすることで扱いがシンプルになります。

これはオブジェクト指向プログラミングのプラクティスとしてよく言われるものですが、ASP.NET MVC で不変オブジェクトを扱うと困ることがあります。

困る例

例えばこんなURLを使いたいと思って、アクションを用意するとします。

/Home/SomeAction?version=1.2
public ActionResult SomeAction(Models.Version version)
{
    ViewBag.Version = version; // version は null で、ModelState にもエラーが入る
    return View("SomeView");
}

当然 version 引数にはクエリパラメータで指定した "1.2" が Major と Minor に入ってきてほしいと思うわけですが、これはうまくいきません。

それもそのはずで、Version という型にどのように値を入れていいか、モデルバインダが知らないからです。

カスタムのモデルバインダを作る

せっかく値オブジェクトをコツコツ作っても、モデルバインダで使えないのでは寂しいので、カスタムのモデルバインダを作りましょう。

上記の Version 型の場合はこんな感じのモデルバインダを作ります。

public class VersionBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var valueKey = bindingContext.ModelName;
        var valueResult = bindingContext.ValueProvider.GetValue(valueKey);
        string rawValue = null;

        if (valueResult != null)
            rawValue = valueResult.AttemptedValue;

        if (string.IsNullOrEmpty(rawValue))
            return null;

        Version result;

        if (Version.TryParse(rawValue, out result))
        {
            return result;
        }
        else
        {
            bindingContext.ModelState.AddModelError(
                valueKey,
                string.Format("'{0}' は無効な値です。", rawValue)
                );

            return null;
        }
    }
}

実際の文字列からの変換は TryParse パターン とかで適当に実装します。

上記のように作ったモデルバインダを、Application_Start 時に登録します。
App_Start ディレクトリに、以下の様な起動用クラスをまとめるといいと思います。

public class BinderConfig
{
    public static void RegisterBinders(ModelBinderDictionary binders)
    {
        binders.Add(typeof(Models.Version), new VersionBinder());
    }
}

これを Global.asax の Application_Start メソッドで呼んであげます。

protected void Application_Start()
{
    // その他いろいろな起動設定...
    BinderConfig.RegisterBinders(ModelBinders.Binders);
    // その他いろいろな起動設定...
}

これで Version 型の値がしっかりモデルバインディングされます。

これたくさん作るの面倒だよね?

モデルバインディングしたい値オブジェクトがたくさんあったら、一つ一つカスタムのモデルバインダを実装するのは恐ろしく面倒です。

コードも定形なのでスーパークラスにまとめると楽です。

public abstract class ValueObjectBinder<T> : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var valueKey = bindingContext.ModelName;
        var valueResult = bindingContext.ValueProvider.GetValue(valueKey);
        string rawValue = null;

        if (valueResult != null)
            rawValue = valueResult.AttemptedValue;

        if (string.IsNullOrEmpty(rawValue))
            return null;

        T result;

        if (TryParse(rawValue, out result))
        {
            return result;
        }
        else
        {
            bindingContext.ModelState.AddModelError(
                valueKey,
                GetParseErrorMessage(rawValue)
                );

            return null;
        }
    }

    protected abstract bool TryParse(string input, out T result);

    protected virtual string GetParseErrorMessage(string rawValue)
    {
        return string.Format("'{0}' は無効な値です。", rawValue);
    }
}

さっきの Version 型の場合、コレを使うとこうなります。

public class VersionBinder : ValueObjectBinder<Version>
{
    protected override bool TryParse(string input, out Version result)
    {
        return Version.TryParse(input, out result);
    }
}

これならまあ作ってもいいかなってレベルじゃないでしょうか。もし大規模なアプリケーションでこれでも大変なら、T4 とか使う手もありそうです。

まとめ

これで値オブジェクトをたくさん使えるよ。

2013年5月18日土曜日

C# で Project Euler に挑戦: Problem 6

問題

二乗和の差

最初の10個の自然数について, その二乗の和は,

12 + 22 + ... + 102 = 385

最初の10個の自然数について, その和の二乗は,

(1 + 2 + ... + 10)2 = 3025

これらの数の差は 3025 - 385 = 2640 となる.

同様にして, 最初の100個の自然数について二乗の和と和の二乗の差を求めよ.

ぼくの解答

static void Main(string[] args)
{
    int n = 100;

    var numbers = Enumerable.Range(1, n);

    var sum = numbers.Sum();
    var sumSquare = sum * sum; // 和の二乗を求めます。
    var squreSum = numbers.Sum(i => i * i); // 二乗数の和を求めます。

    var answer = sumSquare - squreSum;

    Console.WriteLine(answer);
    Console.ReadLine();
}

解説

あまり説明するところがありません... ごく普通に 1 から 100 までの総和の二乗と、二乗の総和を求めています。

この問題はなんか簡単ですね。もっと面白い解き方があるのかな。

2013年5月16日木曜日

C# で Project Euler に挑戦: Problem 5

問題

最小の倍数

2520 は 1 から 10 の数字の全ての整数で割り切れる数字であり, そのような数字の中では最小の値である.

では, 1 から 20 までの整数全てで割り切れる数字の中で最小の正の数はいくらになるか.

ぼくの解答

static void Main(string[] args)
{
    int n = 20;

    int multiple = 1;
    int counter = 1;

    var numbers = Enumerable.Range(1, n);

    foreach (var i in numbers)
    {
        while (counter % i != 0)
        {
            counter += multiple;
        } 

        multiple = counter;
    }

    Console.WriteLine(multiple);
    Console.ReadLine();
}

解説

最小公倍数を求める問題です。単純に 1 から 20 まで順番に最小公倍数を見つけていきます。

最小公倍数を multiple として初期値を 1 とします。カウンタ用の変数(counter)も初期値を 1 にします。

  1. この時点の最小公倍数(1) と 1 の最小公倍数を求める。
    • 1 / 1 = 1 ... 0 => 割り切れるので最小公倍数1 に更新。
  2. この時点の最小公倍数(1) と 2 の最小公倍数を求める。
    • 1 / 2 = 0 ... 1 => 割り切れないので countermultiple を加算。
    • 2 / 2 = 1 ... 0 => 割り切れるので最小公倍数2 に更新。
  3. この時点の最小公倍数(2) と 3 の最小公倍数を求める。
    • 2 / 3 = 0 ... 2 => 割り切れないので countermultiple を加算。
    • 4 / 3 = 1 ... 1 => 割り切れないので countermultiple を加算。
    • 6 / 3 = 2 ... 0 => 割り切れるので最小公倍数6 に更新。
  4. この時点の最小公倍数(6) と 4 の最小公倍数を求める。
    • 6 / 4 = 1 ... 2 => 割り切れないので countermultiple を加算。
    • 12 / 4 = 3 ... 0 => 割り切れるので最小公倍数12 に更新。
  5. この時点の最小公倍数(12) と 5 の最小公倍数を求める。
    • 12 / 5 = 2 ... 2 => 割り切れないので countermultiple を加算。
    • 24 / 5 = 4 ... 4 => 割り切れないので countermultiple を加算。
    • 36 / 5 = 7 ... 1 => 割り切れないので countermultiple を加算。
    • 48 / 5 = 9 ... 3 => 割り切れないので countermultiple を加算。
    • 60 / 5 = 12 ... 0 => 割り切れるので最小公倍数60 に更新。
  6. この時点の最小公倍数(60) と 6 の最小公倍数を求める。
    • 60 / 6 = 10 ... 0 => 割り切れるので最小公倍数60 に更新。
  7. ... という風に続く。

他のプローチ

調べたら他にも良さそうなやり方がありました。最大公約数を元に解くのとか、数学っぽくていいですね。ぼくの頭では思いつきませんでした...

2013年5月13日月曜日

C# で Project Euler に挑戦: Problem 4

問題

最大の回文積

左右どちらから読んでも同じ値になる数を回文数という. 2桁の数の積で表される回文数のうち, 最大のものは 9009 = 91 × 99 である.

では, 3桁の数の積で表される回文数のうち最大のものを求めよ.

ぼくの解答

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        var threeDigitNumbers = Enumerable.Range(100, 900).ToList();
        var answer = threeDigitNumbers
            .SelectMany(i => threeDigitNumbers.Select(j => i * j))
            .Where(i => i.IsPalindrome())
            .Max();

        Console.WriteLine(answer);
        Console.ReadLine();
    }
}

public static class NumberHelper
{
    public static bool IsPalindrome(this int n)
    {
        return n.Digits().SequenceEqual(n.Digits().Reverse());
    }

    public static IEnumerable<int> Digits(this int n)
    {
        if (n == 0)
            yield return 0;

        const int radix = 10;
        var q = n;

        while (q != 0)
        {
            yield return q % radix;
            q /= radix;
        }
    } 
}

解説

この問題のポイントは、どうやって回文数かどうか判定するか、でしょうか。以下のどちらかになると思います。

  1. 文字列で判定する
  2. 各桁の数値をリストにして判定する

文字列でやるほうが簡単ですが、せっかくなので数値の桁を列挙する Digits() メソッドを作って解いてみました。

ある数値の桁の求め方

求めたい進法の基数で割っていって余りを取り出すと桁を求めることができます。例えば 4603 の場合、手順は以下です。

  1. 4603 / 10 = 460 ... 3 (商を次の割られる数へ回します)
  2. 460 / 10 =  46 ... 0
  3.   46 / 10 =   4 ... 6
  4.    4 / 10 =   0 ... 4 (商が 0 になったら終了です)
回文数かどうか

上記のように桁のリストを求めるか、文字列に変換して内容を反転させて元のものと同じかどうか調べればよいですね。

今回は int の拡張メソッドに IsPalindrome というメソッドを用意しています。内容は簡単です。

上記の Digits メソッドで桁を列挙し、Reverse メソッドで反転させたものと同じかを、SequenceEqual メソッドで調べます。SequenceEqual メソッドが true を返せば回文数です。

三桁の数字の積

単純にループしても良いですが、せっかく C# なので LINQ です。

はじめに三桁の数字のリストを作ります。Enumerable.Range(100, 900) で簡単ですね。(100 ∼ 999)

組み合わせを作ってそれらをかければいいので、SelectMany と Select メソッドを使って簡単に記述できます。

実行効率

この解は実行効率が悪いです。三桁の数字の組み合わせを全部作ってしまう点がネックですね。
実行効率でいえば、999 * 999 から 999 * 998, 999 * 997 ... とデクリメントしながら積を求め、回文数になった時点で処理を中断するほうが良いです。

2013年5月9日木曜日

C# で Project Euler に挑戦: Problem 3

問題

最大の素因数

13195 の素因数は 5, 7, 13, 29 である.

600851475143 の素因数のうち最大のものを求めよ.

ぼくの解答

static void Main(string[] args)
{
    var n = 600851475143;

    // まず約数を求め、その中から素数を見つけることで素因数を獲得します。
    var answer = n.Divisors().Where(i => i.IsPrime()).Max();
    Console.WriteLine(answer);

    Console.ReadLine();
}
public static class NumberHelper
{
    public static IEnumerable<long> Divisors(this long n)
    {
        long minor = 1;
        long greater = n;
        var counter = minor;

        while (counter < greater)
        {
            if (n % counter == 0)
            {
                minor = counter;
                greater = n / minor;

                yield return minor;
                yield return greater;
            }

            counter++;
        }            
    } 

    public static bool IsPrime(this long n)
    {
        if (n < 2) return false;
        if (n == 2) return true;
        if (n%2 == 0) return false;

        for (var i = 3; i*i < n; i += 2)
        {
            if (n%i == 0) return false;
        }

        return true;
    }
}

解説

素因数は約数を求めた後、その中から素数を見つけるのが簡単そうだったので、約数を求めるメソッドと、簡単な素数判定メソッドを拡張メソッドで用意しました。

素因数のリストが手に入れば、あとは最大値を返すだけです。

約数の求め方

約数を求めるときは 1 から順に試しに割っていきますが、もし割り切れたら、割った数(カウンタ)除算の結果の両方が約数なので、同時に約数として返していきます。

このとき、除算の結果(商)より大きい約数が今後登場することはないので、終了値を商に置き換えて行きます。

例えば 20 の約数を求めるときは、

  1. 1 で割ってみる -> 20 / 1 = 20 -> 120 が約数 -> カウンタの終了値を 20
  2. 2 で割ってみる -> 20 / 2 = 10 -> 210 が約数 -> カウンタの終了値を 10
  3. 3 で割ってみる -> 割り切れないので次
  4. 4 で割ってみる -> 20 / 4 = 5 -> 45 が約数 -> カウンタの終了値を 5
  5. カウンタが終了値(5)になったので終わり。約数は、1, 20, 2, 10, 4, 5

という感じです。

素数判定

簡単な試し割りです。一度約数を見つけて、その後素数判定をしているので簡単な試し割りで十分かなということで。

2013年4月29日月曜日

C# で Project Euler に挑戦: Problem 2

問題

偶数のフィボナッチ数

フィボナッチ数列の項は前の2つの項の和である. 最初の2項を 1, 2 とすれば, 最初の10項は以下の通りである.

1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

数列の項の値が400万を超えない範囲で, 偶数値の項の総和を求めよ.

ぼくの解答

static void Main(string[] args)
{
    int n = 4000000;
    var answer = FibonacciNumbers(n - 1)
        .Where(i => i%2 == 0)
        .Sum();

    Console.WriteLine(answer);
    Console.ReadLine();
}

static IEnumerable<int> FibonacciNumbers(int max)
{
    foreach (var i in FibonacciNumbers())
    {
        if (max < i)
            yield break;

        yield return i;
    }
} 

static IEnumerable<int> FibonacciNumbers()
{
    var previous = 0;
    var current = 1;
    var next = 0;

    while (true)
    {
        next = previous + current;
        yield return next;

        previous = current;
        current = next;
    }
}

解説

問題を解くだけならもっとシンプルになるんですが、LINQっぽく(?)処理しようと思いました。

フィボナッチ数の無限シーケンスを返すメソッドを作って、最大値でブレークするオーバロードを用意。これで Where, Sum メソッドで簡単に処理できます。

2013年4月28日日曜日

C# で Project Euler に挑戦: Problem 1

問題

3と5の倍数

10未満の自然数のうち, 3 もしくは 5 の倍数になっているものは 3, 5, 6, 9 の4つがあり, これらの合計は 23 になる.

同じようにして, 1000 未満の 3 か 5 の倍数になっている数字の合計を求めよ.

ぼくの解答

static void Main(string[] args)
{
    int n = 1000;

    var answer = Enumerable
        .Range(1, n - 1)
        .Where(i => i % 3 == 0 || i % 5 == 0)
        .Sum();

    Console.WriteLine(answer);
    Console.ReadLine();
}

解説

LINQって便利ですねぇ...

C# で Project Euler に挑戦: はじめに

Project Euler ってなに?

数学の問題をプログラミングで解いていくチャレンジプロジェクトです。
今のところ(2013/04/28時点)400問を超える問題があり、様々なプログラミング言語で世界中の人が挑戦しています。

公式サイトでアカウントを作って、たくさん問題を解いたり、問題を解いた後に参加できるレビューなどに参加すると、Award (Xboxとかの実績みたいなもの)がもらえます。

C# とアルゴリズムの勉強に

数年前から会社の新入社員教育のお題として、比較的簡単な問題を利用させてもらっていたのですが、自身の勉強のためにやってみることにしました。

日頃業務アプリケーション開発ばかりで込み入ったアルゴリズムなどをあまり考えることがなく、新鮮で面白いというのと、ちょっと勉強しておきたいなぁ、というのが動機です。

せっかくなのでC#らしい解き方を心がけたいと思います。例えば、LINQとか。

現時点の成果物

新入社員に教える手前、自分でもある程度解いておこうと思って作ったものが既にあるのですが、もうだいぶ前のことなので、改めてやって行きたいと思います。

のんびり

数学やアルゴリズムなどの分野は非常に苦手なのですが、興味があるのとプログラマとして重要な素養だと思うので、楽しんでやって行きたいと思います。

解いた問題

2013年3月22日金曜日

ASP.NET MVC のエラーハンドリング(未完)

ASP.NET MVC でのエラーハンドリングを見直す機会があったのでメモします。

これまで

今までエラーハンドリングは customErrors の設定と、Application_Error イベントで行なっていました。

<customErrors mode="RemoteOnly" defaultRedirect="~/Error">
  <error statusCode="404" redirect="~/Error/NotFound"/>
</customErrors>
protected void Application_Error()
{
 var exception = Server.GetLastError();
 // 例外をロギングしたり...
}

こんな感じでエラーページの表示と適当に例外を起こすアクションを用意しました。

public class ErrorController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult NotFound()
    {
        return View();
    }

    public ActionResult Raise(int? httpCode)
    {
        if (!httpCode.HasValue)
            throw new ApplicationException("エラーだよ。");

        throw new HttpException(
            httpCode.Value, string.Format("ステータスコード {0} のエラーだよ。", httpCode));
    }
}

これで例外が起きると用意したエラーページが表示され、Application_Error で必要な処理(ロギングとか)を実行できます。

なにが問題?

この方法でもひと通りのことはできているんですが、よく見ると気になることがあります。

例外を起こしてエラーページを表示させてみます。このときの HTTP はこんな風になっています。

エラーページのステータスコードが "200"

エラーページなのにステータスコードが 200 になっています。
できれば 500 やその他適切なステータスコードを返したいところです。

リダイレクトによるエラーページのナビゲート

304 で customErrors で指定したエラーページの URL にリダイレクトしています。
これもできれば、リダイレクトせずにエラーページを表示できるほうが理想的です。

というわけで、"適切なステータスコード""リダイレクトせずに" エラーページを表示する方法を探しました。

エラーページのステータスコード

エラーページがそれぞれ適切なステータスコードで返るようにするのは簡単です。
Response.StatusCode を設定してあげれば OK です。

public class ErrorController : Controller
{
    public ActionResult Index()
    {
        Response.StatusCode = (int)HttpStatusCode.InternalServerError;
        return View();
    }

    public ActionResult NotFound()
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    }

    public ActionResult Raise ...
}

これだけでこんな感じで設定したステータスコードでレスポンスが返ります。

リダイレクトせずにエラーページを表示する

次はリダイレクトせずにエラーページを表示する方法です。

customErrors の redirectMode="ResponseRewrite" ... ?

customerErrors には redirectMode というプロパティがあり、 リダイレクトしてエラーページをナビゲートする "ResponseRedirect" かレスポンスをエラーページの内容で上書きして表示する "ResponseRewrite" かを設定できます。

<customErrors mode="On" defaultRedirect="~/Error" redirectMode="ResponseRewrite">
  <error statusCode="404" redirect="~/Error/NotFound"/>
</customErrors>
残念ながら ASP.NET MVC ではうまく動かない

まさにコレ!という内容のプロパティですが、ASP.NET MVC では思ったように動作しません

ResponseRewrite ではレスポンスの内容を指定されたページの内容で上書きしてくれるのですが、 この際 ASP.NET は指定されたパスで物理ファイルを探しにいってしまいます。

なので、WebForm であればこれはうまく動作する(ハズ)のですが、ASP.NET MVC では動作しません。ざんねん...

物理ファイルで

物理ファイル探しにいくなら、物理ファイルで指定したら動くわけで... 指定しちゃってみます。

適当にエラーページを aspx で用意して、customErrors で指定します。
このとき aspx にはステータスコードを指定するコードを入れておくと任意のステータスコードでレスポンスを返せます。

protected override void OnLoad(EventArgs e)
{
    Response.StatusCode = (int)HttpStatusCode.InternalServerError;   
    base.OnLoad(e);
}
<customErrors mode="On" defaultRedirect="~/Views/Error/Index.aspx" redirectMode="ResponseRewrite">
  <error statusCode="404" redirect="~/Views/Error/NotFound.aspx"/>
</customErrors>

動きますね。

これでいいのか

「レイアウト(_Layout.cshtml)とか使えないじゃん、コレ」とか「エラーページも Razor で書こうよ。」とか...

IIS7とかであれば httpErrors を使ってできたりするようです。

また、カスタムの HandleError を作ることでもできます。が、HandleError はあくまで ActionFilter なので、アクションメソッドの外側で起きるエラーには手出しできません。

一番いいのは ASP.NET MVC が redirectMode="ResponseRewrite" のときに物理ファイルをアテにするのではなく、パスの実行結果を上書きしてくれるようになることなんですが...

ここで時間切れ

いろいろ調べてみたんですが、なかなかコレ!っていう解決策が見つからない&思いつきませんでした。
もしいい方法をご存知の方がいらしたら教えて下さい。

2013年1月22日火曜日

ASP.NET MVC 4 の StyleBundle で注意すること

ASP.NET MVC 4 の StyleBundle を利用する際に気をつけたいことがあったのでメモします。

こんな感じでディレクトリとファイルを構成し、スタイルシートを用意した場合を見ていきます。

ファイル

├─Content
   ├─images
   │  └─picture.jpg
   └─site.css

スタイルシート(site.css)

.picture {
  background-image: url("images/picture.jpg");
}

この構成で次のようなバンドルを設定すると困ったことになります。

bundles.Add(new StyleBundle("~/Content/Site/css").Include(
    "~/Content/site.css"));

このバンドルで site.css が出力されるパスは "~/Content/Site/css" です。
背景に指定されている画像は相対パスで参照しているので、"/Content/Site/images/picture.jpg" というパスになってしまい、画像が参照できません。

このようにバンドル時のパスが実際のファイル構成と異なると、スタイルシートから相対パスで参照している画像などが読めなくなってしまうので注意が必要です。

実際のファイル構成と同じパスでバンドルしてあげる必要があります。

bundles.Add(new StyleBundle("~/Content/css").Include(
    "~/Content/site.css"));

また、違うパスにあるスタイルシートをまとめてバンドルするのも避けたほうがいいですね。

2013年1月21日月曜日

ASP.NET MVC 4 の ScriptBundle はデバッグ時に .min.js を出力しない

ASP.NET MVC 4 の ScriptBundle はファイル名の末尾で利用するファイルを選択します。

例えば以下の様な3つのファイルを用意した場合、

  • sample.js
  • sample.debug.js
  • sample.min.js

バンドルの設定を以下のようにした場合を見ていきます。

bundles.Add(new ScriptBundle("~/bundles/sample").Include("~/Scripts/sample*"));

デバッグ時

デバッグ時(web.config の compilation要素の debug属性が true のとき)は、sample.debug.js が選択されます。

<script src="/Scripts/sample.debug.js"></script>

デバッグ時でないとき

デバッグ環境でないとき(web.config の compilation要素の debug属性が false のとき)は、sample.min.js が選択されます。

また、sample.min.js の内容は minify されて出力されます。今回は一つのファイルですが、複数のファイルを一つのバンドルに設定した場合は、それらのファイルを全てくっつけて一つのファイルとしてレスポンスしてくれます。

<script src="/bundles/sample?v=Rk-zyJQ66YShCJwocw4z0jCjm_jhceIZ5m55SxsnveY1"></script>

sample.js のみの場合

sample.js のみの場合を見てみます。

デバッグ時

普通に sample.js が選択されます。

<script src="/Scripts/sample.js"></script>

デバッグ時でないとき

もちろん sample.js が選択されますが、スクリプトの内容は minify されて出力されます。

<script src="/bundles/sample?v=FleCpml6dOgPyNvg3fS5cE-_9YPt2osVzl-MIlVAIPU1"></script>

sample.min.js のみの場合

sample.min.js のみだったらどうなるでしょうか。

デバッグ時

この場合注意が必要です。何も読み込まれません。 Scriptタグもレンダリングされません。

デバッグ時でないとき

sample.min.js が選択され、minify されて出力されます。

<script src="/bundles/sample?v=Rk-zyJQ66YShCJwocw4z0jCjm_jhceIZ5m55SxsnveY1"></script>

sample.debug.js のみの場合

あまりないと思いますが、sample.debug.js のみではどうなるでしょうか。

デバッグ時

sample.debug.js が選択されます。

<script src="/Scripts/sample.debug.js"></script>

デバッグ時でないとき

スクリプトタグは出力されますが、中身は空です。

<script src="/bundles/sample?v="></script>

注意点

というわけで、xxxxx.min.js のみだとデバッグ時に利用できなくなってしまうので注意が必要です。
xxxxx.js を入手して一緒に入れておくか、xxxxx.debug.js が用意されていれば合わせて入れておくとよさそうです。

この記事を書く前は、xxxxx.min.js はデバッグ時でないときは minify されずにそのまま出力されると思っていましたが、minify されるようです。そのまま出してくれてもいいような気がするんですが...

2013年1月16日水曜日

[NHibernate]enumを文字列でマッピングする

NHibernate で enum のプロパティ/列をマッピングするときのメモです。

普通にマッピングすると

以下の様な enum のプロパティをマッピングすると、"Gender" は 1, "Female" は 2 という感じで enum の中身の整数(int)でマッピングされます。

モデル

public enum Gender
{
    Male = 1,
    Female = 2
}
public class Student
{
    public virtual int Id { get; set; }

    public virtual string Name { get; set; }

    public virtual Gender Gender { get; set; }
}

テーブル

テーブルのほうはこんな感じですね。

CREATE TABLE [dbo].[Students] (
    [Id]     INT            IDENTITY NOT NULL PRIMARY KEY,
    [Name]   NVARCHAR (100) NOT NULL,
    [Gender] INT            NOT NULL,
    CHECK ([Gender] = 1 OR [Gender] = 2)
);

マッピング

マッピングはこうです。

<class name="Student" table="Students">

    <id name="Id">
        <generator class="identity"></generator>
    </id>

    <property name="Name"></property>
    <property name="Gender"></property>

</class>

データ

Id Name Gender
1 スタン 1
2 カイル 1
3 カートマン 1
4 ケニー 1
5 バターズ 1
6 ウェンディ 2

文字列でマッピングしたいときは

でも整数ではなく文字列でテーブルに持ちたいときもあります。データをこんな風にしたいときですね。

Id Name Gender
1 スタン Male
2 カイル Male
3 カートマン Male
4 ケニー Male
5 バターズ Male
6 ウェンディ Female

そんなときは NHibernate の IUserType インターフェイスを実装してマッピングに利用します。 例えばこんな感じの基底クラスを用意しておいて、

public class EnumStringType<TEnum> : IUserType
{
    public bool Equals(object x, object y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x == null || y == null) return false;
        return x.Equals(y);
    }

    public int GetHashCode(object x)
    {
        if (x == null) return 0;
        return x.GetHashCode();
    }

    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        var value = NHibernateUtil.String.NullSafeGet(rs, names);

        if (value == null)
            return null;

        return (TEnum) Enum.Parse(typeof (TEnum), (string) value);
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        string stringValue = null;

        if (value != null)
            stringValue = ((TEnum) value).ToString();

        NHibernateUtil.String.NullSafeSet(cmd, stringValue, index);
    }

    public object DeepCopy(object value)
    {
        return value;
    }

    public object Replace(object original, object target, object owner)
    {
        return original;
    }

    public object Assemble(object cached, object owner)
    {
        return cached;
    }

    public object Disassemble(object value)
    {
        return value;
    }

    public SqlType[] SqlTypes
    {
        get { return new[] {NHibernateUtil.String.SqlType}; }
    }

    public Type ReturnedType 
    {
        get { return typeof (TEnum); }
    }

    public bool IsMutable 
    {
         get { return false; }
    }
}

これを派生した、目的の enum 用のクラスを作ればOKです。

public class GenderStringType : EnumStringType<Gender>
{
}

テーブル

あとはテーブルの列の型を文字列に変更して...

CREATE TABLE [dbo].[Students] (
    [Id]     INT            IDENTITY NOT NULL PRIMARY KEY,
    [Name]   NVARCHAR (100) NOT NULL,
    [Gender] NVARCHAR (10)  NOT NULL,
    CHECK ([Gender] = ‘Male‘ OR [Gender] = ‘Female‘)
);

マッピング

マッピングに利用する UserType を指定してあげます。

<class name="Student" table="Students">

    <id name="Id">
        <generator class="identity"></generator>
    </id>

    <property name="Name"></property>
    <property name="Gender" type="Sample.NH.UserTypes.GenderStringType, Sample.NH"></property>

</class>

これで enum を文字列で永続化できます。

補足

例では簡潔にするために省きましたが、整数/文字列どちらで保存する場合も、 実際にはマスタテーブル(この例だと Genders とか)を作って外部キー制約を指定しておいたほうがいいですね。

CREATE TABLE [dbo].[Students] (
    [Id]     INT            IDENTITY NOT NULL PRIMARY KEY,
    [Name]   NVARCHAR (100) NOT NULL,
    [Gender] NVARCHAR (10)  NOT NULL,
    FOREIGN KEY (Gender) REFERENCES Genders (Value) 
);
CREATE TABLE [dbo].[Genders] (
    [Id]     INT            NOT NULL PRIMARY KEY,
    [Value]   NVARCHAR (10) NOT NULL,
    UNIQUE (Value)
);

2013年1月11日金曜日

Jenkinsでのビルド時にNuGetパッケージを復元する

NuGet でパッケージ管理をしているプロジェクトを Jenkins でビルドしようとするとうまくいきませんでした。その解決方法をメモします。

うまくいかない理由

NuGet のパッケージは通常バージョン管理下に置かれません。
なのでソースコードをチェックアウトした段階ではパッケージがなく、ビルド時にパッケージを復元しないといけないことになります。

NuGetパッケージの復元を有効化

Visual Studio からパッケージの復元を設定できます。
ソリューションエクスプローラから NuGet パッケージの復元を設定できるので有効にしてあげます。

ソリューションを右クリックして、「NuGetパッケージの復元の有効化」を選択します。

こんな感じの確認ダイアログが出るのでOKします。

少し待つとソリューションフォルダが作成されてこんなファイルが追加されます。
これらを使ってビルド時にパッケージを復元してくれます。

Jenkinsでビルドしてみる

これでバッチリ!ということで Jenkins でビルドすると、今度はこんなエラーになります。

パッケージの復元は既定で無効になっています。確認のため、Visual Studio の [オプション] ダイアログ ボックスを開き、Package Manager ノードをクリックして、[NuGet がビルド中に存在しないパッケージをダウンロードするのを許可する] チェック ボックスをオンにします。また、環境変数 'EnableNuGetPackageRestore' を true に設定して確認することもできます。

メッセージの通りではありますが、ソリューションで復元を有効にするだけでは復元を実行してくれません。
サーバの環境変数「EnableNuGetPackageRestore」を true に設定すると復元してくれるようになります。

これで解決

これでパッケージが復元され、無事ビルドを行うことができます。

おまけ: アンオフィシャルなパッケージソース

NuGet公式サイトにて配布されいるパッケージは以上の通りでOKですが、もし自前の NuGet サーバを利用して、そちらからインストールしているパッケージがある場合、もうひと手間必要です。
※ 私は社内に NuGet サーバを立ててパッケージ管理していたのでちょっとハマりました。

「NuGetパッケージの復元の有効化」したときに追加された「NuGet.targets」を開きます。このファイル中に以下のような部分があります。

    <ItemGroup Condition=" '$(PackageSources)' == '' ">
        <!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
        <!-- The official NuGet package source (https://nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
        <!--
            <PackageSource Include="https://nuget.org/api/v2/" />
            <PackageSource Include="https://my-nuget-source/nuget/" />
        -->
    </ItemGroup>

丁寧なコメントの通りですが、自前の NuGet サーバを利用したい場合は、コメントアウトしてある箇所をコメントインして、パッケージソースを指定してやる必要があります。

    <ItemGroup Condition=" '$(PackageSources)' == '' ">
        <!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
        <!-- The official NuGet package source (https://nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
        <PackageSource Include="https://nuget.org/api/v2/" />
        <PackageSource Include="https://my-nuget-source/nuget/" />
    </ItemGroup>

「https://my-nuget-source/nuget/」の部分を自前のパッケージソースに変えてあげればOKですね。

TFT 10.14 Peeba Comp

こちらのガイドの自分用まとめです。 https://www.reddit.com/r/CompetitiveTFT/comments/hraunp/tft_1014_break_the_meta_new_peeba_comp_set_35/ 難しいですが完成すると非常に強く、プレ...