MVC在ModelState.IsValid = false上设置Html.DropdownList
这是一直困惑着我最好的方法,同时保持可维护的代码。下面的代码为支付网关表单设置一个月和年的列表,然后将它们分配给类型为List<SelectListItem>
的变量。MVC在ModelState.IsValid = false上设置Html.DropdownList
INTIAL行动
PayNowViewModel paymentGateway = new PayNowViewModel();
List<SelectListItem> paymentGatewayMonthsList = new List<SelectListItem>();
List<SelectListItem> paymentGatewayYearsList = new List<SelectListItem>();
for (int i = 1; i <= 12; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayMonthsList.Add(selectListItem);
}
int year = DateTime.Now.Year;
for (int i = year; i <= year + 10; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayYearsList.Add(selectListItem);
}
paymentGateway.ExpiryMonth = paymentGatewayMonthsList;
paymentGateway.ExpiryYear = paymentGatewayYearsList;
return View(paymentGateway);
它的代码公平一点,我发现自己重复这个代码,在类似的格式重新设置的下拉列表中选择应ModelState.IsValid是假的,我想要返回到用户纠正错误的视图。
HttpPost行动 - 代码
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmPayment(PayNowViewModel paymentGatewayForm, FormCollection form)
{
if (ModelState.IsValid)
{
// Post processing actions...
return View();
}
else
{
for (int i = 1; i <= 12; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayMonthsList.Add(selectListItem);
}
int year = DateTime.Now.Year;
for (int i = year; i <= year + 10; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayYearsList.Add(selectListItem);
}
form.ExpiryMonth = paymentGatewayMonthsList;
form.ExpiryYear = paymentGatewayYearsList;
return View("MakePayment", form);
}
}
什么是集中显示的下拉菜单设置代码,以便其只在一个地方最好的方法是什么?目前你会看到很大的比例(for循环),重复两次。具有功能的基本控制器?还是像上面那样重新设置会更好?
任何意见赞赏! Mike。
添加一个私有方法来你的控制器(下面的代码假定您的ExpiryMonth
和ExpiryYear
性能IEnumerable<SelectListItem>
这是所有的DropDownListFor()
方法需要)
private void ConfigureViewModel(PayNowViewModel model)
{
model.ExpiryMonth = Enumerable.Range(1, 12).Select(m => new SelectListItem
{
Value = m.ToString(),
Text = m.ToString("00")
});
model.ExpiryYear = Enumerable.Range(DateTime.Today.Year, 10).Select(y => new SelectListItem
{
Value = y.ToString(),
Text = y.ToString("00")
});
}
然后在GET方法
public ActionResult ConfirmPayment()
{
PayNowViewModel model = new PayNowViewModel();
ConfigureViewModel(model);
return View(model);
}
并在POST方法中
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmPayment(PayNowViewModel model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
.... // save and redirect (should not be returning the view here)
}
如果下拉选项的设置是固定的(或者在潜在选项更改后重新编译是OK),则可以使用枚举来存储选项。
public enum Month {
// if the dropdown is not required, add default value 0
Optional = 0,
[Display(Name = @"Month_January")]
January = 1,
[Display(Name = @"Month_February")]
February = 2,
// etc ..
}
为了使这是一个下拉使用EditorTemplate Enum.cshtml
:
@model Enum
@{
var enumType = ViewData.ModelMetadata.ModelType;
var allValues = Enum.GetValues(enumType).Cast<object>().ToSelectList(Model);
// read any attributes like [Required] from ViewData and ModelMetadata ...
var attributes = new Dictionary<string, object>();
}
@Html.DropDownListFor(m => m, allValues, attributes)
的ToSelectList
扩展方法遍历所有的枚举值,并将其转换为SelectListItems:
public static IList<SelectListItem> ToSelectList<T>(this IEnumerable<T> list) {
return ToSelectList<T>(list, list.FirstOrDefault());
}
public static IList<SelectListItem> ToSelectList<T>(this IEnumerable<T> list, T selectedItem) {
var items = new List<SelectListItem>();
var displayAttributeType = typeof(DisplayAttribute);
foreach (var item in list) {
string displayName;
// multi-language:
// assume item is an enum value
var field = item.GetType().GetField(item.ToString());
try {
// read [Display(Name = @"someKey")] attribute
var attrs = (DisplayAttribute)field.GetCustomAttributes(displayAttributeType, false).First();
// lookup translation for someKey in the Resource file
displayName = Resources.ResourceManager.GetString(attrs.Name);
} catch {
// no attribute -> display enum value name
displayName = item.ToString();
}
// keep selected value after postback:
// assume selectedItem is the Model passed from MVC
var isSelected = false;
if (selectedItem != null) {
isSelected = (selectedItem.ToString() == item.ToString());
}
items.Add(new SelectListItem {
Selected = isSelected,
Text = displayName,
Value = item.ToString()
});
}
return items;
}
为了支持多个语言,为显示名称键添加翻译,例如"Month_January"
,以资源文件。现在
,使用一些反射魔法,创建一个新的视图模型设置代码已经被抽象出来是一件轻而易举的事:>
public class PayNowViewModel {
// SelectListItems are only generated if this gets rendered
public Month ExpiryMonth { get; set; }
}
// Intial Action
var paymentGateway = new PayNowViewModel();
return View(paymentGateway);
// Razor View: call the EditorTemplate
@Html.EditorFor(m => m.ExpiryMonth)
注意,在EditorTemplate,Model
作为选定项目传递给ToSelectList
。回发后,模型将保存当前选定的值。因此它会保持选定的,即使你只是返回模型中的错误后的控制器:
// HttpPost Action
if (!ModelState.IsValid) {
return View("MakePayment", paymentGatewayForm);
}
我们花了一些时间来想出了这个解决方案,学分转到Saratiba队。
因此,上面的代码是一个算法,可以在整个项目中重复使用,但有一些细微差别? –
这只是一个例子。该特定代码片段被使用两次,一次加载表单,并且如果ModelState无效(例如发送用户返回以更改表单),则从post方法中重新加载表单。我有20个以上的实例,其下拉菜单是这样设置的,然后在模型状态失败时再次重新设置,但是每个代码和类都不同。 –
您可以创建一个辅助类,它包含两个静态方法,GetPaymentMonthList和GetPaymentYearList,它返回适当的SelectListItem列表。然后你可以使用它而不用重写它两个地方。 –