Remote Validation is a technique that uses client side script to validate user input on the server without posting the entire form. It is typically used to compare the user input with a dynamic list of values. One example of its use would be to prevent duplicate user names being submitted. You can craft your own solution for managing remote validation, but there are also components provided as part of ASP.NET for managing remote validation in Razor Pages applications.
Razor Pages remote validation requires four things:
- The jQuery Unobtrusive Validation library
- Application of the
Remote
orPageRemote
attribute to the model property being validated - The property to be validated must be a direct descendent of the PageModel class (i.e. not a property of a child property of the PageModel, also referred to as a "nested property")
- A remote resource (page handler method or MVC controller action) to perform the validation
Remote or PageRemote?
The RemoteValidation
attribute has been around a long time in one form or another. It was introduced into MVC in the pre .NET Core days, and was the only way to perform remote validation in ASP.NET Core 1.x or 2.x. Being part of MVC, the RemoteValidation
attribute depends on an MVC controller action to do its work. The PageRemoteValidation
attribute was introduced in ASP.NET Core 3.0, and is designed specifically to work with a Razor Pages handler method.
So, if you are working with ASP.NET Core 2.x, or your validation end point is an MVC controller, you must use the RemoteValidation
attribute. If you are working with ASP.NET Core 3.x or newer, AND your validation service is a Razor Pages handler method, you must use the PageRemoteValidation
attribute.
Example
The following example features a Razor PageModel that only has one property, Email
. This is bound to a text input in a form, which has Unobtrusive Validation and Unobtrusive AJAX enabled. The Unobtrusive Validation library is included as part of the standard Razor Pages project template, and included in a page via a partial. The Unobtrusive Ajax library needs to be installed, which you can do by editing the libman.json file in your project to include the following entry:
"libraries": [
{
"provider": "jsdelivr",
"library": "[email protected]",
"destination": "wwwroot/lib/jquery-ajax-unobtrusive/"
}
]
The next block of code shows the PageModel:
public class RemoteValidationDemoModel : PageModel
{
[BindProperty]
public string Email { get; set; }
}
Followed by the form and the scripts section in the content page:
<form method="post">
<input asp-for="Email" />
<span asp-validation-for="Email"></span><br>
<input type="submit"/>
</form>
@section scripts{
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<partial name="_ValidationScriptsPartial" />
<script src="~/lib/jquery-ajax-unobtrusive/dist/jquery.unobtrusive-ajax.min.js"></script>
}
The form has its method set to post
, which will result in a request verification token being included in the rendered HTML. It also includes a validation tag helper for outputting any validation messages relating to the Email
property.
PageRemote Attribute Approach
The following handler method is declared in the same Razor page:
public JsonResult OnPostCheckEmail()
{
var existingEmails = new[] { "[email protected]", "[email protected]", "[email protected]" };
var valid = !existingEmails.Contains(Email);
return new JsonResult(valid);
}
Remote validation attributes require that the validation end point returns a JSON response to indicate whether the submitted value is valid or not. Valid values are:
true
false
undefined
null
- any other string.
The last four values indicate that validation has failed. If a string value other than the first four is returned, it is used as the error message.
This example returns true
if the submitted email is not one that already exists.
The PageRemote
attribute has the following properties:
Property | Description |
---|---|
AdditionalFields |
A comma separated list of additional fields that should be included in the validation request |
ErrorMessage |
The custom error message to be displayed in the event of validation failure |
ErrorMessageResourceName |
The name of the resource where the error message is stored, if one is used |
ErrorMessageResourceType |
The type of the resource used to store the error message |
HttpMethod |
The HTTP verb to be used for the request (GET or POST ). Default is GET |
PageHandler |
The name of the handler method containing the validation, If omitted, the default handler for the HTTP verb will be used |
PageName |
The name of the page. If omitted, the current page is used. |
Taking into account that there is a named page handler method in the current page, the PageRemote
attribute is applied to the Email
property as shown below:
[PageRemote(
ErrorMessage ="Email Address already exists",
AdditionalFields = "__RequestVerificationToken",
HttpMethod ="post",
PageHandler ="CheckEmail"
)]
[BindProperty]
public string Email { get; set; }
The AdditionalFields
property must include the request verification token because request verification takes place by default in Razor Pages. If this field is not included in a POST
request, the request will fail with a 400 Bad Request
status code.
400 error codes are also returned if the property that you are trying to validate is a "nested property", e.g. the EmailAddress
property of a Contact
class, which is then assigned as as property of the PageModel. If you use an input tag helper to generate the form field, the name of the field that is generated is Contact.EmailAddress
. The Contact
prefix is also applied to ALL fields listed in the AdditionalFields
property, including the field containing the request verification token. As a result, the request verification token itself is not found, and request verification fails.
Once this configuration has been completed, the AJAX request is made when the value of the Email input is changed:
Remote Attribute Approach
The following action method is declared in an MVC controller:
public class ValidationController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public JsonResult CheckEmail(string Email)
{
var existingEmails = new[] { "[email protected]", "[email protected]", "[email protected]" };
var valid = !existingEmails.Contains(Email);
return new JsonResult(valid);
}
}
The body of the method and the return type is identical to the Razor Pages handler method example. The ValidateAntiForgeryToken
attribute is optional in MVC, but recommended.
The Remote
attribute shares the following properties with the PageRemote
attribute:
AdditionalFields
ErrorMessage
ErrorMessageResourceName
ErrorMessageResourceType
HttpMethod
It has three overloads. The first takes the name of a route, the second takes the name of an action method and controller, and the third takes an area name in addition to the action and controller names.
Using the second overload, this is how the Remote
attribute is configured on the Email property based on the controller action above:
[Remote(
"checkemail",
"validation",
ErrorMessage ="Email Address already exists",
AdditionalFields = "__RequestVerificationToken",
HttpMethod ="post"
)]
[BindProperty]
public string Email { get; set; }
In either case, Remote
or PageRemote
, if no error message is provided either via the ErrorMessage
or ErrorMessageResourceName
properties, or in the JSON response, the default error message is "'name of property' is invalid"
Using Resource Files For Error Messages
Resource (.resx) files enable you to centralise data, making it easier to maintain. Resource files are particularly useful when used as part of a localisation strategy. Both the PageRemote
and the Remote
attributes provide support for error messages stored in resource files.
When adding a resource file, you must change the access modifier (highlighted below) to public
. Otherwise the resource will not be accessible to the Razor Pages application.
This example shows a file named ErrorMessages.resx, which has been added to a folder named Resources. It has one entry with a key of "DuplicateEmail". The name of the folder becomes the namespace for the resource, so when you configure the PageRemote
attribute, you can either include the namespace as part of the type:
[PageRemote(
ErrorMessageResourceType = typeof(Resources.ErrorMessages),
ErrorMessageResourceName = nameof(Resources.ErrorMessages.DuplicateEmail),
AdditionalFields = "__RequestVerificationToken",
HttpMethod ="post",
PageHandler ="CheckEmail"
)]
[BindProperty]
public string Email { get; set; }
...
Or you can add a using
directive to reference the namespace and omit it from the attribute configuration:
using MyApp.Resources;
...
[PageRemote(
ErrorMessageResourceType = typeof(ErrorMessages),
ErrorMessageResourceName = nameof(ErrorMessages.DuplicateEmail),
AdditionalFields = "__RequestVerificationToken",
HttpMethod ="post",
PageHandler ="CheckEmail"
)]
[BindProperty]
public string Email { get; set; }
...