Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 記錄軟體開發的點點滴滴 著重於微軟技術、網頁開發、DevOps、C#, Asp .net Mvc、Azure、AI、Chatbot、Docker、Data Science

[iThome 第七屆鐵人賽 19] 框架產生下拉式資料的內容

這一篇,回到Controller常常需要做的一件事情,那就是當如果欄位屬於下拉式選單的時候,需要準備好下拉式清單的資料。

如果用的是預設的方式去產生下拉式選單其實有很多問題,這一篇想透過框架的方式,讓產生下拉式清單的資料能夠自動化。

預設Scaffolding的問題

如果是Mvc Scaffolding內建的話,會在Controller的時候產生下拉式清單資料並且塞到ViewBag裡面。

這個最大的問題是:假設某一個地方有需要下拉式選單的資料,但是忘記產生了(最長發生這個問題是在Edit的時候,驗證失敗需要重新顯示View的時候),畫面就會炸掉。

而且這個只有在runtime的時候才會發生,完全沒有辦法在compile的時候檢測出來。

假設有東西能夠保證需要下拉式清單的資料的時候,一定會產生出來,就不需要擔心到底有沒有忘記呼叫要把資料塞到ViewBag裡面。

這就是我們框架要解決的問題。

解決思路

首先,需要下拉式清單的資料的時候,就需要產生出來。能夠確認每一次需要的時候就會產生,就要透過Filter來做。

如果透過Filter來處理產生的邏輯,那麼還需要準備資料給Filter,讓它產生對應的資料並且塞到ViewBag裡面。因此,ViewModel是最適合的,因為在Filter裡面可以取得ViewModel的內容,因此可以做一個特殊的欄位,專門存放這些準備資料。

最後,在顯示的部份,就可以用一般的HtmlHelper產生即可。

實作內容

接下來就看看如何實作。

SelectListViewModel的定義

首先,定義一個SelectListViewModel的Class,這個Class將代表需要產生的下拉式選單:

/// <summary>
/// 代表一個要被產生的SelectList
/// </summary>
public class SelectListViewModel
{
    /// <summary>
    /// 取得或設定此SelectList要和那個ViewModel Property對應
    /// </summary>
    /// <value>
    /// 此SelectList要和那個ViewModel Property對應
    /// </value>
    public string SelectListId { get; set; }

    /// <summary>
    /// 取得或設定資料來源
    /// </summary>
    /// <value>
    /// 資料來源
    /// </value>
    public string Source { get; set; }

    /// <summary>
    /// 取得或設定SelectList值的欄位來源
    /// </summary>
    /// <value>
    /// SelectList值的欄位來源
    /// </value>
    public string DataValueField { get; set; }

    /// <summary>
    /// 取得或設定SelectList顯示的欄位來源
    /// </summary>
    /// <value>
    /// SelectList顯示的欄位來源
    /// </value>
    public string DataTextField { get; set; }

    /// <summary>
    /// 取得或設定SelectList被選取的值
    /// </summary>
    /// <value>
    /// SelectList被選取的值
    /// </value>
    public object SelectedValue { get; set; }

    /// <summary>
    /// SelectList要從那裡被產生出來
    /// </summary>
    private string codeWhere;

    /// <summary>
    /// 取得或設定SelectList要從那裡被產生出來
    /// </summary>
    /// <value>
    /// SelectList要從那裡被產生出來
    /// </value>
    public string CodeWhere
    {
        get
        {
            return codeWhere;
        }

        set { codeWhere = value; }
    }
}

定義ViewModelBase


再來,所有的ViewModel都要從某一個Base做繼承,而這個ViewModelBase會有一個Property,會回傳Array of SelectListViewModel。這個property將代表這個ViewModel會用到的下拉式選單清單。

 /// <summary>
/// Core View Model 的 Base class。所有ViewModel將會繼承這一個。
/// </summary>
public abstract class CoreViewModelBase
{
    /// <summary>
    /// 如果需要產生SelectList到ViewData裡面,
	/// 那麼child class會複寫這個Property,輸入需要產生的SelectList資訊。
    /// </summary>
    /// <value>
    /// 
    /// </value>
    public virtual SelectListFill.SelectListViewModel[] NeedFillSelectList 
    { 
        get 
        { 
            return null; 
        } 
    }
}

因此,某一個的實作可能會是:

public class Create : CoreViewModelBase, IHaveCustomMapping
{
...
	public string PostType {get; set;}

	/// <summary>
	/// 如果需要產生SelectList到ViewData裡面,
	/// 那麼child class會複寫這個Property,
	///	輸入需要產生的SelectList資訊。
	/// </summary>
	public override SelectListViewModel[] NeedFillSelectList
	{
		get
		{
			List<SelectListViewModel> temp = new List<SelectListViewModel>();
	
			temp.Add(new SelectListViewModel()
                {
                    CodeWhere = "where",
                    DataTextField = "Text",
                    DataValueField = "Value",
                    SelectedValue = PostType,
                    SelectListId = "PostType",
                    Source = "Code"
                });
	
			return temp.ToArray();
		}
	}
	
	...
}

上面的例子是有一個PostType的下拉式選單


Filter定義


再來就是實際產生下拉式清單的地方:

/// <summary>
/// 把ViewModel裡面有設定要產生的SelectList產生出來並且寫到ViewData。
/// 需要由此Class的Child來複寫產生SelectList的邏輯
/// </summary>
public abstract class FillSelectListActionFilterBase : ActionFilterAttribute
{
    /// <summary>
    /// 產生SelectList的邏輯
    /// </summary>
    /// <param name="viewModel">提供要如何產出SelectList的資訊</param>
    /// <returns>
    /// 依照ViewModel的資訊產出對應的SelectList
    /// </returns>
    public override System.Web.Mvc.SelectList 
		GetSelectList(SelectListViewModel viewModel)
    {
        SelectList result;

        // 依照SelectListViewModel的值,去產生SelectList

        return result;
    }

    /// <summary>
    /// 把產出的SelectList注入到ViewData裡面
    /// </summary>
    /// <param name="filterContext">The filter context.</param>
    public override void OnActionExecuted
		(ActionExecutedContext filterContext)
    {
        var viewResult = filterContext.Result as ViewResult;

        if (viewResult != null && viewResult.Model is CoreViewModelBase)
        {
            var selectListViewModelArray = 
			((CoreViewModelBase)viewResult.Model).NeedFillSelectList;

            // 假設有設定ViewModel才要做產出的動作
            if (selectListViewModelArray != null
			 && selectListViewModelArray.Count() > 0)
            {
                foreach (var item in selectListViewModelArray)
                {
                    // 假設目前ViewData裡面沒有這個SelectList才產生。
					// 因此,在別的地方產出的SelectList的權重比這一個
                    // filter還高。
                    if (viewResult.ViewData[item.SelectListId] 
						as System.Web.Mvc.SelectList == null)
                    {
                        viewResult.ViewData[item.SelectListId] 
							= this.GetSelectList(item);
                    }
                }
            }
        }

        base.OnActionExecuted(filterContext);
    }
}

基本上就是依照SelectListViewModel去產生SelectList。然後把產生的SelectList塞到ViewData裡面,使用的Key會是 SelectListViewModel裡面的SelectListId


View的使用


最後,就是View的呼叫:

// Create.cshtml

...

@Html.DropDownListFor(model => model.PostType, null, 
		htmlAttributes: new { @class = "form-control" })

....

這邊注意到我們給nullHtml.DropDownListFor,因為如果沒有給SelectList,Mvc會去找ViewData裡面看有沒有一樣的key有這個值,有的話就會使用那個作為清單。而我們的Filter則會產生出來那個清單,因此一定會有東西。


結語


透過使用這種方法,再也不需要擔心需要下拉式清單的時候忘記產生,因為Filter會幫忙做掉。


如果文章對您有幫助,就請我喝杯飲料吧
街口支付QR Code
街口支付QR Code
街口支付QR Code
支付寶QR Code
街口支付QR Code
微信支付QR Code
comments powered by Disqus