This is something that has always puzzled me as to the best way round, while keeping maintainable code. The below code sets up a list of months and years for a payment gateway f
If the set of your dropdown options is fixed (or recompilation is OK after the potential options change), you can use an enum to store your options.
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 ..
}
To render this as a dropdown use an EditorTemplate Enum.cshtml
:
@model Enum
@{
var enumType = ViewData.ModelMetadata.ModelType;
var allValues = Enum.GetValues(enumType).Cast
The ToSelectList
extension method loops over all enum values and converts them to SelectListItems:
public static IList ToSelectList(this IEnumerable list) {
return ToSelectList(list, list.FirstOrDefault());
}
public static IList ToSelectList(this IEnumerable list, T selectedItem) {
var items = new List();
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;
}
To support multiple languages, add translations for the display name keys, e.g. "Month_January"
, to the Resource file.
Now that the setup code has been abstracted away using some reflection magic, creating a new viewmodel is a breeze :>
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)
Note that in the EditorTemplate, Model
is passed as the selected item to ToSelectList
. After postback, Model will hold the currently selected value. Therefore it stays selected, even if you just return the model after an error in the controller:
// HttpPost Action
if (!ModelState.IsValid) {
return View("MakePayment", paymentGatewayForm);
}
Took us some time to come up with this solution, credits go to the Saratiba team.