MVC 3, Technical

Using ASP.net MVC remote validation in the real world – AdditionalFields

Implementing remove validation in MVC 3 is very trivial. Typical examples found on the web after a quick search will usually demonstrate the remove validation required when creating a new user. The typical “Is my user name available” check.

However, on putting together a real world MVC 3 application that made full use of models that could be re-used and partials, I hit a pretty obvious issue. In this application, we were using the same model for creating new users and updating existing users. Why wouldn’t we? The model represents the same entity, and will need the same validation. This was a perfect fit and was ideal until we decided to put some remote validation in against the email address field.

Our email address property in our model, after initially adding remote validation, looked something like this:

        [DisplayName("Email Address")]
        [Required]
        [RegularExpression("[\\w\\.-]+@[\\w-]+\\.(\\w{3}|\\w{2}\\.\\w{2})")]
        [Remote("IsUserNameAvailable", "UserManagement")]
        public string ContactEmailPrimary { get; set; }

This worked fantastically on the create user page. When the user entered an email address that was already registered, the client made an Ajax request to our following controller action, that checked if the email address was not already in the database:

[HttpGet]
        public virtual JsonResult IsUserNameAvailable(string contactEmailPrimary)
        {
                int results = 0;
                var searchResults = _userServiceGateway.SearchUserLogin(contactEmailPrimary);

                if (searchResults.Count > 0)
                    return Json("This Email Address has already been registered", JsonRequestBehavior.AllowGet);

                return Json(result, JsonRequestBehavior.AllowGet);
        }

Which if it was registered, would return false.

“Great” we thought. Feature complete – and it went off to the testers. Only for another bug to have come up. We now couldn’t update the user information of an existing user. This was because the remote validation was firing and checking that the email address of the existing users hadn’t already been registered – which, it obviously had. This resulted in our page failing client side validation, and the client being unable to save any changes to the entity.

So we needed a solution. We first explored the possibility of disabling the Remote Validation somehow for this one page. This proved fruitless. We then looked at possibly splitting the models used, and having one model for the User Create, and one model for the User Update. We resisted this idea for obvious reasons.

We then looked at the possibility of passing additional parameters to the Remote Validation action. Sure enough, we discovered the AdditionalFields attribute of the Remote validation object.

The AdditionalFields attribute allows you to specify the name of another form element that will appear within the same form. ASP.NET MVC3 remote validation will then pass the value of this additional attribute to your controller action that is performing the validation. So to fix our issue, we just altered the code to pass the AdditionalField of “UserAccountId”, which we knew would be greater than zero if the user entity was being edited and not created.

Here’s how our finished working code looked, making use of the AdditionalFields attribute:

Model:

        [DisplayName("Email Address")]
        [Required]
        [RegularExpression("[\\w\\.-]+@[\\w-]+\\.(\\w{3}|\\w{2}\\.\\w{2})")]
        [Remote("IsUserNameAvailable", "UserManagement", AdditionalFields = "UserAccountId")]
        public string ContactEmailPrimary { get; set; }
        public int UserAccountId { get; set; }

View:

</pre>
<div class="columns">
 @Html.LabelFor(model => model.ContactEmailPrimary)
 <span class="relative">
 @Html.TextBoxFor(model => model.ContactEmailPrimary)
 @Html.HiddenFor(model => model.UserAccountId)

 </span></div>
<pre>

And finally our controller, which now gets an AdditionalField of “UserAccountId”:

[HttpGet]
        public virtual JsonResult IsUserNameAvailable(string contactEmailPrimary, int userAccountId)
        {
            if (userAccountId == 0)
            {
                int results = 0;
                var searchResults = _userServiceGateway.SearchUserLogin(contactEmailPrimary);
                bool result = true;

                //We need to check for an exact match here as the search only does a "like"
                foreach (var userAccountDto in searchResults)
                {
                    if (userAccountDto.UserLogin == contactEmailPrimary)
                    {
                        result = false;
                        break;
                    }
                }
                if (!result)
                    return Json("This Email Address has already been registered", JsonRequestBehavior.AllowGet);

                return Json(result, JsonRequestBehavior.AllowGet);
            }
            return Json(true, JsonRequestBehavior.AllowGet);
}

We were now back to create and update functionality working securely.

Recommended Reading:

Pro ASP.NET MVC 3 Framework

6 thoughts on “Using ASP.net MVC remote validation in the real world – AdditionalFields

  1. Hi Ed,

    I have just had to write something similar myself which is how I found your post after the fact. It’s good stuff and very similar to what I did, so thanks for that.

    I’m sure you spotted this bug by now, but in your remote method you seem only to do the checking if the userAccountId is zero (presumably meaning if this is a new user). However, you still need to check that the admin person isn’t assigning another email which is already in use by somebody else.

    In other words, the check needs to be “find all users with this email address who haven’t got this userid”.

    1. Hi Tom,

      Yes you are totally correct. However in this particular application, that check was handled further down the stack on a business service level. We’ve tried to keep all business logic out of the controllers as much as possible 🙂

      1. Hi Ed,

        Yeah good point. The actual production code could easily look like this:

        public virtual JsonResult IsUserNameAvailable(string contactEmailPrimary, int userAccountId)
        {
        return _userServiceGateway.CheckUsernameAvailability(contactEmailPrimary, userAccountId);
        }

  2. I know this is an old post, but I wanted to point out that perhaps a better solution would be to use viewmodels instead of binding the view directly to your business model. The create viewmodel would have the Remote attribute, and the edit viewmodel would not.

    1. Yes you could do that but we didn’t want to duplicate any of our attributes that decorate our properties.

      Although, thinking about it, we could have split our viewmodel in 3. A base for common properties, and a create and edit viewmodel that extend from the base and do slightly different things.

      And then you would have to declare your model on your view as some interface, that both extending view models would implement, as we were using the same view cshtml file for both editing and creating.

      Perhaps the entire thing wanted splitting down the middle?

      hmmm…

Leave a Reply

Your email address will not be published. Required fields are marked *