Lets start with a simple view model:
public class MyViewModel
{
public string Value { get; set; }
public IEnumerable<SelectListItem> Values { get; set; }
}
A drop down list might look like:
@Html.DropDownListFor(m => m.Value, Model.Values)
However, because a drop down list requires two values it can't be used like:
public class MyViewModel
{
[UIHint("DropDownList")]
public string Value { get; set; }
public IEnumerable<SelectListItem> Values { get; set; }
}
with a view containing:
@Html.EditForModel()
Because there is no inherent way for the drop down to know the source, until you derrive from UIHint:
public DropDownListAttribute : UIHintAttribute
{
public DropDownListAttribute (string valuesSource)
: base("DropDownList", "MVC")
{
}
}
Then one might use it like:
public class MyViewModel
{
[DropDownList("Planets")]
public string PlanetId{ get; set; }
public IEnumerable<SelectListItem> Planets { get; set; }
[DropDownList("Cars")]
public string CarId{ get; set; }
public IEnumerable<SelectListItem> Cars { get; set; }
}
However, this isn't really strongly typed, someone renames one of the magic strings or propery names without changing the other and it breaks at run-time.
One theoretical solution is to create a generic attribute:
public DropDownListAttribute<TModel, TValue> : UIHintAttribute
{
public DropDownListAttribute (Expression<Func<TModel, TValue> expression)
: base("DropDownList", "MVC")
{
}
}
and the usage would be:
public class MyViewModel
{
[DropDownList<MyViewModel, IEnumerable<SelectListItem>>( m => m.Planets)]
public string PlanetId{ get; set; }
public IEnumerable<SelectListItem> Planets { get; set; }
}
But (currently) Generic Attributes aren't allowed :/
Another option is to encapsulate the two into a single class that the ModelBinder can recreate on post-back:
public class DropDownListTemplate
{
public string SelectedValue { get; set; }
public IEnumerable<SelectListItem> Values { get; set; }
}
public class MyViewModel
{
public DropDownListTemplate Planet { get; set; }
}
This creates simplicity in the ViewModel, Binding and EditFor/DisplayFor templates but from my limited knowledge of AutoMapper it adds complexity when AutoMapper Mapping to Properties of Class Properties. As far as I know I can't simply:
public class MyPlanets
{
public string SelectedPlanet { get; set; }
}
Mapper.CreateMap<MyPlanets, MyViewModel>();
Mapper.CreateMap<MyViewModel, MyPlanets>();
Is there an easier way with automapper to map these values auto-magically or is there a way to create a strongly-typed non-generic attribute?
To make the property name strongly typed you could use nameof
which is introduced in C# 6.
nameof
is an expression just like typeof
, but instead of returning the value's type it returns the value as a string.
Used to obtain the simple (unqualified) string name of a variable, type, or member. When reporting errors in code, hooking up model-view-controller (MVC) links, firing property changed events, etc., you often want to capture the string name of a method. Using nameof helps keep your code valid when renaming definitions.
public class MyViewModel
{
[DropDownList(nameof(Planets))]
public string PlanetId{ get; set; }
public IEnumerable<SelectListItem> Planets { get; set; }
[DropDownList(nameof(Cars))]
public string CarId{ get; set; }
public IEnumerable<SelectListItem> Cars { get; set; }
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With