Question

I'm completely new to MVC and I'm trying to figure out why a validation summary isn't displaying. Here's the code:

view:

<% using(var form = Html.BeginForm("Create", "User"))
{%>
    <table>
        <thead>
            <th>Create User</th>
            <th><%= Html.ValidationSummary(false) %></th>
        </thead>
        <tbody>
            <tr>
                <td><%= Html.LabelFor(model => model.Creating.Username) %>:</td>
                <td><%= Html.TextBoxFor(model => model.Creating.Username) %></td>
            </tr>
            <tr>
                <td><%= Html.LabelFor(model => model.Creating.Firstname) %>:</td>
                <td><%= Html.TextBoxFor(model => model.Creating.Firstname)%></td>
            </tr>
            <tr>
                <td><%= Html.LabelFor(model => model.Creating.Lastname) %></td>
                <td><%= Html.TextBoxFor(model => model.Creating.Lastname) %></td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" value="Create" />
                </td>
            </tr>
        </tbody>
    </table>

<%}%>

Relevant controller method:

[HttpPost]
public ActionResult Create(User creating)
{
    var response = _service.Save(creating);
    if (response.Success)
        return RedirectToAction("Index");
    response.Errors.CopyToModelState(this.ModelState);
    return RedirectToAction("Index");
}

Business logic method:

public Response Save(User user)
{
    //Place Validation logic here
    //Check username is between 3-30 characters and make sure the username is unique
    //return response if username fails business rules

    bool isDataInvalid = false;
    List<ValidationError> errorList = new List<ValidationError>();
    if ((user.Username.Length < 3) || user.Username.Length > 30)
    {
        ValidationError invalidUsernameLengthError = new ValidationError();
        invalidUsernameLengthError.Property = "Creating.Username";
        invalidUsernameLengthError.ErrorMessage = "must be between 3 and 30 characters long";
        errorList.Add(invalidUsernameLengthError);
        isDataInvalid = true;
    }

    if (isDataInvalid)
    {
        return new Response()
        {
            Success = false,
            Errors = errorList
        };   
    }

    _repository.Save(user);

    return new Response()
    {
        Success = true
    };
}

Helper method:

public static void CopyToModelState(this List<ValidationError> errors,  ModelStateDictionary modelState)
{
    foreach (var error in errors)
    {
        modelState.AddModelError(error.Property, error.ErrorMessage);
    }
}

The logic does what it's supposed to, but nothing displays. I've checked the HTML that gets output, and the validation is just not being written. I've tried assigning properties of the model to the modelState and displaying validation directly on the relevant fields, but this doesn't work either. Any ideas?


Ah! doing a RedirectToAction causes a fresh request, so the error data is lost. Also I'm using a different controller so I need to explicitly call the original view (Index.aspx). Also the model that my index.aspx expects isn't actually a user object, it's a different list object, so I needed to do:

        var users = _service.FindAll();
        return View("Index", new UserListModel() { Users = users });

instead of the RedirectToAction. This is apparently the standard pattern for validating errors - for success (no errors) you use a RedirectToAction, but for errors, you need to return the correct view.


OK, thanks mattytommo - that's really useful. I'm still getting problems though. I now have this for the controller - similar to what you suggested, but still no display of the error messages. I tried the data annotation but couldn't get it to work (I'm using MVC2), and have been trying everything I can think of to get the existing code fixed.

    [HttpPost]
    public ActionResult Create(User creating)
    {
        var response = _service.Save(creating);
        if (response.Success)
            return RedirectToAction("Index");

        foreach (var error in response.Errors)
        {
            ModelState.AddModelError(error.Property, error.ErrorMessage);
        }

        return RedirectToAction("Index");
    }

Any more ideas? I appreciate the advice!

Was it helpful?

Solution

I think you're passing the ModelState variable by value, so changes you make in that function are not actually being kept. Try reproducing what your function does, but to the actual ModelState object, like so:

[HttpPost]
public ActionResult Create(User creating)
{
    var response = _service.Save(creating);
    if (response.Success)
        return RedirectToAction("Index");

    foreach (var error in response.Errors)
    {
        ModelState.AddModelError(error.Property, error.ErrorMessage);
    }

    return View(creating);
}

You should also check out MVC Data Annotations, validation like yours can simply be replaced by putting these two attributes on the Username property:

[MinLength(3), MaxLength(30)]
public string UserName { get; set; }

Or just one attribute using StringLength (thanks @SimonWhitehead):

[StringLength(30, MinimumLength = 3)]

Data annotations: Here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top