0

I have an ASP.NET MVC 5 project. I have some custom validation needs. I know that in the end, a possible result for my HTML looks like this:

<div class="form-group has-error">
  <label for="field">Field Label</label>
  <div id="field">
    <input name="field" class="form-control" type="text" autocomplete="off">
  </div>
  <div>
    Field label is invalid. You need to do something else.
  </div>
</div>

The validation errors are stored in a Dictionary<string, string> in the ViewBag. I was thinking of doing something similar to the following:

@string fieldErrorMessage = getFieldError('field', ViewBag.ValidationErrors)
<div class="form-group @if(fieldErrorMessage.length >0) { 'has-error' } ">
  <label for="field">Field Label</label>
  <div id="field">
    <input name="field" class="form-control" type="text" autocomplete="off">
  </div>
@if (fieldErrorMessage.length > 0) {
  <div>
    @fieldErrorMessage
  </div>
}
</div>

My problem is, I do not know where to define getFieldError. I would like to use this function in multiple views. So I'm not sure a. where to define it. or b. how to call it. I'm not even sure if my approach to applying 'has-error' is correct. I feel like I have pseudocode more than mvc code.

Pluto
  • 2,900
  • 27
  • 38
user70192
  • 13,786
  • 51
  • 160
  • 240
  • 1
    Just curious, but why aren't you using the built in validation message handling? – rossisdead Sep 23 '14 at 18:22
  • @rossisdead Personally I always use built-in validation, but there are annoying problems with the updating the Model, which don't have an obvious solution. For example, [redisplaying changed values with `Html.EditorFor()`](http://stackoverflow.com/a/2678956/1507941). – Pluto Sep 24 '14 at 15:53

1 Answers1

2

Embedded functions in the page

For including functions in the page, you have two options.

Extending HtmlHelper:

You can extend HtmlHelper so that you can call Html.getFieldError("field"). Because ViewBag is in HtmlHelper, you won't need to pass that into the function. The best way to demonstrate this is by showing an example.

public static class ErrorValidation
{
   public static MvcHtmlString getFieldError(this HtmlHelper<TModel> h, string f)
   {
       if(h.ViewBag.ValidationErrors.ContainsKey(f))
       {
           return MvcHtmlString.Create(h.ViewBag.ValidationErrors[f]);
       }
       return MvcHtmlString.Empty;
   }
}

Adding a namespace in your views:

You can include a namespace in your views by adding a line to the Views\Web.config file. Then, you could use static methods like you planned. This is done by adding a line of something like <add namespace="MyProj.Validation" /> inside of <configuration><system.web.webPages.razor><pages><namespaces>. In addition, you can leave this out by calling the full reference to your function each time with MyProj.Validation.getFieldError(...).


Relying on MVC error handling

You can also use API's already built into MVC, which do allow for customized validation.

Doing error checks through model attributes:

The most simple way to do validation is by adding attributes to your model. If your model had a required field, you can simply add [Required] above the field in the class that defines your model. A plethora of validation attributes are provided by System.ComponentModel.DataAnnotations.

If you wanted to do a custom check, you could create your own attribute by implementing abstract class ValidationAttribute. Simply define the IsValid function with your custom logic.

If you have validation checks that need to happen on multiple fields in your model at the same time, you can have your model implement IValidatableObject. And to make this simpler, in the Validate function you don't need to create a list and add all your errors to that; simply return each ValidationResult object by putting the keyword yield at the beginning of the line. This would look like...

public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
    // Duplicate checks
    List<String> fields = new List<String>();
    for (var i=0; i<PhoneNumbers.Count; i++)
    {
        var item = PhoneNumbers[i];
        if (PhoneNumbers.IndexOf(item) != PhoneNumbers.LastIndexOf(item))
        {
            fields.Add("PhoneNumbers["+i+"]");
        }
    }
    if(fields.Count > 0)
    {
        yield return new ValidationResult("You cannot include duplicate phone numbers.", fields.ToArray());
    }
    // More validation checks
}

Doing error checks in the controller:

You can also do error checks in the controller, which allows for validation to vary depending on the action. This also lets you use the validation that already happened in the model with the ModelState object. In order to add errors to ModelState, simply call ModelState.AddModelError(). In order to check if a model is valid after all checks are done, you can check ModelState.IsValid.

Displaying errors in the view:

With validation happening in the ModelState object, you can display errors in your view by using the Html object. This allows you to generate a list of errors by calling Html.ValidationSummary() or display errors for individual properties with Html.ValidationMessageFor(...). Here's an extensive example...

for (var x = 0; x < Model.PhoneNumbers.Count(); x++ )
{
    <tr>
        <td>@Html.EditorFor(m => m.PhoneNumbers.ElementAt(x))</td>
        @if(ViewData.ModelState["PhoneNumbers[" + x + "]"].Errors.Any())
        {
            <td>@Html.ValidationMessageFor(m => m.PhoneNumbers.ElementAt(x))</td>
        }
        else
        {
            <td>Ok!</td>
        }
    </tr>
}
Pluto
  • 2,900
  • 27
  • 38