Wednesday, June 3, 2009

Validation Rule: At least one property is required

Today i stump again upon the same problem . I wanted to throw a validation exception only if both of my properties do not have value. So lets create a validation rule for this one. More info on how we create custom validation rules can be found here Validation Module Since we are talking about multiple property values i think we should go with an attribute that is applied to a class and not to a property.

Ok first we have to define that both our RuleBaseAttribute and RuleBaseProperties derived classes will implement .

public interface IRuleRequiredForAtLeast1PropertyProperties
{
string MessageTemplateMustNotBeEmpty { get; set; }
string Delimiters { get; set; }
string TargetProperties { get; set; }
}


ok that looks nice lets explain what we have here for a bit.


1. a MessageTemplateMustNotBeEmpty that will contain my validation text. Xaf Validation module when starts scan your RuleBaseProperties descendants for all properties that start with “MessageTemplate” and displays them in a special node for editing and localization 2. Delimiters will contain a list of how TargetProperties will be delimiter (,;|) whatever you want after a second look at that interface i got the idea to extract a super interface from this one containing Delimiters and TargetProperties cause they should be on a more generic context

public interface IRuleRequiredForAtLeast1PropertyProperties:IRuleMultiPropertiesValues
{
string MessageTemplateMustNotBeEmpty { get; set; }
}
public interface IRuleMultiPropertiesValues
{
string TargetProperties { get; set; }
string Delimiters { get; set; }
}


now i have my contract lets do the simple work first implement the RuleRequiredForAtLeast1PropertyProperties. Remember this one should implement IRuleRequiredForAtLeast1PropertyProperties interface



public class RuleRequiredForAtLeast1PropertyProperties : RuleBaseProperties, IRuleRequiredForAtLeast1PropertyProperties
{
public string MessageTemplateMustNotBeEmpty { get; set; }
[RulePropertiesRequired, RulePropertiesLocalized]
public string TargetProperties { get; set; }
public string Delimiters { get; set; }
}
that looks nice also. The last time i say that I did refactor my code so lets do it again move TargetProperties,Delimiters to a more appropriate class

public class RuleRequiredForAtLeast1PropertyProperties : RuleMultiPropertiesValues, IRuleRequiredForAtLeast1PropertyProperties
{
public string MessageTemplateMustNotBeEmpty { get; set; }
}
public abstract class RuleMultiPropertiesValues : RuleBaseProperties, IRuleMultiPropertiesValues
{
[RulePropertiesRequired, RulePropertiesLocalized]
public string TargetProperties { get; set; }
public string Delimiters { get; set; }
}


ok now for the next easy part the rule attribute



[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class RuleRequiredForAtLeast1PropertyAttribute : RuleBaseAttribute,IRuleRequiredForAtLeast1PropertyProperties
{
public RuleRequiredForAtLeast1PropertyAttribute(string id, string targetContextIDs, string targetProperties)
: base(id, targetContextIDs)
{
Properties.TargetProperties = targetProperties;
}

public RuleRequiredForAtLeast1PropertyAttribute(string id, DefaultContexts targetContexts,
string targetProperties)
: base(id, targetContexts)
{
Properties.TargetProperties = targetProperties;
}

protected override Type RuleType
{
get { return typeof(RuleRequiredForAtLeast1Property); }
}

protected override Type PropertiesType
{
get { return typeof(RuleRequiredForAtLeast1PropertyProperties); }
}

public new RuleRequiredForAtLeast1PropertyProperties Properties
{
get { return (RuleRequiredForAtLeast1PropertyProperties)base.Properties; }
}
public string MessageTemplateMustNotBeEmpty
{
get { return Properties.MessageTemplateMustNotBeEmpty; }
set { Properties.MessageTemplateMustNotBeEmpty = value; }
}
public string TargetProperties
{
get { return Properties.TargetProperties; }
set { Properties.TargetProperties = value; }
}
public string Delimiters
{
get { return Properties.Delimiters; }
set { Properties.Delimiters = value; }
}
}

next part is to create the rule itself so

[RulePropertiesDefaultValue("Delimiters", ";,.:")]
[RulePropertiesDefaultValue("SkipNullOrEmptyValues", false)]
public class RuleRequiredForAtLeast1Property : RuleBase
{
private static SimpleValueManager<string> defaultMessageTemplateMustNotBeEmpty;
private readonly List<string> properties = new List<string>();

public RuleRequiredForAtLeast1Property()
{
}

public RuleRequiredForAtLeast1Property(RuleSearchObjectProperties properties) : base(properties)
{
}


public RuleRequiredForAtLeast1Property(string id, ContextIdentifiers targetContextIDs, Type objectType)
: base(id, targetContextIDs, objectType)
{
}

public static string DefaultMessageTemplateMustNotBeEmpty
{
get
{
if (defaultMessageTemplateMustNotBeEmpty == null)
defaultMessageTemplateMustNotBeEmpty = new SimpleValueManager<string>();
if (defaultMessageTemplateMustNotBeEmpty.Value == null)
defaultMessageTemplateMustNotBeEmpty.Value =
@"""{TargetProperties}"" must not be empty.";
return defaultMessageTemplateMustNotBeEmpty.Value;
}
set { defaultMessageTemplateMustNotBeEmpty.Value = value; }
}

public override ReadOnlyCollection<string> UsedProperties
{
get
{
return
new ReadOnlyCollection<string>(Properties.TargetProperties.Split(Properties.Delimiters.ToCharArray()));
}
}

public new RuleRequiredForAtLeast1PropertyProperties Properties
{
get { return (RuleRequiredForAtLeast1PropertyProperties) base.Properties; }
}

public override Type PropertiesType
{
get { return typeof (RuleRequiredForAtLeast1PropertyProperties); }
}

protected override bool IsValidInternal(object target, out string errorMessageTemplate)
{
Dictionary<string, object> values = GetValues(target);
int emptyFound = 0;
foreach (var value in values)
{
if (Validator.RuleSet.IsEmptyValue(TargetObject, value.Key, value.Value))
emptyFound++;
}
errorMessageTemplate = Properties.MessageTemplateMustNotBeEmpty;
return !(emptyFound == values.Count);
}

private Dictionary<string, object> GetValues(object target)
{
var objects = new Dictionary<string, object>();
properties.Clear();
properties.AddRange(Properties.TargetProperties.Split(Properties.Delimiters.ToCharArray()));
ITypeInfo targetTypeInfo = XafTypesInfo.Instance.FindTypeInfo(Properties.TargetType);
foreach (string property in properties)
objects.Add(property, targetTypeInfo.FindMember(property).GetValue(target));
return objects;
}
}


Now its time to write some tests for the new rule here is the class that is applied to



[DefaultClassOptions]
[RuleRequiredForAtLeast1Property(null,DefaultContexts.Save, "PropertyName1,PropertyName2")]
public class DomainObject1 : BaseObject
{
public string PropertyName1 { get; set; }
public string PropertyName2 { get; set; }
}

the tests

[SetUp]
public void Setup()
{
XpoDefault.DataLayer =
new SimpleDataLayer(new InMemoryDataStore(new DataSet(), AutoCreateOption.DatabaseAndSchema));
}
[Test]
public void Test_That_Will_Not_Fail_When_At_Least_One_Property_Has_Value()
{
var domainObject1 = new DomainObject1 { PropertyName1 = "PropertyName1" };
Validator.RuleSet.Validate(domainObject1,ContextIdentifier.Save);
}
[Test][ExpectedException(typeof(ValidationException))]
public void Test_That_Will_Fail_When_Both_Properties_Do_Not_Have_Value()
{
var domainObject1 = new DomainObject1();
Validator.RuleSet.Validate(domainObject1,ContextIdentifier.Save);
}

and a visual proof that everything works

allok



sample can be downloaded here

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

0 comments:

Post a Comment