This topic contains information about key features and improvements in the ASP.NET MVC. If you seek more information about ASP.NET MVC, you can contact us at ASPHostDirectory. Only with @ 3.99/month, we will host your ASP.NET MVC site. So, what are you waiting for? Try it!!
We want to cover building a custom validation attribute using the base classes available in System.ComponentModel.DataAnnotations. ASP.NET MVC 2 has built-in support for data annotation validation attributes for doing validation on a server. For details on how data annotations work with ASP.NET MVC 2.
But we won’t stop there. We’ll then cover how to hook into ASP.NET MVC 2’s client validation extensibility so you can have validation logic run as JavaScript on the client.
Finally We will cover some of changes we still want to make for the release candidate.
Here’s the code for the attribute:
public class PriceAttribute : ValidationAttribute {
public double MinPrice { get; set; }
public override bool IsValid(object value) {
if (value == null) {
return true;
}
var price = (double)value;
if (price < MinPrice) {
return false;
}
double cents = price - Math.Truncate(price);
if(cents < 0.99 || cents >= 0.995) {
return false;
}
return true;
}
}
Notice that if the value is null, we return true. This attribute is not intended to validate required fields. We’ll defer to the RequiredAttribute to validate whether the value is required or not. This allows me to place this attribute on an optional value and not have it show an error when the user leaves the field blank.
We can test this out quickly by creating a view model and applying this attribute to the model. Here’s an example of the model.
public class ProductViewModel {
[Price(MinPrice = 1.99)]
public double Price { get; set; }
[Required]
public string Title { get; set; }
}
And let’s quickly write a view (Index.aspx) that will display an edit form which we can use to edit the product.
<%@ Page Language="C#" Inherits="ViewPage<ProductViewModel>" %>
<% using (Html.BeginForm()) { %>
<%= Html.TextBoxFor(m => m.Title) %>
<%= Html.ValidationMessageFor(m => m.Title) %>
<%= Html.TextBoxFor(m => m.Price) %>
<%= Html.ValidationMessageFor(m => m.Price) %>
<input type="submit" />
<% } %>
Now we just need a controller with two actions, one which will render the edit view and the other which will receive the posted ProductViewModel. For the sake of demonstration, these methods are exceedingly simple and don’t do anything useful really.
[HandleError]
public class HomeController : Controller {
public ActionResult Index() {
return View();
}
[HttpPost]
public ActionResult Index(ProductViewModel model) {
return View(model);
}
}
We haven’t enabled client validation yet, but let’s see what happens when we view this page and try to submit some values.
As expected, it posts the form to the server and we see the error messages.
Making It Work In The Client
Great, now we have it working on the server, but how do we get this working with client validation?
The first step is to reference the appropriate scripts. In Site.master, we’ve added the following two script references.
<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcValidation.js" type="text/javascript"
></script>
The next step is to enable client validation for the form by calling EnableClientValidation before we call BeginForm. Under the hood, this sets a flag in the new FormContext which lets the BeginForm method know that client validation is enabled. That way, if you set an id for the form, we’ll know which ID to use when hooking up client validation. If you don’t, the form will render one for you.
<%@ Page Language="C#" Inherits="ViewPage<ProductViewModel>" %>
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm()) { %>
<%= Html.TextBoxFor(m => m.Title) %>
<%= Html.ValidationMessageFor(m => m.Title) %>
<%= Html.TextBoxFor(m => m.Price) %>
<%= Html.ValidationMessageFor(m => m.Price) %>
<input type="submit" />
<% } %>
If you try this now, you’ll notice that the Title field validates on the client, but the Price field doesn’t. We need to take advantage of the validation extensibility available to hook in a client validation function for the price validation attribute we wrote earlier.
The first step is to write a ModelValidator associated with the attribute. Since the attribute is a data annotation, we can simply derive from DataAnnotationsModelValidator<PriceAttribute> like so.
public class PriceValidator : DataAnnotationsModelValidator<PriceAttribute>
{
double _minPrice;
string _message;
public PriceValidator(ModelMetadata metadata, ControllerContext context
, PriceAttribute attribute)
: base(metadata, context, attribute)
{
_minPrice = attribute.MinPrice;
_message = attribute.ErrorMessage;
}
public override IEnumerable<ModelClientValidationRule>
GetClientValidationRules()
{
var rule = new ModelClientValidationRule {
ErrorMessage = _message,
ValidationType = "price"
};
rule.ValidationParameters.Add("min", _minPrice);
return new[] { rule };
}
}
The method GetValidationRules returns an array of ModelClientValidationRule instances. Each of these instances represents metadata for a validation rule that is written in JavaScript and will be run in the client. This is purely metadata at this point and the array will get converted into JSON and emitted in the client so that client validation can hook up all the correct rules.
In this case, we only have one rule and we are calling its validation type “price”. This fact will come into play later.
The next step is for us to now register this validator. Since we wrote this as a Data Annotations validator, we can register it in Application_Start as demonstrated by the following code snippet.
protected void Application_Start() {
RegisterRoutes(RouteTable.Routes);
DataAnnotationsModelValidatorProvider
.RegisterAdapter(typeof(PriceAttribute), typeof(PriceValidator));
}
At this point, we still need to write the actual JavaScript validation logic as well as the hookup to the JSON metadata. For the purposes of this demo, We’ll put the script inline with the view.
<script type="text/javascript">
Sys.Mvc.ValidatorRegistry.validators["price"] = function(rule) {
// initialization code can go here.
var minValue = rule.ValidationParameters["min"];
// we return the function that actually does the validation
return function(value, context) {
if (value > minValue) {
var cents = value - Math.floor(value);
if (cents >= 0.99 && cents < 0.995) {
return true; /* success */
}
}
return rule.ErrorMessage;
};
};
</script>
Now when we run the demo, we can see validation take effect as we tab out of each field. Note that to get the required field validation to fire, you’ll need to type something in the field and then clear it before tabbing out.
Let’s pause for a moment and take a deeper look at what’s going on the code above. At a high level, we’re adding a client validator to a dictionary of validators using the key “price”. You may recall that “price” is the validation type we defined when writing the PriceValidator. That’s how we hook up this client function to the server validation attribute.
You’ll notice that the function we add to the validators itself returns a function which does the actual validation. Why is there this seemingly extra level of indirection? Why not simply add a function that does the validation directly to the dictionary?
This approach allows us to run some initialization code at the time the validator is being hooked up to the metadata (as opposed to every time validation occurs). This is helpful if you have expensive initialization logic. The validate method of the object we return in that initialization method may get called multiple times when the form is being validated.
Notice that in this case, the initialization code grabs the min value from the ValidationParameters dictionary. This is the same dictionary created in the PriceValidator class, but now living on the client.
We then run through similar logic as we did in the server side validation code. The difference here is we return null to indicate that no error occurred and we return an array of error messages if an error occurred.
Validation using jQuery Validation?
Client validation in ASP.NET MVC is meant to be extremely extensible. At the core, we emit some JSON metadata describing what fields to validate and what type of validation to perform. This makes it possible to build adapters which can hook up any client validation library to ASP.NET MVC.
For example, if you’re a fan of using jQuery it’s quite easy to use our adapter to hook up jQuery Validation library to perform client validation.
First, reference the following scripts.
<script src="/Scripts/jquery-1.4.1.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcJQueryValidation.js" type="text/javascript">
</script>
When we emit the JSON in the page, we define it as part of an array which we declare inline. If you view source you’ll see something like this (truncated for brevity):
<script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata) {
window.mvcClientValidationMetadata = [];
}
window.mvcClientValidationMetadata.push({
{"Fields":[{"FieldName":"Title",...,
"ValidationRules":
[{"ErrorMessage":"This field is required",...,
"ValidationType":"required"}]},
...);
//]]>
</script>
Note the array named window.mvcClientValidationMetadata.
Simply by referencing the MicrosoftMvcJQueryValidation script, you’ve hooked up jQuery validation to that metadata. Both of the validation adapter scripts look for the existence of the special array and consumes the JSON within the array.
Do you like this article? If you like it, contact us at ASPHostDirectory.
What is so SPECIAL on ASPHostDirectory.com .NET MVC Hosting?
We know that finding a cheap, reliable web host is not a simple task so we’ve put all the information you need in one place to help you make your decision. At ASPHostDirectory, we pride ourselves in our commitment to our customers and want to make sure they have all the details they need before making that big decision.
We will work tirelessly to provide a refreshing and friendly level of customer service. We believe in creativity, innovation, and a competitive spirit in all that we do. We are sound, honest company who feels that business is more than just the bottom line. We consider every business opportunity a chance to engage and interact with our customers and our community. Neither our clients nor our employees are a commodity. They are part of our family.
The followings are the top 10 reasons you should trust your online business and hosting needs to us:
- FREE domain for Life - ASPHostDirectory gives you your own free domain name for life with our Professional Hosting Plan and 3 free domains with any of Reseller Hosting Plan! There’s no need to panic about renewing your domain as ASPHostDirectory will automatically do this for you to ensure you never lose the all important identity of your site
- 99,9% Uptime Guarantee - ASPHostDirectory promises it’s customers 99.9% network uptime! We are so concerned about uptime that we set up our own company to monitor people’s uptime for them called ASPHostDirectory Uptime
- 24/7-based Support - We never fall asleep and we run a service that is opening 24/7 a year. Even everyone is on holiday during Easter or Christmast/New Year, we are always behind our desk serving our customers
- Customer Tailored Support - if you compare our hosting plans to others you will see that we are offering a much better deal in every aspect; performance, disk quotas, bandwidth allocation, databases, security, control panel features, e-mail services, real-time stats, and service
- Money Back Guarantee - ASPHostDirectory offers a ‘no questions asked’ money back guarantee with all our plans for any cancellations made within the first 30 days of ordering. Our cancellation policy is very simple - if you cancel your account within 30 days of first signing up we will provide you with a full refund
- Experts in .Net MVC Hosting - Given the scale of our environment, we have recruited and developed some of the best talent in the hosting technology that you are using. Our team is strong because of the experience and talents of the individuals who make up ASPHostDirectory
- Daily Backup Service - We realise that your website is very important to your business and hence, we never ever forget to create a daily backup. Your database and website are backup every night into a permanent remote tape drive to ensure that they are always safe and secure. The backup is always ready and available anytime you need it
- Easy Site Administration - With our powerful control panel, you can always administer most of your site features easily without even needing to contact for our Support Team. Additionally, you can also install more than 100 FREE applications directly via our Control Panel in 1 minute!
Happy Hosting!