强类型通用属性替代方案

时间:2015-05-13 21:19:32

标签: c# asp.net-mvc generics attributes automapper

让我们从一个简单的视图模型开始:

public class MyViewModel
{
  public string Value { get; set; }
  public IEnumerable<SelectListItem> Values { get; set; }
}

下拉列表可能如下所示:

@Html.DropDownListFor(m => m.Value, Model.Values)

但是,因为下拉列表需要两个值,所以不能使用它:

public class MyViewModel
{
  [UIHint("DropDownList")]
  public string Value { get; set; }
  public IEnumerable<SelectListItem> Values { get; set; }
}

包含以下内容的视图:

@Html.EditForModel()

因为没有固有的方法让下拉知道源,直到你从UIHint中消失:

public DropDownListAttribute : UIHintAttribute
{
  public DropDownListAttribute (string valuesSource)
    : base("DropDownList", "MVC")
  {
  }
}

然后可以使用它:

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; }
}

但是,这并非真正强类型,有人重命名magic strings或其中一个名称而不更改另一个名称,并且在运行时会中断。

一个理论解决方案是创建一个通用属性:

public DropDownListAttribute<TModel, TValue> : UIHintAttribute
{
  public DropDownListAttribute (Expression<Func<TModel, TValue> expression)
    : base("DropDownList", "MVC")
  {
  }
}

,用法是:

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:/

另一种选择是将两者封装到一个类中,ModelBinder可以在后面重新创建:

public class DropDownListTemplate
{
  public string SelectedValue { get; set; }
  public IEnumerable<SelectListItem> Values { get; set; }
}

public class MyViewModel
{
  public DropDownListTemplate Planet { get; set; }
}

这在ViewModel,Binding和EditFor / DisplayFor模板中创建了简单性,但是由于我对AutoMapper的了解有限,它在AutoMapper Mapping to Properties of Class Properties时增加了复杂性。据我所知,我不能简单地说:

public class MyPlanets
{
  public string SelectedPlanet { get; set; }
}

Mapper.CreateMap<MyPlanets, MyViewModel>();
Mapper.CreateMap<MyViewModel, MyPlanets>();

有一种更简单的方法可以让automapper自动地映射这些值,还是有办法创建一个强类型的非泛型属性?

2 个答案:

答案 0 :(得分:1)

要强制键入属性名称,可以使用C#6中引入的nameof

nameof是一个与typeof类似的表达式,但不是返回值的类型,而是将值作为字符串返回。

  

用于获取变量,类型或成员的简单(非限定)字符串名称。在报告代码中的错误,连接模型 - 视图 - 控制器(MVC)链接,触发属性更改事件等时,通常需要捕获方法的字符串名称。使用nameof有助于在重命名定义时保持代码有效。

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; }
}

答案 1 :(得分:0)

您可以将Planets中的属性命名为“PlanetSelectedValue”,这会导致AutoMapper自动确定其中的值。 (即Planet.SelectedValue映射到PlanetSelectedValue)

这类似于魔术字符串问题,如果您更改属性名称,AutoMapper将不再知道该值的放置位置,但这与任何其他AutoMapper问题没有什么不同。

public class MyPlanets
{
    public string PlanetSelectedValue { get; set; }
}

OR:

使用AutoMapper的IgnoreMap属性忽略DropDownListTemplate属性,并创建一个单独的AutoMapper友好的PlanetId属性来操纵DropdownListTemplate中的值。

public class DropDownListTemplate
{
    public string SelectedValue { get; set; }
    public IEnumerable<SelectListItem> Values { get; set; }
}

public class MyViewModel
{
    [IgnoreMap]
    public DropDownListTemplate Planet { get; set; }
    public string PlanetId
    {
        get { return Planet.SelectedValue; }
        set { Planet.SelectedValue = value; }
    }
}

此外,如果您的项目可以,请查看C#6.0 nameof()表达式,以便在不使用魔术字符串的情况下提取属性名称。