Validation propagating to related objects
I have a model like this:
public class Person
{
public int ID { get; set; }
[Required(ErrorMessage="Name cant be empty")]
public string Name { get; set; }
public Person Friend { get; set; }
}
I want to create a new Person, and made a form with the fields using the strongly typed HtmlHelper
- ID
- Name
- Friend ID (dropdown with options like
<option value="Friend.ID">Friend.Name</option>
When posting the form, my controller takes in a Person object (p
), which is bound using the default modelbinder. Just to be explicit, the modelbinder does the f开发者_StackOverflow中文版ollowing: The ID
and Name
properties are bound as expected. The Friend
is set to a new Person
instance whose ID
equals the ID of the person I chose in the dropdown. The Friend.Name
's value is null
because I didn't provide a value for it in the form.
The problem: I want the RequiredAttribute
to fire on the Name
textbox if it is empty - and it does. The problem is that it also fires on the Friend
's name attribute. So when I post, filling in all the fields, I get that the ModelState is invalid and the error being that p.Friend.Name
is required.
How would I solve this problem? Of couse in this case I don't want to validate the Friend
's properties. I've thought of:
- Using ViewModels for my views, which will somehow solve my problems. I haven't tried this yet as I feel I really shouldn't need to for such a simple problem
- Sending the friend's ID as a separate parameter,
friend_id
, and binding theFriend
property manually. Only theID
andName
attributes of the posted person is bound and I manually set theFriend
property. This involves getting theFriend
from my repository using thefriend_id
to make it a "real" Person object. - Checking the ModelState and removing errors I know don't count. This is just plain wrong and non-scalable (have to remember to do this if I add for example a
SecondFriend
property
I feel option 2 is the most feasable, but ideally I'd like it to be automatic. In addition, I can't use the strongly typed helper, as the friend_id
textbox' name
attribute must match the action method's parameter name.
I feel there's some point I've missed that would make this easier. Hopefully I'm right. Although I think it's a bit tedious using a ViewModel, it that's the correct thing to do, please do tell.
Edit
For now solved the problem using ViewModels with ID
, Name
and Friend_id
as its properties and the same validation attributes as the Person
model. I then map the ID
and Name
values over to a new Person
instance. The Friend property is then set by loadning the specified friend from the repository. Like newPerson.Friend = repository.Get(viewModel.Friend_id)
When I get the time I plan on looking closer at AutoMapper to do this "automatically".
The problem is that model state dictionary keys only have property name as the key. So in your case there are two of them that have the same key: Name
on Person
and Name
on Friend
.
I've always struggled to make things like this work. It would be best if key names would have notation like VarName.PropertyName
. In this case it would work, since you'd have two different ones:
p.Name
p.Friend.Name
Having ClassName.PropertyName
wouldn't work, since you could have two action parameters of the same type but with different name.
But how should one tackle this problem? One way would be to create your own Action filter that does model validation and fill model state errors (by first clearing them all). It would also mean that you'd have to use the non-lambda versions of Html extension methods. So instead of Html.TextBoxFor
, you'd have to use Html.TextBox
extension and provide your custom keys.
To make things more generic on the view side, you could of course write your own Html extension overloads (the ones that you use) that would take an additional parameter of parameter name like:
Html.TextBoxFor("p", model => model.Friend.Name);
That would then generate
<input type="text" name="p.Friend.Name" id="p_Friend_Name" />
And believe me, default model binder will be able to consume these kind of input names. It has the knowledge of parameter names and their properties.
The problem is that both your Person
and Friend
are of the same type, and they have a required attribute on Name
property.
Just to make sure I understand this question
You're providing following values:
p.Name
p.ID
p.Friend.ID
but not p.Friend.Name
.
Solution
I suggest you put an additional hidden input field on your view and provide either the correct value for it or just some dummy value if you don't need it on the server (when all you'll use is friend's ID).
This way your validation won't break on p.Friend.Name
.
精彩评论