2

I am trying to do a bit of a custom error handler. We have 4 tabs (using JQuery tabs), they are all build from one large model. Say for simplicity the model looks like:

myModel.HomeInfo
myModel.PhoneNumbers
myModel.Addresses
myModel.PersonalDetails

Each part is an object that have various bits of information. They all have attributes on them and validate messages.

At the top of the page (above the tabs) I want to display some top level errors, by that I mean the errors for attributes on the "myModel" object. This works when I do the:

foreach (ModelState state in viewData.ModelState.Values)

When I do:

@Html.ValidationSummary(false)

on my view I get all errors from each of the four objects and all their children, (more than 10). But when I go through the errors my self, (code above), I only get 2 errors, (the errors for "myModel" only, not its child properties).

I tried to use ILSPY to see what the validation summary is doing and replicate it. Believe I had the code pretty much line for line, but it still only got the two errors.

I do not know what magic is going on when I use the @Html.ValidationSummary().

What I want to know is how I can get all the errors for the whole object my self to be able to display some of the errors on each tab.

for clarification here is my basic model:

public class MemberProfileModel
{
    [CompanyTabValid]
    public CompanyInformationModel CompanyInformation { get; set; }
    [ContactTabValid]
    public ContactInformationModel ContactInformation { get; set; }
    [InvoiceTabValid]
    public InvoiceInformationModel InvoiceInformation { get; set; }
    [TabProductIdentificationMarkValid]
    public ProductIdentificationMarkModel ProductIdentificationMark { get; set; }
 }

public class CompanyTabValid : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        var model = value as CompanyInformationModel;
        if(model == null) throw new ArgumentNullException("value");

        var failed = new ValidationResult("Company information incomplete.");

        return model.IsValid ? ValidationResult.Success : failed;
    }
}

public class ContactInformationModel : BaseModel
{
    public ContactInformationModel()
    {
        PrimarySiteAddress = new AddressInformation();
        PrimarySiteContact = new ContactInformation();
        RegisteredOfficeAddress = new AddressInformation();
        RegisteredOfficeContact = new ContactInformation();

    }
    public override void Validate()
    {
        IsValid = PrimarySiteAddress.IsValid &&
                  PrimarySiteContact.IsValid &&
                  RegisteredOfficeAddress.IsValid &&
                  RegisteredOfficeContact.IsValid;
    }
    public AddressInformation PrimarySiteAddress { get; set; }
    public ContactInformation PrimarySiteContact { get; set; }
    public AddressInformation RegisteredOfficeAddress { get; set; }
    public ContactInformation RegisteredOfficeContact { get; set; }

}

public class AddressInformation : BaseModel
{
    public int Id { get; set; }
    public Guid MemberId { get; set; }
    /// <summary>
    /// This property is only here to make EF happy, do not use
    /// </summary>
    public int LocationTypeValue { get; set; }
    public LocationType LocationType { get { return (LocationType) LocationTypeValue; }  set { LocationTypeValue = (int) value;  } }

    [Required(AllowEmptyStrings = false, ErrorMessage = "Address Line 1 required.")]
    [Display(Name = "Address Line 1 *")]
    public string AddressLine1 { get; set; }

    [Display(Name = "Address Line 2")]
    public string AddressLine2 { get; set; }

    [Display(Name = "Address Line 3")]
    public string AddressLine3 { get; set; }

    [Required(AllowEmptyStrings = false, ErrorMessage = "Town required.")]
    [Display(Name = "Town *")]
    public string Town { get; set; }

    [Required(AllowEmptyStrings = false, ErrorMessage = "County required.")]
    [Display(Name = "County *")]
    public string County { get; set; }

    [Display(Name = "Country *")]
    public string Country { get; set; }

    [RequiredOneOfTwo("InterationalPostCode", ErrorMessage="PostCode or international PostCode are required.")]
    [Display(Name = "Post Code *")]
    public string PostCode { get; set; }

    [RequiredOneOfTwo("PostCode", ErrorMessage = "International PostCode or PostCode are required.")]
    [Display(Name = "International Post Code *")]
    public string InterationalPostCode { get; set; }

    public override void Validate()
    {

        if (string.IsNullOrEmpty(AddressLine1))
        {
            this.IsValid = false;
            return;
        }
        else if (string.IsNullOrEmpty(Town))
        {
            this.IsValid = false;
            return;
        }
        else if (string.IsNullOrEmpty(County))
        {
            this.IsValid = false;
            return;
        }
        else if (string.IsNullOrEmpty(Country))
        {
            this.IsValid = false;
            return;
        }
        else if (string.IsNullOrEmpty(PostCode) && string.IsNullOrEmpty(InterationalPostCode))
        {
            this.IsValid = false;
            return;
        }

        this.IsValid = true;
        return;
    }
}

I have shown an example of a validation attribute (some of ours are custom, some are normal), the top level MemberProfileModel = myModel in this example, and ContactInformationModel is one of its children which in turn has its own objects such as AddressInformation.

Thanks

tereško
  • 58,060
  • 25
  • 98
  • 150
Jon
  • 15,110
  • 28
  • 92
  • 132
  • 1
    see: http://stackoverflow.com/questions/573302/how-do-i-get-the-collection-of-model-state-errors-in-asp-net-mvc – emragins Aug 23 '12 at 15:55
  • @emragins I have seen this one and a few others. the modelstate dictionary only has two keys when I debug it. I don't see the other 8+ objects / keys that the validationsummary will return – Jon Aug 23 '12 at 16:04
  • 2
    Not sure, then. Using Chris McKenzie's code, I was able to get all my errors (from parent and numerous children) with no problems. Maybe it's an mvc3-mvc4 thing?? – emragins Aug 23 '12 at 16:12

3 Answers3

1

I found out why this wasn't working for me. As usual it was me being silly. Because the model has multiple layers / levels to it, I.e. model.someobject.someotherobject.someproperty, when I called tryValidateModel it would validate the top level but not the inner layers.

The solution to this was to ensure they are all called:

            TryValidateModel(mp);
            TryValidateModel(mp.ContactInformation.PrimarySiteAddress);
            TryValidateModel(mp.ContactInformation.RegisteredOfficeAddress);

So my solution is to either create a method to call try validate on each object level or create a refelctive method to do it for me.

Jon
  • 15,110
  • 28
  • 92
  • 132
0

In the post event of your page, in the controller just add this:

[HttpPost]
public ActionResult Create(TestViewModel testViewModel)
{
// If not Valid
if (!ModelState.IsValid)
{
   return this.View(testViewModel)
}
...
}

And don't forget to add your required and/or other validation to your viewmodel access methods:

[Required(ErrorMessageResourceType = typeof(Resources.MyProject), ErrorMessageResourceName = "validation_Test")]
public virtual string HomeInfo { get; set; }

And in your view:

<div class="editor-row">
        <div class="editor-label">
            @Html.LabelFor(model => model.HomeInfo)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.HomeInfo)
            @Html.ValidationMessageFor(model => model.HomeInfo)
        </div>
</div>
TrizZz
  • 1,200
  • 5
  • 15
  • I have updated my question to show what I had already done with my model. I am trying to get these errors on the GET rather than the post, (which the validationsummary seems happy to do but iterating the error explicitly doesn't return them). I do not want to have to define every validation message property for various reasons, one its verbose, two I want them in an UL LI so valid ones would cause an empty LI – Jon Aug 24 '12 at 13:52
  • @Jon I didn't know that the ModelState has errors in the GET? Would a better interpretation of your question then be, "How can I get all of the potential error messages for my model?" Or maybe your problem is trying to display them? I am very confused now what you're trying to accomplish. – emragins Aug 24 '12 at 17:23
  • @emragins sorry, yes. I validate my model before the GET (user can save bad data to be able to fix it later). I need to be able to get all the errors for that model programmitically. Thx – Jon Aug 28 '12 at 10:22
0

I got this from here: http://www.unknownerror.org/opensource/aspnet/Mvc/q/stackoverflow/1352948/how-to-get-all-errors-from-asp-net-mvc-modelstate

public static List<string> GetErrorListFromModelState
                                              (ModelStateDictionary modelState)
{
      var query = from state in modelState.Values
                  from error in state.Errors
                  select error.ErrorMessage;

      var errorList = query.ToList();
      return errorList;
}
sobelito
  • 1,525
  • 17
  • 13