Silverlight / WCF: fixing the Custom WCF Proxy Generator

Print Content | More

Some days ago I blogged about how you can build your own Custom WCF Proxy generator to extend the classes it generates and add some validation logic (or whatever you like).

Well playing with the MusicStore sample and making some changes to my domain classes there I’ve found a bug in the previous version of the proxy generator, let’s say your domain classes are like these:

[DataContract]
public abstract class DomainObject<TKey>
{
    [DataMember]
    public virtual TKey Id { get; set; }
}
 
[DataContract]
public partial class Album : DomainObject<int>
{
    public Album()
    {
        Tracks = new List<Track>();
    }
 
    //[DataMember]
    //public virtual int Id { get; set; }
 
    [DataMember]
    public virtual string Title { get; set; }
 
    [DataMember]
    public virtual string Author { get; set; }
...

Not only plain objects, like I had before, but you have inheritance from different classes, actually the previous proxy generator was going to extend only the classes that directly inherited from object, so in the proxy generated in the Silverlight project you could find the added code only in the ‘DomainObjectOfInt’ generated class (the base of the hierarchy).

To overcome this issue we have to modify the function FindAllProxyClasses() that identifies the proxy classes to extend, the new behavior will be like this:

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 need to take into account even model classes derived from other classes
               if ((!IsInterface(baseType)) &&
                           (baseType.BaseType != "System.ComponentModel.AsyncCompletedEventArgs") && 
                          (!baseType.BaseType.Contains("System.ServiceModel.ClientBase")))
               {
                  // we have found the proxy!
                  result.Add(type);
                  break;
               }
            }
         }
      }
   }
   return result;
}

This way we exclude from our list all the classes that are: interfaces, derive from AsyncCompletedEventArgs (arguments of async events), derive from ClientBase (the class that actually handles the calls to the web service).

If you ask yourself why I had to write an IsInterface() function to check if a CodeTypeReference is an interface, the answer is: the framework already provides this information...but in an internal member of the class :(.

Here is the full code of the fixed Custom WCF Proxy Generator:

[GuidAttribute("64205D39-7D51-4c6d-8C0F-237E6FE2BD70")]
public class StructuraWcfProxyGenerator : WCFProxyGenerator
{
   protected override void CallCodeGeneratorExtensions(CodeCompileUnit compileUnit)
   {
      //todo: we can implement the mapping of validation attributes using an external xml file placed inside the current project
      //      this way we can use the annotation objects even with wcf services
      //EnvDTE.DTE vs = (EnvDTE.DTE)this.ServiceProvider.GetService(typeof(EnvDTE.DTE));
      //EnvDTE.Project prj = vs.SelectedItems.Item(1).ProjectItem.ContainingProject;
      //System.Diagnostics.Debug.WriteLine(prj.FullName);
 
      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 need to take into account even model classes derived from other classes
                  if ((!IsInterface(baseType)) &&
                              (baseType.BaseType != "System.ComponentModel.AsyncCompletedEventArgs") && 
                             (!baseType.BaseType.Contains("System.ServiceModel.ClientBase")))
                  {
                     // we have found the proxy!
                     result.Add(type);
                     break;
                  }
               }
            }
         }
      }
      return result;
   }
 
    private static bool IsInterface(CodeTypeReference reference)
    {
        // try to create the type and see if it's an interface
        try
        {
            return Type.GetType(reference.BaseType).IsInterface;
        }
        catch (Exception)
        {
            return false;
        }
    }
 
    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);
 
      // the codedom do not support partial methods yet
      //CodeMemberMethod MyMethod = new CodeMemberMethod();
      //MyMethod.Name = "ValidateProperty";
      //MyMethod.ReturnType = new CodeTypeReference("partial void");
      //MyMethod.Attributes = MemberAttributes.ScopeMask;
      //MyMethod.Parameters.Add(new CodeParameterDeclarationExpression("System.String", "propertyName"));
      //MyMethod.Parameters.Add(new CodeParameterDeclarationExpression("System.Object", "value"));
      //type.Members.Add(MyMethod);
   }
 
   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));
         }
      }
   }
}

You can take this code and replace the old version in my previous post, regenerate your proxies and voila...you have the validation code spammed on all your new proxy class hierarchy.

With some more customization you can easily buildup a code generator that closely resembles the one provided by the new RIA services without any modification to you current up and running WCF services.



Custom, Generator, Proxy, Silverlight, Wcf

3 comments

Related Post

  1. #1 da uberVU - social comments - Saturday March 2010 alle 01:15

    Social comments and analytics for this post... This post was mentioned on Twitter by A_Giorgetti: Blogged: Silverlight / WCF: fixing the Custom WCF Proxy Generator http://bit.ly/8TIJ9t...

  2. #2 da Tweets that mention Silverlight / WCF: fixing the Custom WCF Proxy Generator at Primordial Code -- Topsy.com - Saturday March 2010 alle 01:15

    [...] This post was mentioned on Twitter by alkampfer, A_Giorgetti. A_Giorgetti said: Blogged: Silverlight / WCF: fixing the Custom WCF Proxy Generator http://bit.ly/8TIJ9t [...]

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

    Hi, Did you try to run this tool on VS 2010? I added the assembly to GAC then ran the reg file, but later I got an exception when I try to run the custom tool: "The custom tool '....' failed. The Extender Provider failed to return an Extender for this object" Regards

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)