In the first part of this series I explained how we tried to do AOP style validations with WPF. I succeeded in doing that with the NetDataContractSerializer and also provided some links that explain why you actually don't want to use that serializer. At the end of my post I wrote that we were going to try to do something with the IDataContractSurrogate.
So let’s continue. I’ll take you through the process step by step. Beware, we were obliged to use VB.NET, but when we see the chance we’ll create C# projects as well. That means this post is going to contain both VB.NET and C#.
We already have a validation framework in place. So, obviously, we want to reuse that. That is however a bit difficult to do in WPF because WPF validation is based on the IDataErrorInfo interface. We did not want to implement that interface on our models. (if you have a look at the interface you’ll know why). That’s why we took another approach. First we put a validation attribute on our WCF datacontract:
1: <DataMember()> _
2: <NotNullOrEmptyValidationConstraint("RequiredField")> _
3: Public Overridable Property FamilyName() As String
4: Get
5: Return m_FamilyName
6: End Get
7: Set(ByVal Value As String)
8: m_FamilyName = Value
9: End Set
10: End Property
Then we need a way to reuse that attribute for WPF validation. That is where DynamicProxy comes into play. (Note: you could also resolve the entity from the Windsor container of course)
Creating an entity
Let’s first cover the use case of creating an Employee. In our presenter we used to new the Employee entity. Now we use a little class called ObjectFactory that creates the employee for us
1: public static class ObjectFactory
2: {
3: private static readonly ProxyGenerator _generator = new ProxyGenerator();
4:
5: public static object CreateWithValidation(Type t)
6: { var interceptors = new IInterceptor[]
7: {
8: new EditableBehaviorInterceptor(), new DataErrorInfoInterceptor(),
9: new PropertyChangedInterceptor()
10: };
11:
12: var proxy = _generator.CreateClassProxy(t, new[] { typeof(IDataErrorInfo), typeof(INotifyPropertyChanged),typeof(IProxied) },
13: new ProxyGenerationOptions(new ValidationProxyGenerationHook()), interceptors);
14: return proxy;
15: }
16:
17:
18: public static T CreateWithValidation<T>() where T : class
19: {
20: return (T)CreateWithValidation(typeof(T));
21: }
22: }
The “CreateWithValidation” method creates a proxy with the help of DynamicProxy and adds several interceptors to the proxy. In this post it’s the DataErrorInforInterceptor we care about. We use an adapted version of the one written by the guys of the “Unofficial NHibernate addins” and it looks like this:
1: public class DataErrorInfoInterceptor : IInterceptor
2: {
3: #region IInterceptor Members
4:
5: public void Intercept(IInvocation invocation)
6: {
7: if (invocation.Method.DeclaringType.Equals(typeof(IDataErrorInfo)))
8: {
9: if ("get_Item".Equals(invocation.Method.Name))
10: {
11: var summary = new ValidationSummary();
12: Validator.IsValid(invocation.Proxy, ref summary);
13: var invalidValues = summary.Messages.Where(p=> p.PropertyName == Convert.ToString(invocation.Arguments[0])).Select(p => p.Message).ToArray();
14: if (invalidValues.Count() > 0)
15: {
16: invocation.ReturnValue = string.Join(Environment.NewLine, invalidValues);
17: }
18: else
19: {
20: invocation.ReturnValue = null;
21:
22: }
23:
24:
25: }
26: else if ("get_Error".Equals(invocation.Method.Name))
27: {
28: var summary = new ValidationSummary();
29: Validator.IsValid(invocation.Proxy, ref summary);
30: var invalidValues = summary.Messages.Select(p => p.Message).ToArray();
31: if (invalidValues.Count() > 0)
32: {
33: invocation.ReturnValue = string.Join(Environment.NewLine, invalidValues);
34: }
35: else
36: {
37: invocation.ReturnValue = null;
38:
39: }
40:
41: }
42: }
43: else
44: {
45: invocation.Proceed();
46: }
47: }
48:
49: #endregion
50: }
51: }
Now the only thing that is left to do is to add “ValidatesOnDataErrors” on your binding in XAML.
1: <TextBox x:Name="txtLastName" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="1"
2: Text="{Binding Path=Intern.InternInfo.FamilyName,ValidatesOnDataErrors=true}"
3: MinWidth="50" Margin="0,2" Style="{StaticResource UpperCaseTextBox}" />
When you run the WPF application now, you will get those nice red borders around your textboxes when you do someting wrong.
Modifying an entity
It gets more complicated when you want to do the same thing when modiying the same Employee again. We use WCF on this particular project which means we have to find a way to give a proxy that implements IDataErrorInfo to the client. It turns out that the extensibility of WCF can help us here. WCF allows you to hook in the (de) serialization process and you can do that by implementing to IDataContractSurrogate interface.
1: Public Class ValidationDataContractSurrogate
2: Implements IDataContractSurrogate
3:
4: Private m_TypesToProxy() As Type
5:
6: Public Property TypesToProxy() As Type()
7: Get
8: Return m_TypesToProxy
9: End Get
10: Set(ByVal value As Type())
11: m_TypesToProxy = value
12: End Set
13: End Property
14:
15: Public Sub New(ByVal proxyTypes As Type())
16: TypesToProxy = proxyTypes
17: End Sub
18:
19:
20: Public Function GetDataContractType(ByVal type As Type) As Type Implements IDataContractSurrogate.GetDataContractType
21: Return type
22: End Function
23:
24: Public Function GetObjectToSerialize(ByVal obj As Object, ByVal targetType As Type) As Object Implements IDataContractSurrogate.GetObjectToSerialize
25: Return obj
26: End Function
27:
28: Public Function GetDeserializedObject(ByVal obj As Object, ByVal targetType As Type) As Object Implements IDataContractSurrogate.GetDeserializedObject
29: If (TypesToProxy.Contains(obj.GetType)) Then
30: Dim destination = ObjectFactory.CreateWithValidation(obj.GetType)
31: PropertyCopy.Copy(obj, destination)
32: Return destination
33: End If
34:
35: Return obj
36: End Function
37:
38: Public Function GetCustomDataToExport(ByVal memberInfo As MemberInfo, ByVal dataContractType As Type) As Object Implements IDataContractSurrogate.GetCustomDataToExport
39: Throw New NotImplementedException()
40: End Function
41:
42: Public Function GetCustomDataToExport(ByVal clrType As Type, ByVal dataContractType As Type) As Object Implements IDataContractSurrogate.GetCustomDataToExport
43: Throw New NotImplementedException()
44: End Function
45:
46: Public Sub GetKnownCustomDataTypes(ByVal customDataTypes As Collection(Of Type)) Implements IDataContractSurrogate.GetKnownCustomDataTypes
47: Throw New NotImplementedException()
48: End Sub
49:
50: Public Function GetReferencedTypeOnImport(ByVal typeName As String, ByVal typeNamespace As String, ByVal customData As Object) As Type Implements IDataContractSurrogate.GetReferencedTypeOnImport
51: Throw New NotImplementedException()
52: End Function
53:
54: Public Function ProcessImportedType(ByVal typeDeclaration As CodeTypeDeclaration, ByVal compileUnit As CodeCompileUnit) As CodeTypeDeclaration Implements IDataContractSurrogate.ProcessImportedType
55: Throw New NotImplementedException()
56: End Function
57:
58: End Class
When deserializing we create the proxy in the same way we did for a new Employee and we copy the object we receive in the parameter of that method to the proxy. To copy the properties we use a little class from Jon Skeet. Now we need to tell WCF to use our surrogate. We do that with the help of a OperationBehavior:
1: Public Class AddValidationDataContractSerializerOperationBehavior
2: Inherits DataContractSerializerOperationBehavior
3:
4: Private m_TypesToProxy() As Type
5:
6: Public Property TypesToProxy() As Type()
7: Get
8: Return m_TypesToProxy
9: End Get
10: Set(ByVal value As Type())
11: m_TypesToProxy = value
12: End Set
13: End Property
14:
15: Public Sub New(ByVal description As OperationDescription, ByVal proxyTypes As Type())
16: MyBase.New(description)
17: TypesToProxy = proxyTypes
18: End Sub
19:
20: Public Overloads Overrides Function CreateSerializer(ByVal type As Type, ByVal name As XmlDictionaryString, ByVal ns As XmlDictionaryString, ByVal knownTypes As IList(Of Type)) As XmlObjectSerializer
21: 'maxItemsInObjectGraph
22: 'ignoreExtensionDataObject
23: 'preserveObjectReferences
24: 'dataContractSurrogate
25: Return New DataContractSerializer(type, name, ns, knownTypes, &H7FFF, False, _
26: True, New ValidationDataContractSurrogate(TypesToProxy))
27: End Function
28:
29: End Class
1: ''' <summary>
2: ''' Attribute to be put on an operation in the service contract. You need to pass the types that
3: ''' need to be proxied.
4: ''' </summary>
5: ''' <remarks></remarks>
6: Public Class AddValidationToDataContractAttribute
7: Inherits Attribute
8: Implements IOperationBehavior
9:
10: Private m_TypesToProxy() As Type
11:
12: Public Property TypesToProxy() As Type()
13: Get
14: Return m_TypesToProxy
15: End Get
16: Set(ByVal value As Type())
17: m_TypesToProxy = value
18: End Set
19: End Property
20:
21: Public Sub New(ByVal types() As Type)
22: TypesToProxy = types
23: End Sub
24:
25:
26: Public Sub AddBindingParameters(ByVal description As OperationDescription, ByVal parameters As BindingParameterCollection) Implements IOperationBehavior.AddBindingParameters
27: End Sub
28:
29: Public Sub ApplyClientBehavior(ByVal description As OperationDescription, ByVal proxy As System.ServiceModel.Dispatcher.ClientOperation) Implements IOperationBehavior.ApplyClientBehavior
30: Dim innerBehavior As IOperationBehavior = New AddValidationDataContractSerializerOperationBehavior(description, TypesToProxy)
31: innerBehavior.ApplyClientBehavior(description, proxy)
32: End Sub
33: Public Sub ApplyDispatchBehavior(ByVal description As OperationDescription, ByVal dispatch As System.ServiceModel.Dispatcher.DispatchOperation) Implements IOperationBehavior.ApplyDispatchBehavior
34:
35: End Sub
36:
37: Public Sub Validate(ByVal description As OperationDescription) Implements IOperationBehavior.Validate
38:
39: End Sub
40: End Class
Then the only thing left to do is to add our attribute to the operation in the service contract:
1: <OperationContract()> _
2: <TransactionFlow(TransactionFlowOption.Allowed)> _
3: <AddValidationToDataContract(New Type() {GetType(InternInfo), GetType(FrameRealInfo)})> _
4: Function GetIntern(ByVal id As Integer) As InternInfo
That’s it. Now we also have validation for entities that already exist. There is one important remark here. As you can see I have put the attribute on a method that returns 1 instance of a DataContract and there is a pretty good reason for that. In the implementation of IDataContractSurrogate you have seen that we copy the properties to the proxy. That’s something you can do for 1 instance, but when you do that for a large collection of objects it takes way too much time. All in all I think this is a pretty elegant way of dealing with validation in WPF.
