Alexander Manekovskiy

Automating Automation

How Internet Archive Saved My Day

UPDATE: igoogle_themes.zip archive is no longer available through Wayback Machine since it is pointing to mattberseth2.com which is not working. However archive could be found on my SkyDrive.

Internet Wayback Machine Logo Today when I had to find a theme for ASP.NET GridView the first resource I found in my memory was Matt Berseth’s blog (Google also found something for me but I’m convinced that “favorites list” in my memory is a much better and reliable source). Matt had great examples of AJAX control extenders and some other things connected with styling of ASP.NET controls on his site. But while domain still belongs to Matt Berseth, the site is currently down and not available.

Well, I quickly found some references to the article 5 GridView Themes Based on Google’s Personalized Homepage (igoogle) and tried to get to it with the help of The Internet Archive (aka The Wayback Machine). From the “About the Wayback Machine” section:

Browse through over 150 billion web pages archived from 1996 to a few months ago.

In fact it never helped me, but I wanted to get rarely visited pages or downloads of a big size so it is nothing to complain. And at this time I was interested in recovery of popular resource and to my relief the page was crawled 20 times from the 3rd of November, 2007. And sample project download was also available! So it took me something near to 20 minutes to get what I wanted and this is nothing comparing to the efforts needed to create my own CSS for a GridView.

Thank you Internet Archive, you saved my day!

Building Data Annotations Validator Control With Client-Side Validation

When I had worked on ASP.NET MVC project I really liked how input is validated with Data Annotations attributes. And when I had to return to the Web Forms, and write a simple form with some validation, I was wondering how I lived before with standard validator controls. For me, it was never convenient, when I had to write an enormous amount of server tags just to state that “this is required field which accepts only numbers in specified range…”. Yes, there is nothing terrible in declaration of two or three validation controls instead of one. But, if I had a choice, I would like to write only one validator per field and keep all input validation logic as far as I can from the UI markup. And being a developer the code-only approach is most natural for me.

System.ComponentModel.DataAnnotations namespace was introduced in .NET 3.5, and now its classes are supported by wide range of technologies like WPF, Silverlight, RIA Services, ASP.NET MVC, ASP.NET Dynamic Data but not in Web Forms. I thought that someone had already implemented ready-to-use Validator Control with client-side validation, but after searching the Web and most popular open source hosting services I found nothing. Ok, not nothing, but implementations what I have found lacked client-side validation and had some other issues. So I decided to write my own Data Annotations Validator that will also support client-side validation.

Creating Data Annotations Validator Control

Server-Side

As I wanted to achieve compatibility with existing validation controls (new validator is not a replacement for an old ones, it is just an addition to them), it was decided to inherit from BaseValidator. This class does all necessary initialization on both client- and server-sides and exposes all necessary methods for overriding.

1
2
public class DataAnnotationsValidator : BaseValidator
{ }

First of all EvaluateIsValid method of BaseValidator should be overridden.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected override bool EvaluateIsValid()
{
  object value = GetControlValidationValue(ControlToValidate);
  foreach (ValidationAttribute validationAttribute in ValidationAttributes)
  {
      // Here, we will try to convert value to type specified on RangeAttibute.
      // RangeAttribute.OperandType should be either IConvertible or of built in primitive types
      var rangeAttibute = validationAttribute as RangeAttribute;
      if (rangeAttibute != null)
      {
          value = Convert.ChangeType(value, rangeAttibute.OperandType);
      }

      if (validationAttribute.IsValid(value)) continue;

      ErrorMessage = validationAttribute.FormatErrorMessage(DisplayName);
      return false;
  }

  return true;
}

The only interesting aspect of this method is line 16. I’m using FormatErrorMessage method of ValidationAttribute to use all the goodness like support of resources and proper default error message formatting. So, now there is no need to invent something with error messages.

Next thing to deal with is where to get ValidationAttributes collection. There is System.Web.DynamicData.MetaTable class that could be used to retrieve attributes. It was introduced in the first versions of ASP.NET Dynamic Data and now in 4.0 version of Dynamic Data, MetaTable has a static method CreateTable which accepts Type as input parameter. Why using MetaTable, why not retrieve attributes of Type directly from PropertyInfo for specified property name? Because MetaTable also supports retrieving of custom attributes that are applied to property through MetadataTypeAttribute and merges attributes applied to property both in metadata class and entity class. And again, why inventing something new when everything that is needed is right here?

1
2
3
MetaTable.CreateTable(ObjectType))
  .GetColumn(PropertyName).Attributes
  .OfType<ValidationAttribute>()

Now let’s look a bit into the future - if we place ObjectType property into DataAnnotationsValidator, it means that we should specify this property for every validator control on page. This is redundancy and leads to copy-pasting which is not acceptable. Lets step aside and create MetadataSource control that will act like metadata provider for validators on page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MetadataSource : Control
{
  public Type ObjectType { get; set; }

  private MetaTable metaTable;
  private MetaTable MetaTable
  {
      get { return metaTable ?? (metaTable = MetaTable.CreateTable(ObjectType)); }
  }

  public IEnumerable GetValidationAttributes(string property)
  {
      return MetaTable.GetColumn(property).Attributes.OfType();
  }

  public string GetDisplayName(string objectProperty)
  {
      var displayAttribute = MetaTable.GetColumn(objectProperty).Attributes
          .OfType()
          .FirstOrDefault();

      return displayAttribute == null ? objectProperty : displayAttribute.GetName();
  }
}

Here I also thought about DisplayAttribute which is used to format default error message. Now how ObjectType of MetadataSouce should be specified? Well, we can do it programmatically on Page_Load or do it.. programmatically with CodeExpressionBuilder to keep all control setup in one place.

1
2
3
<dav:MetadataSource runat="server"
  ID="msFoo"
  ObjectType="<%$ Code: typeof(Foo) %>" />

Now, with existence of MetadataSource all fields of DataAnnotationsValidator are initialized in the OnInit method

1
2
3
4
5
6
7
8
9
10
11
12
protected override void OnLoad(EventArgs e)
{
  base.OnLoad(e);

  if (!ControlPropertiesValid())
      return;

  MetadataSource = this.FindChildControl(MetadataSourceID);

  ValidationAttributes = MetadataSource.GetValidationAttributes(ObjectProperty);
  DisplayName = MetadataSource.GetDisplayName(ObjectProperty);
}

This is all what was needed to provide server-side validation.

Client-Side

First of all, lets check what standard validator controls could be replaced with Data Annotations attributes.

Data Annotations Attribute Standard Validator Control
RequiredAttribute RequiredFieldValidator
StringLengthAttribute -
RegularExpressionAttribute RegularExpressionValidator
- CompareValidator
RangeAttribute RangeValidator

I have no ideas how to replace CompareValidator and I don’t think it is so critical and necessary to think on it. Time to look how standard validator controls are working on the client side.

Every validator that works on the client side should override AddAttributesToRender method of BaseValidator class. This method adds some fields to resulting javascript object. For example, RequiredFieldValidator adds evaluationfunction and initialvalue fields.

1
2
3
4
5
6
7
8
9
protected override void AddAttributesToRender(HtmlTextWriter writer) {
    base.AddAttributesToRender(writer);
    if (RenderUplevel) {
        string id = ClientID;
        HtmlTextWriter expandoAttributeWriter = (EnableLegacyRendering) ? writer : null;
        AddExpandoAttribute(expandoAttributeWriter, id, "evaluationfunction", "RequiredFieldValidatorEvaluateIsValid", false);
        AddExpandoAttribute(expandoAttributeWriter, id, "initialvalue", InitialValue);
    }
}

And resulting javascript block for RequiredFieldValidator will look next:

1
2
3
4
5
6
7
8
<script type="text/javascript">
//<![CDATA[
var rfvSampleTextBox = document.all ? document.all["rfvSampleTextBox"] : document.getElementById("rfvSampleTextBox");
rfvSampleTextBox.controltovalidate = "tbSampleTextBox";
rfvSampleTextBox.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
rfvSampleTextBox.initialvalue = "";
//]]>
</script>

After examining source code of standard validator controls I found that every control sets evaluationfunction field that states a name for a javascript function that actually performs validation on the client-side. RequiredFieldValidator evaluation function is represented below.

1
2
3
function RequiredFieldValidatorEvaluateIsValid(val) {
    return (ValidatorTrim(ValidatorGetValue(val.controltovalidate)) != ValidatorTrim(val.initialvalue))
}

The val parameter is a validator object that was initialized with all the fields that were set in the AddAttributesToRender method. Plain and simple, if you need to supply your validator on client-side with some information override AddAttributesToRender and add what you want. To replace standard validators DataAnnotationsValidator is doing a little trick - it adds all standard evaluationfunction names, error messages and all necessary fields that are used by standard validation functions. Evaluation function of DataAnnotationsValidator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script type="text/javascript">
//<![CDATA[
function DataAnnotationsValidatorIsValid(val) {
    var functionsToEvaluate = val.validatorFunctions.split(';;');
  var errorMessages = val.errorMessages.split(';;');

    for (var funcIndex in functionsToEvaluate) {
        var result = eval(functionsToEvaluate[funcIndex] + "(val)");
        if(result === false) {
          val.errormessage = errorMessages[funcIndex];
          val.innerText = errorMessages[funcIndex];
            return false;
        }
    }
    return true;
}
//]]>
</script>

This function is registered in the OnPreRender stage of control lifecycle.

1
2
3
4
5
6
7
8
9
10
11
12
13
protected override void OnPreRender(EventArgs e)
{
  base.OnPreRender(e);

  if (RenderUplevel)
  {
        var scriptManager = ScriptManager.GetCurrent(Page);
        if (scriptManager != null && scriptManager.IsInAsyncPostBack)
            ScriptManager.RegisterClientScriptResource(this, GetType(), DAValidationScriptFileName);
        else
          Page.ClientScript.RegisterClientScriptResource(GetType(), DAValidationScriptFileName);
  }
}

The only thing that remains is to get list of fields and values that are needed for validation functions. Every Data Annotation validation attribute will have an Adapter class that stores an array of ClientValidationRule classes. ClientValidationRule is just a container for storing javascript object field names and evaluationfunction.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ClientValidationRule
{
    public string EvaluationFunction { get; set; }
    public Dictionary<string, object> Parameters { get; private set; }

    public string ErrorMessage { get; set; }

    public ClientValidationRule()
    {
        Parameters = new Dictionary<string, object>();
        EvaluationFunction = string.Empty;
    }
}

And ValidationAttributeAdapter acts like a bridge between existing ValidationAttibute and its ClientValidationRules.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
internal class ValidationAttributeAdapter
{
    protected ValidationAttribute Attribute { get; set; }
    protected string DisplayName { get; set; }
    protected string ErrorMessage { get; set; }

    public ValidationAttributeAdapter(ValidationAttribute attribute, string displayName)
    {
        Attribute = attribute;
        DisplayName = displayName;
        ErrorMessage = Attribute.FormatErrorMessage(DisplayName);
    }

    public virtual IEnumerable<ClientValidationRule> GetClientValidationRules()
    {
        return Enumerable.Empty<ClientValidationRule>();
    }
}

All ValidationAttributeAdapter classes are registered within ValidationAttributeAdapterFactory in a Dictionary.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private static readonly Dictionary<Type, Func<ValidationAttribute, string, ValidationAttributeAdapter>> PredefinedCreators
    = new Dictionary<Type, Func<ValidationAttribute, string, ValidationAttributeAdapter>>
    {
        {
            typeof(RangeAttribute),
            (attribute, displayName) => new RangeAttributeAdapter(attribute as RangeAttribute, displayName)
        }, {
            typeof(RegularExpressionAttribute),
            (attribute, displayName) => new RegularExpressionAttributeAdapter(attribute as RegularExpressionAttribute, displayName)
        }, {
            typeof(RequiredAttribute),
            (attribute, displayName) => new RequiredAttributeAdapter(attribute as RequiredAttribute, displayName)
        }, {
            typeof (StringLengthAttribute),
            (attribute, displayName) => new StringLengthAttributeAdapter(attribute as StringLengthAttribute, displayName)
        }
    };

public static ValidationAttributeAdapter Create(ValidationAttribute attribute, string displayName)
{
    Debug.Assert(attribute != null, "attribute parameter must not be null");
    Debug.Assert(!string.IsNullOrWhiteSpace(displayName), "displayName parameter must not be null, empty or whitespace string");

    // Added suport for ValidationAttribute subclassing. See http://davalidation.codeplex.com/workitem/695
    var baseType = attribute.GetType();
    Func<ValidationAttribute, string, ValidationAttributeAdapter> predefinedCreator;
    do
    {
        if (!PredefinedCreators.TryGetValue(baseType, out predefinedCreator))
            baseType = baseType.BaseType;
    }
    while (predefinedCreator == null && baseType != null && baseType != typeof(Attribute));

    return predefinedCreator != null
        ? predefinedCreator(attribute, displayName)
        : new ValidationAttributeAdapter(attribute, displayName);
}

As I said previously, idea was borrowed directly from ASP.NET MVC, so if you are familiar with its validation mechanisms you don’t need to learn how it works here. Approach here is almost identical to ASP.NET MVC which was described well by Brad Wilson. As in ASP.NET MVC DAValidation.IClientValidatable interface exposes an extension point and we can now create a custom validation attribute, implement IClientValidatable interface, write validation function or mix existing ones and get both server- and client-side validation. There is a great set of validation attributes - Data Annotations Extensions created by Scott Kirkland so it is only client function must be changed in order to use them with DataAnnotationsValidator.

1
2
3
4
public interface IClientValidatable
{
    IEnumerable<ClientValidationRule> GetClientValidationRules();
}

And that’s all, now we have fully functional control that makes validation with Data Annotations possible in the ASP.NET Web Forms universe.

Complete source code could be found on Codeplex. There you can download latest version of control and example project.

About Me

Welcome!

My name is Alexander Manekovskiy. I’m .NET developer currently working in BIT, where my main duties are to develop and maintain couple of .NET based solutions and sometimes represent company on regional IT conferences and meetings. Also I’m working as a trainer at ITStep.

I work primarily on the Microsoft .NET technologies stack and my experience is mainly connected with different intranet web-based applications which were tightly integrated with various 3rd party solutions and products such as Microsoft Office, Adobe InDesign, different Storefronts, CRM systems, etc.

I like trying new things and I’m always open to new interesting ideas and concepts. And in this blog I’m going to write about practices, tools and ideas that in my mind could be useful or just interesting for .NET developer. The second idea of running this blog is to have an online 24/7 memory stick.

I’m always open to cooperation and you can freely contact me by email or skype:

Email: manekovskiy[at]gmail.com Skype: alexander.manekovskiy