Tuesday, October 29, 2013

Custom Textbox Editor Template

ASP.NET MVC's built-in editor template is awesome - by just decorating your model, the Html helper EditorFor knows how to read those attributes and display appropriate html elements and html attributes, including validation, display name, or even using custom templates that we specify through DataType or UIHint attributes.

Now, all my string properties in my models have string length attribute, to prevent truncation when they being saved in the database, or for formatting reason. Now the built-in Html helper EditorFor is not smart enough to translate that StringLength attribute to the actual html attribute of maxlength. Secondly, it is also not smart enough to adjust the width of the textbox based on that StringLength - so a ZipCode property which only need 5 characters should have smaller textbox compared to a CompanyName property which needs 50 characters.

So I made an editor template that do just that. You can copy the code below, put it in a partial view called "string.cshtml" placed within "EditorTemplates" folder under "/Views/Shared/". I use Twitter Bootstrap css to adjust my textbox width (using "input-mini", "input-large" classes) - feel free to substitute them with whatever you are using.
@model String
@using System.Globalization
@using System.Linq
@{
    IEnumerable validators = ModelValidatorProviders.Providers.GetValidators(ViewData.ModelMetadata, ViewContext);
    ModelClientValidationRule stringLengthRule = validators
        .SelectMany(v => v.GetClientValidationRules())
        .FirstOrDefault(m => m.ValidationType == "length");
    if (stringLengthRule != null && stringLengthRule.ValidationParameters.ContainsKey("max"))
    {
        int maxLength = int.Parse(stringLengthRule.ValidationParameters["max"].ToString());
        string inputSize = "input-xxlarge";
        if (maxLength < 5)
        {
            inputSize = "input-mini";
        }
        else if (maxLength < 10)
        {
            inputSize = "input-small";
        }
        else if (maxLength < 30)
        {
            inputSize = "input-medium";
        }
        else if (maxLength < 75)
        {
            inputSize = "input-large";
        }
        else if (maxLength < 150)
        {
            inputSize = "input-xlarge";
        }
        Dictionary attributes = new Dictionary { 
            { "class", inputSize }, 
            { "maxlength", maxLength.ToString(CultureInfo.InvariantCulture) } 
        };
        @Html.TextBox("", Model, attributes)
    }
    else
    {
        Dictionary attributes = new Dictionary { 
            { "class", "input-medium" } 
        };
        @Html.TextBox("", Model, attributes)
    }
}

-- read more and comment ...

Tuesday, September 3, 2013

How to expose internal methods to a different assembly

Let's say you create this method - let's call it MethodX. Now MethodX is only used within the project - so you marked it as "internal". So now you want to test this MethodX.

Now, I know that in theory MethodX is used internally in the project, which exposing public members of public classes etc - and those public classes and their members are the ones ought to be tested. Using MethodX or not is an implementation detail that the consumer does not need to know, hence does not need to be tested specifically. Yupe, agreed - that is all good.

Now, let's set that aside for a moment - and if you want to test the internal MethodX directly, how would you do it? Of course, the easiest solution is by making MethodX (and the class containing it) to be public. But, then you must remember to convert it back (and forth) every time you run your tests - which is quickly becoming a pain.

It turns out, there is a flag/attribute that you can set in your class declaration to specifically mark a class and its internals to be visible to a different set of assembly. Take a look at this example below:
[assembly:InternalsVisibleTo("MyProject.Tests")]
namespace MyProject
{
    public class MyClass 
    {
        internal void MethodX() 
        {
            // ...
        }
    }
} 
So normally, although the class MyClass is public and is available to public, but the method MethodX is not accessible from outside the same assembly. But, upon putting the InternalsVisibleToAttribute, now MethodX is accessible from MyProject.Tests assembly. If this consuming assembly is your test project, then you can start to build a test for it just like testing against a public method.
-- read more and comment ...

Saturday, August 24, 2013

How to create a unit test that expect an exception?

In creating unit tests, we want to get maximum code coverage to make sure we are testing all possible scenarios and outcomes of our code. When we have a possible path of execution that leads into an exception being thrown, how do we build a test for that?

Consider this simple method "GoToPage" that takes in an integer as a parameter in the "Book" class.
public class Book
{
    // ...

    public void GoToPage(int page) 
    {
        if (page < 1)
            throw new ArgumentException("Page must be a positive, non-zero integer", "page"); 
        if (page > TotalPage)
            throw new ArgumentException("Page cannot be more than the total page count", "page"); 

        // ...
    }

    // ...
}
So how do we test those boundary scenarios? We can do something like this:
[TestMethod]
public void NegativePage_Exception()
{
    // arrange
    var book = new Book();

    // act
    try 
    {
        // act
        book.GoToPage(-1);
    }
    catch (ArgumentException e)
    {
        // assert
        Assert.AreEqual("Page must be a positive, non-zero integer", e.Message);
    }
    catch (Exception) {
        Assert.Fail();
    }
}
Or, you can write a more concise test such as this:
[TestMethod]
[ExpectedException(typeof(ArgumentException), "Page must be a positive, non-zero integer")]
public void NegativePage_Exception()
{
    // arrange
    var book = new Book();

    // act
    book.GoToPage(-1);
}
-- read more and comment ...

Thursday, August 22, 2013

Creating data validation unit test

Creating a unit test is pretty simple for a method, but when your business/POCO objects are relying on DataAnnotation for validation, how do you make sure that they are implemented and tested? Obviously, one can create all the classes run the test from the UI and check whether one can enter invalid data. Is there another way of doing this instead of waiting to test them from the UI? If we are doing TDD, can we build the test first?

For example, let's say we have this class "Person", which requires that both first name and last name to be required, but middle name is optional.
public class Person
{
    [Required]
    public string FirstName { get; set; } 

    public string MiddleName { get; set; } 

    [Required]
    public string LastName { get; set; } 
}
There are several ways to create tests for this class - the first one is to check whether the property is decorated with the RequiredAttribute.
[TestMethod]
public void Person_FirstName_IsRequired()
{
    // arrange
    var propertyInfo = typeof(Person).GetProperty("FirstName");
 
    // act
    var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute)).Cast().FirstOrDefault();
 
    // assert
    Assert.IsNotNull(attribute);
}
Or another way is by testing the validation itself.
[TestMethod]
public void Person_FirstName_IsRequired()
{
    // arrange
    var person = new Person();
    var context = new ValidationContext(person, null, null);
    var validationResults = new List();

    // act
    var isValid = Validator.TryValidateObject(person, context, validationResults);

    // assert
    Assert.IsTrue(validationResults.Any(e => e.ErrorMessage == "The FirstName field is required"));
}
-- read more and comment ...

Wednesday, April 10, 2013

Handling 404 Error in ASP.NET MVC

In ASP.NET Web Forms, handling 404 errors are easy - which is basically a web.config setting. In ASP.NET MVC, it is a bit more complicated. Why is it more complicated? In comparison, everything is seemingly easier in MVC than WebForm.

It is more complicated mainly because of Routing. In WebForm, most 404 occurs because of non-existent file and each UR: is usually mapped to a particular file (aspx). With MVC, that is not the case. All requests are handled by the Routing table and based on that it will invoke appropriate controller and actions etc. Secondly, our basic default route usually is quite common ({controller}/{action}/{id}) - therefore most URL request will be caught by this route.

So, let's dive in on how can we do proper handling of 404 errors with ASP.NET MVC.

TURN ON CUSTOM ERROR IN WEB.CONFIG

    <customErrors mode="On" defaultRedirect="~/Error/Error">
      <error statusCode="404" redirect="~/Error/Http404" />
    </customErrors>

DECLARE  DETAIL ROUTES MAPPED IN ROUTE TABLE

So instead of just using the default route:
   routes.MapRoute(
      name: "Default",
      url: "{controller}/{action}/{id}",
      defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
   );
Declare all your intended route explicitly and create a "catch-all" to handle non-matching route - which basically a 404. In MVC, a 404 can happen when you try to access a URL where the there is no controller for. This code in the routing table handles that scenario.
   routes.MapRoute(
      name: "Account",
      url: "Account/{action}/{id}",
      defaults: new { controller = "Account", action = "Index", id = UrlParameter.Optional }
   );

   routes.MapRoute(
      name: "Home",
      url: "Home/{action}/{id}",
      defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
   );

   routes.MapRoute(
      name: "Admin",
      url: "Admin/{action}/{id}",
      defaults: new { controller = "Admin", action = "Index", id = UrlParameter.Optional }
   );

   routes.MapRoute(
      name: "404-PageNotFound",
      url: "{*url}",
      defaults: new { controller = "Home", action = "Http404" }
   );

OVERRIDE HANDLEUNKNOWNACTION IN BASECONTROLLER CLASS

Create a Controller base class that every controller in your application inherits from. In that base controller class, override HandleUnknownAction method. Now, another scenario that a 404 may happen is that when the controller exists, but there is no action for it. In this case, the routing table will not be able to trap it easily - but the controller class has a method that handle that.
    [AllowAnonymous]
    public class ErrorController : Controller
    {
        protected override void HandleUnknownAction(string actionName)
        {
            if (this.GetType() != typeof(ErrorController))
            {
                var errorRoute = new RouteData();
                errorRoute.Values.Add("controller", "Error");
                errorRoute.Values.Add("action", "Http404");
                errorRoute.Values.Add("url", HttpContext.Request.Url.OriginalString);
 
                View("Http404").ExecuteResult(this.ControllerContext);
            }
        }
 
        public ActionResult Http404()
        {
            return View();
        }
 
        public ActionResult Error()
        {
            return View();
        }
    }

CREATE CORRESPONDING VIEWS

View for generic error: Error.chtml
@model System.Web.Mvc.HandleErrorInfo
 
@{
    ViewBag.Title = "Error";
}
 
<hgroup class="title">
    <h1 class="error">Error.</h1>
    <br />
    <h2 class="error">An error occurred while processing your request.</h2>
    @if (Request.IsLocal)
    {
        <p>
            @Model.Exception.StackTrace
        </p>
    }
    else
    {
        <h3>@Model.Exception.Message</h3>
    }
</hgroup>
View for 404 error: Http404.chtml
@model System.Web.Mvc.HandleErrorInfo
 
@{
    ViewBag.Title = "404 Error: Page Not Found";
}
<hgroup class="title">
    <h1 class="error">We Couldn't Find Your Page! (404 Error)</h1><br />
    <h2 class="error">Unfortunately, the page you've requested cannot be displayed. </h2><br />
    <h2>It appears that you've lost your way either through an outdated link <br />or a typo on the page you were trying to reach.</h2>    
</hgroup>
-- read more and comment ...

Sunday, April 7, 2013

Properly Deleting Database for Database-Migration

I am working on a simple brand new project with ASP.NET MVC and decided to try EF to connect to my database. This gives me the opportunity to learn EF Code-First, database-migration, etc.

Everything seems to be pretty intuitive until when I am running "update-database" from the Package Manager Console. I created my Configuration class, turn-on automatic migration, and populated my database using Seed method, made sure all my context are correct.

[TL;DR]

When one need to delete/recreate a database, do not delete the mdf file from App_Data, but instead go to "SQL Server Object Explorer" and find your database under (localdb) and delete it from there.

FULL VERSION:


Then, since I want to recreate my database, I delete my database (mdf file) from my App_Data folder under Solution Explorer and then run "update-database". See picture on the left.

But then I am getting an error:
Cannot attach the file D:\Projects\MvcApplication1\App_Data\aspnet-MvcApplication1-20130407085115.mdf' as database 'MvcApplication1'.
I looked in the file explorer and the mdf file is surely gone. Try to close Visual Studio and reopen, same error.

Well, the database was initially created when I try to "Register" or create an account using the site (it's using SimpleMembershipProvider) - so maybe it will recreate it if I simply run the site and try to register again. But then I am getting the same error when running the website.

I went to the recycle bin, restore the mdf file and ran the project again - it worked. It did not have the new tables or new data, but no "cannot attach" error. Restoring this file also restore my default connection. If I try to delete the mdf file again, then my project won't run and my database-migration also won't run.

I almost resort to think that "Code-First" is a lie - that I simply have to add the new tables manually in the SQL table designer, etc. This is so confusing - should not be that hard, I think.

So with the mdf file deleted, I went to Server Explorer and checked that my connection to the database is gone - there is nothing under "Data Connections".

So maybe I need to delete the data connection instead of deleting the mdf file from App_Data? So I did a restore again from my Recycle Bin, made sure my project ran, and then I deleted my connection from the Server Explorer, rebuilt the project and ran it. It worked! But my delight is short-lived, since then I realized that deleting the connection does not necessarily mean deleting the database - which I quickly checked that my mdf file still in the App_Data folder. I simply deleted the connection to view the database via Visual Studio.

I tried more and more things - which increase the frustration level - because at this point I am stuck in my project and all I have been trying to do is to modify my database schema. If I did this using the old way using SQL Management Studio or SQL Express, or even Linq-to-SQL, I could have been making a huge progress in my project.

SOLUTION

Until I stumbled on "SQL Server Object Explorer" under "VIEW" in your VS Studio 2012 top menu/toolbar. I noticed that although my mdf file is deleted from my App_Data folder, but under "SQL Server Object Explorer", my database is still registered under (localdb)\v11.0\Databases.

Out of curiosity, I deleted my database from there and re-ran my project - it worked. It recreated my database (without the new tables and seed data). So I deleted it again from SQL Object Explorer, and ran "update-database" - it ran successfully this time. EUREKA!!

So lesson learned: when one need to delete/recreate a database, do not delete the mdf file from App_Data, but instead go to "SQL Server Object Explorer" and find your database under (localdb) and delete it from there.

-- read more and comment ...

Monday, April 1, 2013

Crumbtrail ActionFilter

Recently, I had to make a crumb-trail in the web application that I am working on (ASP.NET MVC). There are multiple ways of doing this and initially I elected to do this in my controller base class (which is inherited by all my controller classes). I created a method that do the job - but this means that this method has to be called on every single action (with GET method). If a fellow developer miss to call the method, then it would mean that the data in the crumb-trail is not built properly or accurately. If there is just an interceptor that I can hook into that will run automatically every time a controller action is being called ... *sigh

Wait - there is one, ActionFilter!!

So I created an action filter and via configuration register and apply it to all controllers. The logic in my code handles the exceptions to the apply all (such as Account controller, POST method, and non-authenticated users). Here is the code to the ActionFilter code:
    public class CrumbTrailKeyValuePair<TKey, TValue>
    {
        public CrumbTrailKeyValuePair() { }
        public CrumbTrailKeyValuePair(TKey key, TValue value)
        {
            Key = key;
            Value = value;
        }
        public TKey Key { get; set; }
        public TValue Value { get; set; }
    }

    public class CrumbTrailAttribute : ActionFilterAttribute
    {
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            if (filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // skip recording Account controller actions
                if (filterContext.RouteData.Values["controller"] != null && 
                    filterContext.RouteData.Values["controller"].ToString() != "Account")
                {
                    // put in cookies
                    Queue<CrumbTrailKeyValuePair<string, string>> crumbTrailQueue = 
                       new Queue<CrumbTrailKeyValuePair<string, string>>();
                    HttpCookie crumbTrailCookie = 
                       filterContext.HttpContext.Request.Cookies["CrumbTrailLinks"] ?? new HttpCookie("CrumbTrailLinks");
 
                    // initialize serializer
                    var serializer = new JavaScriptSerializer();
 
                    // if crumbTrailCookie is not empty, retrieve value from cookie the rehydrate queue
                    if (crumbTrailCookie != null && !string.IsNullOrEmpty(crumbTrailCookie.Value))
                    {
                        // rehydrate crumbTrailQueue with cookie value         
                        crumbTrailQueue = 
                           new Queue<CrumbTrailKeyValuePair<string, string>>
                              (serializer.Deserialize<IEnumerable<CrumbTrailKeyValuePair<string, string>>>
                                 (HttpUtility.UrlDecode(crumbTrailCookie.Value)));
                    }
 
                    if (filterContext.HttpContext.Request.HttpMethod.ToUpper() == "GET")
                    {
                        // get page title
                        var pageTitle = string.IsNullOrEmpty(filterContext.Controller.ViewBag.Title) ? 
                           "PAGE" : filterContext.Controller.ViewBag.Title;
 
                        // get url
                        string url = filterContext.HttpContext.Request.RawUrl;
 
                        // if current page is not in both queue, then add it
                        if (!crumbTrailQueue.Any(x => x.Value == url && x.Key == pageTitle))
                        {
                            // remove oldest menu item, keep queue length to 6
                            if (crumbTrailQueue.Count >= 5)
                                crumbTrailQueue.Dequeue();
 
                            // insert new menu item into queue
                            crumbTrailQueue.Enqueue(new CrumbTrailKeyValuePair<string, string>(pageTitle, url));
                        }
 
                        crumbTrailCookie.Value = serializer.Serialize(crumbTrailQueue);
                        crumbTrailCookie.Expires = DateTime.Now.AddDays(365);
                        crumbTrailCookie.Path = "/";
                        filterContext.HttpContext.Response.Cookies.Add(crumbTrailCookie);
                    }
 
                    // put in viewbag
                    if (filterContext.Result.GetType().Name == "ViewResult")
                    {
                        (filterContext.Result as ViewResult).ViewBag.QuickAccessQueue = crumbTrailQueue;
                    }
                }
            }
        }
    }
Inside the view, just get the queue from the ViewBag and display accordingly. The queue is stored temporarily in a cookie, so it will be remembered even when the browser is closed and reopen and relogin.
-- read more and comment ...

Monday, March 25, 2013

Creating Validation Adapter for Validation Attribute

If your project is rather big, often you would like your validation to reside on the business layer instead of on the UI layer. So for example, using our previous code sample, it would make perfect sense if the custom attribute code resides in your business layer project (instead of on the UI project). BUT, to enable client validation (here for example), implementing IClientValidatable is required and IClientValidatable is within the System.Web.Mvc namespace. This means that your business layer needs to reference System.Web.Mvc. But why would you want to do that - referencing a UI related reference in your business layer project?

When we look at the built-in validator (such as RequiredAttribute), how did they do this? If we look at the documentation, it is within System.ComponentModel.DataAnnotations.ValidationAttribute namespace and deriving from System.Object - there is no dependency to System.Web.Mvc at all. We can use that validation attribute in WinForm, Silverlight, WebForm, WPF, etc.


There is no magic - the ASP.NET guys simply built a validation adapter for it in the System.Web.Mvc namespace. In this post, I am going to show you how to build one for our MagicNumber validator.

So let's clarify on our namespacing issue - in our business layer, let's call that MyApp.Library project (namespace: MyApp.Library), which includes MyClass.cs (namespace: MyApp.Library or MyApp.Library.MyClass to be complete) and our custom validation attribute MagicNumberAttribute.cs (namespace: MyApp.Library or MyApp.Library.MagicNumberAttribute to be complete).

Here is the code for MyApp class:
   public class MyClass {
      // ... more code
      [MagicNumber(ErrorMessage = "Sum is not correct")]
      public int MyData { get; set; }
      // ... more code
   }
Here is our code for the custom validation attribute in MagicNumberAttribute.cs:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MagicNumberAttribute : ValidationAttribute {
   // constructor to accept the comparison property name
   public MagicNumberAttribute(string thirtySevenProperty) : base() {
      if (thirtySevenProperty == null) {
         throw new ArgumentNullException("thirtySevenProperty");
      }
      ThirtySevenProperty= thirtySevenProperty;
   }

   // property to store the comparison field name
   public string ThirtySevenProperty{ get; private set; }

   protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
      // get property info of the "ThirtySevenProperty"
      PropertyInfo thirtySevenPropertyInfo = validationContext.ObjectType.GetProperty(ThirtySevenProperty);

      // check if that property exists / valid
      if (thirtySevenPropertyInfo == null) {
         return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "UNKNOWN PROPERTY", ThirtySevenProperty));
      }

      // get the value of the property
      object thirtySevenPropertyValue = thirtySevenPropertyInfo.GetValue(validationContext.ObjectInstance, null);

      // do comparison and return validation result with error if not equal
      if (!Equals(value, thirtySevenPropertyValue)) {
         return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
      }

      // return null if everything is ok
      return null;
   }
}
We'll call our ASP.NET MVC project to be MyApp.UI.Mvc (namespace: MyApp.UI.Mvc). Here is our javascript from the previous post, we'll reuse it, put it in a js file:
   jQuery.validator.unobtrusive.adapters.addSingleVal("magicnumber", "other");

   jQuery.validator.addMethod("magicnumber", function (val, element, other) {
      var modelPrefix = element.name.substr(0, element.name.lastIndexOf(".") + 1)
      var otherVal = $("[name=" + modelPrefix + other + "]").val();
      if (val && otherVal) {
        return val == otherVal;
      }
      return true;
   );
So now, instead of implementing IClientValidatable, we will be creating a validation adapter. We will put this in a file in our MyApp.UI.Mvc project, let's name it "MagicNumberAttributeAdapter.cs". The code is simple:
public class MagicNumberAttributeAdapter : DataAnnotationsModelValidator
{
   public MagicNumberAttributeAdapter(ModelMetadata metadata, ControllerContext context, MagicNumberAttribute attribute)
       : base(metadata, context, attribute)
   {
   }

   public override IEnumerable GetClientValidationRules()
   {
      var rule = new ModelClientValidationRule
      {
          ErrorMessage = this.ErrorMessage,
          ValidationType = "magicnumber",
      };
      rule.ValidationParameters.Add("other", this.Attribute.ThirtySevenProperty);
      yield return rule;
   }
}
To make the connection between the adapter and the validation attribute itself, we need to let MVC know about it - so we need to register it inside the global.asax.cs file:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MagicNumberAttribute), typeof(MagicNumberAttributeAdapter));
Once all those are setup, our custom validator will fire on the client-side (as well as server-side), we have clear separation of concern - where the business layer does not reference the UI as dependency, and our custom validation attribute is also reusable. Awesome!

Additional reading:
-- read more and comment ...

Sunday, March 17, 2013

Custom Validation Attribute - Client Side Enabled With Custom Rule

In the previous post, I went over on how to enable client-side validation for our custom validation attribute - using a pre-build jQuery validator. Now what if you want to create your own custom script - because your validator needs to do something truly custom that the pre-build validator is not covering? No problem!

Modify your code to this:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MagicNumberAttribute : ValidationAttribute, IClientValidatable {
   // constructor to accept the comparison property name
   public MagicNumberAttribute(string thirtySevenProperty) : base() {
      if (thirtySevenProperty == null) {
         throw new ArgumentNullException("thirtySevenProperty");
      }
      ThirtySevenProperty= thirtySevenProperty;
   }

   // property to store the comparison field name
   public string ThirtySevenProperty{ get; private set; }

   protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
      // get property info of the "ThirtySevenProperty"
      PropertyInfo thirtySevenPropertyInfo = validationContext.ObjectType.GetProperty(ThirtySevenProperty);

      // check if that property exists / valid
      if (thirtySevenPropertyInfo == null) {
         return new ValidationResult(
            String.Format(CultureInfo.CurrentCulture, "UNKNOWN PROPERTY", ThirtySevenProperty));
      }

      // get the value of the property
      object thirtySevenPropertyValue = thirtySevenPropertyInfo.GetValue(validationContext.ObjectInstance, null);

      // do comparison and return validation result with error if not equal
      if (!Equals(value, thirtySevenPropertyValue)) {
         return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
      }

      // return null if everything is ok
      return null;
   }

   public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
      ModelMetadata metadata, ControllerContext context) {
      var rule = new ModelClientValidationRule
      {
          ErrorMessage = this.ErrorMessage,
          ValidationType = "magicnumber",
      };
      rule.ValidationParameters.Add("other", OtherProperty);
      yield return rule;
   }
}
As we see, in "GetClientValidationRules", I am returning a "ModelClientValidationRule" with "ValidationType" set to "magicnumber". This string is the javascript method name. The name itself is not important, but they need to be consistent between what you put here and the actual method name in the javascript method itself. I am also adding parameters into it - which then will be transformed into a name-value pair that can be retrieved in our javascript method.

Now create a javascript file to be included in your project (it must be referenced AFTER the basic jquery.js and jquery.validate.js and jquery.validaten.unobstrusive.js).
   jQuery.validator.unobtrusive.adapters.addSingleVal("magicnumber", "other");

   jQuery.validator.addMethod("magicnumber", function (val, element, other) {
      var modelPrefix = element.name.substr(0, element.name.lastIndexOf(".") + 1)
      var otherVal = $("[name=" + modelPrefix + other + "]").val();
      if (val && otherVal) {
        return val == otherVal;
      }
      return true;
   );
The first line is to connect our javascript method "magicnumber" into the validation event and the rest is our client-side script to do client side validation. Our "magicnumber" method takes in "other" parameter, which will hold the value for the name for our comparison property/field - we need that to get the value of the field. We also want to consider if our model is using prefix - beyond that it is just doing a dom selector and get the value and returning a comparison result.

Additional reading:
-- read more and comment ...

Tuesday, March 12, 2013

Custom Validation Attribute - Client Side Enabled!

In previous posts, I outlined how to make a custom validation attribute - including a more complex one that depends on another property. Both of those validators are working - but they are working on the server-side. So if you have an ASP.NET MVC web application, the validation will fire after it gets to the server side within your controller. Ideally, we want to do client-side validation as well - so that the form does not need to POST if the data is not valid. How do we do that?

We can make custom javascript, query the fields manually and check for conditions - but this code will be disconnected from the custom validator that we created. Doing custom javascript also means that it will be so specific that the likelihood of being reusable is very very small.

Also, as we have seen with the built-in validators that shipped with ASP.NET MVC, they seem to just work - no custom javascript needed per field or manual client-side validation. When a property or a field is decorated with the correct attribute and client-side validation is enabled in the web.config, then boom - it just works. How can we make our validator to behave in similar manner?


One way to do this is by implementing IClientValidatable in your custom validator. Implementing this interface means we must implement a method called "GetClientValidationRules". This method is what basically will become a connector for our validator to the client-side. The property decorated with our custom validation attribute will then outputting flags that will indicate that it should be evaluated during client-side validation. In this method as well we need to provide additional information to properly evaluate the validity of the property on the client-side - mostly a (javascript) method name to be called during validation and some parameters needed to evaluate property within that method. When we do this, this is the updated validator class will look like:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MagicNumberAttribute : ValidationAttribute, IClientValidatable {
   // constructor to accept the comparison property name
   public MagicNumberAttribute(string thirtySevenProperty) : base() {
      if (thirtySevenProperty == null) {
         throw new ArgumentNullException("thirtySevenProperty");
      }
      ThirtySevenProperty= thirtySevenProperty;
   }

   // property to store the comparison field name
   public string ThirtySevenProperty{ get; private set; }

   protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
      // get property info of the "ThirtySevenProperty"
      PropertyInfo thirtySevenPropertyInfo = validationContext.ObjectType.GetProperty(ThirtySevenProperty);

      // check if that property exists / valid
      if (thirtySevenPropertyInfo == null) {
         return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "UNKNOWN PROPERTY", ThirtySevenProperty));
      }

      // get the value of the property
      object thirtySevenPropertyValue = thirtySevenPropertyInfo.GetValue(validationContext.ObjectInstance, null);

      // do comparison and return validation result with error if not equal
      if (!Equals(value, thirtySevenPropertyValue)) {
         return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
      }

      // return null if everything is ok
      return null;
   }

   public IEnumerable<ModelClientValidationRule$gt; GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
      yield return new ModelClientValidationEqualToRule(this.ErrorMessage, OtherProperty);        
   }
}
In the code above, I am reusing an existing validation rule "ModelClientValidationEqualToRule" which will connect with "equalto" jQuery client-validator. This is a pre-existing rule, so at this point, we can just run this and it should work - no more code needed.

In the next post, I will go over on how to return our own custom ModelClientValidationRule and how to create a corresponding javascript client-side validator.

Additional reading:
-- read more and comment ...

Friday, March 8, 2013

Creating Custom Validation Attribute With Dependency To Other Property

In the last post, I went through the steps to make a custom validation attribute - if you missed it, check it out here: Creating Custom Validation Attribute. Now let's say we want to make our validator to look up on the value "37" from another property in our class (instead of hard-coding it). How do we do that? With the help of reflection, we can do that quite easily.

Here is our modified class:
public class MyClass {
   // ... more code
   [MagicNumber("ThirtySeven", ErrorMessage = "Sum is not correct")]
   public int MyData { get; set; }
   // ... more code
   public int ThirtySeven {
      get { return 37; }
   }
   // ... more code
}

In our new custom validator, we override a different IsValid method (the original one is commented out):
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MagicNumberAttribute : ValidationAttribute {
   // constructor to accept the comparison property name
   public MagicNumberAttribute(string thirtySevenProperty) : base() {
      if (thirtySevenProperty == null) {
         throw new ArgumentNullException("thirtySevenProperty");
      }
      ThirtySevenProperty= thirtySevenProperty;
   }

   // property to store the comparison field name
   public string ThirtySevenProperty{ get; private set; }

   // public override bool IsValid(object value) {
   //    var num = int.Parse(value.ToString());
   //    return num == 37;
   // }

   protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
      // get property info of the "ThirtySevenProperty"
      PropertyInfo thirtySevenPropertyInfo = validationContext.ObjectType.GetProperty(ThirtySevenProperty);

      // check if that property exists / valid
      if (thirtySevenPropertyInfo == null) {
         return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "UNKNOWN PROPERTY", ThirtySevenProperty));
      }

      // get the value of the property
      object thirtySevenPropertyValue = thirtySevenPropertyInfo.GetValue(validationContext.ObjectInstance, null);

      // do comparison and return validation result with error if not equal
      if (!Equals(value, thirtySevenPropertyValue)) {
         return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
      }

      // return null if everything is ok
      return null;
   }
}

That's it!
Additional reading:
-- read more and comment ...

Sunday, March 3, 2013

Creating Custom Validation Attribute

In this post, I will outline steps in making custom validation attribute. This is the first post of a series, which eventually will go all the way to making the validation to be able to evaluated in the client side. But, let's not get too far ahead - first we need to make our custom validator.

In this example, I have a simple validator that need to evaluate whether the value of the field "MyData" is equal to the "37".

Our class:
public class MyClass {
   // ... more code
   [MagicNumber(ErrorMessage = "Magic Number is not correct")]
   public int MyData { get; set; }
   // ... more code
}

Our custom validator:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MagicNumberAttribute : ValidationAttribute {
   public override bool IsValid(object value) {
      var num = int.Parse(value.ToString());
      return num == 37;
   }
}

Done. Simple.
Additional reading:
-- read more and comment ...