Spring for .NET Community Forums    

Go Back   Spring for .NET Community Forums > General > Core Container

Reply
 
Thread Tools Display Modes
  #1  
Old 12-20-2006, 04:13 PM
harald harald is offline
Junior Member
Spring User
 
Join Date: Sep 2006
Posts: 25
Wink Validating collections with the Validation Framework

Is it somehow possible to validate a collection of objects by applying a validator to each element. i'm thinking of something like

<v:group id="EmployeeValidator">
...
</v:group>

<v:group id="ManagerValidator">
<v:ref name="EmployeeValidator" />
<v:collection name="Workers" ref="EmployeeValidator" />
</v:group>

which would yield to true if all employees in the managers Workers collection are valid.

harald.
Reply With Quote
  #2  
Old 12-28-2006, 02:22 PM
Aleks Seovic Aleks Seovic is offline
Spring.NET Co-Lead
Spring TeamSpring User
 
Join Date: Sep 2004
Location: Belgrade, Serbia
Posts: 613
Default

Hi Harald,

We don't have this particular validator built-in, but it shouldn't be too dificult to implement it. I do understand what you are trying to do and it seems like a perfectly valid way to compose your validation rules, so I will add it as soon as I have some time to work on it.

Until then, assuming you don't care too much about actual errors returned by each individual employee validator, you could do something like this:

Code:
<v:group id="EmployeeValidator">
   ...
</v:group>

<v:group id="ManagerValidator">
   <v:ref name="EmployeeValidator" />
   <v:condition test="( #errors = new Spring.Validation.ValidationErrors(); Workers.?{ @(EmployeeValidator).Validate(#this, #errors) == false }.count() == 0 )"/>
</v:group>
This test will use selection expression to select all the workers that are not valid (by invoking employee validator for each element of the collection) and will then check if the resulting list is empty.

Alternatively, you could short-circuit the process using first-match selector and testing for null:

Code:
<v:condition test="( #errors = new Spring.Validation.ValidationErrors(); Workers.^{ @(EmployeeValidator).Validate(#this, #errors) == false } == null )"/>
You can check expression language docs for details, but basically the trick is in using a combination of a selection expression (either full or first-match) and reference expression to invoke employee validator for each element in the Workers collection.

HTH,

Aleks

Last edited by Aleks Seovic; 12-28-2006 at 02:26 PM.
Reply With Quote
  #3  
Old 02-28-2007, 07:38 PM
Aleks Seovic Aleks Seovic is offline
Spring.NET Co-Lead
Spring TeamSpring User
 
Join Date: Sep 2004
Location: Belgrade, Serbia
Posts: 613
Default

Just to close the loop, this functionality has been implemented as follows:

Code:
<v:group id="EmployeeValidator">
...
</v:group>

<v:group id="ManagerValidator">
    <v:ref name="EmployeeValidator" />
    <v:collection context="Workers">
        <v:ref name="EmployeeValidator" />
    </v:collection>
</v:group>
Notice that you can nest any validator within <v:collection> element, not only <v:ref>. That means that you could nest one of the group validators and have multiple validation rules fired for each element in the collection. (Actually, collection validator extends ALL group validator, so you can simply specify multiple validators within it directly and omit top-level <v:group> element. However, if you need ANY or EXCLUSIVE group validator, you will have to nest <v:any> or <v:exclusive> group validators within <v:collection>.)

You can also control whether collection validator should return false as soon as invalid element is encountered (default), or continue processing all of the elements (by setting validate-all attribute to true). Latter can be useful when you want actions to fire for each element of the collection, but default will typically be sufficient.

One thing to keep in mind is that you will get only error messages defined for the collection validator in the final error collection -- messages returned by the nested validators will be discarded, as there is no way to tie them to a particular element of the collection that failed validation.

Regards,

Aleks

Last edited by Aleks Seovic; 02-28-2007 at 07:55 PM.
Reply With Quote
  #4  
Old 03-01-2007, 07:49 AM
PVG PVG is offline
Junior Member
Spring User
 
Join Date: Jun 2006
Posts: 22
Default Collection validation

Not having the errors for the individual elements of the collection is unsatisfying to our problem.

In the situation that you have a company with let's say 100 employees, and I want to validate the company. In that validation process I also validate all the employee.

In case the validation fails, I do not know which employee or employees have validation errors, and I would have to scan through the complete set of employees. Pretty pointless I think.

I don't understand why we can't have the errors of the validation of the indivual elements. If we can have the errors for the manager (in the example) then we can also have the errors for the other employees I think.

I would expect that each Employee element in the collection is passed to the EmployeeValidator. This one then fires and eventually adds a validation message to the ValidationErrors contexts, with as parameters the employee id for example.

Maybe I'm missing something here.

KR

Patrick
Reply With Quote
  #5  
Old 03-01-2007, 08:07 AM
PVG PVG is offline
Junior Member
Spring User
 
Join Date: Jun 2006
Posts: 22
Default Validating Collections

After looking into the code, I would not know why it is not possible to gather the information about the validation errors for the nested validators.

I would be nice to have the option (maybe default as it is now), to collect all errors, in which case the current ValidationErrors object is passed to the nested validator.

The ValidationErrors object is a dictionary of IList, and as such has the possibility to collect multiple ErrorMessage objects for the same key, which would be exactly the case when collectAllErrors is set to true.

It is then up to the developer to specify arguments to the ErrorMessage to differentiate the different indivual elements in the collection.

KR

Patrick
Reply With Quote
  #6  
Old 03-01-2007, 06:31 PM
Aleks Seovic Aleks Seovic is offline
Spring.NET Co-Lead
Spring TeamSpring User
 
Join Date: Sep 2004
Location: Belgrade, Serbia
Posts: 613
Default

You are absolutely right, it is possible to pass existing error collection to each validator, in which case you would get all the errors. The reason we didn't implement it that way is because it is a bit tricky to associate errors to specific elements.

What you have proposed makes sense: we will leave the default as it is now but allow you to specify include-element-errors="true" in order to capture all the errors. Than it is up to you to configure message paramaters in such a way that you have enough information about specific elements that have associated errors, as you said.

Thanks,

Aleks
Reply With Quote
  #7  
Old 03-05-2007, 08:42 PM
Aleks Seovic Aleks Seovic is offline
Spring.NET Co-Lead
Spring TeamSpring User
 
Join Date: Sep 2004
Location: Belgrade, Serbia
Posts: 613
Default

The change described above has been implemented. Please give it a whirl and let me know if that's what you wanted.

- Aleks
Reply With Quote
  #8  
Old 03-06-2007, 08:14 AM
PVG PVG is offline
Junior Member
Spring User
 
Join Date: Jun 2006
Posts: 22
Default Validating Collections

Thanks. I will start with it today.
Reply With Quote
  #9  
Old 10-06-2009, 05:40 PM
jlowe jlowe is offline
Junior Member
New User
 
Join Date: Oct 2009
Posts: 4
Default

I'm trying to use the validation framework in a rich client (WPF) application, and trying to implement this scenario:

Let's say that we're editing an Order, which has multiple OrderItems. The UI contains two forms, one for the Order and one for an OrderItem. The UI also contains a grid containing the OrderItems. When the user selects an OrderItem in the grid, the OrderItem form is populated.

When the user submits, we bundle up the Order and the list of OrderItems into a command object, which we then validate using the validator framework. If there are errors with the Order, then we display them on the Order form. If there are errors with an OrderItem, then we store the errors with each individual OrderItem in the OrderItem grid. When the user selects an OrderItem, the OrderItem form is populated and the error messages are displayed on the form.

Right now I can use the framework to validate the Order and collection of OrderItems, but I can't see any way to get the subset of messages for each OrderItem.

One approach, in the XML configuration, would to specify an expression for a provider, something like providers="OrderItems[${Id}]", where Id is a property of OrderItem. Then I can use errors.GetErrors("OrderItems[2]") to get desired errors.

Am I missing something? Is there currently a way to do this?
Reply With Quote
  #10  
Old 10-07-2009, 03:26 PM
jlowe jlowe is offline
Junior Member
New User
 
Join Date: Oct 2009
Posts: 4
Default

I came up with a solution to my problem, by creating a custom IValidationAction as follows, by copying the ErrorMessageAction:

Code:
public class EnhancedErrorMessageAction : BaseValidationAction
{
    private string messageId;
    private string[] providers;
    private IExpression[] providerExpressions;
    private IExpression[] messageParams;

    public string MessageId
    {
        set { messageId = value; }
        get { return messageId; }
    }

    public string[] Providers
    {
        set { providers = value; }
        get { return providers; }
    }

    public IExpression[] ProviderExpressions
    {
        set { providerExpressions = value; }
        get { return providerExpressions; }
    }

    public IExpression[] Parameters
    {
        set { messageParams = value; }
        get { return messageParams; }
    }

    protected override void OnInvalid(object validationContext, IDictionary contextParams, IValidationErrors errors)
    {
        ErrorMessage error = CreateErrorMessage(validationContext, contextParams);

        if (providerExpressions != null && providerExpressions.Length > 0)
        {
            foreach (IExpression expression in this.providerExpressions)
            {
                string provider = (string)expression.GetValue(validationContext);
                errors.AddError(provider.Trim(), error);
            }
        }

        if (providers != null && providers.Length > 0)
        {
            foreach (string provider in this.providers)
            {
                errors.AddError(provider.Trim(), error);
            }
        }


    }

    private ErrorMessage CreateErrorMessage(object validationContext, IDictionary contextParams)
    {
        if (messageParams != null && messageParams.Length > 0)
        {
            object[] parameters = ResolveMessageParameters(messageParams, validationContext, contextParams);
            return new ErrorMessage(messageId, parameters);
        }
        else
        {
            return new ErrorMessage(messageId, null);
        }
    }

    private object[] ResolveMessageParameters(IList messageParams, object validationContext, IDictionary contextParams)
    {
        object[] parameters = new object[messageParams.Count];
        for (int i = 0; i < messageParams.Count; i++)
        {
            parameters[i] = ((IExpression)messageParams[i]).GetValue(validationContext, contextParams);
        }

        return parameters;
    }


}
It's then referenced in the configuration as below:
Code:
<v:group id="OrderValidator">

  <v:required test="Name">
    <v:message id="error.order.name.required" providers="all,order"/>
  </v:required>

  <v:required test="Date">
    <v:message id="error.order.date.required" providers="all,order"/>
  </v:required>

  <v:collection context="ItemList" validate-all="true" include-element-errors="true">
    <v:ref name="OrderItemValidator" />
  </v:collection>

</v:group>


<v:group id="OrderItemValidator">

  <v:required test="Product">
    <v:action type="ServiceLayer.EnhancedErrorMessageAction, ServiceLayer">
      <v:property name="MessageId" value="error.orderitem.product.required"/>
      <v:property name="Providers" value="all"/>
      <v:property name="ProviderExpressions" value="'order/items/' + Oid"/>
    </v:action>
  </v:required>
  
</v:group>
Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT. The time now is 12:10 AM.


Contegix provides first-class managed hosting and partial sponsorship of these forums.

Powered by vBulletin® Version 3.8.4
Copyright ©2000 - 2010, Jelsoft Enterprises Ltd.