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/ 難しいですが完成すると非常に強く、プレ...