What is the recommended way to handle binding a complex viewmodel? For example, using the objects below:
class PersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
//I have also tried a generic object instead of dynamic with the same results
public dynamic PreferredSportsTeam { get; set; }
}
class SportsTeamViewModel
{
public string Name { get; set; }
public string City { get; set; }
}
class BaseballTeamViewModel : SportsTeamViewModel
{
public double TeamERA { get; set; }
}
class HockeyTeamViewModel : SportsTeamViewModel
{
public int TotalSaves { get; set; }
}
I am trying to put either an instance of a BaseballTeamViewModel OR a HockeyTeamViewModel into the "PreferredSportsTeam" property of the PersonViewModel and then pass that PersonViewModel to the Edit View to allow the user to edit all of the fields.
I have successfully used EditorTemplates to display the corresponding View of the object stored in the "PreferredSportsTeam" property. However, when submitting the Edit Form the MVC model binder cannot reconstruct whatever object was stored in the PreferredSportsTeam. It does correctly fill the FirstName and LastName properties but the PreferredSportsTeam property always returns as a generic object that I cannot cast into anything else.
Is there something that I am missing or is it simply not possible with the default MVC model binder? I am aware of custom model binders and think they may be a solution but I am not knowledgeable enough about them to understand how they could be used to fix the problem.
Below code sample shows how to use dynamic object :
var team = new Team();
team.FirstName = "FirstName";
team.LastName = "LastName";
var baseballTeam = new BaseBallTeam();
baseballTeam.TotalSaves = 100;
team.PreferredSportsTeam = new ExpandoObject();
team.PreferredSportsTeam.BaseBallTeam = baseballTeam;
See This for more detailed explanation.
Related
As a follow on from my previous question (MVC abstract ViewModel, retain Validation Attributes (dynamically)) I thought I'd ask the question in an alternate version.
So, let's consider the same situation, where I have a core ViewModel:
public class SampleViewModel {
[Required]
public string Property1 { get; set; }
[Required]
[EmailAddress]
public string Property2 { get; set; }
public IList<AnotherModel> Items { get; set; }
}
And another model:
public AnotherModel {
public string Value { get; set; }
}
And then within a controller, I perform the following:
var model = new SampleViewModel();
var fields = new List<AnotherModel>() {
new AnotherModel() { Value = model.Property1 },
new AnotherModel() { Value = model.Property2 },
};
So, my question is, how can I get the AnotherModel models to respond to the properties that are passed to their respective Value property.
In the sample above, the first AnotherModel will be Required, and the second will be Required, and an EmailAddress.
How is this possible?
Thank you
Update
For the purpose of this, lets say that each of those AnotherModel objects is represented by a form field. When the form is posted back, I use a custom model binder to obtain the Value from the AnotherModel and place it back into the source property (so Property1). My model is reconstructed correctly, and ModelState.IsValid is working. So, I have server-side validation of my SampleViewModel on post-back. Can this be somehow passed to the client to validate for me, based on the model's validation attributes?
Thanks
Goal: to save ViewModel object by Entity Framework. I have UserViewModel object which has list of UnitViewModel. Then, I have a UserAdapter class which converts UserViewModel into Entity Framework User object (see Convert()below how).
Now, my question is how do I convert this list of UnitViewModel to its corresponding Entity Framework Unit list? - Do I have to get each object from DB Context by calling something like context.Units.Where(u=>myListofUnitIDs.Contains(u.UnitID))?
public class UserViewModel
{
public Guid? UserID { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public DateTime? CreateTime { get; set; }
public List<UnitViewModel> UserUnits { get; set; }
}
public class UnitViewModel
{
public Guid UnitID { get; set; }
public string Name { get; set; }
public int? SortIndex { get; set; }
public DateTime CreateTime { get; set; }
public bool Assigned { get; set; }
}
public class UserAdapter
{
public static User Convert(UserViewModel userView)
{
User user;
if (userView.UserID.HasValue)
{
using (var provider = new CoinsDB.UsersProvider())
{
user = provider.GetUser(userView.UserID.Value);
}
}
else
{
user = new User();
}
user.FirstName = userView.FirstName;
user.LastName = user.LastName;
user.Password = StringHelper.GetSHA1(userView.Password);
user.UserName = user.UserName;
user.CreateTime = DateTime.Now;
// Problem here :)
// user.Units = userView.UserUnits;
return user;
}
}
UPDATE: The main concern here is that I have to retrieve each Unit from database to match (or map) it with ViewModel.Unit objects, right? Can I avoid it?
For your information, this operation is called as Mapping mainly. So, you want to map your view model object to the entity object.
For this, you can either use already existed 3rd party library as AutoMapper. It will map properties by reflection which have same name. Also you can add your custom logic with After method. But, this approach has some advantages and disadvantages. Being aware of these disadvantages could help you to decide whether you must use this API or not. So, I suggest you to read some articles about advantages and disadvantages of AutoMapper especially for converting entities to other models. One of such disadvantages is that it can be problem to change the name of one property in the view model in the future, and AutoMapper will not handle this anymore and you won't get any warning about this.
foreach(var item in userView.UserUnits)
{
// get the mapped instance of UnitViewModel as Unit
var userUnit = Mapper.Map<UnitViewModel, UserUnit>(item);
user.Units.Add(userUnit);
}
So, I recommend to write your custom mappers.
For example, I have created a custom library for this and it maps objects lik this:
user.Units = userView.UserUnits
.Select(userUnitViewModel => userUnitViewModel.MapTo<UserUnit>())
.ToList();
And I am implementing these mapping functions as:
public class UserUnitMapper:
IMapToNew<UnitViewModel, UserUnit>
{
public UnitViewModel Map(UserUnit source)
{
return new UnitViewModel
{
Name = source.Name,
...
};
}
}
And then in runtime, I am detecting the types of the objects which will be used during mapping, and then call the Map method. In this way, your mappers will be seperated from your action methods. But, if you want it urgently, of course you can use this:
foreach(var item in userView.UserUnits)
{
// get the mapped instance of UnitViewModel as Unit
var userUnit= new UserUnit()
{
Name = item.Name,
...
};
user.Units.Add(userUnit);
}
What are good strategies for rebuilding/enriching a nested or complex ViewModel?
A common way to rebuild a flat ViewModel is shown here
But building and rebuilding a nested ViewModel using that method is too complex.
Models
public class PersonInfo
{
public int Id { get; set; }
public string Name { get; set; }
public int Nationality { get; set; }
public List<Address> Addresses { get; set; }
}
public class Address
{
public int AddressTypeID { get; set; }
public string Country { get; set; }
public string PostalCode { get; set; }
}
public class AddressType
{
public int Id { get; set; }
public string Description { get; set; }
}
view models
public class PersonEditModel
{
public int Id { get; set; }
public string Name { get; set; } //read-only
public int Nationality { get; set; }
public List<AddressEditModel> Addresses { get; set; }
public List<SelectListItem> NationalitySelectList { get; set; } //read-only
}
public class AddressEditModel
{
public int AddressTypeId { get; set; }
public string AddressDescription { get; set; } //read-only
public string Country { get; set; }
public string PostalCode { get; set; }
public List<SelectListItem> CountrySelectList { get; set; } //read-only
}
actions
public ActionResult Update(int id)
{
var addressTypes = service.GetAddressTypes();
var person = service.GetPerson(id);
var personEditModel= Map<PersonEditModel>.From(person);
foreach(var addressType in addressTypes)
{
var address = person.Addresses.SingleOrDefault(i => i.AddressTypeId == addressType.Id)
if(address == null)
{
personEditModel.Addresses.Add(new AddressEditModel
{
AddressTypeId = addressType.Id
});
}
else
{
personEditModel.Addresses.Add(Map<AddressEditModel>.From(address));
}
}
EnrichViewModel(personEditModel, person, addressTypes); //populate read-only data such as SelectList
return Index(personEditModel);
}
[HttpPost]
public ActionResult Update(PersonEditModel editModel)
{
if(!ModelState.IsValid)
{
var person = service.GetPerson(editModel.Id);
var addressTypes = service.GetAddressTypes();
EnrichViewModel(editModel, person, addressTypes);
return View(editModel);
}
service.Save(...);
return RedirectToAction("Index");
}
//populate read-only data such as SelectList
private void EnrichViewModel(PersonEditModel personEditModel, Person person, IEnumerable<AddressType> addressTypes)
{
personEditModel.Name = person.Name;
personEditModel.NationalitySelectList = GetNationalitySelectList();
foreach(var addressEditModel in personEditModel.Addresses)
{
addressEditModel.Description = addressTypes.Where(i => i.Id = addressEditModel.AddressTypeId).Select(i => i.Description).FirstOrDefault();
addressEditModel.CountrySelectListItems = GetCountrySelectList(addressEditModel.AddressTypeId);
}
}
My code for building and rebuilding the ViewModels (PersonEditModel and AddressEditModel) is too ugly. How do I restructure my code to clean this mess?
One easy way is to always build a new view model instead of merging/rebuilding since MVC will overwrite the fields with the values in ModelState anyway
[HttpPost]
public ActionResult Update(PersonEditModel editModel)
{
if(!ModelState.IsValid)
{
var newEditModel = BuildPersonEditModel(editModel.Id);
return View(newEditModel);
}
but I'm not sure that this is a good idea. Is it? Are there other solutions besides AJAX?
I'm going to tackle your specific pain points one-by-one and I'll try to present my own experience and likely solutions along the way. I'm afraid there is no best answer here. You just have to pick the lesser of the evils.
Rebuilding Dropdownlists
They are a bitch! There is no escaping rebuilding them when you re-render the page. While HTML Forms are good at remembering the selected index (and they will happily restore it for you), you have to rebuild them. If you don't want to rebuild them, switch to Ajax.
Rebuilding Rest of View Model (even nested)
HTML forms are good at rebuilding the whole model for you, as long as you stick to inputs and hidden fields and other form elements (selects, textarea, etc).
There is no avoiding posting back the data if you don't want to rebuild them, but in this case you need to ask yourself - which one is more efficient - posting back few extra bytes or making another query to fetch the missing pieces?
If you don't want to post back the readonly fields, but still want the model binder to work, you can exclude the properties via [Bind(Exclude="Name,SomeOtherProperty")] on the view model class. In this case, you probably need to set them again before sending them back to browser.
// excluding specific props. note that you can also "Include" instead of "Exclude".
[Bind(Exclude="Name,NationalitySelectList")]
public class PersonEditModel
{
...
If you exclude those properties, you don't have to resort to hidden fields and posting them back - as the model binder will simply ignore them and you still will get the values you need populated back.
Personally, I use Edit Models which contain just post-able data instead of Bind magic. Apart from avoiding magic string like you need with Bind, they give me the benefits of strong typing and a clearer intent. I use my own mapper classes to do the mapping but you can use something like Automapper to manage the mapping for you as well.
Another idea may be to cache the initial ViewModel in Session till a successful POST is made. That way, you do not have to rebuild it from grounds up. You just merge the initial one with the submitted one in case of validation errors.
I fight these same battles every time I work with Forms and finally, I've started to just suck it up and go fully AJAX for anything that's not a simple name-value collection type form. Besides being headache free, it also leads to better UX.
P.S. The link you posted is essentially doing the same thing that you're doing - just that its using a mapper framework to map properties between domain and view model.
Based off of the example located here:
Multiple ViewModels in View
Can anyone show this example without adding the individual items but by showing a datasource. So for instance if my business Logic layer has the means to pull the customer address then my two models would be CustomerInfo & CustomerLocations - doing it this way I am having trouble with the last line shown (the .Add) .. I tried removing the ToList but either way it says I have invalid arguements - my ViewModel class looks exactly like the example.
var ccus = new List<ViewModel.CustomerInfo>();
var cloc = new List<ViewModel.CustomerLocations>();
var cust = new ViewModel.Customers();
var cI1 = new Business.CustomerLogic.BLCustomerAddress();
cI1.LoadCustomerAddress(decryConcept, decryBnumber, intCustid).ToList();
ccus.Add(cI1);
In the previous example he declares the code inline
var car1 = new Car
{
Id = 1,
Name = "Passat"
};
I want to replace this piece with a call to my Business Logic
I have no reference to the BusinesLogic model in the new ViewModel I have setup per the example and perhaps this is where I need to tie the two objects together. Here is my code for that piece.
public class ViewModel
{ public class Customers
{ public IEnumerable<CustomerInfo> CInfo { get; set; }
public IEnumerable<CustomerLocations> CLoc { get; set; }
}
public class CustomerInfo
{
public int CustomerID { get; set; }
public string AccountNo { get; set; }
public bool Active { get; set; }
public string Company { get; set; }
.........
ccus is of type List<ViewModel.CustomerInfo> so the add method expects a ViewModel.CustomerInfo but you are passing in cI1 which is of type Business.CustomerLogic.BLCustomerAddress. Calling ToList() won't do anything as you are not storing the result of LoadCustomerAddress, so turning the result into a list has no affect on anything.
I have a class at my Domain layer something as below:
public class Employee : AuditableEntity
{
public string FirstName { get; set; }
public string Surname { get; set; }
public double Salary{ get; set; }
public int PhoneNo { get; set; }
public double Bonus
{
get { return Salary + EmployeeAdditionals.Sum(e => e.Value); }
}
// virtual allow lazy loading
public virtual ReadOnlyCollection<EmployeeAdditional> EmployeeAdditionals { get; private set; }
// Paramterless constructor for EF
public Employee()
{
EmployeeAdditionals = new List<EmployeeAdditional>();
}
public void AddAdditionalInfo(EmployeeAdditional additionalInfo)
{
additionalInfo.Employee = this;
additionalInfo.EmployeeId = Id;
((Collection<EmployeeAdditional>) EmployeeAdditionals).Add(additionalInfo);
}
I then have the following viewModel class in my MVC 5 Application:
public class EmployeeDetailsViewModel : BaseViewModel
{
public EmployeeDetailsViewModel()
{
Employee = new Employee();
}
public Employee Employee { get; set; }
//Other fields
}
public class Employee
{
[DisplayName("Employee First Name")]
public string FirstName { get; set; }
[DisplayName("Employee Surname")]
public string Surname{ get; set; }
public double Salary{ get; set; }
[RegEx for Phone Number and Error Mesage]
public int PhoneNo{ get; set; }
public double Bonus { get; set; }
}
I have mapping then in my controllers and viewModel builder to map my viewmodel to my domain model and domain model back to view model and I am attempting to use AutoMapper for this.
Using manual mapping something like below:
// set domain from viewModel
employee.FirstName= model.Employee.FirstName;
//etc for rest of fields
// set view model from domain
viewModel.Employee.FirstName = employee.FirstName;
//etc for rest of fields
Now in my AutoMapperBootstrapper class in Setup method I have the following:
Mapper.CreateMap<Domain.Models.Employee, Employee>();
Mapper.CreateMap<Employee, Domain.Models.Employee>();
Then I am using this is following in my controller and viewmodel builder:
//set domain from viewModel
Mapper.Map(employee, model.Employee);
// set viewmodel from domain
Mapper.Map(viewModel.Employee, employee);
When I hit a breakpoint on mapping from domain to viewModel I see the model.Employee with the values from screen but when I step over this line the values do not get mapped to domain employee and then the model.Emplyee values are reset to null for the strings, 0.0 for double, etc.
Is there something incorrect with my configuration or use of automapper?
You pass wrong parameter. If you want to use existing instance, make sure first parameter is source and second parameter is destination.
Try to switch the parameter.
//set domain from viewModel (viewModel is the source)
Mapper.Map(model.Employee, employee);
^^^^^ ^^^^^
source destination
// set viewmodel from domain (domain is the source)
Mapper.Map(employee, viewModel.Employee);
^^^^^ ^^^^^
source destination
Shouldn't your map code actually be like this? You have to use instances for Map(), it looks like you are using Types
//set domain from viewModel
Mapper.Map(viewEmployee, new model.Employee());
// set viewmodel from domain
Mapper.Map( employee, new viewmodel.Employee());
This is how I would expect you to use it:
var viewModel = Mapper.Map<ViewModel.Employee>(domainModel); <-- this creates a ViewModel for a Domain Model
and the reverse would be:
var domainModel = Mapper.Map<Domain.Employee>(viewModel);