问题
In my ViewModel (also in my Domain model), I have kinda dynamic Property Structure where the Profile Elements are a List of the base class ProfileVM
and refer to a ProfileDefinitionElement (just to explain the ViewModel without pasting the full thing).
public class OwnProfileVM
{
public OwnProfileVM() {}
public ProfileDefinitionVM ProfileDefinitionVM { get; set; }
public ProfileVM ProfileVM { get; set; }
}
So I bind my Properties using a Linq Single statement:
@Model.ProfileDefinitionVM.ProfileElementDefinitions.Single(p => p.Key == ProfileElementKey.CompanyName.ToString()).Title
This works for showing data. But when posting back like this:
@Html.TextBoxFor(model => ((ProfileElementTextVM)model.ProfileVM.ProfileElements
.Single(p=> p.ProfileElementDefinition.Key == ProfileElementKey.CompanyName.ToString()))
.Text
..the model properties are null.
This is because of the parameterless constructor which builds the OwnProfileVM
object without any properties filled in.
After some research I found out that there are two ways to solve this:
- "Flatten" the ViewModel. So I would have a fixed Property for every Profile Element. This would work, but the disadvantage would be that I couldn't map the data with the Automapper. I would have to fill the ViewModel to the Model "manually". This would result in more Code in the Controller and a "bigger", but simpler ViewModel. Seen in this article
- Find a way to pass the Definition data into the ViewModel Constructor to build the list of Properties before posting back.
Now my questions:
- Is the second way even possible and if yes, how would this be done? I havent found a way to do this.
- If the first question can be answered with yes, which way would you prefer?
回答1:
Looks complicated. It may be best to simplify it a bit.
In my experience, model properties are null in the controller because the binder cannot understand how to link the form element name with the associated property. For example, I've seen it with lists where foreach has been used:
(model has a) List<Something> Somethings.....
foreach (Something thing in Model.Somethings)
{
@Html.EditorFor(m => thing)
}
This is rendered in the resulting html as <input name="thing".....
which is useless. The solution here is to use a for loop and access the model's properties via their path rather than copying pointers to instances, such as:
for (int i = 0; i < Model.Somethings.Count; i++)
{
@Html.EditorFor(m => Model.Somethings[i])
}
This is then rendered with the correct <input name="Model.Somethings[i]".....
and will be understood by the model binder.
I expect this issue you're facing here is similar. You need to add the necessary accessors to your properties so that the correct names and ids can be rendered in your view and picked up by the binder.
I'm not sure of the exact definition of your class so this example is not likely to be completely right.
This class includes a this[string index] method which will get and set the element using your property key as the index:
public class ProfileElements : List<ProfileElement>
{
public ProfileElement this[string index]
{
get
{
return base.First(p => p.ProfileElementDefinition.Key == index);
}
set
{
base[index] = value;
}
}
}
And in your view, you could use this like:
@Html.TextBoxFor(model => model.ProfileVM.ProfileElements[ProfileElementKey.CompanyName.ToString()].Text)
Hopefully, this will do what you need.
来源:https://stackoverflow.com/questions/34997672/mvc-viewmodel-binding-construction-vs-flattening