A common practice is to do property-level validation in the property setter of an object like the following.
public String Name { get { return this.name; } set { if (value == null) { throw new System.ArgumentNullException("value"); } if (value.Length == 0) { throw new System.ArgumentException("Name cannot be empty.", "value"); } this.name = value; } }
Most of the time though, this violates the DRY principle (Don't Repeat Yourself), because you may need to do the same exact validation in a constructor for example.
public Customer(String name) { get { return this.name; } set { if (name == null) { throw new System.ArgumentNullException("name"); } if (name.Length == 0) { throw new System.ArgumentException("Name cannot be empty.", "name"); } this.name = value; } }
Now you might be asking at this point, "Why are you not just using the Name
property in the constructor, which would invoke the setter validation?". Often times when writing POCO's for Entity Framework, you will use virtual
properties to enable proxy creation. Calling into a virtual property is a Microsoft Usage
violation (CA2214:DoNotCallOverridableMethodsInConstructors
), because if the property is overridden and the deriver does not call the base property, your setter is not invoked and your validation is ignored.
I started thinking, and I came up with an interesting idea to use a helper class with a fluent-api to do validation. Usually when I come up with ideas, I write code how I would like to write it, and then implement the API based on how I want it to look. I wrote this psuedo code.
public String Name { get { return this.name; } set { PropertyHelper.Validate<String>("Name", value) .ArgumentNullException() .ArgumentException(v => v.Length == 0) .ArgumentOutOfRangeException(v => v.Length > 32); this.name = value; } }
Next, I started to actually write the implementation, which obviously had to start with PropertyHelper
.
internal static class PropertyHelper { internal static ? Validate<T>(String propertyName, T value) { ? } }
At this point I was thinking about how a fluent-api is actually designed, and I had to figure out the following:
- What does
Validate<T>
return? - How do I pass the property name and value correctly so it is known for each API call?
To solve these I answered each. First, each 'fluent-api' will really be a type of exception that can be thrown by some condition that represents the validation rule. Secondly, I would need to provide the property name and value to it, and it would have to provide that to the next call. I came up with this.
internal class PropertySetter<T> { private string propertyName; private T value; public PropertySetter(String propertyName, T value) { this.propertyName = propertyName; this.value = value; } }
This completes the two key questions in the prior method I wrote for PropertyHelper
.
internal static class PropertyHelper { internal static PropertySetter<T> Validate<T>(String propertyName, T value) { return new PropertySetter<T>(propertyName, value); } }
Now I needed to write the actual exception-api's, so I started with the most simple ArgumentNullException
.
internal static class PropertyHelper { internal static PropertySetter<T> Validate<T>(String propertyName, T value) { return new PropertySetter<T>(propertyName, value); } internal static PropertySetter<T> ArgumentNullException(Func<T, Boolean> predicate) { if (!predicate(value)) { throw new System.ArgumentNullException(propertyName); } else { return this; } } }
This works well also. Each time a call is made to ArgumentNullException(), it will return itself to the caller, thus passing the property name and value along with it. The predicate is just a simple anonymous function that takes T
which is the type of the value. This is the same predicate that is used for SingleOrDefault(Func<T, Boolean>)
that is used with IEnumerable
. Next, I can implement a few more methods.
internal static class PropertyHelper { internal static PropertySetter<T> Validate<T>(String propertyName, T value) { return new PropertySetter<T>(propertyName, value); } internal static PropertySetter<T> ArgumentNullException(Func<T, Boolean> predicate) { if (!predicate(value)) { throw new System.ArgumentNullException(propertyName); } else { return this; } } internal static PropertySetter<T> ArgumentException(Func<T, Boolean> predicate, String message) { if (!predicate(value)) { throw new System.ArgumentException(message, propertyName); } else { return this; } } }
Because each one returns a PropertySetter<T>
you can keep chaining them, and you can chain them in the order you want. Whichever predicate returns false first will throw that exception.
public Int32 Value { get { return this.value; } set { PropertyHelper .Validate<Int32>("Value", value) .ArgumentException(v => v == 1, "Value cannot be 1."); this.value = value; } }
There is still some level of DRY here, but it is a lot cleaner and easier to maintain. It also allows you to abstract away the logic that throws the exceptions and checks the predicate, allowing you to write code that visibly focuses on the validation itself. Although I called it PropertyHelper
, you could really use this almost anywhere. Properties, methods, and constructors. This also isn't the best approach probably, but it was an interesting approach and a fun one to write. You can grab the full implementation below.
//----------------------------------------------------------------------------- // <copyright file="PropertyHelper.cs" company="DCOM Productions"> // Copyright (c) DCOM Productions. All rights reserved. // Open Source. Personal and Commercial usage allowed. // </copyright> //----------------------------------------------------------------------------- namespace Northwind.Data { using System; using System.Globalization; using System.Linq; /// <devdoc> /// Helper methods for property setters. /// </devdoc> public static class PropertyHelper { /// <summary> /// Performs property validation for the property setter. /// </summary> /// <typeparam name="T">The type of property value.</typeparam> public static PropertySetter<T> Validate<T>(String propertyName, T value) { return new PropertySetter<T>(propertyName, value); } } /// <summary> /// Provides methods for the SetValue() method of a property. /// </summary> /// <typeparam name="T">The type of property value.</typeparam> public class PropertySetter<T> { private string propertyName; private T value; /// <summary> /// Initializes a new instance of the FaultTrack.Data.PropertyHelper class. /// </summary> public PropertySetter(String propertyName, T value) { this.propertyName = propertyName; this.value = value; } /// <summary> /// Throws a System.ArgumentNullException if the property value is null. /// </summary> public PropertySetter<T> ArgumentNullException() { if (value == null) throw new System.ArgumentNullException(propertyName); else return this; } /// <summary> /// Throws a System.ArgumentException if the property value causes the specified predicate to return false. /// </summary> public PropertySetter<T> ArgumentException(Func<T,bool> predicate, String message) { if (!predicate(value)) throw new System.ArgumentException(message, propertyName); else return this; } /// <summary> /// Throws a System.ArgumentOutOfRangeException if the property value causes the specified predicate to return false. /// </summary> public PropertySetter<T> ArgumentOutOfRangeException(Func<T,bool> predicate, String message) { if (!predicate(value)) throw new System.ArgumentOutOfRangeException(propertyName, message); else return this; } } }