Search code examples
asp.net-mvcvalidationasp.net-mvc-3remote-validation

Remote Validation seems buggy


imagine this situation:

SetUp

in the default MVC3 project, create a new complex type in the AccountModels.cs

public class GlobalAccount
{
    public GlobalAccount()
    {
        this.LogOn = new LogOnModel();
        this.Register = new RegisterModel();
    }

    public LogOnModel LogOn { get; set; }
    public RegisterModel Register { get; set; }
}

In the RegisterModel change the UserName to:

[Required]
[Remote("UserNameExists", "Validation", "", ErrorMessage = "Username is already taken.")]
[RegularExpression(@"(\S)+", ErrorMessage = "White space is not allowed.")]
[Display(Name = "Username (spaces will be stripped, must be at least 6 characters long)")]
public string UserName { get; set; }

The UserNameExists method in a Validation controller is as follow:

public class ValidationController : Controller
{
    public JsonResult UserNameExists(string UserName)
    {
        string user = null;
        if (!String.IsNullOrWhiteSpace(UserName) && UserName.Length >= 6)
            user = UserName == "abcdef" ? "ok" : null;

        return user == null ?
            Json(true, JsonRequestBehavior.AllowGet) :
            Json(string.Format("{0} is not available.", UserName), JsonRequestBehavior.AllowGet);
    }
}

Now in the Register View, use the GlobalAccount Model instead of the RegisterModel

the username input box will be like:

@model Your.NameSpace.Models.GlobalAccount

and

 <div class="field fade-label">
    @Html.LabelFor(model => model.Register.UserName, new { @class = "text" })
    @Html.TextBoxFor(model => model.Register.UserName, new { spellcheck = "false", size = "30" })
</div>

this will result in something like this, in the HTML

<div class="field fade-label">
    <label class="text" for="Register_UserName"><span>Username (spaces will be stripped, must be at least 6 characters long)</span></label>
    <input data-val="true" data-val-regex="White space is not allowed." data-val-regex-pattern="(\S)+" data-val-remote="Username is already taken." data-val-remote-additionalfields="*.UserName" data-val-remote-url="/beta/Validation/UserNameExists" data-val-required="The Username (spaces will be stripped, must be at least 6 characters long) field is required." id="Register_UserName" name="Register.UserName" size="30" spellcheck="false" type="text" value="">
</div>

Debug

If you use FireBug to check what's going on ... the Remote Validation is sending the attribute name instead of the attribute id to the Validation method (the UserNameExists one) as:

Register.UserName instead of Register_UserName

So I can't fetch this value ... ever :(

Is this really a bug or is something that someone already found and I couldn't get from Googling it?

Here is a simple image of the actual problem:

enter image description here


Solution

  • How about:

    public ActionResult UserNameExists(
        [Bind(Include = "UserName")]RegisterModel register
    )
    {
        string user = null;
        if (!String.IsNullOrWhiteSpace(register.UserName) && register.UserName.Length >= 6)
            user = register.UserName == "abcdef" ? "ok" : null;
    
        return user == null ?
            Json(true, JsonRequestBehavior.AllowGet) :
            Json(string.Format("{0} is not available.", register.UserName), JsonRequestBehavior.AllowGet);
    }
    

    Another possibility is to define a special view model:

    public class UserNameExistsViewModel
    {
        public string UserName { get; set; }
    }
    

    and then:

    public ActionResult UserNameExists(UserNameExistsViewModel register)
    {
        string user = null;
        if (!String.IsNullOrWhiteSpace(register.UserName) && register.UserName.Length >= 6)
            user = register.UserName == "abcdef" ? "ok" : null;
    
        return user == null ?
            Json(true, JsonRequestBehavior.AllowGet) :
            Json(string.Format("{0} is not available.", register.UserName), JsonRequestBehavior.AllowGet);
    }
    

    What is annoying is that the following doesn't work:

    public ActionResult UserNameExists(
        [Bind(Prefix = "Register")]string UserName
    )
    

    Go figure :-) I would probably go with a custom view model. It looks cleanest.