2012/12/08

【ASP.NET】テンプレートを使ってカレンダー付きテキストボックスを作ってみる【MVC】

 超絶久しぶりな更新ですが、あっさりめに済ませます。いきなりですが、通常通り作った場合のサンプルを表示します。今回はコントローラーでは何もしないので省略です。

Pepoleモデル
public class People
{
    public string Name { get; set; }

    public DateTime Birthday { get; set; }
 
}

Createビュー
@model MvcApplicationTest3.Models.People
 
@{
    ViewBag.Title = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
<h2>Create</h2>
 
@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
 
    <fieldset>
        <legend>People</legend>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Birthday)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Birthday)
            @Html.ValidationMessageFor(model => model.Birthday)
        </div>
 
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}
 
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
 
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

実行結果

image

  当然ながらBirthdayにカーソルがあたっても何も起きません。こういう時、大抵はカレンダーが表示された方が便利がいいですよねって話です。んでMVCの場合、jqueryUIが使えるんだからUIのDatePickerを使ってみようってなるんですが、日付型が出てくるたびにいちいち定義してたら面倒ですよね?ってことでテンプレートの出番になるわけです。

 早速、ヘルパーテンプレートを作成します。テンプレートはある程度決まったルール(フォルダ名)で配置することで、自動で検索されて自動で定義されます(明示的に定義することも可能です)。何言ってんだって感じですが、まぁやってみれば分かります。

フォルダーとテンプレートファイルの追加

image

 このようにテンプレートファイルは利用するヘルパー系統フォルダー(EditorTemplates)の下にデータ型の名前で定義します(DateTime.cshtml)。今回はEditor系のテンプレートなのでEditorTemplatesというフォルダ名になっていますが、Display系のテンプレートであればDisplayTemplatesとなります。これで構築される際にEditorヘルパーでDateTime型だった場合、このテンプレートが呼び出されます。また、今回はShared配下に置いていますが、利用がそのビューに限定される場合はそのビューの配下に置くことも可能です。

 次はテンプレートの中身です。

テンプレートファイル(DateTime.cshtml)
@model System.DateTime?
 
@Html.TextBoxFor(model=>model, new { @class="datepicker"})
 
<!-- DatePicker定義 -->
<script type="text/javascript">
    $(function () {
        $('.datepicker').datepicker();
    });
</script>

 シンプルなので特に解説することもないのですが、jqueryUIの定義は他でしたほうが良さそうなのでレイアウトなり、ビューなりでやっちゃってください。ちなみにモデルはnull対策のためnull許容型にしています。

 次にテンプレートを呼び出すためにモデルのデータ型を属性で明示してやります。

Peopleモデル
public class People
{
    public string Name { get; set; }

    [DataType(DataType.DateTime)] //←これを追加
    public DateTime Birthday { get; set; }
 
}

 これで完了です。ビュー側は自動的にテンプレートが呼び出されるので変更する必要はありません。

実行結果

image

 こんな具合で望んだ通りになりました。Shared配下にテンプレートを作成したのでこれ以外のビューでもモデルにDateTime型が明示してあればビューは自動でこのテンプレートを割り当てます。

 以上、久しぶりに更新すると余計に日本語が怪しくなってる気がする…。

2012/11/22

【EF CodeFirst】リレーションの連鎖削除について

 何かすごく久しぶりの更新なのに大した話ではないんですが、メモついでに…。

 コードファーストでリレーションを組んだ場合の連鎖削除についてです。
 まぁ、以下のようなCategoryとSubCategoryと名付けた親子のテーブルがあるとします。

Categoryテーブル
public class Category
{
    public int Id { get; set; }

    public string Name { get; set; }

    public virtual ICollection<subcategory> SubCategories { get; set; }
}

SubCategoryテーブル
public class SubCategory
{
    public int Id { get; set; }

    public int ClassId { get; set; }

    public string Name { get; set; }

    public virtual Category Category { get; set; }
}

 見ての通り、ClassIdを外部キーとしてリレーションするわけですが、Categoryのデータを削除した場合、SubCategoryのデータはどうなるのでしょうか?答えは連鎖削除されちゃいます。まったくEntityFrameworkって気が利いてますねって感じです。

 では逆に連鎖削除してほしくない時はどうすればよいのでしょうか?自分、他のテーブルとの関係で連鎖削除OFFにしたくてずいぶん悩みました。答えは以下のようにテーブルを変更します。

Categoryテーブル
public class Category
{
    public int Id { get; set; }

    public string Name { get; set; }

    public virtual ICollection<subcategory> SubCategories { get; set; }
}

SubCategoryテーブル
public class SubCategory
{
    public int Id { get; set; }

    public int? ClassId { get; set; }

    public string Name { get; set; }

    public virtual Category Category { get; set; }
}

 ちょっとどこが変わったか分かりにくいですが、SubCategoryのClassIdの型をNull許容型に変更しています。こうすることでリレーションを保ったまま連鎖削除を切ることができます。

 ちなみにこれらの設定はMigrationのForeignKeyをいじることでも行うことができます。

2012/11/12

【雑記】絶賛放置中

いやぁ、まじめに仕事するってすばらしい…(涙)

って感じでメモすることもなくブログを絶賛放置中です。

どうでもよいことですMacTypeなるフリーのフォントレンダリングソフトを入れてみました。

何かと競合しちゃったりしたら怖いけどとりあえずフォントが綺麗になったのには感動しました。

…ただそれだけです。まじめに働いてねーじゃねーかという気もしますが、これからまじめモードに…。


レンダーが効いてる図

2012/10/26

【雑記】本日の箇条書き

  • 相変わらずまじめに開発入っちゃってるからこれまた相変わらずネタがない。
  • jQueryが楽しいせいで本体が疎かに…。デザインは楽しいけど終わりないのが問題。やっぱりこーゆーのは分業したい。そして自分はデザイン側に…(飽きたら逆のことを言い出す気もするけど)。
  • でもプラグインはどこまで使うは悩む。頼りきると将来的に痛い目を見る気がする。かといってあるものをいちいち書くのか?って気もしてる。
  • とりあえず逆説的だけどシンプルなものは取り入れて、複雑なものは敬遠、どうしても必要なら書く方向で…。

2012/10/18

【ASP.Net】部分ビューの更新【MVC】

 久しぶりの更新です。

 唐突ですが、カテゴリーの配下にサブカテゴリーがある場合、入力時なんかはカテゴリーが選択された後はサブカテゴリーはそのカテゴリーの配下のものに絞られた方が便利ですよね。Winフォームとかだったらカテゴリー決定後にイベント発生させてってできるわけなんですがWebになるとどうすりゃいいいのって話になるわけです。
 というわけでとりあえずモデルを提示しときます。

カテゴリーモデル
public class Category
{
    public int Id { get; set; }
 
    public string Name { get; set; }
 
    public virtual List<SubCategory> SubCategories { get; set; }
 
}

サブカテゴリーモデル
public class SubCategory
{
 
    public int Id { get; set; }
 
    public int CategoryId { get; set; }
 
    public string Name { get; set; }
    
    public virtual Category Category { get; set; }
 
    public virtual List<Article> Articles { get; set; }
}

商品モデル
public class Article
{
 
    public int Id { get; set; }
 
    public string Name { get; set; }
 
    public int SubCategoryId { get; set; }
 
    public virtual SubCategory SubCategory { get; set; }
 
}

 イメージとしては商品入力時、カテゴリードロップダウンリストを決定したら、サブカテゴリードロップダウンリストの部分のみを更新って感じでしょうか。1部分のみの更新といえば部分ビューの出番ですね。

 さっさと結果から言っちゃうとできたことはできたんですが、カテゴリーをAJAXヘルパーを使うため商品モデルの拡張とビュー側でドロップダウンリストが使えないという問題が…。以下、サンプルコードです。

商品モデル拡張
public class ArticleEdit
{
    public Article Article { get; set; }
 
    public List<Category> Categories { get; set; }
}

コントローラー(一部抜粋)
public ActionResult Create()
{
    var model = new ArticleEdit
    {
        Categories = db.Categories.ToList()
    };
 
    ViewBag.SubCategoryId = new SelectList(db.SubCategories, "Id", "Name");
    return View(model);
}
 
[HttpPost]
public ActionResult Create(ArticleEdit model)
{
    if (ModelState.IsValid)
    {
        db.Articles.Add(model.Article);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
 
    ViewBag.SubCategoryId = new SelectList(db.SubCategories, "Id", "Name", model.Article.SubCategoryId);
    return View(model);
}

//これが部分ビューのアクション 
public ActionResult SubCategoryDropDownList(int categoryId = 1)
{
    ViewBag.SubCategoryId = new SelectList(db.SubCategories.Where(p => p.CategoryId == categoryId), "Id", "Name");
    return View();
}

Createビュー(フォーム部分のみ)
@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
 
    <fieldset>
        <legend>Article</legend>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Article.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Article.Name)
            @Html.ValidationMessageFor(model => model.Article.Name)
        </div>
 
        <div class="editor-label">
            カテゴリー
        </div>
        
        <div class="editor-field">
            <ul>
                @foreach (var category in Model.Categories)
                {
                    <li>
                        @Ajax.ActionLink(category.Name, "SubCategoryDropDownList", new { categoryId = category.Id}, new AjaxOptions{UpdateTargetId="subcategoryarea"})
                    </li>
                }
            </ul>
        </div>
        
        <div class="editor-label">
            サブカテゴリー
        </div>
        
        <div id="subcategoryarea" class="editor-field">
            @Html.Partial("SubCategoryDropDownList")
            @Html.ValidationMessageFor(model=>model.Article.SubCategoryId)
        </div>
        
 
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

SubCategoryDropDownListビュー
<div class="editor-field">
    @Html.DropDownList("Article.SubCategoryId", ViewBag.SubCategoryId as SelectList)
</div>

 何とも分かりにくいサンプルになってしまいましたが、ポイントはCreateビューの「@AJAX.ActionLink()」メソッドと「@Html.Partial()」メソッドです。初回呼出時にはPartialメソッドによって部分ビュー(SubCategoryDropDownList)が呼び出され、カテゴリーのリンククリック時にはActionLinkメソッドで部分ビューが更新されます。ActionLinkメソッドでは動的なパラメータは使えませんと怒られたのでカテゴリーをわざわざViewBagではなくてモデルに配置しています。カテゴリーはとりあえず<li>要素で表してますが、jQueryのプラグインでドロップダウンに見せることもできるでしょう(適当)。

実行結果

20121018012012101802

 以上、久しぶりの更新なのにかなり駆け足でしたが、今日はここまで。…正直、ここまで試行錯誤しすぎて疲れました。

2012/10/12

【ASP.Net】PartialViewをjQueryでお手軽モーダル表示…に失敗【MVC】

 なんだかあと一歩な感じなんでメモっとく。

 何がしたいかというとリストから新規作成を選択したらページ遷移せずにモーダルウィンドウで表示してみたいんです。で、色々考えてみた結果、PartialViewとjQueryのjqModalプラグインを使えばできるんじゃねーかという結論にいたったので実験したわけです。結果的に失敗しているので今回もちゃっちゃっとサンプルを載せます。

 jqModalプラグインについてはこちら→jqModal :: Minimalistic Modaling for jQuery
 
 モデル等については例によって使い回しです。

Userモデル
public class User:IValidatableObject
{
    [Required]
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    public int CategoryId { get; set; } 
        
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if(10 < this.Name.Length)
        {
            yield return new ValidationResult("サーバーサイドで引っかかりました", new[] { "Name" });
        }
    }
}

 検証確認のため、適当な検証をくっつけてます。

Userコントローラー
public class UserController : Controller
{
    public ActionResult Index()
    {
        var categoris = new List<Category> { 
            new Category { Id = 1, Name = "A" }, 
            new Category { Id = 2, Name = "B" } 
        };
 
        var useres = new List<User> { 
            new User { Id = 1, CategoryId = 1, Name = "Tanaka" }, 
            new User { Id = 2, CategoryId = 2, Name = "Sato" } 
        };
 
        return View(useres);
    }
 
    public ActionResult Create()
    {
        return PartialView();
    }
 
    [HttpPost]
    public ActionResult Create(User target)
    {
        if (ModelState.IsValid)
        {
            //追加処理は省略
 
            return RedirectToAction("Index");
        }
 
        return PartialView(target);
    }
}

 これまたいい加減ですが、Create実行時に目的であるPartialViewで表示するように設定してます。

Indexビュー
@model IEnumerable<MvcTestProject01.Models.User>
 
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
@section styles{
    <link href="~/Content/jqModal.css" rel="stylesheet" type="text/css" />
}
 
@section scripts{
    <script src="~/Scripts/jqModal.js" type="text/javascript"></script>
    <script type="text/javascript">
 
        function createShow() {
            $('#jqmdialog').jqm({
                modal: true,
                ajax: '/User/Create'
            });
            $('#jqmdialog').jqmShow();
        }
 
    </script>
}
 
<h2>Index</h2>
 
<div id="jqmdialog" class="jqmWindow"></div>
 
<p>
    <a href="#" onclick="createShow()">Create New</a>
</p>
<table>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.CategoryId)
        </th>
        <th></th>
    </tr>
 
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.CategoryId)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
            @Html.ActionLink("Details", "Details", new { id=item.Id }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.Id })
        </td>
    </tr>
}
 
</table>

 今回の肝であるjqModalプラグインを設定しています。流れとしては「リンククリックイベント(createShow())→ダイアログの定義→ダイアログの表示」って感じです。

Createビュー
@model MvcTestProject01.Models.User
 
<script src="~/Scripts/jquery-1.7.1.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
 
@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
 
    <fieldset>
        <legend>User</legend>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.CategoryId)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.CategoryId)
            @Html.ValidationMessageFor(model => model.CategoryId)
        </div>
 
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}
 
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

 こっちは何の変哲もないビューです。使ってみて初めて気がつきましたが、どうやら部分ビューは部分ビューでjquery等を定義してないと動かない模様です。最初これ飛ばしててクライアント検証が発生しなくてかなり困りました。

実行結果(Index表示時)

image

実行結果(Createクリック時)

image

 おぉー、出来た出来た!…と思いきや。

実行結果(サーバーサイド検証時)

image

 ちょっと分かりづらいんですがサーバーサイドで検証に引っかかってもう一度PartialView()が実行されるとモーダル表示が解けるどころかページ遷移が発生しちゃってるんですよ。アドレスも思いっきり「User/Create」って出ちゃってます。

 …そして今に至るわけです。ちょっと手の込んだことしないと解決できなそうな気がする。何とかできたら追記するかも?

2012/10/09

【雑記】久しぶりに本日の箇条書き

  • 最近まじめにコード書いてるのでネタが無い。
  • CodeFirstのSeedメソッドで詰む。パッケージマネージャーコンソールが「System.Data.Entity.Validation.DbEntityValidationException: 1 つ以上のエンティティで検証が失敗しました。詳細については 'EntityValidationErrors' プロパティを参照してください。」と仰る。モデルに付けた検証が引っかかってるみたいだけどコンソールじゃプロパティ見れないよって話で…。仕方が無いのでコードから手動でSeedメソッド呼び出した。
  • ディスプレイを1台LEDのフルHDにしてから目がチカチカしてたのに限界を感じ、今まで面倒で避けてたけどNVIDIAのコントロールパネルで微調整してみた。ついで古い方の黄ばみ消しにも挑戦してみた。新しい方は明るさ・コントラスト・ガンマをがっつり下げてこれってほんとにLED?(笑)みたいな暗さにしてやった。古い方の黄ばみは青色強くしたりしてみたけど、やっぱり黄色い、これはもう修正不可能なレベルなんだなぁと思い知った。ついでに開発環境も配色テーマを濃色に変えて暗くしてやった。





HP Directplus オンラインストア

2012/10/06

【C#】列挙型での日本語の取り扱い…ついてでにMVCも【MVC】

 列挙型で日本語っつーか全角文字列の扱いってどうすりゃいんだ?っての話の始まりです。どういうことかというと下記のコードを見てもらった方が早いでしょう。

列挙型A
public enum Positions
{
    Start,
    Middle,
    End
}

列挙型B
public enum Positions
{
    最初,
    間,
    最後
}

 どっちでいいだろ?って言われたらお終いなんですが、今まで半角英数で統一してんのに列挙型だけ例外とするのも気持ちが悪いというか…。当然、開発上のルールとしてメンバは必ず半角英数でって話だってあると思うのでとりあえず何とかしたいと思ったわけです(そんなものとは無縁の世界で生きてはいますが…)。

 一番簡単な案としては列挙型をやめちゃってDictionaryなりListなりのユーザー定義型でやっちゃえばいいんですが、それだと根本的な趣旨から外れちゃうんで却下。で、色々と調べたところカスタム属性を使う方法等もあったんですが今回は拡張メソッドで何とかなりそうだぞってことが分かったのでこれでやってみます。(参考にさせていただいた記事:当面C#と.NETな記録)(ついでにMSDN:方法 : 列挙型対応の新しいメソッドを作成する

列挙型Aに拡張メソッドを実装
public enum Positions
{
    Start,
    Middle,
    End
}
 
public static class PositonExtensions
{
    public static string GetDisplayName(this Positions position)
    {
        switch (position)
        {
            case Positions.Start:
                return "最初";
            case Positions.Middle:
                return "間";
            case Positions.End:
                return "最後";
            default:
                return string.Empty;
        }
    }
}

 コード自体はシンプルです。さて、これで今日は終了としても良いですが、このままじゃ中身のないマルパク記事じゃねーかって話になっちゃうので使い回しでこの間作ったサンプルにこの列挙型を追加して、MVCで表示してみます。

Userモデルを修正
public class User
{
    //・・・中略

    public Positions Position { get; set; }
}

コントローラーを修正
public ActionResult Edit()
{
    //・・・中略

    //SelectListで使えるようLinqを使って定義してみた
    var divisons = from p in (Positions[])Enum.GetValues(typeof(Positions))
                    select new
                    {
                        Value = p,
                        Display = p.GetDisplayName()
                    };
 
    ViewBag.Positions = new SelectList(divisons, "Value", "Display", user.Position);
 
    return View(user);
}

ビューを修正(一部抜粋)
<div class="editor-label">
    @Html.LabelFor(model => model.Position)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Position, ViewBag.Positions as SelectList)
    @Html.ValidationMessageFor(model => model.Position)
</div>

実行結果

image

 駆け足ですがこんな感じになります。コントローラーでのViewBagのSelectListに値と表示を定義するためにLINQを使った以外はほとんど過去記事と変わりません。

 では今日はここまで。







ビックカメラ.com

2012/10/03

【ASP.Net】ViewBagを利用してDropDownList【MVC】

 最近こればっかだけどまたまたMVCネタです。

 以前の記事でDropDownListはモデル側にListを用意してやらんといかんと書きましたが、ViewBagを利用することで簡単に作成することができることが分かっちゃったので…。

 大した内容じゃないのでちゃちゃっとサンプルのみ書きます。

Userモデル
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CategoryId { get; set; }
}

Categoryモデル
public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

コントローラー(Editのみ)
public ActionResult Edit()
{
    //今回は横着します
    var user = new User { Id = 1, CategoryId = 1, Name = "Tanaka" };
    var categoris = new List<Category> { new Category { Id = 1, Name = "A" }, new Category { Id = 2, Name = "B" } };
 
    //ViewBag定義
    ViewBag.Categories = new SelectList(categoris, "Id", "Name", user.CategoryId);
 
    return View(user);
}

ビュー
@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
 
    <fieldset>
        <legend>User</legend>
 
        @Html.HiddenFor(model => model.Id)
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.CategoryId)
        </div>
        <div class="editor-field">
            @Html.DropDownListFor(model => model.CategoryId, ViewBag.Categories as SelectList)
            @Html.ValidationMessageFor(model => model.CategoryId)
        </div>
 
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

 ViewBagっていまいち分かってなかったけど便利なんですね。でも何でもかんでも突っ込むと碌なことになさそうな気もするなぁ。

 今日はこんだけ。






HP Directplus オンラインストア

2012/10/02

【ASP.Net】個人的な検証機能のおさらい クライアントサイド編【MVC】

 サーバーサイド編に続き、今回はクライアント編です。

 ひとえにクライアント編とは言ってもクライアント側であればスクリプト次第で色々と方法はあるとは思いますが、今回はjQueryのValidateプラグインを使ってみます。
 まずは基本的な使い方から。

Editビューへの追加(一部抜粋)
<script src="~/Scripts/jquery-1.8.2.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script type="text/javascript">
    $(function () {
        $('form').validate({
            rules: {
                UserName: { maxlength: 50 },
                Birthday: { date: true },
                Email: {
                    required: true,
                    email: true
                },
                CompareEmail: {
                    required: true,
                    equalTo: '#Email'
                },
                Tell: { number: true }
            },
            messages: {
                UserName: 'UserNameは最大50字までです' ,
                Birthday: 'Birthdayが不正です',
                Email: {
                    required: 'Emailは必須項目です',
                    email: 'Emailが不正です'
                },
                CompareEmail: {
                    required: 'Emailは必須項目です',
                    equalTo: '入力されたEmailと異なっています'
                },
                Tell: 'Tellが不正です'
            }
        })
    });
</script>

 マスターページやレイアウトページがあるならjQuery本体やValidateプラグインはそっちに配置してしまえばOKです。検証の設定は見ての通りって感じですが、一応解説すると「$(’form’).validate(…)」でこのページのすべてのformを検証の対象としています。「rules:」プロパティは各name属性に検証のルールを定義しています。「messages:…」は引っかかった際のメッセージを定義しています。プロパティ等の詳細についてはこちら(Plugins/Validation - jQuery Wiki)を確認してください。

実行結果

image

 やっぱりプラグインは便利ですね~。でも、これじゃサーバーサイドの定義とクライアントサイドの定義で2度手間じゃねーの?って感じちゃいます。そこでMS製のValidateプラグインの派生プラグイン(?)「jquery.validate.unobtrusive」を利用します。これはNuGetで取得できます。これを読み込むだけでサーバーサイドで定義した検証をクライアントサイドで動作させることができます。

Editビューへの追加(一部抜粋)
<script src="~/Scripts/jquery-1.8.2.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>
<!-- とりあえず以下はコメントアウトしときます
<script type="text/javascript">
    $(function () {
        $('form').validate({
            rules: {
                UserName: { maxlength: 50 },
                Birthday: { date: true },
                Email: {
                    required: true,
                    email: true
                },
                CompareEmail: {
                    required: true,
                    equalTo: '#Email'
                },
                Tell: { number: true }
            },
            messages: {
                UserName: 'UserNameは最大50字までです',
                Birthday: 'Birthdayが不正です',
                Email: {
                    required: 'Emailは必須項目です',
                    email: 'Emailが不正です'
                },
                CompareEmail: {
                    required: 'Emailは必須項目です',
                    equalTo: '入力されたEmailと異なっています'
                },
                Tell: 'Tellが不正です'
            }
        })
    });
</script>
-->


 ほんとに読み込んだだけですが、実行してみましょう。

実行結果(Post発生前)

image

 見ての通り、Post発生前でありながら前回のPost発生後と同じ画面になっています。たったこれだけで、前半部分が無駄になっちゃいました。まぁValidateプラグインの知識のひとつということにしときます。

 さて、次は独自の検証機能を持った属性を用意してサーバーサイドとクライアントサイドで連携してみたいと思います。前回紹介しなかった「System.ComponentModel.DataAnnotations.ValidationAttribute」クラスの出番です。これと「System.Web.Mvc.IClientValidatable」インターフェースを組み合わせて利用します。早速、独自検証クラスを作ります。検証内容は「Birthdayが18歳以上か?」で考えてみます。

独自検証クラス
public class UserMinorAttribute : ValidationAttribute, IClientValidatable
{
    //エラーメッセージの初期化
    public UserMinorAttribute()
    {
        this.ErrorMessage = "18未満の方は登録できません。(サーバーサイド)";
    }
 
    //検証
    public override bool IsValid(object value)
    {
        var birthday = value as DateTime?;
 
        if (birthday != null && birthday <= DateTime.Now.AddYears(-18))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
 
    //クライアントサイド用定義
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
            {
                ErrorMessage = "18未満の方は登録できません。(クライアントサイド)",
                ValidationType = "userminor"
            };
 
        yield return rule;
    }
 
}

 インターフェースのメンバである「GetClientValidationRulesメソッド」って分かりづらい感ばりばりですが、今回は「userminor」というクライアントで使用する検証の名前とエラーメッセージを定義してるだけです。検証に別のパラメータ等を使用する場合はここで定義することができます(今回はややっこしくなるのでやめました)。次にモデルにこの属性を定義します。

Userモデルへの追加
public class User : IValidatableObject
{
 
    public int UserId { get; set; }
 
    [StringLength(50)]
    public string UserName { get; set; }
 
    [Column("Gender", TypeName = "int")]
    [EnumDataType(typeof(Genders))]
    public Genders Gender { get; set; }
 
    [UserMinor]
    public DateTime Birthday { get; set; }
 
    [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "アドレスが不正です。")]
    public string Email { get; set; }
 
    [Compare("Email", ErrorMessage = "入力されたアドレスと異なっています。")]
    public string CompareEmail { get; set; }
 
    [RegularExpression(@"[0-9]*$", ErrorMessage = "半角数値のみで入力して下さい。")]
    public string Tell { get; set; }
 
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        using (var context = new MyContext())
        {
            var overlap = context.Users.Where((p) => p.UserName == this.UserName).FirstOrDefault();
 
            if(overlap !=null)
            {
                yield return new ValidationResult("そのユーザー名は既に使われています。", new[] { "UserName" });
            }
        }
    }
}

 こっちは「Birthday」に「UserMinor」属性をつけただけでお終いです。ちなみにこのままで実行すると他の属性とは異なり、サーバーサイドでの検証は発生しますが、クライアントサイドでの検証は発生しません。一応、実行結果載せます。

実行結果(Post発生後)

image

 これをクライアントサイドでも検証が発生するようにスクリプトを定義します。これに関しては、はっきり言って2度手間です。

Editビューへの追加(一部抜粋)
<script type="text/javascript">
    $.validator.addMethod('userminor', function (value, element, params) {
        var today = new Date();
        var overYear = today.getFullYear() - 18;
        var overBirthday = new Date(overYear, today.getMonth(), today.getDay());
 
        if (!value) {
            return true;
        } else {
            if (overBirthday < Date.parse(value)) {
                return false;
            } else {
                return true;
            }
        }
    });
 
    $.validator.unobtrusive.adapters.addBool('userminor');
</script>

 サーバーサイドでの検証はValidateプラグインの「addMethod」関数とUnobtrusiveプラグインの「addBool」関数を利用します。前者で検証内容を定義し、後者で検証のパラメータ等を登録します。今回はパラメータがないので「addBool」関数を使用していますが、パラメータがある場合、別の関数を利用したり、独自に定義したりすることができます。以下、実行結果です。

実行結果(Post発生前)

image

 う~ん、何かここまですると面倒くさいから独自検証に関してはサーバーサイドだけでいいんじゃ?って気もしないでもないですね。まぁ、無駄なトラフィックを発生させたくなければって感じでしょうか?

 というわけで今回はここまで。







デル株式会社

2012/09/28

【ASP.Net】個人的な検証機能のおさらい サーバーサイド編【MVC】

 今回は個人的にMVCの検証機能をちょっと復習したいなと思いまして…。長ったらしくなりそうなのでサーバーサイド編とクライアントサイド編で分けて載せます。

 というわけで今回はサーバーサイド編です。サーバーサイドでの基本的な検証はモデルに検証用の属性をつけることで実装します。独自に定義したい場合は「ValidationAttribute」 を継承した属性を作成するか、モデルに「IValidatableObject」インターフェースを実装します。

 とりあえず基本的な属性付けから。前回使用したUserモデルを流用します。…といか元々はこれように作ったモデルだったりする。

Userモデル
public class User
{
 
    public int UserId { get; set; }
 
    public string UserName { get; set; }
 
    [Column("Gender", TypeName = "int")]
    public Genders Gender { get; set; }
 
    public DateTime Birthday { get; set; }
 
    public string Email { get; set; }
 
    public string CompareEmail { get; set; }
 
    public string Tell { get; set; }
        
}

 これに検証を設ける場合、ぱっと見思いつくのは…


  • UserNameの文字列数
  • Emailの正規表現
  • CompareEmailのEmailとの差異
  • Tellの正規表現

 ってとこでしょうか?

Userモデル(検証属性追加)
public class User
{
 
    public int UserId { get; set; }
 
    [StringLength(50)]
    public string UserName { get; set; }
 
    [Column("Gender", TypeName = "int")]
    [EnumDataType(typeof(Genders))]
    public Genders Gender { get; set; }
 
    public DateTime Birthday { get; set; }
 
    [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "アドレスが不正です。")]
    public string Email { get; set; }
 
    [Compare("Email", ErrorMessage = "入力されたアドレスと異なっています。")]
    public string CompareEmail { get; set; }
 
    [RegularExpression(@"[0-9]*$", ErrorMessage = "半角数値のみで入力して下さい。")]
    public string Tell { get; set; }
        
}

 こんな感じになります。各属性の詳細はこちら(System.ComponentModel.DataAnnotations 名前空間)。んで下図が全部引っかかってみた例です。

Createビュー実行結果(Post発生後)

image

 当然ながらサーバーサイドなのでPost発生後に検証されます。実にシンプルで簡単です。次に「ValidationAttribute」は置いておいて「IValidatableObject」を使ってみます。検証内容は「入力されたUserNameが既にデータベースに登録されていないか?」です。

IValidatableObject実装
public class User : IValidatableObject
{
 
    //・・・中略
 
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        using (var context = new MyContext())
        {
            var overlap = context.Users.Where((p) => p.UserName == this.UserName).FirstOrDefault();
 
            if(overlap !=null)
            {
                yield return new ValidationResult("そのユーザー名は既に使われています。", new[] { "UserName" });
            }
        }
    }
}

 中身は見ての通りです。この検証は必ず実行され、下図の通りに検証されます。

Createビュー実行結果(Post発生後)

image

 これまた期待通りに動作しています。このインターフェースを使えば異なるメンバの比較等の検証を行うことも可能ですね。

 さて、本日はこの辺でお仕舞いで、ValidationAttributeに関してはサーバーサイドでの検証にも利用するのでサーバーサイド編で…。






いきなりPDF Ver.2

2012/09/27

【ASP.Net】ビューで列挙型のドロップダウンリストを表示【MVC】

 EntityFrameworkで列挙型もサポートされることだし、モデルに列挙型使ってドロップダウンリストで表示してみようぜってな話です。早速、モデルと列挙型を用意します。

モデル:User
public class User
{
 
    public int UserId { get; set; }
 
    public string UserName { get; set; }
 
    [Column("Gender", TypeName = "int")]
    public Genders Gender { get; set; }
 
    public DateTime Birthday { get; set; }
 
    public string Email { get; set; }
 
    public string CompareEmail { get; set; }
 
    public string Tell { get; set; }
        
}


列挙型:Genders
public enum Genders
{
    Male = 1,
    Female
}

 Gendersって何やねんって感じですが、分かりやすくするため無理矢理複数形にしました。EntityFramework対応でモデルの方にColumn属性をつけてます。これでアプリ側ではGenders列挙型、データベース側ではint型で扱われるわけです。
 ではモデルを元にコントローラーとビューを作ります。今回は0からではなく「EntityFrameworkを使用した…」テンプレートを使ってちゃっちゃっと作ってしまいましょう(この辺の課程は省略)。ウィザードによってIndexビューやEditビューができあがると思います。ここでやってみて分かったんですがモデルに列挙型を使っているとそのメンバだけテンプレートから弾かれちゃうみたいです。

実際のEditビューの実行結果

image

 

 見ての通りGenderの項目がありません。まぁこれだけの話だったらHtmlヘルパーの「EditorFor()」とか使ってテキストボックスを自力で用意すればいい話なんですが、せっかく列挙型使ってんだからドロップダウンリストにしたらどうなのって思ってしまったわけです。

 ここでやっと本題です。ドロップダウンリスト自体はHtmlヘルパーの「DropdownListFor()」で作れるんですが、どうやって列挙型の値をデータソースして渡すことができるのかってのが問題になってきます。ユーザー定義型つくってList化して渡すってのも出来なくもないですが、ちょっと回りくどいので今回は却下します。まぁ、長々書いても仕方ないので答えから書いちゃいますが以下をビューに追加します。

ビュー(Edit)に追加
<div class="editor-label">
    @Html.LabelFor(model => model.Gender)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Gender, new SelectList(Enum.GetValues(Model.Gender.GetType())))
</div>

 簡単に解説すると「Enumクラス」の「GetValues」メソッドで列挙型の値を取得し、リストを渡してます。

追加後の実行結果

image

 ばっちり、列挙型でドロップダウンリストが出来ました。でもこれで調子に乗ってCreateビューも同じようにやってみると…。

Createビューの実行結果

image

 NullだよNullってわけです。モデルがインスタンス化されてない模様です。これの原因はコントローラー側も見れば一目瞭然です。

Createメソッド
public ActionResult Create()
{
    return View();
}

 うーん、まさに何もしてないわけです。ここで頑張ってモデル側がNullでもどうにかしちゃう拡張ヘルパーを作ってもいいかもしれませんが、ここは手っ取り早くCreateメソッドの方に手を加えて解決にしちゃいます。

Createメソッドの修正
public ActionResult Create()
{
    var model = new User();
 
    return View(model);
}

 当たり前ですがこれでモデルはインスタンス化されビューに渡されます。一応、実行結果を貼っておきます。

修正後の実行結果

image

 以上で、今回は終了です。列挙型についてはもっと応用が効きそうな気がしますが、まだまだ実力不足でアイデアが…。






サンワダイレクト リングマウス

2012/09/26

【ASP.Net】MVC4で何か作ってみる その6(最終回)【MVC】

 前回の続きです。

 前回はjqueryのDataTablesプラグインで簡単に並べ替えとフィルターを実装することができました。今回はリストをドラッグアンドドロップで並べ替えしてみたいと思います。

 いやぁー、ぶっちゃけ今回が一番苦労しました。ドラッグアンドドロップで並べ替えたまでは良かったんですが、その後のデータベースへの保存にかなり手こずりました。正直、諦めようかと思ったくらいでしたが、何とか出来てよかったです。

 では早速、最初のモデル作成とコントローラー作成は今まで同じなのでちゃちゃっとやっちゃいます。

社員順序モデル
public class EmployeeSort
{
 
    /// <summary>
    /// 社員ID
    /// </summary>
    public int Id { get; set; }
 
    /// <summary>
    /// ソート順
    /// </summary>
    public int SortNumber { get; set; }
 
    /// <summary>
    /// 社員名
    /// </summary>
    public string EmployeeName { get; set; }
 
}

Indexメソッド(コントローラー)
public ActionResult Index()
{
    var resultList = new List<EmployeeSort>();
 
    using (var context = new MvcTestContext())
    {
        var items = from p in context.Employees
                    orderby p.SortNumber
                    select p;
 
        foreach (var item in items)
        {
            resultList.Add(new EmployeeSort
                {
                    Id= item.EmployeeId,
                    SortNumber = (int)item.SortNumber,
                    EmployeeName = item.EmployeeName
                });
        }
    }
 
    return View(resultList);
}

 ここまでは特に問題ないでしょう。次にビューですが、今回はリスト上でのドラッグアンドドロップで並べ替えを実現するためにjqueryUIの「Sortableプラグイン」を利用します。細かな使い方はこちらを見てもらうとして、ビューで使用する準備をしましょう。

Sortableプラグイン使用準備(ビューに追加)
<script src="~/Scripts/jquery-ui-1.8.23.min.js"></script>
<link href="~/Content/themes/smoothness/jquery-ui-1.8.23.custom.css" rel="stylesheet" />
 
<script type="text/javascript">
    $(function () {
        $('#sortable').sortable();
 
    });
</script>

 ついでだったんでthemeも突っ込んどきました。これで<li>タグをドラッグアンドドロップで並べ替えできます。後は並べ替えた後、どう保存するかってとこなんですが、とりあえず単純に保存ボタンを押したら並べ替えた順に社員テーブルのIdを受け取ってテーブルを更新しちゃえばよいかと考えました。というわけで一端ビューは置いといて、Post時のIndexメソッドです。

Post時のIndexメソッド
[HttpPost]
public ActionResult Index(int[] id)
{
    using (var context = new MvcTestContext())
    {
        for (var i = 0; i < id.Length; i++)
        {
            var overlap = context.Employees.Find(id[i]);
 
            if (overlap != null)
            {
                overlap.SortNumber = i + 1;
            }
        }
 
        context.SaveChanges();
 
        return RedirectToAction("Index");
    }
}

 コードはいたってシンプルです。これで行けるはず。…とこの時点では余裕で考えてて、何気なしにビューの続きも作成しました。

ビュー全景
@model IEnumerable<MVCTestProject.Models.EmployeeSort>
 
@{
    ViewBag.Title = "Sort";
    Layout = "~/Views/Shared/_LayoutPage.cshtml";
}
 
<script src="~/Scripts/jquery-ui-1.8.23.min.js"></script>
<link href="~/Content/themes/smoothness/jquery-ui-1.8.23.custom.css" rel="stylesheet" />
 
<script type="text/javascript">
    $(function () {
        $('#sortable').sortable();
 
    });
</script>
 
<h2>並べ替え</h2>
 
<ul id="sortable">
@foreach (var item in Model)
{
    <li class="ui-state-default" style="padding: 5px; width: 200px">
        @Html.HiddenFor(model => item.Id)
        @Html.DisplayFor(model => item.SortNumber)&nbsp;:&nbsp;@Html.DisplayFor(model=>item.EmployeeName)
    </li>
}
</ul>
 
@using (Html.BeginForm()) {
        <input type="submit" value="保存" />
}

 見ての通りモデルをリスト化し、最後の<input>タグでPostが発生するようにしています。これで「おぉー、出来た出来た」と早速、実行してみました。

実行結果(並び替え)
image

 ちょっと地味ですがプラグインは正常に動作し、ドラッグアンドドロップで並べ替えが効いています。次は保存ボタンをクリックしてみます。

実行結果(保存)
image

 はい、こけました。お久しぶりの「NullReferenceException」さんです。引数の「id」がNullだとおっしゃってます。どうやらビューからコントローラーに引数が渡ってない模様。…ここから長い旅路が始まりました。

 何故でしょう?「配列は渡せません」なんてことはないはずなんですが…。基本的にMVCではコントローラに引数を渡す際に、自動的に引数と同名のデータを収集・変換してくれてるはずです。それが渡ってないってことは収集されてないのか?ってな具合に試行錯誤しました。まぁ、1から試したこと書いてたらキリが無いので、途中経過はすっとばしますが、1つ目の間違いは「Html.BeginForm()」の位置でした。この辺、今までPHPとか全然構ってない人間なのでピンときてませんでしたが、対象となるデータが<form>タグに入ってなかったら送れないみたいです。ってこう書くと当たり前じゃねーかって気もしますね…。では修正です。

ビューを修正(部分抜粋)
@using (Html.BeginForm()) {
<ul id="sortable">
@foreach (var item in Model)
{
    <li class="ui-state-default" style="padding: 5px; width: 200px">
        @Html.HiddenFor(model => item.Id)
        @Html.DisplayFor(model => item.SortNumber)&nbsp;:&nbsp;@Html.DisplayFor(model=>item.EmployeeName)
    </li>
}
</ul>
 
    <input type="submit" value="保存" />
}

 「Html.BigenForm()」を<ul>タグの前に持ってきました。これでどうだって感じで実行してみます。

実行結果
image

 …駄目でした。でも「Html.BeginForm()」位置の修正に関しては間違ってないはずなのでこのままにしときます。それにしても何で「id」が収集されんのだろうか…とPage Inspectorの<li>タグをじっくり見直してみました。

Page Inspector(部分抜粋)
<li class="ui-state-default" style="padding: 5px; width: 200px;">
<input name="item.Id" id="item_Id" type="hidden" data-val-required="Id フィールドが必要です。" data-val-number="フィールド Id には数字を指定してください。" data-val="true" value="83"></input>
</input>
        1 : 0子
</li>

 「!!」っと、ここでようやく気がつきました。…今まで自分は何を見てたんでしょう。<input>タグのname属性にご注目。「name=”item.Id”」となってるんですよ。上にも書きましたがMVCはコントローラーの引数名でデータを収集するってことは「item.Id ≠ id」ってなわけで…。自分、name属性なんて指定せんでも勝手にやってくれるだろうと思ってたんです(ビュー側のモデルがIEnumerableやIListでなく単体の場合はそのままでOKっぽいです)…。これに気がつくのに数時間…。

 何はともあれ原因っぽいのは分かったんで、ビューをまたもや修正します。

ビューの再修正(部分抜粋)
@using (Html.BeginForm()) {
<ul id="sortable">
@foreach (var item in Model)
{
    <li class="ui-state-default" style="padding: 5px; width: 200px">
        @Html.Hidden("id", item.Id)
        @Html.DisplayFor(model => item.SortNumber)&nbsp;:&nbsp;@Html.DisplayFor(model=>item.EmployeeName)
    </li>
}
</ul>
 
    <input type="submit" value="保存" />
}

 ちょっと違いが分かりにくいですが、「@Html.HiddenFor」から「@Html.Hidden」に変更し、「id」という名前をつけてます。最初はname属性の付け方が分からなくてまたもや時間を喰いました。「@Html.HIddenFor」の方、MSDN(InputExtensions.HiddenFor(TModel, TProperty) メソッド (HtmlHelper(TModel), Expression(Func(TModel, TProperty)), IDictionary(String, Object)) (System.Web.Mvc.Html))にはHtml属性を定義できるとありますが、自分はどうしてもname属性をつけることができませんでした(他の属性はできました)。

実行結果
image

 ちょっと分かりにくいですが、並べ替えを保存に成功した後です。結果自体は地味でしたがようやくドラッグアンドドロップで並べ替えを実装することができました。…わりとまじめにしんどかったです。

 今回で「MVC4で何か作ってみる」は終了です。作ってみた感想としてはやっぱりまだまだ関係資料が少ないなぁと感じました。MSDNもまだほとんどがMVC3のヘルプを見ろって感じです(ぶっちゃけ見ても分かりづらいですが)。それでも個人的にはRazorの方がASPXより好みだし、jqueryとの連携もやっぱり面白いです。今回は見た目の方はやっつけで済ましましたが、もっとリッチなインターフェースを作成できそうな気がします(jqueryの勉強も必要ですが…)。








デル株式会社

2012/09/25

【Asp.Net】MVC4で何か作ってみる その5【MVC】

 前回の続きです。

 前回は追加・更新・削除とメインとなる機能を作成しました。今回からは見た目の部分をやっていこうかと思います。まずは今後jqueryやcssを使うにあたってレイアウトページがあった方が定義をまとめたりしやすそうなのでレイアウトページを作成します。

 プロジェクトにレイアウトページを追加します。

レイアウトページの追加
 2012092301

 この名前の_(アンダーバー)って何かルール的なもんがあるんでしょうか?とりあえず試しにレイアウトにヘッダーとフッターを設け、ついでにCSSも適用してみます。

レイアウト
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <link href="~/Content/css/default.css" rel="stylesheet" type="text/css" />
    <title>@ViewBag.Title</title>
</head>
<body>
    <header id="header">
        <h1>MVCTestProject</h1>
    </header>
    <div id="main">
        @RenderBody()
    </div>
    <footer id="footer">
        <address>
            これはMVCTestProjectです
        </address>
    </footer>
</body>
</html>

default.css
#header h1 {
    text-align:center;
    background-color: #333;
    color: #FFF;
}

#footer address {
    text-align:center;
    background-color: #333;
    color: #FFF;
}

#main h2 {
    color: #00F;
}

 次にHomeのIndexビューにレイアウト適用してみます。

Indexビュー(Home)
@{
    ViewBag.Title = "Index";
    
    <!-- レイアウトの適用 下記を追加 -->
    Layout = "~/Views/Shared/_LayoutPage.cshtml";
}
 
<h2>Index</h2>

実行結果

2012092302

 レイアウト、CSSともに適用されてますね。それにしてもVS2012の「Page Inspector」は本当に便利です。結果やデザイン確認するのにいちいちブラウザ立ち上げてページ移動してってのが軽減されるのは大きいです。

 個人的に多少は満足出来たので次はいよいよ課題(その1を参照)にあげてたjqueryでテーブルをあれこれにチャレンジしてみます。まずはレイアウトからjqueryを呼び出せるようにします。

レイアウト
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <link href="~/Content/css/default.css" rel="stylesheet" type="text/css" />
    
    <!-- jqueryへの参照を追加 -->
    <script src="~/Scripts/jquery-1.8.1.min.js" type="text/javascript"></script>
    
    <title>@ViewBag.Title</title>
</head>
<body>
    <header id="header">
        <h1>MVCTestProject</h1>
    </header>
    <div id="main">
        @RenderBody()
    </div>
    <footer id="footer">
        <address>
            これはMVCTestProjectです
        </address>
    </footer>
</body>
</html>


 次はテーブル操作系のプラグインに頼ろうかな…といろいろ探してみたんですが、すごくちょうどいいのがありました。「DataTables (table plug-in for jQuery)」です。何か見た感じこれ1つでほとんど解決しちゃいそうです。

 公式からzipをダウンロードし、展開したフォルダ内で必要そうなのだけ抜き出して社員リストのIndexビューにスクリプトとともに設置します。抜き出したファイルは「jquery.dataTables.min.js」「demo_table_jui.css」です。よく分かんなかったり、抜き出すのが面倒な方はmediaというフォルダをまるっと持ってくればOKです。あと、jqueryUIとも連携可能のでjqueryUIのテーマも持ってきました。スクリプトではパラメータの部分で更新列と削除列の検索と並べ替えを禁止してます。

Indexビュー(EmployeeList)
@model IEnumerable<MVCTestProject.Models.EmployeeList>
 
@{
    ViewBag.Title = "社員リスト";
    Layout = "~/Views/Shared/_LayoutPage.cshtml";
}
 
<script src="~/Scripts/jquery.dataTables.min.js"></script>
<link href="~/Content/css/demo_table_jui.css" rel="stylesheet" />
<link href="~/Content/themes/smoothness/jquery-ui-1.8.23.custom.css" rel="stylesheet" />
 
<script type="text/javascript">
    $(function () {
        $('#employeelist').dataTable({
            "bJQueryUI": true,
            "sPaginationType": "full_numbers",
            "aoColumnDefs": [
                { "bSearchable": false, "bSortable": false, "aTargets": [2] },
                { "bSearchable": false, "bSortable": false, "aTargets": [3] }
            ]
        });
    });
</script>
 
<h2>社員リスト</h2>
 
<div>
    @Html.ActionLink("追加", "Update", -1)
</div>
 
<table id="employeelist" class="display">
    <thead>
        <tr>
            <th>
                社員名         
            </th>
            <th>
                配属先
            </th>
            <th>
                更新
            </th>
            <th>
                削除
            </th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model)
{
        <tr>
            <td>
                @Html.DisplayFor(modelitem => item.EmployeeName)
            </td>
            <td style="text-align: center;">
                @Html.DisplayFor(modelitem => item.AssignName)
            </td>
            <td style="text-align: center;">
                @Html.ActionLink("更新", "Update", new { id = item.Id })
            </td>
            <td style="text-align: center;">
                @Html.ActionLink("削除", "Delete", new { id = item.Id })
            </td>
        </tr>       
}
    </tbody>
</table>

 テーブルに<thead>タグと<tbody>タグも追加しています。どうやらこれがないとプラグインが動作しないみたいです。他のプラグインでも動かないケースがあるので覚えておいた方が良さそうです。これで準備OK。さっそく実行してみます。

実行結果

image

 おぉー、中々スマートになったじゃないですか?ちょっと英語の部分を日本語に変えたい気もしますが、それはまた今度で…。

 今回はこれまで。次回はドラッグアンドドロップで並べ替え編です。






HP Directplus オンラインストア

2012/09/22

【ASP.Net】MVC4で何か作ってみる その4【MVC】

 前回の続きです。

 前回は自作の社員リストモデル(EmployeeList)をビューで表示しました。今回は表示したデータを利用し、社員テーブルの追加・更新・削除の機能を実装してみましょう。

 まずは面倒な方からいきます。追加・更新です。これらは別々に作っても良いのですがページ自体が同じイメージになるので一緒にしちゃいます。処理の流れとしてはIndexビューのActionLinkからIdを受け取り、Idが「-1」等の存在しない値だったら新規用を、それ以外なら社員テーブルからデータを抽出して更新用を表示、保存時は既存Idだったら更新、なければ新規として扱うって感じでどうでしょう?

 あとせっかくなんで配属先IDの入力はドロップダウンリストにしてみようかと思います。…なんて簡単に考えてたんですが、実際やってみるとちょっとモデルをよく考えないといけなそうです。MVCでないASP.Net等だったら別にドロップダウンリストのソースだけ配属先テーブルから取ってきてバインドなりすりゃすんだわけなんですが、MVCはModelViewControllerなわけでモデルに無いものは扱えませんよっていうか扱っちゃいけませんよってのが基本ですよね。当然、配属先テーブルは社員テーブルには無いわけです。なので追加・更新用に新たに配属先テーブルを持つモデルを作成します。

 追記:別の方法もありました(ViewBagを追加ってDropDownListを作ってみる

社員編集用モデル
public class EmployeeUpdate
{
 
    /// <summary>
    /// 社員ID
    /// </summary>
    public int Id { get; set; }
 
    /// <summary>
    /// 社員名
    /// </summary>
    public string EmployeeName { get; set; }
 
    /// <summary>
    /// 配属先ID
    /// </summary>
    public int AssignId { get; set; }
 
    /// <summary>
    /// 配属先テーブルリスト
    /// </summary>
    public List<Assign> Assigns { get; set; }
 
}

 モデルは見ての通り、社員テーブルのデータと配属先テーブルのリストで作られています。それではこれを使ってコントローラーに表示用メソッドを追加します。

Updateメソッド
public ActionResult Update(int id = -1)
{
    using (var context = new MvcTestContext())
    {
        var overlap = context.Employees.Find(id);
 
        if (overlap == null)
        {
            var result = new EmployeeUpdate
                {
                    Assigns = context.Assigns.ToList()
                };
 
            return View(result);
        }
        else
        {
            var result = new EmployeeUpdate
                {
                    Id = overlap.EmployeeId,
                    EmployeeName = overlap.EmployeeName,
                    AssignId = overlap.AssignId,
                    Assigns = context.Assigns.ToList()
                };
 
            return View(result);
        }
    }
}

 いまいちスマートじゃないけど中身はシンプルなメソッドなので解説なしで、一端これを元にビューを作成しますか。

Updateビュー
@model MVCTestProject.Models.EmployeeUpdate
 
@{
    ViewBag.Title = "Update";
}
 
@if (Model.Id != 0)
{
    <h2>更新</h2>       
}else
{
    <h2>追加</h2>
}
 
@using (Html.BeginForm())
{
    <!-- IdはPost時に利用する必要があるので隠しときます -->
    @Html.HiddenFor(model => model.Id)
 
    <div class="editor-label">社員名</div>
    <div class="editor-field">
        @Html.EditorFor(model => model.EmployeeName)
    </div>
    <div class="editor-label">配属先</div>
    <div class="editor-field">
        @Html.DropDownListFor(model => model.AssignId, new SelectList(Model.Assigns,"AssignId", "AssignName"))
    </div>
        
    <p>
        <input type="submit" value="保存" />
    </p>
}
 
<div>
    @Html.ActionLink("戻る", "Index")
</div>

 ドロップダウンリストの書き方、資料がなさ過ぎて苦労しました。MSDN(SelectExtensions.DropDownListFor(TModel, TProperty) メソッド (HtmlHelper(TModel), Expression(Func(TModel, TProperty)), IEnumerable(SelectListItem)) (System.Web.Mvc.Html))分かりにくいです。「@Html.DropDownListFor(モデルの値, 使用するリスト(値フィールド, 表示フィールド))」って感じです。これで保存ボタンをクリックするとPostが発生するようになるので次はコントローラーに戻り、Post時のメソッドを書きます。

Post時のUpdateメソッド
[HttpPost]
public ActionResult Update(EmployeeUpdate employeeUpdate)
{
    using (var context = new MvcTestContext())
    {
        var overlap = context.Employees.Find(employeeUpdate.Id);
 
        if (overlap == null)
        {
            context.Employees.Add(new Employee
                {
                    EmployeeName = employeeUpdate.EmployeeName,
                    AssignId = employeeUpdate.AssignId,
                    SortNumber = (from p in context.Employees
                                    select p.SortNumber).Max() + 1
                });
        }
        else
        {
            overlap.EmployeeName = employeeUpdate.EmployeeName;
            overlap.AssignId = employeeUpdate.AssignId;
        }
 
        context.SaveChanges();
        return RedirectToAction("Index");
    }
}

 [HttpPost]を忘れないよう。これないとPost時に動いてくれません。あれPost時にモデルって渡したっけ?って感じですがどうやら自動的に渡してくれてる模様です。処理自体は最初に考えてたとおりに処理しています。IDの重複があれば更新、なければ追加です。んで最終的にIndexに移動します。深い意味はありませんがSortNumberフィールドは追加時は最大値を取るようにしてます。

 以下、実行結果です。

実行結果
追加

更新


 追加時、更新時でURLのパラメータの違うのが分かりますね。さて追加・更新ができたので次は削除です。ちょっと疲れてきましたが一気にやってしまいましょう。

社員削除用モデル
public class EmployeeDelete
{
 
    /// <summary>
    /// ID
    /// </summary>
    public int Id { get; set; }
 
    /// <summary>
    /// 社員名
    /// </summary>
    public string EmployeeName { get; set; }
 
    /// <summary>
    /// 配属先名
    /// </summary>
    public string AssignName { get; set; }
 
}

 Indexで使ったEmployeeListモデルと内容が同じになっちゃいましたが一応使い分けが効くように別にモデルを作りました。

Deleteメソッド
public ActionResult Delete(int id = -1)
{
    using (var context = new MvcTestContext())
    {
        var overlap = (from p in context.Employees
                        join g in context.Assigns
                        on p.AssignId equals g.AssignId into gg
                        from ggg in gg.DefaultIfEmpty()
                        where p.EmployeeId == id
                        select new
                        {
                            p.EmployeeId,
                            p.EmployeeName,
                            AssignName = (ggg != null ? ggg.AssignName : string.Empty)
                        }).FirstOrDefault();
 
        if (overlap == null)
        {
            return HttpNotFound();
        }
        else
        {
            var result = new EmployeeDelete
                {
                    Id = overlap.EmployeeId,
                    EmployeeName = overlap.EmployeeName,
                    AssignName = overlap.AssignName
                };
 
            return View(result);
        }
    }
}

 対象IDが見つからなかった場合、「HttpNotFund」を呼び出し、オブジェクトが見つからなかったぞと通知するようにしてます。

Deleteメソッド(Post時)
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
    using (var context = new MvcTestContext())
    {
        var overlap = context.Employees.Find(id);
        context.Employees.Remove(overlap);
        context.SaveChanges();
        return RedirectToAction("Index");
    }
}

 前述のDeleteメソッドとメソッド名とパラメータが被っちゃうので別名にし、ActionName属性でアクション(”Delete")を指定してます。

Deleteビュー
@model MVCTestProject.Models.EmployeeDelete
 
@{
    ViewBag.Title = "Delete";
}
 
<h2>このデータを削除してもよろしいですか?</h2>
 
<fieldset>
    <legend>社員データ</legend>
    <div class="display-label">社員名</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.EmployeeName)
    </div>
    <div class="display-label">配属先</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.AssignName)
    </div>
</fieldset>
@using (Html.BeginForm()) {
    <input type="submit" value="Delete" />
    @Html.ActionLink("戻る", "Index")
}

 これは特に言うことも無いでしょう。次に実行結果です。

実行結果

 あー、疲れた。とりあえずは思った通りにできたので今回はここまでにします。それにしても予想以上にシンプルな流れで作れるので素直にMVCに感動しました。






1616 / arita japan

2012/09/21

【ASP.Net】MVC4で何か作ってみる その3【MVC】

 前回の続きです。

 前回はCodeFirstでデータベースを作成し、テンプレートを使ってEntityFrameworkと連動したページを作成しました。今回はテンプレートを使わず、社員テーブルと配属先テーブルを結合したモデルを表示し、それを元にデータを編集するページを作成してみたいと思います。
 
 まずはモデルを定義します。
社員リスト
public class EmployeeList
{
 
    /// <summary>
    /// ID
    /// </summary>
    public int Id { get; set; }
 
    /// <summary>
    /// 社員名
    /// </summary>
    public string EmployeeName { get; set; }

  /// <summary>
    /// 配属先名
    /// </summary>
    public string AssignName { get; set; }

}

 次はコントローラーを作成します。モデルを指定しない空のコントローラーです。

空のコントローラーの作成

2012092101

 では早速、作成したクラスのIndexメソッドをいじくってみます。

Indexメソッド
public ActionResult Index()
{
    var resultList = new List<EmployeeList>();
            
    using(var context = new MvcTestContext())
    {
        var items = from p in context.Employees
                    join g in context.Assigns
                    on p.AssignId equals g.AssignId into gg 
                    from ggg in gg.DefaultIfEmpty()
                    orderby p.SortNumber ascending
                    select new
                    {
                        p.EmployeeId,
                        p.EmployeeName,
                        AssignName = (ggg != null ? ggg.AssignName:string.Empty)
                    };
        
        //どうでもいいけどParallel使うと順序が狂うよ!
    foreach (var item in items)
        {
            resultList.Add(new EmployeeList
            {
                Id = item.EmployeeId,
                EmployeeName = item.EmployeeName,
                AssignName = item.AssignName
            });
        }
    }
 
    return View(resultList);
}

 コード自体は単純なので特に解説は不要でしょう。続けて、ビューも作ります。ビューもせっかくですから空のビューを作ります。

ビューの作成

2012092102

 ではRazorに挑戦します。挑戦とは言っても前回作成したEntityFrameworkと連動したテンプレートを見本にすれば特に難しいこともないです。モデルを宣言し、受けたデータをぶん回してデーブルで表示するだけです。

Index.cshtml
@model IEnumerable<MVCTestProject.Models.EmployeeList>
 
@{
    ViewBag.Title = "社員リスト";
}
 
<h2>社員リスト</h2>
 
<table>
    <tr>
        <th>
            社員名         
        </th>
        <th>
            配属先
        </th>
    </tr>
 
@foreach (var item in Model)
{
    <tr>
        <td>
            @Html.DisplayFor(modelitem => item.EmployeeName)
        </td>
        <td>
            @Html.DisplayFor(modelitem => item.AssignName)
        </td>
    </tr>       
}
</table>

 ここで一端ブラウザで表示してみます。

実行結果

2012092103

 どうやら希望通りの結果が表示されてるみたいです(IDは表示する必要ないので省きました。)。

 内容は簡単だったんですが、ちょっと長くなりすぎたのでこの辺で切ります。編集機能は次回にします。






ソフマップ・ドットコム
pagetop