2

I have a PartialView that contains a form that asynchronously posts data to my controller. The controller will add the user if the ModelState is valid, if not, it will return the PartialView with the invalid model. The problem I'm running into is that the ModelState is always valid no matter what. I can see that the form is being serialized properly and all the properties for DynamicActionUserModel.RegisterModel are being populated.

I don't understand why this would be the case but is it possible the Model Binding isn't working because I have a model within a model?

Here's my code...

View

@model IEnumerable<RobotDog.Models.UserModel>
<!-- other stuff -->
<div class="createUser">
    @Html.Partial("_UserPartial", new DynamicActionUserModel { Action = "CreateUser", RegisterModel = new RegisterModel() })    
</div>
<script type="text/javascript">
$(function () {
    $('form.ajax').submit(function () {
        if ($(this).valid()) {
            $.ajax({
                url: this.action,
                type: this.method,
                data: $(this).serialize(),
                success: function (result) {
                    if (result.success) {
                        location.reload(true);
                    } else {
                        $('.createUser').html(result);                          
                    }
                }
            });
        }
        return false;
    });
});
</script>

_UserPartial View

@model RobotDog.Models.DynamicActionUserModel

@using(Html.BeginForm(Model.Action,"Admin", FormMethod.Post, new { @class = "ajax" })) {
    @Html.ValidationSummary(true, "Registration unsuccessful. Please correct errors and try again.")

    @Html.TextBoxFor(x => x.RegisterModel.Email, new { @class = "input-xlarge", @placeholder = "Email"})
    @Html.TextBoxFor(x => x.RegisterModel.UserName, new { @class = "input-xlarge", @placeholder = "User Name"})
    @Html.PasswordFor(x => x.RegisterModel.Password, new { @class = "input-xlarge", @placeholder = "Password"})
    @Html.ListBoxFor(x => x.RegisterModel.SelectedRoles, Model.RegisterModel.Roles)
    <input type="submit" value="Submit" class="btn"/>
}
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

DynamicActionUserModel

public class DynamicActionUserModel {
    public string Action { get; set; }
    public RegisterModel RegisterModel { get; set; }
}

public class RegisterModel {
    [Required(AllowEmptyStrings = false)]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    [Required]
    [DataType(DataType.EmailAddress)]
    [Display(Name = "Email address")]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    public string[] SelectedRoles { get; set; }
    public MultiSelectList Roles { 
        get { return new MultiSelectList(System.Web.Security.Roles.GetAllRoles()); }
    }
}

Controller

[HttpPost]
public ActionResult CreateUser(DynamicActionUserModel model) {
    if (!ModelState.IsValid) {
        foreach(var value in ModelState.Values) {
            foreach (var error in value.Errors) {
                ModelState.AddModelError(string.Empty, error.ErrorMessage);
            }
        }
        return PartialView("_UserPartial", model);
    }

    try { 
        ...
        return Json(new { success = true });
    }
    catch(Exception e) { ... }

    return PartialView("_UserPartial", model);
}
bflemi3
  • 6,698
  • 20
  • 88
  • 155
  • What happens if you take the AllowEmptyStrings=false out? FYI, this is the default anyways, and MVC ignores this property (it's purpose is for DynamicData). I've seen it cause weird validation problems though. – Erik Funkenbusch Oct 12 '12 at 20:36
  • 1
    By the way, you shouldn't use @placeholder, the @ is used on class because class is a reserved word in c#, it's not used on non-reserved attribute names. – Erik Funkenbusch Oct 12 '12 at 20:42
  • Are you not including jquery.validation in your layout? I'm not sure if including it as you are might be causing problems, since it may be getting included twice – Erik Funkenbusch Oct 12 '12 at 20:43
  • 1
    It's also bad practice to do data access in your view model. I assume the Roles property loads something from the database? Shouldn't be causing this problem though. The reason it's bad practice is that it effectively makes the view database aware. – Erik Funkenbusch Oct 12 '12 at 20:45
  • @MystereMan thanks, there is a lot of good info here. I'm learning the mvc framework and this much appreciated advice. – bflemi3 Oct 13 '12 at 19:46
  • Did you solve your question? I am having the exact same problem, and I have a similar scenario where I want it to be validating a Model-inside-a-Model, so perhaps that is a key part of problem... – Tim Lovell-Smith Aug 19 '13 at 20:09
  • And am also using ajax... maybe that is it? – Tim Lovell-Smith Aug 19 '13 at 20:45
  • So it turns out the answer for me is: it was neither! I did a rebase which removed my [Required] attributes and I didn't even realize it. – Tim Lovell-Smith Aug 19 '13 at 22:23
  • @TimLovell-Smith I think the solution would be to pass the specific model you want to be validated to the action instead of the view model. And instead of using `Html.BeginForm` with jQuery I would use `Ajax.BeginForm`. The `Ajax` helper will handle all the client side async requests as well as validation. Here's a good example... http://stackoverflow.com/questions/5410055/using-ajax-beginform-with-asp-net-mvc-3-razor – bflemi3 Aug 21 '13 at 13:57

0 Answers0