views:

206

answers:

2

I've got a form with a dropdownlist in my MVC app. Now that I'm trying to add validation to the mix it seems that a dropdownlist fails validation no matter what it's value is.

Without the validation it will allow the controller to work and redirect as planned. With the validation it does seem to allow the database changes to occur but ModelState.IsValid is false.

I'm stuck. Is this a known issue?

View:

<label for="parent">Child of:</label>
<%= Html.DropDownList("parent", (SelectList)ViewData["pageList"])%>
<%= Html.ValidationMessage("parent") %>

Controller action:

[AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
[ValidateAntiForgeryToken()]
public ActionResult Create(Page page)
{
    try
    {
        pageRepository.Insert(page);
    }
    catch (RuleException ex)
 {  
     ex.CopyToModelState(ModelState);
 }

 if (!ModelState.IsValid)
 {
     var pageSelectList = pageRepository.GetTop().ToList();
     pageSelectList.Add(new Page
 {
     menuTitle = "None"
 });
     ViewData["pageList"] = new SelectList(pageSelectList.OrderBy(x => x.listOrder), "ID", "menuTitle");
     return View();
 }
 return RedirectToAction("List");
}

The error returned is: The value 'x' is invalid. Where 'x' is the numeric value of the current selection. The failure occurs no matter what the chosen value is.

public class Page
{
    private EntityRef<Page> _parent = default(EntityRef<Page>);
    private EntitySet<Page> _children = new EntitySet<Page>();

    public int ID { get; set; }
    public string pageTitle { get; set; }
    public string menuTitle { get; set; }
    public string content { get; set; }
    public int listOrder { get; set; }
    public bool visible { get; set; }
    public int parent { get; set; }
    public DateTime? created { get; set; }
    public DateTime? edited { get; set; }
    public string createdBy { get; set; }
    public string lastEditBy { get; set; }
    public string linkInfo { get; set; }
    public bool IsSelected { get; set; }

    public Page Parent
    {
        // return the current entity
        get { return this._parent.Entity; }
        set { this._parent.Entity = value; }
    }

    public EntitySet<Page> Children
    {
        get { return this._children; }
        set { this._children.Assign(value); }
    }

    public static Page Error404()
    {
        return (new Page
        {
            content = "<p>Page not found</p>",
            pageTitle = "404.  Page not found"
        });
    }   
}

Here's what I tried for a workaround:

public ActionResult Create([Bind(Exclude="parent")] Page page)
{
    page.parent = Convert.ToInt32(Request.Form["parent"]);
...

I just excluded the dropdownlist from the ModelBinding and reloaded it back in via the Request.Form. Is it good practice?

A: 

What's throwing the RuleException? I'm assuming you're using some sort of validation engine to determine whether the "parent" property is valid or not. I'd step through to see why this exception is being thrown. Maybe the value isn't passing into your controller action correctly or maybe your validation rules are different than what you think they are.

Kevin Pang
That's just it. I wasn't testing for parent at all. It's a bound field in the model but there was no test to determine it's validity. I'm thinking that since it's defined as an int in the DomainModel and the value of a dropdownlist is always a string that may be the reason that it failed validation automatically during binding.
mark123
If that is the case how do I bind a DropDownList to an int?
mark123
You should paste your Page class so that we can see it. The fact that it's throwing a "RuleException" tells me you're using some sort of validation engine that will throw this when trying to save an invalid Page object to the database. In which case, you need to figure out what rule it's violating. Maybe there's a min/max value placed on it?
Kevin Pang
Yes, of course. Sorry about that. See the answer above, it won't fit in a comment.
mark123
Hmm...I'm still not sure where the RuleException is being thrown from. I'm guessing you have the validation checks in the pageRepository.Insert call? Have you tried stepping in and seeing where the exception is being thrown from? It has to be thrown from somewhere, otherwise your ModelState wouldn't be flagging as invalid.
Kevin Pang
I have stepped through the validation but I don't see anything pertinent which could be due to the fact that I'm not checking the DropDownList in the validation.
mark123
I think the issue occurs in DefaultModelBinder considering the errors thrown are the default errors.
mark123
A: 

I ended up testing against ModelState["parent"].Value.AttemptedValue instead of the entity property which was nulling out at the attempt to put a string into an int?.

mark123
I should also mention that I clear the model state: ModelState.Clear();Then add the errors and the values back in place: ModelState.AddModelError(field, error); ModelState.SetModelValue(field, ValueProvider[field]);
mark123