问题
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