4. WCF Service Validation Integration
The Validation block allows you to add validation attributes to
the parameters of methods defined in your WCF service contract, and
have the values of these automatically validated each time the method
is invoked by a client.
To use WCF integration, you edit your service contract, edit the
WCF configuration to add the Validation block and behaviors, and then
handle errors that arise due to validation failures. In addition to
the other assemblies required by Enterprise Library and the Validation
block, you must add the assembly named Microsoft.Practices.Enterprise
Library.Validation.Integration.WCF to your application and reference
them all in your service project.
The example, Validating Parameters in a WCF
Service, demonstrates validation in a simple WCF service. It uses a service named ProductService (defined in the Example Service project of the solution). This
service contains a method named AddNewProduct that accepts a set of values for
a product, and adds this product to its internal list of
products.
Defining Validation in the Service Contract
The service contract, shown below, carries the ValidationBehavior attribute, and each
service method defines a fault contract of type ValidationFault.
[ServiceContract]
[ValidationBehavior]
public interface IProductService
{
[OperationContract]
[FaultContract(typeof(ValidationFault))]
bool AddNewProduct(
[NotNullValidator(MessageTemplate = "Must specify a product ID.")]
[StringLengthValidator(6, RangeBoundaryType.Inclusive,
6, RangeBoundaryType.Inclusive,
MessageTemplate = "Product ID must be {3} characters.")]
[RegexValidator("[A-Z]{2}[0-9]{4}",
MessageTemplate = "Product ID must be 2 letters and 4 numbers.")]
string id,
...
[IgnoreNulls(MessageTemplate = "Description can be NULL or a string value.")]
[StringLengthValidator(5, RangeBoundaryType.Inclusive,
100, RangeBoundaryType.Inclusive,
MessageTemplate = "Description must be between {3} and {5} characters.")]
string description,
[EnumConversionValidator(typeof(ProductType),
MessageTemplate = "Must be a value from the '{3}' enumeration.")]
string prodType,
...
[ValidatorComposition(CompositionType.Or,
MessageTemplate = "Date must be between today and six months time.")]
[NotNullValidator(Negated = true,
MessageTemplate = "Value can be NULL or a date.")]
[RelativeDateTimeValidator(0, DateTimeUnit.Day, 6, DateTimeUnit.Month,
MessageTemplate = "Value can be NULL or a date.")]
DateTime? dateDue);
}
You can see that the service contract defines a method named
AddNewProduct that takes as
parameters the value for each property of the Product class we've used throughout the
examples. Although the previous listing omits some attributes to
limit duplication and make it easier to see the structure of the
contract, the rules applied in the example service we provide are
the same as you saw in earlier examples of validating a Product instance. The method implementation
within the WCF service is simple—it just uses the values provided to create a
new Product and adds it to a generic
List.
Editing the Service Configuration
After you define the service and its validation rules, you must edit the service
configuration to force validation to occur. The first step is to specify the
Validation block as a behavior extension. You will need to provide
the appropriate version information for the assembly, which you can
obtain from the configuration file generated by the configuration
tool for the client application, or from the source code of the
example, depending on whether you are using the assemblies provided
with Enterprise Library or assemblies you have compiled
yourself.
<extensions>
<behaviorExtensions>
<add name="validation"
type="Microsoft.Practices...WCF.ValidationElement,
Microsoft.Practices...WCF" />
</behaviorExtensions>
... other existing behavior extensions here ...
</extensions>
Next, you edit the <behaviors> section of the
configuration to define the validation behavior you want to apply. As well as
turning on validation here, you can specify a rule set name (as
shown) if you want to perform validation using only a subset of the
rules defined in the service. Validation will then only include rules
defined in validation attributes that contain the appropriate
Ruleset parameter (the configuration
for the example application does not specify a rule set name
here).
<behaviors>
<endpointBehaviors>
<behavior name="ValidationBehavior">
<validation enabled="true" ruleset="MyRuleset" />
</behavior>
</endpointBehaviors>
... other existing behaviors here ...
</behaviors>
Note
Note that you cannot use a configuration rule set
with a WCF service—all validation rules must be in
attributes.
Finally, you edit the <services> section of the configuration
to link the ValidationBehavior
defined above to the service your WCF application exposes. You do
this by adding the behaviorConfiguration attribute to the
service element for your service, as shown here.
<services>
<service behaviorConfiguration="ExampleService.ProductServiceBehavior"
name="ExampleService.ProductService">
<endpoint address="" behaviorConfiguration="ValidationBehavior"
binding="wsHttpBinding" contract="ExampleService.IProductService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
...
</services>
Using the Product Service and Detecting Validation
Errors
At last you can use the WCF service you have created. The
example uses a service reference added to the main project, and
initializes the service using the service reference in the usual
way. It then creates a new instance of a Product class, populates it with valid
values, and calls the AddNewProduct
method of the WCF service. Then it repeats the process, but this
time by populating the product instance with invalid values. You can
examine the code in the example to see this if you wish.
However, one important issue is the way that service
exceptions are handled. The example code specifically catches
exceptions of type FaultException<ValidationFault>
. This is the exception generated by the service, and
ValidationFault is the type of the
fault contract we specified in the service contract.
Validation errors detected in the WCF service are returned in the Details property of the exception as a
collection. You can simply iterate this collection to see the
validation errors. However, if you want to combine them into a
ValidationResults instance for
display, especially if this is part of a multi-step process that may
cause other validation errors, you must convert the collection of
validation errors returned in the exception.
The example application does this using a method named ConvertToValidationResults, as shown here.
Notice that the validation errors returned in the ValidationFault do not contain information
about the validator that generated the error, and so we must use a
null value for this when creating each ValidationResult instance.
// Convert the validation details in the exception to individual
// ValidationResult instances and add them to the collection.
ValidationResults adaptedResults = new ValidationResults();
foreach (ValidationDetail result in results)
{
adaptedResults.AddResult(new ValidationResult(result.Message, target,
result.Key, result.Tag, null));
}
return adaptedResults;
When you execute this example, you will see a message
indicating the service being started—this may take a while the first
time, and may even time out so that you need to try again. Then the
output shows the result of validating the valid Product
instance (which succeeds) and the result of
validating the invalid instance (which produces the now familiar
list of validation errors shown here).
The following 6 validation errors were detected:
+ Target object: Product, Member:
- Detected by: [none]
- Tag value: id
- Message: 'Product ID must be two capital letters and four numbers.'
...
+ Target object: Product, Member:
- Detected by: [none]
- Tag value: description
- Message: 'Description can be NULL or a string value.'
+ Target object: Product, Member:
- Detected by: [none]
- Tag value: prodType
- Message: 'Product type must be a value from the 'ProductType' enumeration.'
...
+ Target object: Product, Member:
- Detected by: [none]
- Tag value: dateDue
- Message: 'Date due must be between today and six months time.'
Again, we've omitted some of the duplication so that you can
more easily see the result. Notice that there is no value available
for the name of the member being validated or the validator that was
used. This is a form of exception shielding that prevents external
clients from gaining information about the internal workings of the
service. However, the Tag value returns the name of the parameter
that failed validation (the parameter names are exposed by the
service), allowing you to see which of the values you
sent to the service actually failed validation.
5. User Interface Validation Integration
The Validation block contains integration components that make
it easy to use the Validation block mechanism and rules to validate
user input within the user interface of ASP.NET, Windows Forms, and
WPF applications. While these technologies do include facilities to
perform validation, this validation is generally based on individual
controls and values.
When you integrate the Validation block with your applications,
you can validate entire objects, and collections of objects, using
sets of rules you define. You can also apply complex validation using
the wide range of validators included with the Validation block. This
allows you to centrally define a single set of validation rules, and
apply them in more than one layer and when using different UI
technologies.
Note
The UI integration technologies provided with the
Validation block do not instantiate the classes that contain the
validation rules. This means that you cannot use self-validation
with these technologies.
ASP.NET User Interface Validation
The Validation block includes the PropertyProxyValidator
class that derives from the ASP.NET BaseValidator control, and can therefore take
part in the standard ASP.NET validation cycle. It acts as a wrapper
that links an ASP.NET control on your Web page to a rule set defined
in your application through configuration, attributes, and
self-validation.
To use the PropertyProxyValidator, you add the assembly
named
Microsoft.Practices.EnterpriseLibrary.Validation.Integration.AspNet
to your application, and reference it in your project. You must also
include a Register directive in your
Web pages to specify this assembly and the prefix for the element
that will insert the PropertyProxyValidator into your page.
<% @Register TagPrefix="EntLibValidators"
Assembly="Microsoft.Practices.EnterpriseLibrary.Validation.Integration.AspNet"
Namespace="Microsoft.Practices.EnterpriseLibrary.Validation.Integration.AspNet"
%>
Then you can define the validation controls in your page. The
following shows an example that validates a text box that accepts a
value for the FirstName property of a
Customer class, and validates it
using the rule set named RuleSetA.
<EntLibValidators:PropertyProxyValidator id="firstNameValidator"
runat="server" ControlToValidate="firstNameTextBox"
PropertyName="FirstName" RulesetName="RuleSetA"
SourceTypeName="ValidationQuickStart.BusinessEntities.Customer" />
One point to be aware of is that, unlike the ASP.NET validation controls, the Validation block
PropertyProxyValidator control does
not perform client-side validation. However, it does integrate with
the server-based code and will display validation error messages in
the page in the same way as the ASP.NET validation controls.
Windows Forms User Interface Validation
The Validation block includes the ValidationProvider
component that extends Windows Forms controls to
provide validation using a rule set defined in your application
through configuration, attributes, and self-validation. You can
handle the Validating event to
perform validation, or invoke validation by calling the PerformValidation method of the control. You
can also specify an ErrorProvider
that will receive formatted validation error messages.
To use the ValidationProvider,
you add the assembly named
Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WinForms
to your application, and reference it in your project.
WPF User Interface Validation
The Validation block includes the ValidatorRule
component that you can use in the binding of a WPF
control to provide validation using a rule set defined in your
application through configuration, attributes, and self-validation.
To use the ValidatorRule, you add the
assembly named
Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WPF to
your application, and reference it in your project.
As an example, you can add a validation rule directly to a
control, as shown here.
<TextBox x:Name="TextBox1">
<TextBox.Text>
<Binding Path="ValidatedStringProperty" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<vab:ValidatorRule SourceType="{x:Type test:ValidatedObject}"
SourcePropertyName="ValidatedStringProperty"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
You can also specify a rule set using the RulesetName property, and use the Validation SpecificationSource property to
refine the way that the block creates the validator for the
property.