Mějme tedy takovýto ViewModel:
public class Schedule
{
public int Duration { get; set; }
}
{
public int Duration { get; set; }
}
Vlastnost Duration udává dobu trvání v minutách. Na stránce ovšem chceme uživateli umožnit výběr pomocí dvou dropdownů - jeden pro nastavení hodin, druhý pro minut:
@model Schedule
<!DOCTYPE html>
<html>
<head>
<title>Schedule</title>
</head>
<body>
<div>
@using (Html.BeginForm())
{
@:Hours: @Html.DropDownList("hours", this.GetHours()) Minutes: @Html.DropDownList("minutes", this.GetMinutes())<br />
<input type="submit" />
}
</div>
</body>
</html>
@functions {
IEnumerable<SelectListItem> GetHours()
{
int hours = Model.Duration / 60;
int maxHours = (5 * 60) / 60;
for (int i = 0; i < maxHours; i++)
{
yield return new SelectListItem()
{
Text = i.ToString(),
Value = i.ToString(),
Selected = (hours == i)
};
}
}
IEnumerable<SelectListItem> GetMinutes()
{
int minutes = ((Model.Duration % 60) / 5) * 5;
for (int i = 0; i < 60; i = i + 5)
{
yield return new SelectListItem()
{
Text = i.ToString(),
Value = i.ToString(),
Selected = (minutes == i)
};
}
}
}
<!DOCTYPE html>
<html>
<head>
<title>Schedule</title>
</head>
<body>
<div>
@using (Html.BeginForm())
{
@:Hours: @Html.DropDownList("hours", this.GetHours()) Minutes: @Html.DropDownList("minutes", this.GetMinutes())<br />
<input type="submit" />
}
</div>
</body>
</html>
@functions {
IEnumerable<SelectListItem> GetHours()
{
int hours = Model.Duration / 60;
int maxHours = (5 * 60) / 60;
for (int i = 0; i < maxHours; i++)
{
yield return new SelectListItem()
{
Text = i.ToString(),
Value = i.ToString(),
Selected = (hours == i)
};
}
}
IEnumerable<SelectListItem> GetMinutes()
{
int minutes = ((Model.Duration % 60) / 5) * 5;
for (int i = 0; i < 60; i = i + 5)
{
yield return new SelectListItem()
{
Text = i.ToString(),
Value = i.ToString(),
Selected = (minutes == i)
};
}
}
}
Přizpůsobené bindování
Po odeslání formuláře je nutné nějak získat z hodnot těchto dvou prvků hodnotu Duration. Jedním z řešení je napsání vlastního binderu, tedy vlastně předpisu, jak se mají hodnoty pro vlastnost Duration získat z hodnot požadavku.
Není to navíc nic složitého ´- založíme si novu třídu ScheduleBinder, která bude dědit z třídy DefaultModelBinder. U ní přepíšeme metodu BindProperty - nejprve necháme proběhnout původní metodu a poté otestujeme, zda je nastavována vlastnost Duration a pokud ano, tak získat hodnoty pro Hours a Minutes, převést na číslo a nastavit vlastnost Duration:
public class ScheduleBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
if (!string.Equals(propertyDescriptor.Name, "Duration"))
return;
var hoursValue= bindingContext.ValueProvider.GetValue("hours");
var minutesValue = bindingContext.ValueProvider.GetValue("minutes");
if (hoursValue == null || minutesValue == null)
return;
int hours = 0;
int minutes = 0;
if(!int.TryParse(hoursValue.AttemptedValue, out hours) || !int.TryParse(minutesValue.AttemptedValue, out minutes))
return;
propertyDescriptor.SetValue(bindingContext.Model, (hours * 60 + minutes));
}
}
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
if (!string.Equals(propertyDescriptor.Name, "Duration"))
return;
var hoursValue= bindingContext.ValueProvider.GetValue("hours");
var minutesValue = bindingContext.ValueProvider.GetValue("minutes");
if (hoursValue == null || minutesValue == null)
return;
int hours = 0;
int minutes = 0;
if(!int.TryParse(hoursValue.AttemptedValue, out hours) || !int.TryParse(minutesValue.AttemptedValue, out minutes))
return;
propertyDescriptor.SetValue(bindingContext.Model, (hours * 60 + minutes));
}
}
Za povšimnutí stojí, že pro získání hodnot hours a minutes se použivá ValueProvider - neříká se tedy, odkud mají hodnoty pocházet. Poskytovatelů hodnot je hned několik a MVC je postupně volá, dokud některý z nich nevrátí hodnotu. Na následujícím obrázku je toto zachyceno, všimněte si prosím, že pořadí je důležité, např. hodnoty předané POST (Form) formulářem mají přednost před parametry volání (GET - Query String):
Nyní jenom zbývá sdělit MVC, že chceme tento binder používat. To můžeme udělat na třech místech:
- pomocí atributu ModelBinderAttribute před parametrem u ActionMetody v controlleru:
public ActionResult Schedule([ModelBinder(typeof(ScheduleBinder))] Schedule model)
- pomocí atributu ModelBinderAttribute před definicí třídy:
[ModelBinder(typeof(ScheduleBinder)]
public class Schedule
{
public int Duration { get; set; }
}
public class Schedule
{
public int Duration { get; set; }
}
- globálním nastavením v Global.asax, v metodě Application_Start:
protected void Application_Start()
{
ModelBinders.Binders.Add(typeof(Schedule), new ScheduleBinder());
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
{
ModelBinders.Binders.Add(typeof(Schedule), new ScheduleBinder());
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
Upřednosťuji zápis přímo u parametru u ActionMetody - hned vidím, podle čeho je daný parametr plněn a mohu to změnit.
Další úpravy
Bylo by dobré před nastavováním vlastnosti Duration napřed zjistit, zda už nebyla nastavena, defaultním binderem, například proto, že v požadavku, co na server dorazil, byla již hodnota duration (například požadavak vypadal ~/Home/Schedule?Duration=200) - toho docílíme testováním objektu ModelState na přítomnost klíče Duration - pokud existuje, byla tato vlastnost již nastavena a není třeba ji nastavovat jinak.
Pokud nebudou v příchozím požadavku žádné hodnoty, které bychom mohli použít, tedy ani Duration a ani Hours a Minutes, můžeme v binderu rovnou nastavit výchozí hodnotu, například 180 minut.
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
if (!string.Equals(propertyDescriptor.Name, "Duration") || bindingContext.ModelState.ContainsKey("Duration"))
return;
var hoursValue= bindingContext.ValueProvider.GetValue("hours");
var minutesValue = bindingContext.ValueProvider.GetValue("minutes");
int hours = 0;
int minutes = 0;
if (hoursValue == null || minutesValue == null ||
!int.TryParse(hoursValue.AttemptedValue, out hours) || !int.TryParse(minutesValue.AttemptedValue, out minutes))
{
hours = 3;
}
propertyDescriptor.SetValue(bindingContext.Model, (hours * 60 + minutes));
}
}
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
if (!string.Equals(propertyDescriptor.Name, "Duration") || bindingContext.ModelState.ContainsKey("Duration"))
return;
var hoursValue= bindingContext.ValueProvider.GetValue("hours");
var minutesValue = bindingContext.ValueProvider.GetValue("minutes");
int hours = 0;
int minutes = 0;
if (hoursValue == null || minutesValue == null ||
!int.TryParse(hoursValue.AttemptedValue, out hours) || !int.TryParse(minutesValue.AttemptedValue, out minutes))
{
hours = 3;
}
propertyDescriptor.SetValue(bindingContext.Model, (hours * 60 + minutes));
}
}
Žádné komentáře:
Okomentovat