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