简体   繁体   English

强类型通用属性替代方案

[英]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: 因为下拉知道源代码没有固有的方法,直到你从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: 另一种选择是将两者封装到一个类中,ModelBinder可以在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 . 这在ViewModel,Binding和EditFor / DisplayFor模板中创建了简单性,但是由于我对AutoMapper的有限了解,它在AutoMapper映射到类属性的属性时增加了复杂性。 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? 有没有一种更简单的方法可以让automapper自动地映射这些值,或者有没有办法创建一个强类型的非泛型属性?

To make the property name strongly typed you could use nameof which is introduced in C# 6. 要使属性名称强类型,您可以使用C#6中引入的nameof

nameof is an expression just like typeof , but instead of returning the value's type it returns the value as a string. nameof是一个类似于typeof的表达式,但它不是返回值的类型,而是将值作为字符串返回。

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. 在报告代码中的错误,连接模型 - 视图 - 控制器(MVC)链接,触发属性更改事件等时,通常需要捕获方法的字符串名称。 Using nameof helps keep your code valid when renaming definitions. 使用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; }
}

You could name your property in Planets as "PlanetSelectedValue" which would cause AutoMapper to automagically determine what value goes there. 您可以将Planets中的属性命名为“PlanetSelectedValue”,这将导致AutoMapper自动确定其中的值。 (ie Planet.SelectedValue maps to PlanetSelectedValue) (即Planet.SelectedValue映射到PlanetSelectedValue)

This is similar to the magic string problem in that if you change the property name, AutoMapper will no longer know where to put that value, but that is no different than any other AutoMapper issue. 这类似于魔术字符串问题,如果您更改属性名称,AutoMapper将不再知道将该值放在何处,但这与任何其他AutoMapper问题没有什么不同。

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

OR: 要么:

Use AutoMapper's IgnoreMap attribute to ignore the DropDownListTemplate property and make a separate AutoMapper-friendly PlanetId property that manipulates the values inside the DropdownListTemplate. 使用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; }
    }
}

Also, when it is possible for your project, look at the C# 6.0 nameof() expression to pull in the name of a property without using the magic string. 此外,当您的项目可能时,请查看C#6.0 nameof()表达式以在不使用魔术字符串的情况下提取属性的名称。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM