It’s been a while since I’ve blogged and but I have a valid excuse; I moved to WA state recently and 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:
- public class User
- {
- public string Name {get;set;}
- public int Age {get;set;}
- public int Zip {get;set;}
- }
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
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.Serialization;
- using System.ServiceModel;
- using System.Text;
-
- namespace T4WCFService
- {
- [ServiceContract]
- public interface IT4WCFService
- {
-
- [OperationContract]
- Dictionary<string,string> GetUser(string userID);
- }
- }
and its implementation...
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.Serialization;
- using System.ServiceModel;
- using System.Text;
-
- namespace T4WCFService
- {
- public class T4Service : IT4WCFService
- {
-
- #region IT4WCFService Members
-
- public Dictionary<string, string> GetUser(string userID)
- {
- if (userID == "jsmith")
- {
- return new Dictionary<string, string>
- {
- { "Name", "Jack smith" },
- { "Age", "30" },
- { "City", "San francisco" },
- { "State", "CA" },
- { "Zip", "94061" },
- { "Country", "USA" }
-
-
- };
- }
- if (userID == "jasmith")
- {
- return new Dictionary<string, string>
- {
- { "Name", "Jane smith" },
- { "Age", "28" },
- { "City", "Portland" },
- { "State", "OR" },
- { "Zip", "98685" },
- { "Country", "USA" }
- };
- }
- return new Dictionary<string, string>();
- }
-
- #endregion
- }
- }
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:
- <system.serviceModel>
- <services>
- <service behaviorConfiguration="T4WCFService.Service1Behavior"
- name="T4WCFService.T4Service">
- <endpoint address="WsHttpT4Service" binding="wsHttpBinding" name="WsHttpT4Service"
- contract="T4WCFService.IT4WCFService">
- <identity>
- <dns value="localhost" />
- </identity>
- </endpoint>
- <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
- <endpoint address="basicHttpTService" binding="basicHttpBinding"
- name="basicHttpTService" contract="T4WCFService.IT4WCFService" />
- <host>
- <baseAddresses>
- <add baseAddress="http://WCFServices" />
- </baseAddresses>
- </host>
- </service>
- </services>
- <behaviors>
- <serviceBehaviors>
- <behavior name="T4WCFService.Service1Behavior">
-
- <serviceMetadata httpGetEnabled="true"/>
-
- <serviceDebug includeExceptionDetailInFaults="false"/>
- </behavior>
- </serviceBehaviors>
- </behaviors>
- </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
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:
- <#@ template language="C#v3.5"#>
- <#@ output extension=".cs"#>
- <#@ assembly name ="System.ServiceModel" #>
- <#@ import namespace="System.Collections.Generic"#>
- <#@ import namespace="System.Reflection"#>
- <#@ import namespace="System.ServiceModel.Channels"#>
- <#@ import namespace="System.ServiceModel"#>
-
- <#+
- public class WCFServiceInvoker<TInterfaceType,TReturnType> where TReturnType:class
- {
- public string EndPointAddress {get;set;}
- public string EndPointName {get;set;}
- public Type ServiceInterfaceType {get;set;}
- public Binding Binding {get;set;}
- public string MethodName {get;set;}
- public object[] Parameters {get;set;}
- public TReturnType InvokeMethod()
- {
- EndpointAddress endpoint = new EndpointAddress(EndPointAddress);
- Type typeDef = typeof(ChannelFactory<>).MakeGenericType(typeof(TInterfaceType));
- var typeInst = Activator.CreateInstance(typeDef,new object[]{Binding,endpoint});
- var channel = typeInst.GetType().InvokeMember("CreateChannel", BindingFlags.InvokeMethod,null, typeInst, null);
- var results = channel.GetType().InvokeMember(MethodName, BindingFlags.InvokeMethod, null,channel, Parameters);
- return results as TReturnType;
- }
-
- }
- #>
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

As before delete the code from the .tt file and add the following:
- <#@ assembly name ="System.ServiceModel" #>
- <#@ assembly name ="T4WCFService.dll" #>
- <#@ assembly name ="System.ServiceModel" #>
- <#@ import namespace="System.Collections.Generic"#>
- <#@ import namespace="System.Reflection"#>
- <#@ import namespace="System.ServiceModel.Channels"#>
- <#@ import namespace="System.ServiceModel"#>
- <#@ import namespace="T4WCFService"#>
- <#@ include file="WCFServiceInvoker.tt" #>
- using System.Collections.Generic;
- namespace T4ClassFromDict
- {
- public class User
- {
- <#
- WCFServiceInvoker<IT4WCFService,Dictionary<string,string>> invoker = new WCFServiceInvoker<IT4WCFService,Dictionary<string,string>>();
- invoker.EndPointAddress = "http://localhost:24551/Service1.svc/WsHttpT4Service";
- invoker.EndPointName = "WsHttpT4Service";
- invoker.ServiceInterfaceType = typeof(IT4WCFService);
- invoker.Binding = new WSHttpBinding();
- invoker.MethodName = "GetUser";
- invoker.Parameters = new object[]{"jsmith"};
- Dictionary<string,string> result = invoker.InvokeMethod();
- #>
- <#foreach(KeyValuePair<string,string> item in result){
- #>
-
- public <#= item.Key.GetType().Name.ToLower()#> <#=item.Key#>
- {
- get;
- set;
- }
- <#}#>
-
- public static User GetUser(Dictionary<string,string> dict)
- {
- var user = new User();
- foreach(KeyValuePair<string,string> kvp in dict)
- {
- typeof(User).GetProperty(kvp.Key).SetValue(user,kvp.Value,null);
- }
- return user;
- }
- }
- }
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.
- using System.Collections.Generic;
- namespace T4ClassFromDict
- {
- public class User
- {
-
- public string Name
- {
- get;
- set;
- }
-
- public string Age
- {
- get;
- set;
- }
-
- public string City
- {
- get;
- set;
- }
-
- public string State
- {
- get;
- set;
- }
-
- public string Zip
- {
- get;
- set;
- }
-
- public string Country
- {
- get;
- set;
- }
-
- public static User GetUser(Dictionary<string,string> dict)
- {
- var user = new User();
- foreach(KeyValuePair<string,string> kvp in dict)
- {
- typeof(User).GetProperty(kvp.Key).SetValue(user,kvp.Value,null);
- }
- return user;
- }
- }
- }
A client can then use the generates class as follows:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Net;
- using System.IO;
- using System.ServiceModel;
- using T4WCFService;
- using System.Reflection;
-
-
- namespace T4ClassFromDict
- {
-
- class Program
- {
-
- public string Foo { get; set; }
- static void Main(string[] args)
- {
- var client = new T4Service.T4WCFServiceClient("WsHttpT4Service");
- var data = User.GetUser(client.GetUser("jsmith"));
- }
- }
- }
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