Silverlight / WCF : Writing your own Custom WCF Proxy Generator to support validation

Print Content | More
Working on the data validation section of different projects in Silverlight we usually have to face the standard problem: basically in Silverlight 3 the data validation framework relies on exceptions thrown in the setter of objects properties.

In a real-world application our data will surely come from a Web Service (we are not using RIA Services yet), if we do not have control over the service itself we are forced to use the proxy classes generated by svcutil or Visual Studio as part of our domain model.

The main problem is we do not have huge control over the classes that are generated by the standard WCF Proxy Generator that Visual Studio provide, and the classes it generates are pretty simple (they only support the INotifyPropertyChanged interface).

Using the RIA services framework can help solving the validation problem because the proxy classes it generate have a richer set of functionalities built in.

However if we are stuck with plain WCF services we have several ways to overcome this problem and add validation logic (or any other business logic) to these classes.

One of the possible approach (not the simplest one I have to admit) is to write you own WCF Proxy Class Generator and alter the code that the standard generator provide; I had this idea looking at this post that explains the basis of customizing the generator: Customizing WCF Proxy Generation in Visual Studio 2008.

For a first implementation we want to be able to generate a class like this:
   1: [System.Diagnostics.DebuggerStepThroughAttribute()]
   2: [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
   3: [System.Runtime.Serialization.DataContractAttribute(Name="CompositeType", Namespace="http://schemas.datacontract.org/2004/07/MusicStore.WebService")]
   4: public partial class CompositeType : object, System.ComponentModel.INotifyPropertyChanged {
   5:
   6:     private bool BoolValueField;
   7:
   8:     private string StringValueField;
   9:
  10:     partial void ValidateProperty(string propertyName, object value);
  11:
  12:     [System.Runtime.Serialization.DataMemberAttribute()]
  13:     public bool BoolValue {
  14:         get {
  15:             return this.BoolValueField;
  16:         }
  17:         set {
  18:             this.ValidateProperty("BoolValue", value);
  19:             if ((this.BoolValueField.Equals(value) != true)) {
  20:                 this.BoolValueField = value;
  21:                 this.RaisePropertyChanged("BoolValue");
  22:             }
  23:         }
  24:     }
  25:     ...
The key points here are lines 10 and 18: we ‘declare’ a partial method and we use it to validate the property value before assigning it. Partial methods are very useful especially for code generators and designers, because if you do not provide an implementation in a partial class they are removed at compile time (that is the method declaration and its usage disappear).

We chose this approach to leave you the freedom to implement the validation rules the way you like most.

To extend the WCF Proxy Generator we basically have to inherit from the WCFProxyGenerator class and override the CallCodeGeneratorExtensions(CodeCompileUnit compileUnit), here we have access to the CodeDom representation of the class that was generated, we can then inspect it looking for the business entity classes (those that directly inherit from object) and, playing with the CodeDom classes, modify them adding the partial method declaration and the method call:
[GuidAttribute("64205D39-7D51-4c6d-8C0F-237E6FE2BD70")]
public class StructuraWcfProxyGenerator : WCFProxyGenerator
{
   protected override void CallCodeGeneratorExtensions(CodeCompileUnit compileUnit)
   {
      base.CallCodeGeneratorExtensions(compileUnit);
      // find all classes that inherit from ClientBase (all proxies)
      var proxies = FindAllProxyClasses(compileUnit);
      // add impersonation code to their constructors
      foreach (CodeTypeDeclaration proxy in proxies)
      {
         AddPartialMethods(proxy);
         AddValidationToProperties(proxy);
      }
   }
   protected virtual CodeTypeDeclarationCollection FindAllProxyClasses(CodeCompileUnit compileUnit)
   {
      CodeTypeDeclarationCollection result = new CodeTypeDeclarationCollection();
      // search for all the proxy class (the one that inherits from ClientBase)
      foreach (CodeNamespace ns in compileUnit.Namespaces)
      {
         foreach (CodeTypeDeclaration type in ns.Types)
         {
            // does this type inherit from ClientBase?
            if (type.IsClass && type.IsPartial)
            {
               foreach (CodeTypeReference baseType in type.BaseTypes)
               {
                  if (baseType.BaseType == "System.Object")
                  {
                     // we have found the proxy!
                     result.Add(type);
                     break;
                  }
               }
            }
         }
      }
      return result;
   }
   protected virtual void AddPartialMethods(CodeTypeDeclaration type)
   {
      IVsSingleFileGenerator ivs = (IVsSingleFileGenerator)this;
      // ugly, but it's the only way I found to identify the language used
      string ext;
      ivs.DefaultExtension(out ext);
      CodeSnippetTypeMember literalMember;
      if (ext.Contains("cs"))
      {
         // csharp
         literalMember = new CodeSnippetTypeMember(
            "partial void ValidateProperty(string propertyName, object value);");
      }
      else
      {
         // vb 
         literalMember = new CodeSnippetTypeMember(
            "Partial Sub ValidateProperty(byval propertyName as String, byval value as Object)");
      }
      type.Members.Add(literalMember);
   }
   protected virtual void AddValidationToProperties(CodeTypeDeclaration type)
   {
      foreach (CodeTypeMember member in type.Members)
      {
         CodeMemberProperty ctor = member as CodeMemberProperty;
         if (ctor != null)
         {
            // create a code statement like:
            // this.ValidateProperty("Title", value)
            CodeMethodInvokeExpression method = new CodeMethodInvokeExpression(
                new CodeThisReferenceExpression(),
                "ValidateProperty",
               new CodeExpression[] {
                  new CodePrimitiveExpression(ctor.Name),
                  new CodePropertySetValueReferenceExpression()
               });
            // we got a constructor
            ctor.SetStatements.Insert(0, new CodeExpressionStatement(method));
         }
      }
   }
}
The tricky part here is the AddPartialMethods() function: the CodeDom do not have support for partial methods so, we have to provide the full string with the method signature.

To use this extension you have to do 4 things: 1- compile the project and deploy the signed assembly to the GAC 2- double click the attached .reg file to register the extension in Visual Studio 2008 3- add a Service Reference to your project 4- navigate to the Reference.svcmap file and change the custom tool to use the brand new ‘Structura WCF Proxy Generator’ (the name must match the keys added in the reg file), optionally rerun the tool.

WCFProxyGeneratorCustomTool Then you can just implement the partial method in your partial class to throw an exception in the setter if the value violate your client-side validation rule, this way you get the standard validation framework to work with proxy generated classes:
public partial class Album
  {
     #region "exception validation methods"
     partial void ValidateProperty(string propertyName, object value)
     {
        // Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = propertyName });
        switch (propertyName)
        {
           case "Title":
              if (!ValidateTitle(value))
                 throw new SystemException("The field is Required");
              break;
           case "PublicationDate":
              if (!ValidatePublicationDate(value))
                 throw new SystemException("Invalid date (must be > 1900)");
              break;
        }
     }
...
This isn’t the cleanest solution in the world, but we can easily use this project as a base to add the full support for validation using the new data annotations.

Side note: if you want to create your own custom WCF Proxy Generator you need the reference to some assemblies related to visual studio that are actually deployed in the GAC, I haven’t found any way to add them using the Visual Studio designers and I had to edit the project file in the Notepad and add the following references manually:
<Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<Reference Include="Microsoft.VisualStudio.Editors, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
Sample project:


Custom, Generator, Proxy, Silverlight, Wcf

5 comments

Related Post

  1. #1 da Tweets that mention Silverlight / WCF : Writing your own Custom WCF Proxy Generator to support validation at Primordial Code -- Topsy.com - Saturday March 2010 alle 01:15

    [...] This post was mentioned on Twitter by DotNetMarche, A_Giorgetti. A_Giorgetti said: Blogged: Silverlight / WCF : Writing your own Custom WCF Proxy Generator to support validation http://bit.ly/6mv2TK [...]

  2. #2 da Guardian - Saturday March 2010 alle 01:15

    No, I haven't had time to try it on VS 2010. But I'll do it as soon as I find time setup my VS 2010 test environment up.

  3. #3 da Bill - Saturday March 2010 alle 01:15

    Thanks, I posted about the problem on VS 2010 forums here: http://social.msdn.microsoft.com/Forums/en-US/vsxprerelease/thread/b18643f3-1257-4981-9a6a-96a034ec5243/ In case you found a solution to this, would appreciate an email from you! Thanks

  4. #4 da Bill - Saturday March 2010 alle 01:15

    Hi, I converted the project to VS 2010 and it is failing to run, have you tried running it on VS 2010? Thanks

  5. #5 da Ehsan Zargar Ershadi - Tuesday May 2011 alle 01:42

    Hi, after kicking around , found out how to convert it to VS 2010 , and it works perfectly.
    you can find it in : http://wcfproxygenerator.codeplex.com/discussions/258107

All fields are required and you must provide valid data in order to be able to comment on this post.


(will not be published)
(es: http://www.mysite.com)