Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strongly Typed Generic Attribute alternatives

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?

like image 750
Erik Philips Avatar asked May 13 '15 21:05

Erik Philips


1 Answers

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; }
}
like image 164
Waaghals Avatar answered Nov 20 '22 00:11

Waaghals