MVC 在 ModelState.IsValid = false 上设置 Html.DropdownList

MVC setting up Html.DropdownList on ModelState.IsValid = false

关于最好的方法,同时保持可维护的代码,这一直让我感到困惑。下面的代码在将这些分配给 List<SelectListItem>.

类型的变量之前,为支付网关表单设置了月份和年份列表

初始操作

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 为假并且我想 return 返回,我发现自己以类似的格式重复这段代码以重新设置下拉列表选项到视图供用户更正那里的错误。

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 循环),正好重复了两次。具有功能的基本控制器?还是像上面那样重新设置比较好?

感谢任何建议! 迈克.

如果您的下拉选项集是固定的(或者在潜在选项更改后可以重新编译),您可以使用枚举来存储您的选项。

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。回发后,模型将保留当前选择的值。因此它保持选中状态,即使您只是 return 控制器出错后的模型:

// HttpPost Action
if (!ModelState.IsValid) {
    return View("MakePayment", paymentGatewayForm);
}

我们花了一些时间才想出这个解决方案,感谢 Saratiba 团队。

向您的控制器添加一个私有方法(以下代码假设您的 ExpiryMonthExpiryYear 属性是 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)
}