views:

39

answers:

2

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>

alt text

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 following: 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. alt text

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:

  1. 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
  2. Sending the friend's ID as a separate parameter, friend_id, and binding the Friend property manually. Only the ID and Name attributes of the posted person is bound and I manually set the Friend property. This involves getting the Friend from my repository using the friend_id to make it a "real" Person object.
  3. 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".

A: 

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.

Robert Koritnik
Thanks for answering, but if I read you correctly, this solution is for separating/fixing model binding of the Person.Name field and Person.Friend.Name fields. But there is no Person.Friend.Name field, since all i want is a reference to Person.Friend.ID so that I know its primary key (and can set the relation in the database). My example of using a textbox for the friend's ID may have been a bad one. Instead consider it as a dropdown with value=Person.Friend.ID. I currently fell back on using viewModels, as I discovered it had some strenghts
serex
A: 

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.

Robert Koritnik
Yes, you understood it correctly. That will work, but as a generic solution it's not very scalable. In one of my real cases I have 3 referenced objects and they all contain several properties which will trigger validation errors, and putting hidden fields for all of them won't be very elegant).
serex