Click here to Skip to main content
15,886,026 members
Articles / Programming Languages / C#

Runtime Generated WCF Service Exposing .NET or COM Types

Rate me:
Please Sign up or sign in to vote.
4.69/5 (38 votes)
24 Apr 2008CPOL8 min read 79.8K   1.1K   51  
A WCF service wrapper is generated at runtime around a .NET or COM type to expose its interface.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Runtime.Remoting;

namespace WcfServiceHost
{
    public static class WcfGenerator
    {
        ///<summary>
        /// Generate type for WCF service. This type serves as proxy of a true type.
        /// </summary>
        /// <param name="path">Path to an assembly file containing the true type.</param>
        /// <param name="className">Name of a generated WCF proxy type.</param>
        /// <param name="sessionAllowed">Session support indicator.</param>
        /// <returns>Type for WCF service.</returns>
        static public Type GenerateServiceType(string path, string className, bool sessionAllowed)
        {
            Type retType = null;
            Assembly assm = Assembly.LoadFrom(path);
            Type serviceContract;

            string csCode = GenerateInterfaceAndClass(path, className, assm, sessionAllowed, out serviceContract);

            if (!string.IsNullOrEmpty(csCode))
            {
                CompilerResults res = BuildGeneratedCode(csCode, path, className, assm, serviceContract, false);

                foreach (Type type in res.CompiledAssembly.GetTypes())
                    if (type.Name.Contains(className) && type.IsClass)
                    {
                        retType = type;
                        break;
                    }
            }

            return retType;
        }

        /// <summary>
        /// Generate a string containing C# code for WCF service type and its base interface and
        /// produce type of generated WCF service.
        /// </summary>
        /// <param name="path">Path to an assembly file containing the true type.</param>
        /// <param name="genClassName">Name of a generated WCF proxy type. It is extended with "_PerCall suffix in appropriate case.</param>
        /// <param name="assm">Assembly containing the true type; located by the "path" parameter.</param>
        /// <param name="sessionAllowed">Session support indicator.</param>
        /// <param name="serviceContract">Type of generated WCF service.</param>
        /// <returns>String containing C# code for WCF service type and its base interface.</returns>
        static private string GenerateInterfaceAndClass(string path, string genClassName, Assembly assm, bool sessionAllowed, out Type serviceContract)
        {
            string className = sessionAllowed ? genClassName : genClassName + "_PerCall";
            string serviceContractForInterface = "[ServiceContract(SessionMode = SessionMode.Allowed)]";

            string serviceBehaviorForClass =
                "[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, " +
                string.Format("InstanceContextMode = InstanceContextMode.Per{0})]", sessionAllowed ? "Session" : "Call");

            string sr = null;
            List<MethodInfo> lstMethod = new List<MethodInfo>();
            Dictionary<string, StringBuilder> dctSbMethodBody = new Dictionary<string, StringBuilder>();
            Dictionary<string, string> dctClassMethod = new Dictionary<string, string>();
            StringBuilder sb = new StringBuilder();

            serviceContract = null;

            // Find class with name matched to given
            foreach (Type type in assm.GetTypes())
                if (type.Name.Contains(genClassName) && !type.IsInterface)
                {
                    serviceContract = type;
                    break;
                }

            if (null != serviceContract)
            {
                foreach (MethodInfo method in serviceContract.GetMethods())
                    if (IsExposed(method))
                        lstMethod.Add(method);

                if (lstMethod.Count > 0)
                {
                    string interfaceName = string.Format("I{0}", className);
                    sb.Append("$Prefix$\npublic $Class$$BaseInterface$\n");
                    sb.Append("{\n");

                    sb.Append("$Ctor$");

                    foreach (MethodInfo method in lstMethod)
                    {
                        StringBuilder sbMethod = new StringBuilder();
                        sbMethod.AppendFormat("$OperationContractAttribute$\n$Public${0} {1}(", method.ReturnType.FullName.ToString(), method.Name);
                        Dictionary<int, ParameterInfo> dctParam = new Dictionary<int, ParameterInfo>();
                        foreach (ParameterInfo param in method.GetParameters())
                            dctParam.Add(param.Position, param);

                        for (int i = 0; i < dctParam.Count; i++)
                        {
                            ParameterInfo param = dctParam[i];
                            string prefix = string.Empty;
                            if (param.IsOut)
                                prefix = "out ";
                            if (param.IsIn)
                                prefix = "ref ";

                            sbMethod.AppendFormat("{0}{1} {2}", prefix,
                                    param.ParameterType.FullName.ToString(),
                                    param.Name);
                            if (i + 1 < dctParam.Count)
                                sbMethod.Append(",\n");
                        }

                        sbMethod.Append(")");

                        sbMethod.Append("$BeforeMethodBody$");
                        sbMethod.AppendFormat("$MethodBody_{0}$", method.Name);

                        StringBuilder sbMethodBody = new StringBuilder();
                        sbMethodBody.Append("ArrayList alParam = new ArrayList();\n");
                        for (int i = 0; i < dctParam.Count; i++)
                            sbMethodBody.AppendFormat("alParam.Add({0});\n", dctParam[i].Name);

                        string returnTypeName = method.ReturnType.FullName.ToString();
                        string sInvoke = string.Format("typeof({0}).GetMethod(\"{1}\").Invoke(obj, alParam.ToArray());\n", serviceContract.FullName, method.Name);

                        if ("System.Void" == returnTypeName)
                            sbMethodBody.Append(sInvoke);
                        else
                            sbMethodBody.AppendFormat("return ({0}){1}", returnTypeName, sInvoke);

                        dctSbMethodBody.Add(method.Name, sbMethodBody);

                        sbMethod.Append("$AfterMethodBody$");
                        dctClassMethod.Add(method.Name, sbMethod.ToString());

                        sb.AppendFormat("${0}$\n", method.Name);
                    }

                    sb.Append("}\n");

                    string sTemp = sb.ToString();
                    string sTemp1 = sTemp;
                    foreach (string methodName in dctClassMethod.Keys)
                    {
                        sTemp1 = sTemp.Replace(string.Format("${0}$", methodName), dctClassMethod[methodName]);
                        sTemp = sTemp1;
                    }

                    string sInterface, sClass;
                    sInterface = sClass = sTemp;

                    string interfaceBody = sInterface.
                        Replace("$Prefix$", serviceContractForInterface).
                        Replace("$BaseInterface$", string.Empty).
                        Replace("$OperationContractAttribute$", "[OperationContract]").
                        Replace("$Public$", string.Empty).
                        Replace("$BeforeMethodBody$", ";\n").
                        Replace("$AfterMethodBody$", string.Empty).
                        Replace("$Ctor$", string.Empty).
                        Replace("$Class$", string.Format("interface {0}", interfaceName));

                    string classBody = sClass.
                        Replace("$Prefix$", serviceBehaviorForClass).
                        Replace("$BaseInterface$", string.Format(" : {0}", interfaceName)).
                        Replace("$OperationContractAttribute$", string.Empty).
                        Replace("$Public$", "public ").
                        Replace("$BeforeMethodBody$", "\n{\n").
                        Replace("$AfterMethodBody$", "}").
                        Replace("$Ctor$", string.Format(
                                "\n{0} obj;\n\npublic {1}()\n{2}\nSystem.Reflection.Assembly assm = System.Reflection.Assembly.GetAssembly(typeof({0}));\nobj = ({0})assm.CreateInstance(\"{0}\");\n{3}\n",
                                serviceContract.FullName, className, "{", "}")).
                        Replace("$Class$", string.Format("class {0}", className));

                    foreach (string methodName in dctSbMethodBody.Keys)
                    {
                        string pattern = string.Format("$MethodBody_{0}$", methodName);
                        sInterface = interfaceBody.Replace(pattern, string.Empty);
                        sClass = classBody.Replace(pattern, dctSbMethodBody[methodName].ToString());

                        interfaceBody = sInterface;
                        classBody = sClass;
                    }

                    StringBuilder sbInterface = new StringBuilder(interfaceBody);
                    StringBuilder sbClass = new StringBuilder(classBody);
                    sb.Remove(0, sb.Length);

                    sb.Append("using System;\nusing System.ServiceModel;\nusing System.Collections;\n\n");
                    sb.AppendFormat("{0}\n\n{1}", interfaceBody, classBody);

                    sr = sb.ToString().Replace("System.Void", "void");
                }
                else
                    Console.WriteLine("ERROR! No methods exposed.\n");
            }
            else
                Console.WriteLine("ERROR! Unable to generate Service Contract.\n");

            return sr;
        }

        /// <summary>
        /// Perform build of the generated C# of WCF proxy type.
        /// </summary>
        /// <param name="csCode">C# code for WCF service type and its base interface.</param>
        /// <param name="path">Path to an assembly file containing the true type.</param>
        /// <param name="className">Name of a generated WCF proxy type.</param>
        /// <param name="assm">Assembly containing the true type; located by the "path" parameter.</param>
        /// <param name="serviceContract"></param>
        /// <param name="debugInfo">Debug info and the temporary files generation indicator.</param>
        /// <returns>Result of the generated WCF type in-memory compilation.</returns>
        static private CompilerResults BuildGeneratedCode(string csCode,  string path, string className, Assembly assm, Type serviceContract, bool debugInfo)
        {
            CompilerResults res = null;

            try
            {
                CSharpCodeProvider codeProvider = new CSharpCodeProvider();
                CompilerParameters cp = new CompilerParameters();

                // Add references for compilation
                cp.ReferencedAssemblies.Add("System.dll");
                cp.ReferencedAssemblies.Add(@"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.ServiceModel.dll");
                cp.ReferencedAssemblies.Add(@"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.Runtime.Serialization.dll");
                cp.ReferencedAssemblies.Add(path);
                AddReferencedAssm(cp, serviceContract);

                cp.GenerateExecutable = false;
                cp.IncludeDebugInformation = debugInfo;
                cp.GenerateInMemory = true;
                cp.WarningLevel = 3;
                cp.TreatWarningsAsErrors = false;
                cp.CompilerOptions = "/optimize";

                if (cp.IncludeDebugInformation)
                    // Set a temporary files collection.
                    // The TempFileCollection stores the temporary files
                    // generated during a build in the current directory,
                    // and does not delete them after compilation.
                    cp.TempFiles = new TempFileCollection(@"..\_Temp", true);
                else
                {
                    cp.TempFiles.Delete();
                    cp.TempFiles = null;
                }

                res = codeProvider.CompileAssemblyFromSource(cp, new string[] { csCode });

                if (res.Errors.HasErrors)
                {
                    Console.WriteLine("{0} Compilation ERROR(S):\n", res.Errors.Count);
                    foreach (CompilerError ce in res.Errors)
                        Console.WriteLine("    Line {0}. {1}\n", ce.Line, ce.ErrorText);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR while trying to build generated code: {0}\n", e.Message);
            }

            return res;
        }

        /// <summary>
        /// Rucursive method to add references assemblies to compiler parameters.
        /// </summary>
        /// <param name="cp">Compiler parameters.</param>
        /// <param name="type">Type that belongs to the assembly to be added.</param>
        static private void AddReferencedAssm(CompilerParameters cp, Type type)
        {
            Assembly assm = Assembly.GetAssembly(type);
            if (null != assm)
                foreach (AssemblyName refAssmName in assm.GetReferencedAssemblies())
                    if (refAssmName.Name.ToLower() != "mscorlib")
                    {
                        cp.ReferencedAssemblies.Add(assm.Location.Replace(assm.GetName().Name, refAssmName.Name));
                        Type baseType = assm.GetType(type.FullName).BaseType;
                        if (null != baseType)
                        {
                            Assembly assmBase = Assembly.GetAssembly(baseType);
                            if (assm != assmBase)
                                AddReferencedAssm(cp, baseType);
                        }
                    }
        }

        /// <summary>
        /// Filter the true type methods to be exposed with WCF service.
        /// </summary>
        /// <param name="method">Method info.</param>
        /// <returns></returns>
        static private bool IsExposed(MethodInfo method)
        {
            return method.IsPublic &&
                   method.Name != "Equals" &&
                   method.Name != "GetType" &&
                   method.Name != "GetHashCode" &&
                   method.Name != "ToString" &&

                   method.Name != "CreateObjRef" &&
                   method.Name != "GetLifetimeService" &&
                   method.Name != "InitializeLifetimeService";
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Software Developer (Senior)
Israel Israel


  • Nov 2010: Code Project Contests - Windows Azure Apps - Winner
  • Feb 2011: Code Project Contests - Windows Azure Apps - Grand Prize Winner



Comments and Discussions