【问题标题】:.NET - Convert/map list into object and vis versa.NET - 将列表转换/映射为对象,反之亦然
【发布时间】:2017-09-14 08:17:57
【问题描述】:

我有以下列表,其中包含字段名称和值。

public class FormField 
{
    public string FieldName { get; set;}
    public string FieldValue { get; set;}
}

var formData = new List<FormField>();
formData.Add(new FormField { FieldName = "Date", FieldValue = "2017-09-14" });
formData.Add(new FormField { FieldName = "Name", FieldValue = "Job blogs" });
formData.Add(new FormField { FieldName = "IsEnabled", FieldValue = "true" });

如何将列表转换或映射到以下类?注意 FieldNames 映射到类的属性。

public class MyViewModel 
{
    [Required]
    public DateTime Date { get; set; } = DateTime.now;

    [Required]
    public string Name { get; set; }

    public boolean IsEnabled { get; set; }

    public IEnumerable<SelectListItem> Titles 
    {
        get
        {
            var options = new List<SelectListItem>
            {
                new SelectListItem(){ Value = "Mr", Text = "Mr" },
                new SelectListItem(){ Value = "Mrs", Text = "Mrs" }                    
            };

            return options;
        }
    } 
}

任何帮助表示赞赏。我需要以某种方式序列化列表吗?自动映射器可以做到这一点吗?

* 更新 *

我尝试了以下方法,但它不起作用,尽管自动映射器文档声明您可以直接从字典转到对象:

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Dictionary<string, object>, MyViewModel>();
    }
}

var viewModel = Mapper.Map<MyViewModel>(formData.ToDictionary(x => x.FieldName, x => (object) x.FieldValue))

注意:作为记录,我使用的是 automapper v 5.0.2

我还需要从对象返回到字典,但能够排除 public IEnumerable&lt;SelectListItem&gt; Titles {get;} 等属性

【问题讨论】:

  • 是的 Automapper 可以做到这一点,或者如果您想避免 Automapper 的开销,您可以通过将 FormField 的对象作为参数传递来手动映射 MyViewModel 的构造函数中的值。
  • @TechGirl 有什么代码可以帮忙吗?构造函数中的映射并不理想。
  • @TechGirl,真的吗?我非常怀疑。
  • 您需要遍历集合,获取属性名称并使用条件语句(if (FieldName == "Date") { model.Date = ...)设置关联的属性值,或者使用反射来匹配属性名称(使用PropertyInfo
  • 我的想法与@StephenMuecke 相同,但您可以将 if 语句替换为“switch”,当然您可以在不同的函数中执行此操作,并在需要时调用它

标签: c# asp.net-mvc list serialization automapper


【解决方案1】:

您似乎正在尝试创建一个包含动态数量字段的对象,例如 ViewBag。您不需要为此进行映射。这已经由 .NET 通过 ExpandoObject、DynamicObject 类和dynamic 关键字提供。

创建一个 ExpandoObject 并向其中添加字段,而不是构建字段和值的列表,就像使用 ViewBag 一样:

dynamic formData=new ExpandoObject();
formData.Name = "Job blogs";
formData.Date = DateTime.Today;
formData.IsEnabled = true;
formData.Titles = new []{ 
                           new SelectedListeItem{Text="Mr",Value="Mr"},
                           new SelectedListeItem{Text="Mrs",Value="Mrs"}
                        };

您可以将该对象用作您的 ViewModel,就像 ViewBag 一样。

// Controller 

public ActionResult Index(..)
{
    ....
    View(formData);
}


//View 
@model dynamic 

<h1>@Model.Name</h1>

更新 - 从字段列表中展开

ExpandoObject 显式实现了IDictionary&lt;string, object&gt;,这意味着可以在运行时不知道其编号或名称的情况下进行属性,例如:

var fields = new (string name,object value) [] 
             {
                        ("Name","Job blogs"),
                        ("Date", DateTime.Today),
                        ("IsEnabled",true)
             };

dynamic viewModel=new ExpandoObject();
var dict=(IDictionary<string,object>)viewModel;
foreach(var field in fields)
{
    dict.Add(field.name,field.value);
}

我使用元组语法只是为了避免重复输入 FormField

更新 2 - 具有字典存储的强类型类

正如 Stephen Muecke 评论的那样,使用动态类进行绑定和验证更加困难。另一方面,如果预先知道字段,为什么还要使用映射或反射呢?

可以创建一个 ViewModel 类来接受一些文件,将它们转换为字典(类似于 ExpandoObject 所做的)并将字典用作属性的后备存储。

有了一点 C# 的魔力,比如 CallMemberName 属性,额外的代码是最少的。 字典查找存在运行时损失,只有在 大量 读取/写入时才会显现:

class MyViewModel
{
    Dictionary<string,object> _dict=new Dictionary<string,object>();

    //Get helper
    private T getter<T>([CallerMemberName]string name=null)
    {
        return _dict.TryGetValue(name,out object value)
            ? (T)Convert.ChangeType(value,typeof(T))
            : default(T);            
    }

    private void setter(object value,[CallerMemberName]string name=null)
    {
        _dict[name]=value;
    }

    public DateTime Date { 
        get => getter<DateTime>();            
        set => setter(value); 
    }               
    public string Name { 
        get => getter<string>();            
        set => setter(value); 
    }
    public bool IsEnabled { 
        get => getter<bool>();            
        set => setter(value); 
    }

    public MyViewModel(IEnumerable<FormField> fields)
    {
        _dict=fields.ToDictionary(
                         field=>field.FieldName,
                         field=>(object)field.FieldValue);
    }

}

....

var formData = new [] {
    new FormField { FieldName = "Date", FieldValue = "2017-09-14" },
    new FormField { FieldName = "Name", FieldValue = "Job blogs" },
    new FormField { FieldName = "IsEnabled", FieldValue = "true" }
};

var myViewModel = new MyViewModel(formData);

每个属性的设置器只是使用属性的名称作为键设置一个字典值。 getter 使用CallerMemberName 获取属性的名称并将其用作键

【讨论】:

  • 字段名称和值的列表实际上是通过 web 服务作为数组检索的,所以我不能随意创建 expando 对象
  • @adam78 为什么不呢?您可以在循环中添加名称。通过从 DynamicObject 派生,您可以添加一个实际读取该数组的构造函数
  • @adam78 重要的是您不需要映射。你只需要构造一个动态对象。
  • @PanagiotisKanavos,这个问题是模型将用于编辑数据(注意 OP 有一个下拉列表的 IEnumerable&lt;SelectListItem&gt; 属性。dynamic 在这种情况下不会工作得很好 - 没有 2 -way 模型绑定,无验证等)
  • @StephenMuecke 如果事先知道字段的数量,那么无论如何都没有理由使用映射或反射。只需使用接受 FormField[] 并设置单个值的构造函数创建一个 ViewModel。
【解决方案2】:

您可以使用反射来获取和设置模型中的匹配属性

var formData = new List<FormField>();
formData.Add(new FormField { FieldName = "Date", FieldValue = "2017-09-14" });
formData.Add(new FormField { FieldName = "Name", FieldValue = "Job blogs" });
formData.Add(new FormField { FieldName = "IsEnabled", FieldValue = "true" });

// Initialize a new instance of the model
MyViewmodel model = new MyViewmodel();
// Get its property info
PropertyInfo[] properties = model.GetType().GetProperties();
// Loop through your form field data
foreach(var field in formData)
{
    // Get the matching property name
    PropertyInfo pi = properties.FirstOrDefault(x => x.Name == field.FieldName);
    if (pi == null)
    {
        continue;
    }
    // Convert the value to match the property type
    object value = Convert.ChangeType(field.FieldValue, pi.PropertyType);
    // Set the value of the property
    pi.SetValue(model, value);
}

【讨论】:

  • 如何使用中间序列化参见下文。在这个实例中,我会将列表转换为字典 - (stackoverflow.com/questions/7310533/…)
  • 正如达林所说 - 你可以作弊,这将涉及额外的开销代码和开销
  • 实际上,额外的开销只有在使用 AutoMapper 时才适用,这可能没有必要。
  • 视图模型中的布尔属性怎么样。在我的列表中,它们是字符串,所以如果我只是使用 Darins 方法作弊,它们会被映射到对应的布尔值吗?
  • 不确定 - 只测试了我的答案(使用 Convert.ChangeType(..) 会正确)
【解决方案3】:

默认情况下,如果属性名称匹配,AM 可以从 IDictionary&lt;string, object&gt;(或 DynamicObject)映射到任何目标。所以你需要从你的结构映射到字典,LINQ 可能最适合,然后到你的目的地。 docstests

【讨论】:

  • 以下不起作用:var viewModel = Mapper.Map&lt;Dictionary&lt;string, string&gt;, MyViewmodel&gt;(formData.ToDictionary(x =&gt; x.FieldName, x =&gt; x.FieldValue))
  • 类型错误。 IDictionary&lt;string, object&gt;.
  • 当我将类型更改为对象时,在尝试将列表转换为字典时,我的 LINQ 表达式出现错误,因为值是字符串?
  • var viewModel = Mapper.Map&lt;MyViewmodel&gt;(formData.ToDictionary(x =&gt; x.FieldName, x =&gt; (object) x.FieldValue);
猜你喜欢
  • 2011-06-24
  • 2015-03-05
  • 2013-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-29
相关资源
最近更新 更多