WittyCoding

Coding blog

Web service management using T4 – Contracts generation (2/2)

Previous posts in the series
Introduction & Goals
Model Description
Contract Generation (1/2)

Now that all is up and running, we can start to generate the actual contracts.

As we will generate some ServiceContract interfaces, we have to add a reference to System.ServiceModel.dll in the current project.

references

Then, at the bottom of the script file, iterate on all web services, and declare a public interface called I<ServiceName>Contract, decorated by the System.ServiceModel.ServiceContractAttribute, by adding this code after the XML is parsed.

foreach(var ws in webServices){    #>
 
[System.ServiceModel.ServiceContract]
public interface I<#=ws.Name#>Contract{
 
}
<#
       }
#>

Now, we have to add all operations to each interface. Between the interface brackets, iterate on all operations, and output the right method.

foreach(var ws in webServices){
#>
[System.ServiceModel.ServiceContract]
public interface I<#=ws.Name#>Contract{
<#
             foreach      (var op in ws.Operations){
#>
       [System.ServiceModel.OperationContract]
 
       <#=op.ReturnType#> <#= op.Name #>();
<#
             } // end of foreach(operations)
#>
}
<#
       } // end of foreach(webServices)
#>

Then, we have to add parameters to each operation. This is a little bit tricky. We have to create a string that consists of all attributes separated by a comma. But we have to remove the last comma, as it has to end with the last parameter name to be C# valid code. In our example, the UploadFile operation declaration should be as this :

void UploadFile(string Filename, byte[] Content, string RelativePath);

This is a good place where we would usually use a method to retrieve the argument string to clean the code. To declare a method in the script file, you have to enclose it between <#+ and #> tags at the end of file. As we are currently using anonymous types, we cannot pass the list of arguments directly to the method, so I will temporarily use Tuple (this will be corrected in later tutorials).

<#+
       string GetArgumentStrings(System.Collections.Generic.IEnumerable<System.Tuple<string, string>> arguments){
             System.Text.StringBuilder builder=new System.Text.StringBuilder();
             foreach(var arg in arguments){
                    //Append type
                    builder.Append(arg.Item1);
                    builder.Append(" ");
 
                    //Append name
                    builder.Append(arg.Item2);
                    builder.Append(",");
             }
 
             //Remove last comma
             if(arguments.Any())
                    builder.Remove(builder.Length-1,1);
             return builder.ToString();
       }
#>

(Note : you could use String.Join instead of building the string manually).

Now, we only have to call this at the right place in the script above. As before, do not care about the Tuple thing as it won’t last long.

[System.ServiceModel.OperationContract]
       <#=op.ReturnType#> <#= op.Name #>(<#= GetArgumentStrings(op.Arguments.Select(arg=>new System.Tuple<string, string>(arg.Type, arg.Name))) #>);

If you look at the output file, it should be almost compilable. The only error lasting in my case is about the “Guid” return type that is not defined. This is the normal behavior as .NET Guid class belongs to the System namespace. We must add the right “using” at the top of the file. Add this using right before iterating on all webservices.

#>
using System;
<# foreach="" var="" ws="" in="" webservices=""> 

The output should look like this :

result

As you may see, the output is directly usable. If you add a new webservice, a new operation, remove one, or modify one, you will just have to regenerate this class and it will remain synchronized.

There are many ways we could improve the output. We should add services, operations and arguments comments, and perhaps declares more “using” statements to clean the output. Consider this as a homework to do. The output should ultimately look like this:

#region using statements
using System;
using System.ServiceModel;
#endregion
 
/// <summary>
/// Manage all files, including upload and download
/// </summary>
[ServiceContract]
public interface IFileServiceContract
{
    /// <summary>
    /// Upload a file to the web server
    /// </summary>
    /// <param name="Filename">Name of the uploaded file</param>
    /// <param name="Content">Content of the file</param>
    /// <param name="RelativePath">Relative path of the file</param>
    [OperationContract]
    void UploadFile(string Filename,byte[] Content,string RelativePath);
    /// <summary>
    /// Download the file from the server
    /// </summary>
    /// <param name="Filename">Downloaded filename</param>
    /// <param name="RelativePath">Relative path</param>
    [OperationContract]
    byte[] DownloadFile(string Filename,string RelativePath);
}
 
/// <summary>
/// Manage user authentication
/// </summary>
[ServiceContract]
public interface IAuthenticationServiceContract
{
    /// <summary>
    /// Connect to the system and return the session id
    /// </summary>
    /// <param name="Login">Login name</param>
    /// <param name="Password">Password (encoded with salt)</param>
    [OperationContract]
    Guid Connect(string Login,string Password);
 
    /// <summary>
    /// Disconnect the given session
    /// </summary>
    /// <param name="ConnectionID">Session ID</param>
    [OperationContract]
    void Disconnect(Guid ConnectionID);
}

To conclude, we now have fully functional services contracts generation. It is easily expandable and manageable, but some remarks have to be pointed:

  • One improvement that is clearly needed is that each interface should be written in another “.cs” file as we usually do (one file for each contract).
  • We have seen the limitation of using anonymous types when loading the model, as we have had to use Tuple and generic types. In regular software, we would have a class hierarchy that represents our model, so we can pass instance of that model to functions etc. We will have to do the same here.
  • This will become more important as when we will add new scripts to outputs other results, such as client-side access layers and so on, we will have to use exactly the same code to load the model as the one we have wrote in this topic. We have to find a way to refactor the code in order to be able to load the model from other scripts, perhaps by using a Factory ?
  • This technique will be interesting if the gain in productivity balances the time you take to write your scripts. In order to be really interesting, scripts should be usable among different projects. As now, this is not the case as you would have to copy paste the T4 code, and modify the scripts. If you find bugs on your scripts, you will have to correct it on all your other projects.
  • Last but not the least; you may have seen that T4 editor is just a plain old text editor, without any intellisense. You can download or buy custom editors that mimic intellisense, but you will never have the same coding experience as in regular C# editor. And as you will write templates, T4 scripts will start to become messy. We have to organize how we code scripts in order to keep a good code quality and maintainability.

All these points will be addressed in the next posts.