Geeks With Blogs

News

Currently Reading

CLR via C#m
Under The Influence(of code) Abhijeet Patel's blog

It’s been a while since I’ve blogged since things have been crazy busy. I finally decided to kick myself and do a post so without further ado, the problem at hand:
A co-worker of mine asked me whether its possible to dynamically create a type from some data he is receiving from a WCF service (over HTTP).
My natural response was, why would you need to do that since Visual Studio creates a proxy from the metadata exposed by the service and the return types would typically be DataContracts.
Well, it turns out that instead of returning a strongly typed domain specific object, the service is returning a Dictionary<string,string> where the Key represents an attribute and the Value represents the value of the attribute.

This has obvious problems:

 

  • Nothing is strongly typed
  • No contract versioning
  • The service could return a variable number of fields
A naive approach is to inspect the data returned by the service and hand code a class with properties based on the type of each key in the Dictionary.
For example, if the Dictionary contained the following KeyValuePairs.
{“Name”,”Jon Smith”}
{“Age”,”20”}
{“Zip”,”90876”}

The class definition would look something like this:
 
  1. public class User  
  2. {  
  3.   public string Name {get;set;}  
  4.   public int Age {get;set;}  
  5.   public int Zip {get;set;}  
  6. }  

That’s fine when you have a couple of KeyValuePairs but what if you had 10 or 20, it gets unwieldy very quickly.
Enter T4 templates, a slick way to generate code for repetitive tasks. I'd never written a T4 template before so I figured this would be a nice excuse to dabble in it a little.
 

Let’s create a simple service to simulate some data, nothing too fancy
 

 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Runtime.Serialization;  
  5. using System.ServiceModel;  
  6. using System.Text;  
  7.   
  8. namespace T4WCFService  
  9. {  
  10.     [ServiceContract]  
  11.     public interface IT4WCFService  
  12.     {  
  13.   
  14.         [OperationContract]  
  15.         Dictionary<string,string> GetUser(string userID);         
  16.     }  
  17. }  
and its implementation...
 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Runtime.Serialization;  
  5. using System.ServiceModel;  
  6. using System.Text;  
  7.   
  8. namespace T4WCFService  
  9. {  
  10.     public class T4Service : IT4WCFService  
  11.     {  
  12.  
  13.         #region IT4WCFService Members  
  14.   
  15.         public Dictionary<stringstring> GetUser(string userID)  
  16.         {  
  17.             if (userID == "jsmith")  
  18.             {  
  19.                 return new Dictionary<stringstring>   
  20.                         {   
  21.                             { "Name""Jack smith" },  
  22.                             { "Age""30" },   
  23.                             { "City""San francisco" },  
  24.                             { "State""CA"  },   
  25.                             { "Zip""94061"  },  
  26.                             { "Country""USA"  }   
  27.   
  28.   
  29.                         };    
  30.             }  
  31.             if (userID == "jasmith")  
  32.             {  
  33.                 return new Dictionary<stringstring>   
  34.                         {   
  35.                             { "Name""Jane smith" },  
  36.                             { "Age""28" },   
  37.                             { "City""Portland" },  
  38.                             { "State""OR"  },   
  39.                             { "Zip""98685"  },  
  40.                             { "Country""USA"  }   
  41.                         };  
  42.             }  
  43.             return new Dictionary<stringstring>();              
  44.         }  
  45.  
  46.         #endregion  
  47.     }  
  48. }  

The service just simulates returning some dummy users based on a userID and returns an empty Dictionary if no matching user is found, and it’s exposed using
WSHttpBinding

The <system.servicemodel> looks as follows:
 
  1. <system.serviceModel>  
  2.   <services>  
  3.     <service behaviorConfiguration="T4WCFService.Service1Behavior"  
  4.       name="T4WCFService.T4Service">  
  5.       <endpoint address="WsHttpT4Service" binding="wsHttpBinding" name="WsHttpT4Service"  
  6.         contract="T4WCFService.IT4WCFService">  
  7.         <identity>  
  8.           <dns value="localhost" />  
  9.         </identity>  
  10.       </endpoint>  
  11.       <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />  
  12.       <endpoint address="basicHttpTService" binding="basicHttpBinding"  
  13.         name="basicHttpTService" contract="T4WCFService.IT4WCFService" />  
  14.       <host>  
  15.         <baseAddresses>  
  16.           <add baseAddress="http://WCFServices" />  
  17.         </baseAddresses>  
  18.       </host>  
  19.     </service>  
  20.   </services>  
  21.   <behaviors>  
  22.     <serviceBehaviors>  
  23.       <behavior name="T4WCFService.Service1Behavior">  
  24.         <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->  
  25.         <serviceMetadata httpGetEnabled="true"/>  
  26.         <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->  
  27.         <serviceDebug includeExceptionDetailInFaults="false"/>  
  28.       </behavior>  
  29.     </serviceBehaviors>  
  30.   </behaviors>  
  31. </system.serviceModel>  

If you(like me) have never created a T4 template before, I’d strongly suggest reading this article first.

Welcome back, now that you know how to create a simple T4 template, let’s get started.
T4 enables writing reusable modules that can be consumed from other T4 modules. For the purpose of this exercise I decided to create a template which encapsulates invoking a WCF service and returning the result back to the caller.

Create a new Console Application and add a service reference to the service we created above, also add a new T4 file to your project called WCFServiceInvoker.tt

T4_WCFServiceInvoker

Double click on WCFServiceInvoker.tt and the file should open up in markup mode.Delete the default code in the .tt file and add the following code:

 

 
  1. <#@ template language="C#v3.5"#>  
  2. <#@ output extension=".cs"#>   
  3. <#@ assembly name ="System.ServiceModel" #>   
  4. <#@ import namespace="System.Collections.Generic"#>  
  5. <#@ import namespace="System.Reflection"#>  
  6. <#@ import namespace="System.ServiceModel.Channels"#>  
  7. <#@ import namespace="System.ServiceModel"#>  
  8.   
  9. <#+  
  10.     public class WCFServiceInvoker<TInterfaceType,TReturnType> where TReturnType:class  
  11.     {  
  12.       public string EndPointAddress {get;set;}  
  13.       public string EndPointName {get;set;}  
  14.       public Type ServiceInterfaceType {get;set;}        
  15.       public Binding Binding {get;set;}  
  16.       public string MethodName {get;set;}  
  17.       public object[] Parameters {get;set;}  
  18.       public TReturnType InvokeMethod()  
  19.       {  
  20.             EndpointAddress endpoint = new EndpointAddress(EndPointAddress);  
  21.             Type typeDef = typeof(ChannelFactory<>).MakeGenericType(typeof(TInterfaceType));  
  22.             var typeInst = Activator.CreateInstance(typeDef,new object[]{Binding,endpoint});  
  23.             var channel = typeInst.GetType().InvokeMember("CreateChannel", BindingFlags.InvokeMethod,null, typeInst, null);  
  24.             var results = channel.GetType().InvokeMember(MethodName, BindingFlags.InvokeMethod, null,channel, Parameters);  
  25.             return results as TReturnType;  
  26.       }  
  27.         
  28.     }  
  29. #>  

There is a lot going on here.

  • The template and output directives indicate that the code is written in C# and the generated file needs to be have an extension of .cs.
     
  • The <#@assembly> directive is similar to an “Add Reference” in Visual Studio, it allows for referencing external assemblies.
  • The <#@import> directive is similar to a “using” statement, in that it brings the types in the specified namespace in scope so that the template can avail of them.
  • The <#+ #> directive is called Class feature block and it allows for writing class definitions within the template.
    This allows for encapsulating reusable logic.
    NOTE: The class definitions in this block are not emitted out to the final .cs file.
  • The WCFServiceInvoker class contains a bunch of properties, whose values(when set) are used for invoking a specified service dynamically.
  • The type parameters “TInterfaceType” represents the type of the interface on which the method specified in the "MethodName" parameter will be invoked.
  • The type parameter “TReturnType” represents the type of the result returned from the method call.

Using “TInterfaceType” we dynamically create a type definition for ChannelFactory<T> using reflection, and then create an instance of it, on which we then call CreateChannel which in turns returns us an instance of a type which implements TInterfaceType. Once we have a “TInterfaceType” instance we then go ahead and invoke the specified “MethodName” with the parameters specified in the “Parameters” object[] property and get the result back which is typed to “TReturnType” and returned to the caller.

Phew! that’s a mouthful and we are not done yet. For now just keep in mind that this template is merely a helper which encapsulates the details on dynamically invoking a WCF service in a strongly typed manner.

Now onto the main task of writing a template to solve our problem.

Add another T4 template to the project and call it UserClass.tt
T4_UserClass

As before delete the code from the .tt file and add the following:
 

 
  1. <#@ assembly name ="System.ServiceModel" #>   
  2. <#@ assembly name ="T4WCFService.dll" #>   
  3. <#@ assembly name ="System.ServiceModel" #>   
  4. <#@ import namespace="System.Collections.Generic"#>  
  5. <#@ import namespace="System.Reflection"#>  
  6. <#@ import namespace="System.ServiceModel.Channels"#>  
  7. <#@ import namespace="System.ServiceModel"#>  
  8. <#@ import namespace="T4WCFService"#>  
  9. <#@ include file="WCFServiceInvoker.tt" #>  
  10. using System.Collections.Generic;  
  11. namespace T4ClassFromDict  
  12. {  
  13.     public class User  
  14.     {  
  15.         <#  
  16.         WCFServiceInvoker<IT4WCFService,Dictionary<string,string>> invoker = new WCFServiceInvoker<IT4WCFService,Dictionary<string,string>>();  
  17.         invoker.EndPointAddress = "http://localhost:24551/Service1.svc/WsHttpT4Service";  
  18.         invoker.EndPointName = "WsHttpT4Service";  
  19.         invoker.ServiceInterfaceType = typeof(IT4WCFService);  
  20.         invoker.Binding = new WSHttpBinding();  
  21.         invoker.MethodName = "GetUser";  
  22.         invoker.Parameters = new object[]{"jsmith"};      
  23.         Dictionary<string,string> result = invoker.InvokeMethod();  
  24.         #>  
  25.          <#foreach(KeyValuePair<string,string> item in result){  
  26.          #>  
  27.            
  28.          public <#= item.Key.GetType().Name.ToLower()#> <#=item.Key#>  
  29.          {  
  30.              get;         
  31.              set;  
  32.          }  
  33.          <#}#>  
  34.            
  35.          public static User GetUser(Dictionary<string,string> dict)  
  36.          {  
  37.              var user = new User();  
  38.              foreach(KeyValuePair<string,string> kvp in dict)  
  39.              {  
  40.                 typeof(User).GetProperty(kvp.Key).SetValue(user,kvp.Value,null);                  
  41.              }  
  42.              return user;  
  43.          }  
  44.     }  
  45. }  

We've included the template file we created before so that we can reuse it to invoke any WCF Service.
We then proceed to create an instance of WCFServiceInvoker and set it's properties to invoke the WCF service we've created.
The result of "invoker.InvokeMethod()" is a Dictionary<string,string> which we then loop through creating public properties.

We use the type of the Key as the type of the property and the Key itself as the property name.
 

We've also created the definition of factory method which accepts a Dictionary<string,string> and returns a User object.

Note that we've added a direct reference to the service assembly in this template so that we can get a reference to IT4WCFService.
This is just to keep the example simple, ideally you would want to seperate the interface and implementation assemblies and have a reference to just the interface assembly.

T4 templates are executed typically when saved or by right clicking on the .tt file and selecting "Run Custom Tool".
Before running the template, ensure that the WCF service is up and running.

On running/saving the template, the following class definition is generated.
 

 

 
  1. using System.Collections.Generic;  
  2. namespace T4ClassFromDict  
  3. {  
  4.     public class User  
  5.     {  
  6.                            
  7.          public string Name  
  8.          {  
  9.              get;         
  10.              set;  
  11.          }  
  12.                    
  13.          public string Age  
  14.          {  
  15.              get;         
  16.              set;  
  17.          }  
  18.                    
  19.          public string City  
  20.          {  
  21.              get;         
  22.              set;  
  23.          }  
  24.                    
  25.          public string State  
  26.          {  
  27.              get;         
  28.              set;  
  29.          }  
  30.                    
  31.          public string Zip  
  32.          {  
  33.              get;         
  34.              set;  
  35.          }  
  36.                    
  37.          public string Country  
  38.          {  
  39.              get;         
  40.              set;  
  41.          }  
  42.                    
  43.          public static User GetUser(Dictionary<string,string> dict)  
  44.          {  
  45.              var user = new User();  
  46.              foreach(KeyValuePair<string,string> kvp in dict)  
  47.              {  
  48.                 typeof(User).GetProperty(kvp.Key).SetValue(user,kvp.Value,null);                  
  49.              }  
  50.              return user;  
  51.          }  
  52.     }  
  53. }  

A client can then use the generates class as follows:
 

 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Net;  
  6. using System.IO;  
  7. using System.ServiceModel;  
  8. using T4WCFService;  
  9. using System.Reflection;  
  10.   
  11.   
  12. namespace T4ClassFromDict  
  13. {    
  14.   
  15.     class Program  
  16.     {  
  17.   
  18.         public string Foo { getset; }  
  19.         static void Main(string[] args)  
  20.         {            
  21.             var client = new T4Service.T4WCFServiceClient("WsHttpT4Service");  
  22.             var data = User.GetUser(client.GetUser("jsmith"));  
  23.         }  
  24.     }  
  25. }  

 

NOTE: The console app hosting the project has a direct project reference to WCF service project since it needs the IT4WCFService definition. and since T4 engine is running inside Visual studio it locks the assembly, so rebuilding the WCF service after template generation does not work.
As mentioned previously the solution to this is to have seperate assemblies for the interface and service implementation.
Download Code
Posted on Sunday, October 18, 2009 12:06 AM C# , WCF | Back to top


Comments on this post: T4 template consuming a WCF service

# re: T4 template consuming a WCF service
Requesting Gravatar...
Your article is very interesting, I have introduced a lot of friends look at this article, the content of the articles there will be a lot of attractive people to appreciate, I have to thank you such an article.
Left by True Religion jeans on Oct 19, 2009 2:14 AM

# re: T4 template consuming a WCF service
Requesting Gravatar...
Thank you very much. I am wonderring if I can share your article in the bookmarks of society.
Left by Jordan 6 Rings on Oct 19, 2009 2:15 AM

# re: T4 template consuming a WCF service
Requesting Gravatar...
@Thomas: My apologies, it's fixed now. Thanks for pointing that out.
Left by Abhijeet on Oct 19, 2009 8:43 AM

# re: T4 template consuming a WCF service
Requesting Gravatar...
@Jordan: Please feel free to share the article and thanks for reading.
Left by Abhijeet on Oct 20, 2009 8:42 PM

# re: T4 template consuming a WCF service
Requesting Gravatar...
@SupraShoes: Glad you enjoyed it
Left by Abhijeet on Oct 20, 2009 8:45 PM

# re: T4 template consuming a WCF service
Requesting Gravatar...
thank you for you sharing. dress you well
Left by dressyouwell on Apr 04, 2010 11:48 AM

# re: T4 template consuming a WCF service
Requesting Gravatar...
We launched the 2010 latest and most fashionable True Religion jeans on our shop, you are welcome to patronize.Here are many discounts for you.
Our aim is to provide high-quality products and excellent after-sales service.
Left by True Religion jeans on Apr 27, 2010 11:18 PM

# re: T4 template consuming a WCF service
Requesting Gravatar...
In the cold winter, if you want to have a good sleep, Moncler knows what to do for that.Discount Moncler jackets,Discount Moncler T-shirts Looking here!
Left by Discount Moncler jackets on Aug 03, 2010 8:53 PM

# re: T4 template consuming a WCF service
Requesting Gravatar...

They accomplish absolutely abundant boots and of advance you appetite as abundant advice as you can

get your easily on! If you accept any questions about accustomed Online Sale Store or about the cast in

accepted amuse feel chargeless to accord us. Happy UGG Day。 Another abundant catechism is about the

aftereffect of baptize on UGG Boots.
http://www.uggsbootsclassic.com
Left by ugg boots bailey on Sep 28, 2010 8:33 PM

# re: T4 template consuming a WCF service
Requesting Gravatar...
Hi
Could you please provide few inputs on WCF Service generation using T4 template
Left by Ravi Ayitha on Aug 07, 2012 4:35 AM

Your comment:
 (will show your gravatar)


Copyright © Abhijeet Patel | Powered by: GeeksWithBlogs.net