Loading C# MVC .NET Data Annotation Attributes From XML, Form Validation

modified

Introduction

Validating web forms is a common task of almost any C# MVC ASP .NET web application. Typically, form field validation is implemented in MVC web applications through the use of .NET data annotations on class member fields. While data annotations offer a powerful and maintainable feature for adding form validation to a web application, occasionally more flexibility is required for adding and removing form field validation in a more dynamic fashion. Some examples might include driving C# MVC form field validation from a content management system CMS, allowing control by the user, or allowing 3rd-party tools to modify form validation. In these cases, a dynamic method is required to offer dynamic data annotation form validation through an external file.

In this tutorial, we’ll walk through dynamically loading C# MVC .NET data annotations for form validation from an XML file. The XML file will specify types of validation (required fields, comparison fields, regular expression fields, error messages, show/hide, etc) and be loaded at run-time into the web application. The XML file will configure which data annotations will be automatically set on the target class member fields.

Dynamic C# MVC Data Annotation Attributes Loaded from XML

For those familiar with the popular MMO game, Warcraft III or World of Warcraft, our example application will simulate a creep data entry form, allowing a user to enter data for a new creep. Form field validation on the creep data entry form will come directly from data annotations on the class fields, as configured via an XML file.

Moving Data Annotations from C# Code to XML

Normally, hard-coded data annotations within the code are a benefit to maintainability of the C# MVC .NET web application. They allow programmatic access and offer shared functionality for validation logic attributed to the fields. However, in cases where validation should be specified by an external tool, such as a CMS content management system, the validation may need to be driven externally.

The following class uses hard-coded data annotation attributes to specify form field validation:

1
2
3
4
5
6
7
public class Creep
{
[Required(ErrorMessage="Please enter the monster's name.")]
public string Name { get; set; }
[Required(ErrorMessage="Please enter the monster's description.")]
public string Description { get; set; }
}

In contrast, we’ll be moving the data annotations out of the C# class and into an XML file, defined as follows:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8" ?>
<Validators>
<Validator Type="Creep">
<Property Name="Name" Required="1" Visible="1" ErrorMessage="Enter a name." />
<Property Name="Description" Required="1" Visible="1" ErrorMessage="Enter a description." />
</Validator>
</Validators>

As you can see in the above XML, we can offer considerable flexibility when defining data annotation attributes via XML, including various types of validation, show/hide functionality, and specification of error messages.

Designing the Validation XML Schema

We can begin the process, as shown above, by defining our desired XML schema format. For our example, we’ll specify a class to add data annotation attributes on, along with a list of property names. Each property will specify if it is required, visible, if it contains regular expression validation, comparison validation, and a specific error message to display.

For our example, the complete XML schema appears as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8" ?>
<Validators>
<Validator Type="Creep">
<Property Name="Name" Required="1" Visible="1" ErrorMessage="Enter the monster's name." />
<Property Name="Class" Required="0" Visible="1" ErrorMessage="Enter the monster's class." />
<Property Name="Level" Required="0" Visible="1" ErrorMessage="Enter the monster's level."/>
<Property Name="CreatorName" Required="1" Visible="1" ErrorMessage="Enter your name." />
<Property Name="CreatorEmail" Required="1" Visible="1" RegularExpression="^\S+@\S+\.\S+$"
ErrorMessage="Please enter your email address."/>
<Property Name="ConfirmCreatorEmail" Required="1" Visible="1"
RegularExpression="^\S+@\S+\.\S+$"
Compare="CreatorEmail" ErrorMessage="Please confirm your email address."/>
</Validator>
<Validator Type="Attack">
<Property Name="Name" Required="0" Visible="1" ErrorMessage="Enter a weapon name." />
<Property Name="Description" Required="0" Visible="1"
ErrorMessage="Please enter a weapon description." />
</Validator>
</Validators>

Transitioning from XML to a C# Model

To offer a clean method for loading the XML, we’ll convert the XML to an XSD file and generate the associated C# class model for the XSD template. We can then load the XML from C# code and work with the class model for our validation.

Generating XSD from XML

To generate an XSD from an XML file, we can use the Visual Studio xsd.exe tool. Open a Visual Studio 2010 Command Prompt and enter the following command:

xsd validation.xml

This will produce validation.xsd. You may need to manually edit validation.xsd to specify desired types. For example, xsd.exe assumes most fields are string, whereas we actually want boolean for certain types, as follows:

Edit the xsd and change:

1
2
<xs:attribute name="Required" type="xs:string" />
<xs:attribute name="Visible" type="xs:string" />

to:

1
2
<xs:attribute name="Required" type="xs:boolean" default="0" />
<xs:attribute name="Visible" type="xs:boolean" default="1" />

Finally, we can generate the C# model class from the XSD by issuing the following command:

xsd /c validation.xsd

This will generate Validation.cs which may be added to the Visual Studio solution.

The Model

We can define our Creep model (the main model uses on the data entry form) with a simple class, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Creep
{
public string Name { get; set; }
public string Class { get; set; }
public int? Level { get; set; }
public string CreatorName { get; set; }
public string CreatorEmail { get; set; }
public string ConfirmCreatorEmail { get; set; }
public Attack Weapon { get; set; }

public Creep()
{
Weapon = new Attack();
}
}

public class Attack
{
public string Name { get; set; }
public string Description { get; set; }
}

Notice in the above code, we are not defining hard-coded data annotation attributes. Validation attributes will be defined in the XML file and loaded at runtime.

Loading XML Validation

With the MVC .NET models defined, we can create a manager class for loading the XML validation schema, as follows:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public static class ValidationManager
{
#region Validation Library

private static Validators _validatorLib;
/// <summary>
/// Deserialized contents of validator.xml file.
/// </summary>
private static Validators validatorLib
{
get
{
if (_validatorLib == null)
{
XmlSerializer serializer = new XmlSerializer(typeof(Validators));
string path = HttpContext.Current.ApplicationInstance.Server.MapPath(
ConfigurationManager.AppSettings["ValidationXmlPath"]);
using (XmlReader reader = XmlReader.Create(path))
{
_validatorLib = (Validators)serializer.Deserialize(reader);
}
}

return _validatorLib;
}
}

private static Dictionary<string, Dictionary<string, ValidatorsValidatorProperty>>
_validators = null;

/// <summary>
/// Hashed set of validator data, based upon validator.xml file.
/// Example usage:
/// Validators["Contact"]["FirstName"].Required
/// </summary>
public static Dictionary<string, Dictionary<string,
ValidatorsValidatorProperty>> Validators
{
get
{
if (_validators == null)
{
_validators = new Dictionary<string,
Dictionary<string, ValidatorsValidatorProperty>>();

// Convert the list of validators into a hash array for fast access.
// First, get each Validator entry.
foreach (var validatorItem in validatorLib.Items)
{
Dictionary<string, ValidatorsValidatorProperty> properties = new
Dictionary<string, ValidatorsValidatorProperty>();

// Next, get each property entry for this validator
// and hash it by name.
foreach (var property in validatorItem.Property)
{
properties.Add(property.Name, property);
}

// Add the property hash to this validator.
_validators.Add(validatorItem.Type, properties);
}
}

return _validators;
}
}

#endregion

#region Validation Library Helpers

/// <summary>
/// Empties and refreshes the validator library.
/// Reloads configuration from the xml file.
/// </summary>
public static void Refresh()
{
// Clear lists and set to null.
_validators.Clear();
_validators = null;

_validatorLib = null;
}

#endregion
}

In the above code, the first private member field deserializes the XML file and loads the contents into a static variable, as generated by the xsd.exe utility tool.

A Need for Speed

Since our dynamically loaded data annotation attributes will be loaded from XML and set on each class member field, as defined, we’re going to need to be efficient and fast when accessing the list of data annotation attributes and finding the one we need. We can do this with a Dictionary (similar to a hash). Our key into the dictionary will comprise of two parts: the class name and the member name. The resulting value will be a validation property object. For example: Validators[“Contact”][“FirstName”].Required

This method allows us to quickly access the XML configuration for a particular class’s member field without having to loop to search.

Creating a Custom Model Validation Binder

With our C# MVC .NET data annotation form validation loaded from XML, the next step is to dynamically apply the configuration to the class member fields. We can do this by dynamically creating data annotation attributes on the member fields with a custom model validation binder.

First, we’ll add a line to the Global.asax.cs to inject our custom model validator provider:

1
2
3
4
5
6
7
8
protected void Application_Start()
{
...

// Add our custom model validator provider.
ModelValidatorProviders.Providers.Add(new
ExtendedDataAnnotationsModelValidatorProvider());
}

This will call our custom model validator provider each time a model class is instantiated, automatically adding the data annotation attributes as defined in our XML.

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/// <summary>
/// Adds a custom model validation binder, allowing data annotation attributes to be added
/// to models dynamically.
/// Include the following line in Application_Start of the Global.asax.cs:
/// ModelValidatorProviders.Providers.Add(new
/// ExtendedDataAnnotationsModelValidatorProvider());
/// </summary>
public class ExtendedDataAnnotationsModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory = Create;
internal static Dictionary<Type, DataAnnotationsModelValidationFactory>
AttributeFactories = new Dictionary<Type,
DataAnnotationsModelValidationFactory>()
{
{
typeof(RegularExpressionAttribute),
(metadata, context, attribute) => new
RegularExpressionAttributeAdapter(metadata, context,
(RegularExpressionAttribute)attribute)
},
{
typeof(RequiredAttribute),
(metadata, context, attribute) => new
RequiredAttributeAdapter(metadata, context, (RequiredAttribute)attribute)
}
};

internal static ModelValidator Create(ModelMetadata metadata,
ControllerContext context,
ValidationAttribute attribute)
{
return new DataAnnotationsModelValidator(metadata, context, attribute);
}

protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata,
ControllerContext context, IEnumerable<Attribute> attributes)
{
List<ModelValidator> vals =
base.GetValidators(metadata, context, attributes).ToList();
DataAnnotationsModelValidationFactory factory;

// Inject our new validator.
if (metadata.ContainerType != null)
{
// Check if we have validation for this class name.
if (ValidationManager.Validators.ContainsKey(metadata.ContainerType.Name))
{
var validator =
ValidationManager.Validators[metadata.ContainerType.Name];

// Check if we have validation for this property name.
if (validator.ContainsKey(metadata.PropertyName))
{
var property = validator[metadata.PropertyName];

// Only add validation to visible properties.
if (property.Visible)
{
// Required attribute.
if (property.Required)
{
ValidationAttribute required;

if (metadata.ModelType == typeof(bool))
{
// For required booleans, enforce true.
required = new EnforceTrueAttribute
{ ErrorMessage = property.ErrorMessage };
}
else if (metadata.ModelType == typeof(int) ||
metadata.ModelType == typeof(long) ||
metadata.ModelType == typeof(double) ||
metadata.ModelType == typeof(float))
{
// For required int, long, double, float (dropdownlists),
// We'll enforce > 0.
required = new GreaterThanZeroAttribute()
{ ErrorMessage = property.ErrorMessage };
}
else
{
required = new RequiredAttribute
{ ErrorMessage = property.ErrorMessage };
}

if (!AttributeFactories.TryGetValue(required.GetType(),
out factory))
{
factory = DefaultAttributeFactory;
}

yield return factory(metadata, context, required);
}

// Regular expression attribute.
if (!string.IsNullOrEmpty(property.RegularExpression))
{
RegularExpressionAttribute regEx = new
RegularExpressionAttribute(property.RegularExpression)
{ ErrorMessage = property.ErrorMessage };

if (!AttributeFactories.TryGetValue(regEx.GetType(),
out factory))
{
factory = DefaultAttributeFactory;
}

yield return factory(metadata, context, regEx);
}

// Compare attribute.
if (!string.IsNullOrEmpty(property.Compare))
{
CompareAttribute compare = new
CompareAttribute(property.Compare)
{ ErrorMessage = property.ErrorMessage };

if (!AttributeFactories.TryGetValue(compare.GetType(),
out factory))
{
factory = DefaultAttributeFactory;
}

yield return factory(metadata, context, compare);
}
}
}
}
}
}
}

The above class inherits from DataAnnotationsModelValidatorProvider, which allows us to dynamically specify data annotation attributes. This class is where most of the work happens, specifically, in the GetValidators() method. In this method, we first confirm the ContainerType and class name to exist within our loaded XML. This indicates if the current class should have dynamic data annotations applied. Assuming it exists within the loaded XML, we then check the property name. If we have a key defined in the XML for the current property name, we can proceed to process the property’s visible attribute, required attribute, regular expression attribute, and comparison attribute. We bind the actual data annotation class type to the property, creating a true C# MVC .NET data annotation for the property.

MVC Data Annotations Plus

Since we’re defining our own method for injecting C# data annotations, we can take it a step further and allow easy definition of required fields for different types (string, int, bool). We know the type, based on the metadata.ModelType. Therefore, we can enact specific assumptions for required attributes on differing types. For example, for the case of a string, we simply add the Required attribute. For the case of a boolean, we can add an EnforceTrueAttribute. For the case of an int, we can add a GreaterThanZeroAttribute (note, the assumption here is that a positive integer is required).

Likewise, for custom validation, we insert a RegularExpressionAttribute and use the regex as defined in the XML for the property.

It’s Magic

WIth the data annotation manager code complete, we now automatically inject C# data annotation attributes on the class fields. This is done automatically when a class is instantiated. No additional work is required.

Adding Logic to the View

To take full advantage of the XML data annotation attribute configuration, we’ll need to add some logic to the view. This will allow us to enable the show/hide property and automatically draw an indicator if the field is required or not.

Each field in our MVC view’s form will contain logic, as follows:

1
2
3
4
5
6
7
8
@if (ValidationManager.Validators["Creep"]["Name"].Visible)
{
@Html.LabelFor(e => e.Name, "Name")
@Html.TextBoxFor(e => e.Name)
@if (ValidationManager.Validators["Creep"]["Name"].Required)
{ <text>*</text> } else { <text>&nbsp;</text> }
@Html.ValidationMessageFor(e => e.Name)
}

In the above block of logic, we check the validation dictionary for the current class property to determine if the field is visible. We include an inner logic check to draw a required asterisk indicator if the field is a required field, as configured in the XML file.

The Simple Controller

We can implement a basic controller for our MVC web application form and check the validation in the normal fashion. The data attributes that are dynamically added to the class will function as true hard-coded data annotation attributes.

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
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}

[HttpPost]
public ActionResult Index(Creep creep)
{
// Validate the model server-side.
if (ModelState.IsValid)
{
// Do something with the submitted data ..

// Redirect to confirmation page.
return RedirectToAction("Confirm", new { name = creep.Name });
}
else
{
// Let the user correct any errors.
return View(creep);
}
}

public ActionResult Confirm(string name)
{
TempData["Name"] = name;

return View();
}
}

Notice, in the above code we simply check ModelState.IsValid on the server-side. Client-side validation is automatically enabled as well.

Complete External Control

WIth the system defined, we can now fully control form field validation by changing the configuration of the XML file. Simply set a property’s Visible option to false and the field will be hidden from the view. Change the error message, regular expression for a password, add/remove fields, etc.

The real power comes when connecting the XML file to be driven from a 3rd-party tool, such as a CMS content management system. The CMS can automatically drive the XML configuration, setting and changing properties as driven by the user. Changes to the XML file will, in turn, change the form field validation on the end C# ASP .NET MVC web application.

See It In Action

Download @ GitHub

You can download the project source code on GitHub by visiting the project home page.

About the Author

This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.

Share