.NET - Convert/map list into object and vis versa

北慕城南 提交于 2019-12-02 22:51:08

问题


I have the following list which contains field names and values.

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

How can I convert or map the list into the following class? Note FieldNames map to the properties of the class.

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

Any help appreciated. Do I need to serialize the list somehow? Can automapper do this?

* UPDATE *

I tried the following but it doesnt work despite the automapper docs stating that you can go directly from dictionary to object:

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))

Note: for the record I'm using automapper v 5.0.2

I also need to go back from object to dictionary but be able to exclude properties such as public IEnumerable<SelectListItem> Titles {get;}


回答1:


You could use reflection to get and set the matching properties in the model

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



回答2:


It seems like you are trying to create an object that contains a dynamic number of fields, like ViewBag. You don't need mapping for this. That's already provided by .NET, through the ExpandoObject, DynamicObject classes and dynamic keyword.

Instead of building a list of fields and values, create an ExpandoObject and add fields to it, just as you would with 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"}
                        };

You can use that object as your ViewModel, just like ViewBag.

// Controller 

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


//View 
@model dynamic 

<h1>@Model.Name</h1>

UPDATE - Expando from field list

ExpandoObject implements IDictionary<string, object> explicitly, which means that one can properties without knowing their number or names at runtime, eg:

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

I'm using tuple syntaxt just to avoid typing FormField repeatedly

UPDATE 2 - Strongly typed class with dictionary storage

As Stephen Muecke commented, it's harder to do binding and validation with a dynamic class. On the other hand, if the fields are known in advance, why use mapping or reflection at all?

One could create a ViewModel class that accepts some fileds, converts them to a dictionary (similar to what ExpandoObject would do) and use the dictionary as the backing store for the properties.

With a bit of C# magic like the CallMemberName attribute, the extra code is minimal. There is a runtime penalty for dictionary lookups, which only becomes apparent if there are a lot of reads/writes :

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

The setter for each property just sets a dictionary value using the property's name as the key. The getter uses CallerMemberName to get the property's name and use it as the key




回答3:


AM can map by default from IDictionary<string, object>(or DynamicObject) to any destination if the property names match. So you need to map from your structure to a dictionary, LINQ probably fits best, and then to your destination. The docs and the tests.



来源:https://stackoverflow.com/questions/46213943/net-convert-map-list-into-object-and-vis-versa

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!