![]() |
|
#1
|
|||
|
|||
|
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. |
|
#2
|
|||
|
|||
|
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>
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 )"/>
HTH, Aleks Last edited by Aleks Seovic; 12-28-2006 at 02:26 PM. |
|
#3
|
|||
|
|||
|
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>
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. |
|
#4
|
|||
|
|||
|
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 |
|
#5
|
|||
|
|||
|
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 |
|
#6
|
|||
|
|||
|
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 |
|
#7
|
|||
|
|||
|
The change described above has been implemented. Please give it a whirl and let me know if that's what you wanted.
- Aleks |
|
#8
|
|||
|
|||
|
Thanks. I will start with it today.
|
|
#9
|
|||
|
|||
|
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? |
|
#10
|
|||
|
|||
|
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;
}
}
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>
|
![]() |
| Thread Tools | |
| Display Modes | |
|
|