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

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