Problem
I am currently adding automapping to my MVC project and I am stuck. Right now I have a User model used to represent data in the database. I have to map that model to a EditUserModel which will be used when the Edit method is called. The EditUserModel has IEnumerable<SelectListItem>
(for a dropdown menu) that I can't seem to figure out how to map.
Attempted Solution
As of right now I haven't tried to implement anything. I am unsure where the best place for the IEnumerable<SelectListItem>
or where to populate it. Right now it is being populated in the Controller.
User.cs
public class User
{
[Key]
public int UserID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public int RoleID { get; set; }
[ForeignKey("RoleID")]
public virtual Role Role { get; set; }
}
EditUserModel.cs
public class EditUserViewModel
{
[HiddenInput(DisplayValue = false)]
public int UserID { get; set; }
[Required]
public String Username { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[DisplayName("Role")]
[Required]
public int RoleID { get; set; }
//The trouble field
public IEnumerable<SelectListItem> Roles { get; set; }
}
Controller.cs
EditUserViewModel model = new EditUserViewModel();
//Population of the dropdown menu
model.Roles = context.Roles
.ToList()
.Select(x => new SelectListItem
{
Text = x.Description,
Value = x.RoleID.ToString()
});
//Mapping that the automaper will take care of
model.UserID = user.UserID;
model.Username = user.Username;
model.RoleID = user.RoleID;
For the record, here is what I was talking about in the comments to Jakub's answer:
public static class EnumerableExtensions
{
public static IEnumerable<SelectListItem> ToSelectList<T, TTextProperty, TValueProperty>(this IEnumerable<T> instance, Func<T, TTextProperty> text, Func<T, TValueProperty> value, Func<T, bool> selectedItem = null)
{
return instance.Select(t => new SelectListItem
{
Text = Convert.ToString(text(t)),
Value = Convert.ToString(value(t)),
Selected = selectedItem != null ? selectedItem(t) : false
});
}
}
Needless to say, this is dramatically simpler and accomplishes the same thing (and is actually more robust in the event that the property paths are non-simple, since Jakub's solution will not work with nested properties).
(This is not really an answer, I'm posting it as a community wiki just to help elaborate a point)
Controller is a perfect place to populate your view models.
You can remove plumbing code by using this extension method:
public static class EnumerableExtensions
{
public static IEnumerable<SelectListItem> ToSelectList<T, TTextProperty, TValueProperty>(this IEnumerable<T> instance, Expression<Func<T, TTextProperty>> text, Expression<Func<T, TValueProperty>> value, Func<T, bool> selectedItem = null)
{
return instance.Select(t => new SelectListItem
{
Text = Convert.ToString(text.ToPropertyInfo().GetValue(t, null)),
Value = Convert.ToString(value.ToPropertyInfo().GetValue(t, null)),
Selected = selectedItem != null ? selectedItem(t) : false
});
}
public static PropertyInfo ToPropertyInfo(this LambdaExpression expression)
{
MemberExpression body = expression.Body as MemberExpression;
if (body != null)
{
PropertyInfo member = body.Member as PropertyInfo;
if (member != null)
{
return member;
}
}
throw new ArgumentException("Expression is not a Property");
}
}
model.Roles = context.Roles.ToSelectList(r => r.RoleID, r => r.Description);