Click here to Skip to main content
Click here to Skip to main content

Using T4 to Generate a Wrapper for a WCF Client

, 14 Jun 2010
Rate this:
Please Sign up or sign in to vote.
Using T4 to generate a wrapper for a WCF Client

Background

The idea for this article came from two places:

  1. I'm writing an application that gets all of its data from a WCF service. Each time I call the WCF service, I end up having to write a bunch of duplicate code and of course I don't like to type, so I was trying to find a way to not have to write that code.
  2. The app I'm developing is an internal business application, and we release weekly. Each release to the WCF services could be incompatible with the previous client. That means that I need to do the release during off hours. I don't like working nights, so I wanted to find a way to have multiple copies of the service running and have the client choose which service to access based on its version, but I didn't want to have to keep changing the config file for the client with each release.

First I'll solve those two problems, and then I'll demonstrate how to use T4 to generate this wrapper from the WCF service reference.

Prerequisites

To run the code, you’ll need to download and install the T4Toolbox.

Problem #1

A typical WCF call looks like this:

var proxy = new WCFServiceWrapper.WcfSvc.Service1Client()
try
{
    returnValue = proxy.GetData(value);
    proxy.Close();
}
catch
{
    proxy.Abort();
    throw;
}
Proxy = null;

To keep from having to write all that code every time I make a call to the WCF service, I created a static class that has a static method with the same signature as the WCF method I'm calling.

public static partial class SvcWrapper
{    
    public static string GetData(int value)
    {
        var proxy = GetServiceClient()
        try
        {
            var returnValue = proxy.GetData(value);
            proxy.Close();
            return returnValue;
        }
        catch
        {
            proxy.Abort();
            throw;
        }
    }
}

Now that same call that took 12 lines of code is now just this one line:

returnValue = SvcWrapper.GetData(value);

Problem #2

To solve the second problem, I just added another method that creates an instance of the service client for me.

public partial class SvcWrapper
{
    private static string _serviceAddress;
    private static string _configName;
    private static bool _needsConfig = true;
    internal static WcfSvc.Service1Client GetServiceClient()
    {
        if (_needsConfig)
        {
            //At this point I'd do some hoopajoop to determine what the 
            //current service address is for this version
            //something like:
 
            //ServiceConfig config = SomeWCFService.GetServiceConfig(versionNo);
            //_serviceAddress = config.Address;
            //_configName = config.ClientEndPointName;
            //The address of the service endpoint
            _serviceAddress = "http://localhost:50324/Service1.svc"
            //This string is the Name of the Client Endpoint 
            //as defined in the running EXE's app.config
            _configName = "WSHttpBinding_IService1";
        }
        return new WCFServiceWrapper.WcfSvc.Service1Client
				(_configName, _serviceAddress);
    }
}

There's nothing earth shattering about that code, and I haven't even implemented the look of the address and config yet, but the shell is there for me to finish at a later date. But now if I want to add another endpoint configuration to the app.config file for this service, I can do that and have only one place to change which endpoint the app uses.

Using T4 to Generate Wrapper Methods

Now, I've solved my original 2 problems, but I've created another one. I'm going to have to create and maintain a wrapper method for every method exposed by the WCF Service. This is the perfect opportunity to do a little code generation with T4.

First thing you need to do is add references to the EnvDTE and T4ToolBox. Then add a new text file called GenerateServiceWrapper.t4. This file holds the code that is not specific to the service we're wrapping, and the t4 extension doesn't create the child .cs file that the .tt extension creates.

This file has 5 methods:

  • GetServiceInterface – takes the name of the service and searches the project for a file that matches. Then it calls FindInterface.
  • FindInterface – takes a project item and searches it for an interface. It returns the first one it finds, and null if it doesn't find one. It could maybe use some error handling but…
  • GetMethodSignature – this takes one of the public methods found on the interface and returns a string that will be the method signature of the wrapper method.
  • GetMethodCall – this takes one of the public methods found on the interface and returns a string that will be the call to that method on the WCF Service.
  • GetServiceWrapper – is where the rubber meets the road. This calls GetServiceInterface to get the interface, loops through the public methods and generates the wrapper methods.

Here’s the contents of that file, you’ll need to get a third party plugin to get syntax highlighting in Visual Studio.

<#@ Template Language="C#" #>
<#@ import namespace="EnvDTE" #>
<#@ include File="T4Toolbox.tt" #>
<#+
public void GetServiceWrapper(string LocalServiceName)
{
    EnvDTE.CodeInterface svcInterface = 
	GetServiceInterface(LocalServiceName + @"\reference.cs");
    foreach (var ce in svcInterface.Members)
    {
        var meth = ce as CodeFunction;
        if (meth != null)
        {
            if (meth.Access == vsCMAccess.vsCMAccessPublic)
            {
                string methodSignature = GetMethodSignature(meth); 
                string methodCall = GetMethodCall(meth);
                bool returnsVoid = false;
                if (meth.Type.AsString.Equals("void"))
                {
                    returnsVoid = true;
                }
                #>
        <#=methodSignature #>
        {
            var proxy = GetServiceClient();
            try
            {
            <#+
                if (returnsVoid)
                {
                       #>    proxy.<#=methodCall #>;
                            proxy.Close();
            <#+
                }
                else
                {
            #>    var returnValue = proxy.<#=methodCall #>;
                proxy.Close();
                return returnValue;
<#+
                }
#>
            }
            catch
            {
                proxy.Abort();
                throw;
            }
        }
        
<#+
            }
        }
    }
}

public EnvDTE.CodeInterface GetServiceInterface(string interfaceFile)
{
    ProjectItem projectItem = TransformationContext.FindProjectItem(interfaceFile);
    FileCodeModel codeModel = projectItem.FileCodeModel;
    return FindInterface(codeModel.CodeElements);
}

public CodeInterface FindInterface(CodeElements elements)
{
    foreach (CodeElement element in elements)
    {
        CodeInterface myInterface = element as CodeInterface;
        if (myInterface != null)
            return myInterface;
        myInterface = FindInterface(element.Children);
        if (myInterface != null)
            return myInterface;
    }
    return null;
}

public string GetMethodSignature(CodeFunction method)
{
    var methodSignature = new System.Text.StringBuilder();
    methodSignature.Append("public static ");
    methodSignature.Append(method.Type.AsString);
    methodSignature.Append(" ");
    methodSignature.Append(method.Name);
    methodSignature.Append("(");
    bool isFirstParameter = true;
    foreach (var prm in method.Parameters)
    {
        CodeParameter p = prm as CodeParameter;
        if (!isFirstParameter)
        {
            methodSignature.Append(", ");
        }
        else
        {
            isFirstParameter = false;
        }
        methodSignature.Append(p.Type.AsString);
        methodSignature.Append(" ");
        methodSignature.Append(p.Name);
    }
    methodSignature.Append(")");
    return methodSignature.ToString();
}

public string GetMethodCall(CodeFunction method)
{
    var methodCall = new System.Text.StringBuilder();
    methodCall.Append(method.Name);
    methodCall.Append("(");
    bool isFirstParameter = true;
    foreach (var prm in method.Parameters)
    {
        CodeParameter p = prm as CodeParameter;
        if (!isFirstParameter)
        {
            methodCall.Append(", ");
        }
        else
        {
            isFirstParameter = false;
        }
        methodCall.Append(p.Name);
    }
    methodCall.Append(")");
    return methodCall.ToString();
}       
#>

Lastly create a text file named ServiceWrapper.tt, or something less generic as this will define the actual wrapper for the WCF Service.

It contains a link to our GenerateServiceWrapper.t4 file, the definition of our class, and a call to GetServiceWrapper to which we pass the name of the WCF Service Reference.

<#@ template language="C#v3.5" hostspecific="True" #>
<#@ include File="T4Templates\GenerateServiceWrapper.t4" #>
using System;

namespace WCFServiceWrapper
{
    public static partial class SvcWrapper
    {    
<#      GetServiceWrapper("WcfSvc");#>
    }
}

Once you save, it should generate a SvcWrapper.cs file with static methods to wrap all of your calls. Now if I want to do something like add logging to each WCF call, all I have to do is add that code to the GetServiceWrapper method in the GenerateService.t4 file, poof all my methods WCF calls are logged.

Notice that I created this class as a partial class, this allows us to put the GetServiceClient method in the same class, but in a separate file. We could have either created that method in a completely separate class, or within the ServiceWrapper.tt file. You never want to edit the generated file, as those edits will be overwritten.

Points of Interest

Syntax highlight for T4 requires a 3rd party plugin. I've tried a few, and they are all pretty weak.

You can't step through T4 code (or I haven't figured out how), so it can take some serious guess and check to figure out what’s going wrong when something goes wrong. The output window is your friend.

History

  • 06/11/10 - First published

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jeremy Hutchinson
Software Developer Winxnet
United States United States
No Biography provided
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
GeneralMy vote of 5 PinmvpKanasz Robert5-Nov-12 2:47 
GeneralMy vote of 5 PinmemberPatrick Kalkman19-May-11 3:08 
QuestionWhat about data classes? PinmemberTheBC28-Jun-10 3:55 
AnswerRe: What about data classes? PinmemberJeremy Hutchinson28-Jun-10 4:33 
GeneralRe: What about data classes? PinmemberTheBC28-Jun-10 5:32 
GeneralRe: What about data classes? PinmemberTheBC29-Jun-10 23:05 
GeneralRe: What about data classes? PinmemberJeremy Hutchinson30-Jun-10 1:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140821.2 | Last Updated 14 Jun 2010
Article Copyright 2010 by Jeremy Hutchinson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid