Collection of Nested ValidationResults

May 3, 2010 at 4:39 PM

Collection of Nested ValidationResults. If you validate a deep object graph, this will contain all validation result for the children objects. For example if a Class has a collection that is validated, a ValidationResult for the collection will contain all the broken items in the collection.

 

I have the following scenario:

 

Order -> OrderItems -> OrderItem

 

Every of this classes has own ValidationsSpecification.

 

If I validate the Order I would like to get a Nested ValidationResult:

 

OrderValidationResult                           -> contains broken rules from Order class
  OrderItemsNestedValidationResult   -> contains broken rules from OrderItems class, has own rules
      OrderItemValidationResult             -> Contains broken rules from specific OrderItem

 

From the Class design perspective I'm missing a class ValidationResultSet : IEnumerable<ValidationResult> which has a property "NestedValidationResultSet"

 

ValidationResultSet                                                          -> contains broken rules from Order class                                                  
   ValidationResultSet.NestedValidationResultSet        -> contains broken rules from OrderItems class, has own rules
      ValidationResultSet.NestedValidationResultSet     -> contains broken rules from specific OrderItem
         and so on...

 

Actually you have the NestedValidationResult property on the ValidationResult class. Well if in my example the order has more than just one ValidationResult, which of them should contain the ValidationResults from the OrderItems?

 

Because of the SRP and loose coupling the Order will deletage the validation to the items them self and use the ValidationResultSet as nested ValidationResultSet.

 

Coordinator
May 3, 2010 at 6:10 PM
If I understand you correctly, I think that SpecExpress already has this functionality. If given a Contact with a collection of Addresses, and you called ToString() on the ValidationNotification that is returned from ValidationCatalog.Validate(), you would get something like this: First Name is required. <-- ValidationResult Last Name is required. <-- ValidationResult Addresses is invalid. <-- ValidationResult Address 1 in Addresses is invalid. <-- NestedValidationResult Street is required. Country is required. Primary Address is invalid. Primary Address Street is required. Primary Address City is required. Primary Address Country is required. How is this different than what you were expecting?
Coordinator
May 3, 2010 at 6:15 PM
I added a screenshot of an example of a NestedValidationResult with a deep object graph at http://specexpress.codeplex.com/wikipage?title=Validate.
May 3, 2010 at 10:36 PM
Edited May 3, 2010 at 10:40 PM

The result looks alike I'm expecting. In my example the code would look like

ValidationResultSet Order.ValidateSave()
{
    OrderSaveSpecification orderSaveSpec = new OrderSaveSpecifiction();
    ValidationResultSet vrs = orderSaveSpec.Validate(this);
    vrs.NestedValidationResultSet.Add(oitems => oitems.OrderItems, this.OrderItems.ValidateSave());
    return vrs;
}

In your example the ValidateSave Specification of the Order already has to know which specification should be used to validate the OrderItems. OrderItems is a property of the Order class so if it is a collection or a class with own validation specification you will add the List<ValidationResult> of the "OrderItems" to the "OrderItems" ValidationResult.NestedValidationResult of the Order. To be able to validate the OrderItems inside of the OrderSaveSpecification you will have to pass the OrderItemsValidateSave specification. It's a different approach. In my case its the job of OrderItems to keep them self always consistent and not the job of the Order. OrderItems has its own responsibility. It also decides itself which specification should be used for ValidateSave purpose. The main difference is, that you build the three in the specifications and me build the three in my Domain Objects. You also validate instances outside of the Domain Objects and me inside of the Domain Objects.

Do you have maybe an idea I could write the code above using your framework?

How was you able to build the tree in your documentation, because  you (ValidationCatalog) have no idea which specifications should be used for the ValidateSave purpose? The only way is to always use the default Validation, which to my opinion for persistent objects realy rare exists, or to pass a Specification which contains the whole Specification three for whole Domain Objects three. 

Coordinator
May 4, 2010 at 1:12 AM

Let me try and repeat what I think the key points that you're trying to make. You want to validate a deep object graph (Order, OrderItems, OrderItem) without explicitly defining the Specification to use, and the specification needed may be different than the default specification? To do this currently, you are explicitly defining the specification to use for that Domain object actually in the object.


If this is correct, then I think you are looking for Validation Contexts. This feature will allow you to compose a set of specifications for a particular context such as OrderSubmit, OrderSave, or OrderReject. Then you can validate your object graph given the context without needing explicitly define the specifications required except within the Context. For example:

 

//Define your Specifications for your context
public class SubmitOrderSpecification : Validates<Order>
{
	public SubmitOrderSpecification()
	{
		Check(o => o.FirstName).Required();
		Check(o => o.LastName).Required();
		
		Check(o => o.OrderItems).Required().And
			.CountGreaterThanEqualTo(1)
			//Validate each item in the collection with the 
			//default specification registered in the ValidationCatalog 
			//for the current context
			.And.ForEachSpecification(); 
	}
}

public class SubmitOrderItemSpecification : Validates<OrderItem>
{
	public SubmitOrderItemSpecification()
	{
		Check(i => i.Quantity).Required().GreaterThanEqualTo(1);
		Check(i => i.ItemCode).Required();
	}
}

//Define Context

public class SubmitOrderContext : SpecExpress.ValidationContext
   {
       public SubmitOrderContext()
       {
           //Add all specifications from ValidationCatalog that begin with 'SubmitOrder' to the Context
           AddSpecifications(cfg => cfg.Where(s => s.GetType().Name.StartsWith("SubmitOrder") == true));
           
	   //Or explicitly add specifications
           //AddSpecification<SubmitOrderSpecification>();
	   //AddSpecification<SubmitOrderItemSpecification>();
		   
       }
   }

   
//Validate for context
ValidationCatalog.Scan(x => x.TheCallingAssembly());
var notification = ValidationCatalog<SubmitOrderContext).Validate(order);

 

I haven't tried compiling this code so caveat emptor. If I misunderstood your goal, please let me know.

 

May 7, 2010 at 1:01 PM

Hi

In my case I can solve the problem using the context as you desribed it above.

Thanks a lot :-)