More

    框架產生下拉式資料的內容 (.NET MVC 5 Ch-19)

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

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

    同步發表於我的部落格:http://alantsai2007.blogspot.com/2014/10/BuildYourOwnApplicationFrameworkOnMvc-19-GenerateSelectListFroDropDownList.html

    預設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" })
    
    ....
    

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

    結語

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

    Recent Articles

    spot_img

    Related Stories

    Stay on op - Ge the daily news in your inbox