最近有很多關於這個話題的討論。在日期,日期範圍和多選複選框列表中遇到類似的障礙。任何地方你可能想使用豐富的html控件。我一直在試驗兒童ViewModels的概念,我認爲解決方案比我嘗試過的其他方法更清潔。
基本的概念是,您可以定義一個與自定義EditorTemplate緊密耦合的小視圖模型。
在你的榜樣,我們會用一個(孩子)視圖模型這是具體到一個單一的選擇列表開始:
public class SelectModel
{
#region SelectModel(string value, IEnumerable<SelectListItem> items)
public SelectModel(string value, IEnumerable<SelectListItem> items)
{
_value = value;
Items = new List<SelectListItem>(items);
_Select();
}
#endregion
// Properties
public List<SelectListItem> Items { get; private set; }
public string Value
{
get { return _value; }
set { _value = value; _Select();}
}
private string _value;
// Methods
private void _Select()
{
Items.ForEach(x => x.Selected = (Value != null && x.Value == Value));
}
}
在想要使用您撰寫的選擇模型中下拉列表的視圖模型(我們」重新使用的所有視圖模型,右):
public class EmailModel
{
// Constructors
public EmailModel()
{
Priority = new SelectModel("normal", _ToPrioritySelectItems());
}
// Properties
public SelectModel Priority { get; set; }
// Methods
private IEnumerable<SelectListItem> _ToPrioritySelectItems()
{
List<SelectListItem> result = new List<SelectListItem>();
result.Add(new SelectListItem() { Text = "High", Value = "high" });
...
}
注意,這是一個簡單的例子,有一組固定的下拉菜單項。如果它們來自域圖層,則控制器將它們傳遞給ViewModel。
然後在共享/ EditorTemplates添加編輯模板SelectModel.ascx
<%@ Control Inherits="System.Web.Mvc.ViewUserControl<SelectModel>" %>
<div class="set">
<%= Html.LabelFor(model => model) %>
<select id="<%= ViewData.ModelMetadata.PropertyName %>_Value" name="<%=ViewData.ModelMetadata.PropertyName %>.Value">
<% foreach (var item in Model.Items) { %>
<%= Html.OptionFor(item) %>
<% } %>
</select>
</div>
注:OptionFor是一個自定義的擴展,它確實明顯
訣竅這裏是ID和名稱使用設置默認ModelBinder期望的複合格式。在我們的例子「Priority.Value」中。因此,被定義爲SelectModel的一部分的基於字符串的Value屬性被直接設置。如果我們需要重新顯示錶單,setter負責更新Items的列表以設置默認選擇選項。
這個「子視圖模型」方法真正發揮的是更復雜的「控件標記片段」。我現在有多個子視圖模型,它們遵循MultiSelect列表,開始/結束日期範圍和日期+時間組合的類似方法。
只要你走下這條路,下一個明顯的問題就變成了驗證。
我結束了有所有我的孩子的視圖模型的實現標準接口:
public interface IValidatable
{
bool HasValue { get; }
bool IsValid { get; }
}
然後,我有一個自定義ValidationAttribute:
public class IsValidAttribute : ValidationAttribute
{
// Constructors
public IsValidAttribute()
{
ErrorMessage = "(not valid)";
}
// Properties
public bool IsRequired { get; set; }
// Methods
private bool Is(object value)
{
return value != null && !"".Equals(value);
}
public override bool IsValid(object value)
{
if (!Is(value) && !IsRequired)
return true;
if (!(value is IValidatable))
throw new InvalidOperationException("IsValidAttribute requires underlying property to implement IValidatable");
IValidatable validatable = value as IValidatable;
return validatable.IsValid;
}
}
現在,你可以把屬性上的性能子ViewModel基於任何標量屬性:
[IsValid(ErrorMessage = "Please enter a valid start date/time")]
public DateAndTimeModel Start { get; set; }