0

I'm working on a project using MVC3. I've first created a database (with the necessary constraints like PK and FK's) and added this database to my webapplicatie using the ADO.NET Entity Data Model. Everything seems to work fine but I can't make validation flexible. Here some code to clarify my question.


Edit: Added the View + correct models. This version is without importing a database but results the same.


--Models----

namespace CustomValidation.Models
{
    public class Person : IValidatableObject
    {   

    public int Id { get; set; }
    [Required]
    public String FirstName { get; set; }
    [Required]
    public String LastName { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (String.IsNullOrEmpty(FirstName))
        {
            var field = new[] { "FirstName" };
            yield return new ValidationResult("Firstname can't be null", field);
        }
    }
}
}



namespace CustomValidation.Models
{
    public class Address : IValidatableObject
    {

    public int Id { get; set; }
    [Required]
    public String Streetname { get; set; }
    [Required]
    public String Zipcode { get; set; }
    [Required]
    public String City { get; set; }
    [Required]
    public String Country { get; set; }
    public Person Person { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (String.IsNullOrEmpty(Streetname))
        {
            var field = new[] { "Streetname" };
            yield return new ValidationResult("Streetname can't be null", field);
        }
    }
}
}


public class MasterModel
{
    public List<Address> Addressess { get; set; }
    public Person Person { get; set; }
    }
}





namespace CustomValidation.Models
{
    public class DBEntities : DbContext
    {
    public DbSet<Person> People { get; set; }
    public DbSet<Address> Addressess { get; set; }
    }
}

----Controller----

namespace CustomValidation.Controllers
{
public class HomeController : Controller
{
    public ActionResult Index()
    {
        MasterModel Model = new MasterModel();
        Model.Person = new Person();
        Model.Addressess = new List<Address>();
        Model.Addressess.Add(new Address());
        Model.Addressess.Add(new Address());
        return View(Model);
    }


    [HttpPost]
    public ActionResult Index(MasterModel Model)
    {
        DBEntities _db = new DBEntities();
        if (TryValidateModel(Model.Person))
        {
            _db.People.Add(Model.Person);
            _db.SaveChanges();
        }
        else
        {
            return View(Model);
        }

        for (int i = 0; i < Model.Addressess.Count; i++)
        {
            if (!String.IsNullOrEmpty(Model.Addressess[i].Country))
            {
                if (TryValidateModel(Model.Addressess[i]))
                {
                    Model.Addressess[i].Person = Model.Person;
                    _db.Addressess.Add(Model.Addressess[i]);
                    _db.SaveChanges();
                }
                else
                {
                    return View(Model);
                }
            }
        }
        return RedirectToAction("Index");
    }
 }

Here is my View:

@model CustomValidation.Models.MasterModel
@{
ViewBag.Title = "Index";
}

<h2>Create</h2>

@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Person</legend>
<div class="editor-label">
@Html.LabelFor(m => Model.Person.FirstName)
@Html.EditorFor(m => Model.Person.FirstName)
</div>
<div class="editor-label">
@Html.LabelFor(m => Model.Person.LastName)
@Html.EditorFor(m => Model.Person.LastName)
</div>

</fieldset>
for (int i = 0; i < Model.Addressess.Count; i++)
{
<fieldset>
    <legend>Address</legend>

    <div class="editor-label">
        @Html.LabelFor(model => Model.Addressess[i].Streetname)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Addressess[i].Streetname)
        @Html.ValidationMessageFor(model => Model.Addressess[i].Streetname)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => Model.Addressess[i].Zipcode)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => Model.Addressess[i].Zipcode)
        @Html.ValidationMessageFor(model => Model.Addressess[i].Zipcode)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => Model.Addressess[i].City)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => Model.Addressess[i].City)
        @Html.ValidationMessageFor(model => Model.Addressess[i].City)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => Model.Addressess[i].Country)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => Model.Addressess[i].Country)
        @Html.ValidationMessageFor(model => Model.Addressess[i].Country)
    </div>

</fieldset>

}
    <p>
        <input type="submit" value="Create" />
    </p>
}

The problem (that I've noticed while debugging something like this) is that before the request gets inside of my action MVC first runs validators for your collection. So if I've only filled out Person in my form, while validating Model.Person it just return false (i guess because of the Required fields for Address) and so it will bounce back to my View.

I need to have some workaround that lets me decided which instances of my collection I want to validate in my action.

I've not been able to find any solution after a lot of time searching on the net.

I hope that someone can help me out.

PS. I'm quite a newbie with Visual Studio and MVC 3 so please don't be to hard on me :)

4
  • Let's not take the view for granted. Please add your view, as how the view is structured affects how the model will be bound when it is POSTed back to your action. Commented Oct 12, 2011 at 15:14
  • Ok, I will be home in a couple of hours and then I'll (re)post everything exactly. I didn't consider that the view could affect the way of how the model is bound. Commented Oct 12, 2011 at 15:44
  • Binding works by convention, extending to convention for naming elements in the view. Commented Oct 12, 2011 at 15:50
  • I'm sorry but I don't get it..? By the way: I've added all the code (including the view :) ) Commented Oct 12, 2011 at 19:36

1 Answer 1

2

In order to validate sub-models of the model returned to your action, you need to apply TryValidateModel to the specific sub-model, and use the overload which includes the prefix for the model. If you look at your generated HTML, you will see that the Person fields have Person as a prefix, the first Address fields have Addressess[0] as the prefix, and the second Address fields have Addressess[1] as the prefix.

In addition, you must clear all model errors before invoking TryValidateModel (or the existing errors will cause TryValidateModel to return false).

Do the following:

[HttpPost]
public ActionResult Index(MasterModel model)
{
    var reloadView = true;

    // clear any existing errors
    foreach (var key in ModelState.Keys)
        ModelState[key].Errors.Clear();

    if (TryValidateModel(model.Person, "Person"))   
    {   
        _db.People.Add(model.Person);   
        _db.SaveChanges();   
        reloadView = false;

        for (var i = 0; i < model.Addressess.Count(); i++)
        {
            foreach (var key in ModelState.Keys)
                ModelState[key].Errors.Clear();

            if (TryValidateModel(model.Addressess[i], "Addressess[" + i.ToString() + "]"))
            {
                **// UPDATED CODE**
                // add Person to Address model
                model.Addressess[i].Person = model.Person;

                _db.Addressess.Add(model.Addressess[i]);   
                _db.SaveChanges();   
            }
        }
    }

    if (reloadView)
    {
        // clear and re-add all errors
        foreach (var key in ModelState.Keys)
            ModelState[key].Errors.Clear();

        TryValidateModel(model);
        return View(model);
    }
    else
    {
        // go to some other view
    }
}
Sign up to request clarification or add additional context in comments.

9 Comments

Just awesome! Exactly what i needed. Thanks a lot!!!! You can't even imagine how happy I'm right now :). Thanks again!
Lol, I've got now another problem....When I try to use TryValidateModel the ModelState isn't returning false if the Model don't have the required the fields. It just tries to savechanges on db and crashes on SQL constraints....any ideas?
Oops, that reminds me of a change I made to your models. I removed from the models the inheritance from IValidatableObject and the Validate methods. IValidatableObject is unnecessary in your scenario, and may be the trouble, since I ran several different tests, and the correct validity was returned by TryValidateModel under every scenario I tested.
Ok I've tried to run the same but without IValidatableObject (instead i've used an imported data entity model). If i don't clear the modelstate it just bounces back as in the first scenario i've posted. If I DO clear the modelstate; TryValidateModel doesn't do hes job and my webapp keeps crashing on SQL constraints.....I'm not sure what i'm missing....? Let's say I keep clearing the errors for the modelstate....Is there some kind of method that let's you add manually entities that need to be checked by the modelstate on constraints before passing it to the TryValidateModel method? Thanks!
My bad. I missed that the problem is with constraints. If you notice, I coded so that Address entities would only be added if there is a valid Person entity. See the changes I made to the code above. If this doesn't work, then check your .edmx file to see the FK constraint between the Person object and the Address object.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.