Want to show your appreciation?
Please a cup of tea.

Wednesday, August 20, 2008

Generate Fast Startup .Net Web Service Client That Implements Domain Interface

In my previous blog, I provided a manual solution to the slow startup problem of Spring.Net dynamic proxy Web Service client. And I promised that I'll continue my venture to provide a better solution for applications with a few dozens of Web Service clients.

 

In this blog, I'm going to show how to use CodeDom to generate the very same source code that I have posted in the manual solution.

 

The WebServiceClientGenerator is the class that will generate the source code of a full functional Web Service client that implements a domain interface. So that the class can be used to directly replace the WebServiceProxyFactory in the application context.

 

With the help of the generator class, the same MyTestClient.cs can be generate by the code below:

 

            new WebServiceClientGenerator(

                "Example.Client", "MyTestClient",

                typeof(IMyTest), "http://tempuri.org/"

            ).Generate("..\\..");

 

The next step is some how to integrate this to the VS 2005 or NAnt build process. I need some idea...

 

Here is the generator source code. You can find the CodeUtil class in this blog.

 

/// <summary>

/// Generate source code of a Web Service client that implements a given

/// interface.

/// </summary>

public class WebServiceClientGenerator

{

    #region Private Fields

 

    private static readonly CodeTypeReference _typeSoapHttpClientProtocol =

        new CodeTypeReference(typeof(SoapHttpClientProtocol));

 

    private static readonly CodeTypeReference _typeWebServiceBindingAttribute =

        new CodeTypeReference(typeof(WebServiceBindingAttribute));

 

    private static readonly CodeTypeReference _objectArrayType =

        new CodeTypeReference(typeof(object[]));

 

    private static readonly CodeAttributeDeclaration _soapDocumentMethodAttribute =

        new CodeAttributeDeclaration(

            new CodeTypeReference(typeof(SoapDocumentMethodAttribute)));

 

    private string _fileName;

    private string _fileExtension;

    private string _classNamespace;

    private string _className;

    private CodeDomProvider _codeDomProvider;

    private CodeGeneratorOptions _codeGeneratorOptions;

    private string _wsdlNamespace;

    private Type _interfaceType;

 

    #endregion

 

    #region Public Properites

 

    /// <summary>

    /// Gets and sets the namespace for the generated class.

    /// </summary>

    public string ClassNamespace

    {

        get

        {

            return _classNamespace;

        }

        set

        {

            CheckNullSetter(value);

            _classNamespace = value;

        }

    }

 

    /// <summary>

    /// Gets and sets the name of the generated class.

    /// </summary>

    public string ClassName

    {

        get

        {

            return _className;

        }

        set

        {

            CheckNullSetter(value);

            _className = value;

        }

    }

 

    /// <summary>

    /// Gets and sets the file name without extension. Default to be same

    /// as <see cref="ClassName"/>.

    /// </summary>

    public string FileName

    {

        get { return _fileName ?? _className; }

        set

        {

            _fileName = value;

        }

    }

 

    /// <summary>

    /// Gets and sets the file extension.

    /// </summary>

    public string FileExtension

    {

        get

        {

            return _fileExtension ?? CodeDomProvider.FileExtension;

        }

        set { _fileExtension = value; }

    }

 

    /// <summary>

    /// Gets and sets the interface that the generated class will implement.

    /// </summary>

    public Type Interface

    {

        get

        {

            return _interfaceType;

        }

        set

        {

            CheckNullSetter(value);

            _interfaceType = value;

        }

    }

 

    /// <summary>

    /// Gets and sets the namespace of the Web Service.

    /// </summary>

    public string WsdlNamespace

    {

        get

        {

            return _wsdlNamespace;

        }

        set

        {

            CheckNullSetter(value);

            _wsdlNamespace = value;

        }

    }

 

    /// <summary>

    /// Gets and sets the <see cref="CodeDomProvider"/>.

    /// Default is <see cref="CSharpCodeProvider"/>.

    /// </summary>

    public CodeDomProvider CodeDomProvider

    {

        get

        {

            if (_codeDomProvider == null)

            {

                _codeDomProvider = new CSharpCodeProvider();

            }

            return _codeDomProvider;

        }

        set

        {

            CheckNullSetter(value);

            _codeDomProvider = value;

        }

    }

 

    /// <summary>

    /// Gets and sets the <see cref="CodeGeneratorOptions"/>.

    /// </summary>

    public CodeGeneratorOptions CodeGeneratorOptions

    {

        get

        {

            if (_codeGeneratorOptions == null)

            {

                _codeGeneratorOptions = new CodeGeneratorOptions();

            }

            return _codeGeneratorOptions;

        }

        set

        {

            CheckNullSetter(value);

            _codeGeneratorOptions = value;

        }

    }

 

    #endregion

 

    #region Constructors

    /// <summary>

    /// Construct a new generator instance.

    /// </summary>

    /// <param name="classNamespace">

    /// The namespace for the class being generated.

    /// </param>

    /// <param name="className">

    /// The name of the class.

    /// </param>

    /// <param name="interfaceType">

    /// The interface to implement.

    /// </param>

    /// <param name="wsdlNamespace">

    /// The namespace of the Web Service.

    /// </param>

    /// <exception cref="ArgumentNullException">

    /// When any parameter is <see langword="null"/>.

    /// </exception>

    public WebServiceClientGenerator(

        string classNamespace, string className,

        Type interfaceType, string wsdlNamespace)

    {

        CheckArgumentNull(classNamespace, "classNamespace");

        CheckArgumentNull(className, "className");

        CheckArgumentNull(interfaceType, "interfaceType");

        CheckArgumentNull(wsdlNamespace, "wsdlNamespace");

 

        _classNamespace = classNamespace;

        _className = className;

        _interfaceType = interfaceType;

        _wsdlNamespace = wsdlNamespace;

    }

    #endregion

 

    #region Public Instance Methods

 

    /// <summary>

    /// Generate the source file in a directory specified by

    /// <paramref name="filePath"/>.

    /// </summary>

    /// <remarks>

    /// The name of the file is determined by the <see cref="FileName"/>

    /// property and the <see cref="FileExtension"/> property.

    /// </remarks>

    /// <param name="filePath">

    /// The directory where the source file will be generated.

    /// </param>

    /// <exception cref="ArgumentNullException">

    /// When <paramref name="filePath"/> is <see langword="null"/>.

    /// </exception>

    public void Generate(string filePath)

    {

        CheckArgumentNull(filePath, "filePath");

        string fullPath = Path.Combine(filePath, FileName + "." + FileExtension);

        using (Stream s = File.Open(fullPath, FileMode.Create))

        {

            StreamWriter sw = new StreamWriter(s);

            Generate(sw);

            //Close StreamWriter

            sw.Close();

            s.Close();

        }

    }

 

    /// <summary>

    /// Write out the code to the <paramref name="writer"/>.

    /// </summary>

    /// <param name="writer">

    /// The writer to where the generated code will be sent.

    /// </param>

    /// <exception cref="ArgumentNullException">

    /// When <paramref name="writer"/> is <see langword="null"/>.

    /// </exception>

    public void Generate(TextWriter writer)

    {

        CheckArgumentNull(writer, "writer");

 

        ICodeGenerator generator = CodeDomProvider.CreateGenerator(writer);

        CodeGeneratorOptions options = CodeGeneratorOptions;

 

        //Disable documentation warning

        generator.GenerateCodeFromCompileUnit(

            new CodeSnippetCompileUnit("#pragma warning disable 1591"),

            writer, options);

 

        // generate the code

        generator.GenerateCodeFromNamespace(

            ImplementWebServiceClient(ClassNamespace, ClassName, Interface, WsdlNamespace),

            writer, options);

 

        //Restore documentation warning

        generator.GenerateCodeFromCompileUnit(

            new CodeSnippetCompileUnit("#pragma warning restore 1591"),

            writer, options);

    }

 

    #endregion

 

    #region Private Methods

 

    // implements the web service client

    private static CodeNamespace ImplementWebServiceClient(

        string classNamespace,

        string className,

        Type interfaceType,

        string wsdlNamespace)

    {

        //Create Namespaces

        CodeNamespace codeNamespace = new CodeNamespace(classNamespace);

 

        //Create web service binding attribute

        CodeAttributeDeclaration webServiceBindingAttribute =

            new CodeAttributeDeclaration(

                _typeWebServiceBindingAttribute,

                new CodeAttributeArgument(

                    "Namespace",

                    new CodePrimitiveExpression(wsdlNamespace)

                    )

                );

 

        //Create Class Declaration

        CodeTypeDeclaration codeClass = CodeUtils.DefineClass(

            new CodeAttributeDeclaration[] { webServiceBindingAttribute },

            TypeAttributes.Public,

            className,

            _typeSoapHttpClientProtocol,

            new CodeTypeReference(interfaceType));

 

        // implements interface

        codeClass.Members.AddRange(ImplementInterface(interfaceType));

 

        codeNamespace.Types.Add(codeClass);

 

        return codeNamespace;

    }

 

    // implements all the methods defined in the interface.

    private static CodeMemberMethod[] ImplementInterface(Type theInterface)

    {

        MethodInfo[] methods = theInterface.GetMethods();

        CodeMemberMethod[] codeMethods = new CodeMemberMethod[methods.Length];

        for (int i = 0; i < methods.Length; i++)

        {

            codeMethods[i] = ImplementWebMethod(methods[i]);

        }

        return codeMethods;

    }

 

    // implement one web method

    private static CodeMemberMethod ImplementWebMethod(MethodInfo method)

    {

        CodeTypeReference returnType =

            new CodeTypeReference(method.ReturnType);

 

        CodeMemberMethod codeMethod = CodeUtils.DefineMethod(

            new CodeAttributeDeclaration[] { _soapDocumentMethodAttribute },

            MemberAttributes.Public,

            returnType,

            method.Name);

 

        // Define all parameters and seperate them by in/out parameter

        // type. Ref type is considered both in and out.

        List<CodeVariableReferenceExpression> inParams =

            new List<CodeVariableReferenceExpression>();

 

        List<OutParam> outParams = new List<OutParam>();

 

        foreach (ParameterInfo param in method.GetParameters())

        {

            // Determine the true parameter type

            Type paramType = param.ParameterType;

            if (paramType.IsByRef) paramType = paramType.GetElementType();

 

            CodeTypeReference codeParamType =

                new CodeTypeReference(paramType);

 

            FieldDirection direction =

                CodeUtils.DetermineParameterDirection(param);

 

            CodeVariableReferenceExpression codeParam =

                CodeUtils.DefineParameter(

                    codeMethod, codeParamType, direction, param.Name);

 

            if (direction != FieldDirection.Out) // In or Ref

            {

                inParams.Add(codeParam);

            }

 

            if (direction != FieldDirection.In) // Out or Ref

            {

                //outParamTypes.Add(codeParamType);

                outParams.Add(new OutParam(codeParamType, codeParam));

            }

 

        }

 

        // Calls the Invoke method on base class with all 'in' parameters

        CodeMethodInvokeExpression invokation = new CodeMethodInvokeExpression(

            new CodeThisReferenceExpression(), "Invoke",

            new CodePrimitiveExpression(method.Name),

            new CodeArrayCreateExpression(_objectArrayType, inParams.ToArray())

            );

 

        // Define the result variable and initialize it with the result of

        // the invoke method.

        CodeVariableReferenceExpression result = CodeUtils.DefineVariable(

            codeMethod, _objectArrayType, "result", invokation);

 

        // Return value and out parameter index

        int index = 0;

 

        // Define the return statement

        CodeMethodReturnStatement codeReturn = new CodeMethodReturnStatement();

        if (method.ReturnType != typeof(void))

        {

            // Return the result if return type is not void.

            codeReturn.Expression = new CodeCastExpression(

                returnType, CodeUtils.ArrayIndex(result, index++));

        }

 

        // Assign result to 'out' parameters

        foreach (OutParam outParam in outParams)

        {

            codeMethod.Statements.Add(

                new CodeAssignStatement(

                    outParam.Variable,

                    new CodeCastExpression(outParam.Type,

                                           CodeUtils.ArrayIndex(result, index++))

                    )

                );

        }

 

        // Add the return statement

        codeMethod.Statements.Add(codeReturn);

 

        return codeMethod;

    }

 

    private static void CheckArgumentNull(object value, string name)

    {

        if (value == null)

        {

            throw new ArgumentNullException(name);

        }

    }

 

    private static void CheckNullSetter(object value)

    {

        CheckArgumentNull(value, "value");

    }

 

    #endregion

 

    private struct OutParam

    {

        public CodeTypeReference Type;

        public CodeVariableReferenceExpression Variable;

 

        public OutParam(

            CodeTypeReference type,

            CodeVariableReferenceExpression variable)

        {

            Type = type;

            Variable = variable;

        }

    }

}

 

Generate Fast Startup .Net Web Service Client That Implements Domain Interface

Tuesday, August 19, 2008

Slow Startup Performance with Spring.Net WebServiceProxyFactory

Update: This is the beginning of a series blog posts of how this problem is described and eventually addressed. I'm including a table of content here for easier navigation.

Table Of Content

Describe the Problem: Slow Startup Performance with Spring.Net WebServiceProxyFactory (this post)

Propose the Solution: Generate Fast Startup .Net Web Service Client That Implements Domain Interface

Implement the Solution: Integrate Web Service Client Generation into Build Process

Resolve ClickOnce Deployment Issue: ClickOnce Deployment + SGen Problem and The Workaround

Integrate with Spring.Net: Using WSCodeGen with Spring.Net

Release the WSCodeGen Project: Getting Ready for WSCodeGen 1.0.1 Release

Conclusion: Web Service Client Startup Performance: Spring.Net Proxy v.s. WSCodeGen

If anybody is still writing the Web Service using the WebService and WebMethod attributes at server side and adding Web Reference at the client size, then they should seriously look at how Spring.Net does the Web Service.

Let's list some benefits of using Spring.Net for Web Service.

  1. You can just write your web method in PONO and ask Spring.Net to expose it as Web Service.
  2. Your PONO will typically implement a service interface. And of course, you can use dependency inject.
  3. Spring.Net can expose the same PONO as other type of service, for example .Net Remoting.
  4. At the client side, Spring.Net can generate a Web Service client dynamic proxy that implements the same interface.
  5. Because the proxy client implements the same interface that the service object does. You can actually use your own domain objects in the Web Service calls. Just like calling directly calling the service object at the server side. Cool, isn't it?

Yes, we have been using it in our projects and running in production for a year now. Everything was great until recently we started to look at the startup performance of our SmartClient application.

Regardless of being Web Reference or Spring.Net dynamic proxy, they all inherit from SoapHttpClientProtocol class. That class uses XmlSerializer to convert between object and XML. When the first time XmlSerializer for a given type was created, it actually creates a temporary assembly using c# compiler. You can see those temporary files in the %TEMP% folder if you configure your application to diagnose the XmlSerializer by adding the following XML snip to the app.config file. This compiling process is slow and it does this for every type it encounters.

  <!--

   Never deliver your application with this.

   It fills up your temp folder in no time.

  -->

  <system.diagnostics>

    <switches>

      <add name="XmlSerialization.Compilation" value="4"/>

    </switches>

  </system.diagnostics>

When our SmartClient needs to connect to multiple Web Services at startup, the XmlSerializer assembly creation process was taking significant amount of time during application startup.

Microsoft realized that problem so a utility called SGen.exe was released. SGen scans the types in an assembly, say Example.dll, and generates all serializer into a sister assembly, named Example.XmlSerializers.dll. VS 2005 also provides "generate serialization assembly" on the Build tab of project property. Grant Drake has a nice blog about this.

When the "generate serialization assembly" option was set to Auto or On, the SGen does generate the serializer assembly for Web Reference client to eliminate the temporary assembly creation in runtime. This greatly improves the runtime startup performance.

But when come to the Spring.Net dynamic proxy Web Service client, no mater how I tweaked the options, the temporary assembly was always created in runtime which seriously impacts the startup performance of the client applications, especially when multiple dynamic proxies are used on a slower PC.

I started a thread about this problem at the Spring.Net's support forum back in Feb 2008. Since then, I started to explore a way out myself.

My first venture was to manually create the Web Service client. I copied the code from the Web Reference generated source and changed the class to implements our service interface. I can then use this class to replace the dynamic proxy in the Spring.Net's application context. SGen is happily pre-compiling the serializer for it, just like it does to Web Reference client!

Look at the following code as an example of Web Service clients that I have manually created. This solution works, fast, not difficult to write. It is a good option for a client that only calls a few Web Services. But for a client that talks to a few dozens of Web Services, I have to say that it is repeated task and tedious. Can we generate them in source code at compile time instead of using dynamic proxy at runtime? stay tuned...

#pragma warning disable 1591

namespace Example.Client {

 

 

    [System.Web.Services.WebServiceBindingAttribute(Namespace="http://tempuri.org/")]

    public class MyTestClient : System.Web.Services.Protocols.SoapHttpClientProtocol, Example.BizService.Client.Program.IMyTest {

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual void Simple() {

            object[] result = this.Invoke("Simple", new object[0]);

            return;

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual void OneParam(int i) {

            object[] result = this.Invoke("OneParam", new object[] {

                        i});

            return;

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual void AnotherParam(Example.BizService.Client.Program.Another i) {

            object[] result = this.Invoke("AnotherParam", new object[] {

                        i});

            return;

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual int SimpleReturn() {

            object[] result = this.Invoke("SimpleReturn", new object[0]);

            return ((int)(result[0]));

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual int OneParamReturn() {

            object[] result = this.Invoke("OneParamReturn", new object[0]);

            return ((int)(result[0]));

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual string RefParam(ref int i, int j) {

            object[] result = this.Invoke("RefParam", new object[] {

                        i,

                        j});

            i = ((int)(result[1]));

            return ((string)(result[0]));

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual string OutParam(out int i) {

            object[] result = this.Invoke("OutParam", new object[0]);

            i = ((int)(result[1]));

            return ((string)(result[0]));

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual string RefAnother(ref Example.BizService.Client.Program.Another a, out int i) {

            object[] result = this.Invoke("RefAnother", new object[] {

                        a});

            a = ((Example.BizService.Client.Program.Another)(result[1]));

            i = ((int)(result[2]));

            return ((string)(result[0]));

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual void OutAnother(out Example.BizService.Client.Program.Another a) {

            object[] result = this.Invoke("OutAnother", new object[0]);

            a = ((Example.BizService.Client.Program.Another)(result[0]));

            return;

        }

    }

}

#pragma warning restore 1591

Monday, August 18, 2008

Utility methods for easier and better looking CodeDom program

I'm forced to get into the code generation business when I had to generate the Web Service client ourselves for a reason that I'll blog later. While CodeDom API is every powerful but all those CodeXXX classes quickly made my code generator program a monster.

This in turn forced me into creating a utility class and it did help to have my generator code organized. Now I can write below in one line:

CodeUtils.DefineClass(TypeAttributes.Public, "MyClass", baseType);

Here it is the utility class source code:

/// <summary>

/// Utility methods for generating source code using CodeDom.

/// </summary>

/// <author>Kenneth Xu</author>

public static class CodeUtils

{

    /// <summary>

    /// Single dimension array access expression.

    /// </summary>

    /// <param name="array">The array.</param>

    /// <param name="index">The index.</param>

    /// <returns>

    /// An instance of <see cref="CodeArrayIndexerExpression"/>

    /// </returns>

    public static CodeArrayIndexerExpression ArrayIndex(

        CodeExpression array, int index)

    {

        return new CodeArrayIndexerExpression(

            array, new CodePrimitiveExpression(index));

    }

 

    /// <summary>

    /// Determine the if a parameter is <see langword="ref"/> or

    /// <see langword="out"/>.

    /// </summary>

    /// <param name="parameter">The parameter from reflection.</param>

    /// <returns>Parameter direction</returns>

    public static FieldDirection DetermineParameterDirection(

        ParameterInfo parameter)

    {

        FieldDirection direction;

        if (parameter.IsOut)

            direction = FieldDirection.Out;

        else if (parameter.ParameterType.IsByRef)

            direction = FieldDirection.Ref;

        else

            direction = FieldDirection.In;

        return direction;

    }

 

    /// <summary>

    /// Define a new method.

    /// </summary>

    /// <param name="attributes">Custom attributes.</param>

    /// <param name="modifier">Access modifier.</param>

    /// <param name="returnType">Data type to return</param>

    /// <param name="name">Name of the method.</param>

    /// <param name="parameters">

    /// Parameter definition of this method.

    /// </param>

    /// <returns>A <see cref="CodeMemberMethod"/>.</returns>

    public static CodeMemberMethod DefineMethod(

        CodeAttributeDeclaration[] attributes,

        MemberAttributes modifier,

        CodeTypeReference returnType,

        string name,

        params CodeParameterDeclarationExpression[] parameters

        )

    {

        CodeMemberMethod method = new CodeMemberMethod();

        method.Name = name;

        method.Attributes = modifier;

        method.ReturnType = returnType;

        method.CustomAttributes.AddRange(attributes);

        method.Parameters.AddRange(parameters);

        return method;

    }

 

    /// <summary>

    /// Define a local variable in a <paramref name="method"/>.

    /// </summary>

    /// <param name="method">

    /// The method that new varaible will be defined in.

    /// </param>

    /// <param name="type">The type of the variable</param>

    /// <param name="name">The name of the variable</param>

    /// <param name="initializer">The variable initializer.</param>

    /// <returns>

    /// A <see cref="CodeVariableReferenceExpression"/> that can be used

    /// to refer to the defined variable.

    /// </returns>

    public static CodeVariableReferenceExpression DefineVariable(

        CodeMemberMethod method,

        CodeTypeReference type,

        string name,

        CodeExpression initializer)

    {

        CodeVariableDeclarationStatement statement =

            new CodeVariableDeclarationStatement(type, name, initializer);

        method.Statements.Add(statement);

        return new CodeVariableReferenceExpression(name);

    }

 

    /// <summary>

    /// Define a parameter for the given <paramref name="method"/>.

    /// </summary>

    /// <param name="method">The method to define the paramter.</param>

    /// <param name="type">The type of the parameter.</param>

    /// <param name="name">The name of the parameter.</param>

    /// <returns>

    /// A <see cref="CodeVariableReferenceExpression"/> that can be used

    /// to refer to defined parameter.

    /// </returns>

    public static CodeVariableReferenceExpression DefineParameter(

        CodeMemberMethod method,

        CodeTypeReference type,

        string name)

    {

        return DefineParameter(method, type, FieldDirection.In, name);

    }

 

    /// <summary>

    /// Define a parameter for the given <paramref name="method"/>.

    /// Optionally the parameter can be <see langword="out"/> or

    /// <see langword="ref"/>.

    /// </summary>

    /// <param name="method">The method to define the paramter.</param>

    /// <param name="type">The type of the parameter.</param>

    /// <param name="direction">To specify ref or out parameter.</param>

    /// <param name="name">The name of the parameter.</param>

    /// <returns>

    /// A <see cref="CodeVariableReferenceExpression"/> that can be used

    /// to refer to defined parameter.

    /// </returns>

    public static CodeVariableReferenceExpression DefineParameter(

        CodeMemberMethod method,

        CodeTypeReference type,

        FieldDirection direction,

        string name)

    {

        CodeParameterDeclarationExpression parameter =

            new CodeParameterDeclarationExpression(type, name);

        parameter.Direction = direction;

        method.Parameters.Add(parameter);

        return new CodeVariableReferenceExpression(name);

    }

 

    /// <summary>

    /// Define a new class without custom attribute. It has no base

    /// class and doesn't implement any interface.

    /// </summary>

    /// <param name="modifier">Access modifier</param>

    /// <param name="name">Class name</param>

    /// <param name="typeParameters">

    /// Optional type parameter if this is a generic class.

    /// </param>

    /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>

    public static CodeTypeDeclaration DefineClass(

        TypeAttributes modifier,

        string name,

        params CodeTypeParameter[] typeParameters)

    {

        return DefineClass(

            null, modifier, name, typeParameters, null, null);

    }

 

    /// <summary>

    /// Define a new non-generic class with no custom attributes.

    /// </summary>

    /// <remarks>

    /// Generic type parameters and custom attributes can be added later.

    /// </remarks>

    /// <param name="modifier">Access modifier</param>

    /// <param name="name">Class name</param>

    /// <param name="baseType">The base class.</param>

    /// <param name="interfaces">Optional interfaces to implement.</param>

    /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>

    public static CodeTypeDeclaration DefineClass(

        TypeAttributes modifier,

        string name,

        CodeTypeReference baseType,

        params CodeTypeReference[] interfaces)

    {

        return DefineClass(

            null, modifier, name, null, baseType, interfaces);

    }

 

    /// <summary>

    /// Define a new non-generic class.

    /// </summary>

    /// <remarks>

    /// Generic type parameters can be added later.

    /// </remarks>

    /// <param name="attributes">Custome attributes for the class.</param>

    /// <param name="modifier">Access modifier</param>

    /// <param name="name">Class name</param>

    /// <param name="baseType">The base class, or null.</param>

    /// <param name="interfaces">Optional interfaces to implement.</param>

    /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>

    public static CodeTypeDeclaration DefineClass(

        CodeAttributeDeclaration[] attributes,

        TypeAttributes modifier,

        string name,

        CodeTypeReference baseType,

        params CodeTypeReference[] interfaces)

    {

        return DefineClass(

            attributes, modifier, name, null, baseType, interfaces);

    }

 

    /// <summary>

    /// Define a new class.

    /// </summary>

    /// <param name="attributes">Custome attributes for the class.</param>

    /// <param name="modifier">Access modifier</param>

    /// <param name="name">Class name</param>

    /// <param name="typeParameters">

    /// Type parameter if this is a generic class, null otherwise.

    /// </param>

    /// <param name="baseType">The base class.</param>

    /// <param name="interfaces">The interfaces to implement.</param>

    /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>

    public static CodeTypeDeclaration DefineClass(

        CodeAttributeDeclaration[] attributes,

        TypeAttributes modifier,

        string name,

        CodeTypeParameter[] typeParameters,

        CodeTypeReference baseType,

        params CodeTypeReference[] interfaces)

    {

        return DefineType(attributes, modifier, TypeType.Class,

            name, typeParameters, baseType, interfaces);

    }

 

    private static CodeTypeDeclaration DefineType(

        CodeAttributeDeclaration[] attributes,

        TypeAttributes modifier,

        TypeType typeType,

        string name,

        CodeTypeParameter[] typeParameters,

        CodeTypeReference baseType,

        CodeTypeReference[] interfaces)

    {

        CodeTypeDeclaration ctd = new CodeTypeDeclaration();

        ctd.Name = name;

        ctd.TypeAttributes = modifier;

        switch (typeType)

        {

            case TypeType.Class:

                ctd.IsClass = true;

                break;

            case TypeType.Interface:

                ctd.IsInterface = true;

                break;

            case TypeType.Struct:

                ctd.IsStruct = true;

                break;

            case TypeType.Enum:

                ctd.IsEnum = true;

                break;

        }

        if (attributes != null)

        {

            ctd.CustomAttributes.AddRange(attributes);

        }

        if (typeParameters != null)

        {

            ctd.TypeParameters.AddRange(typeParameters);

        }

        if (baseType != null)

        {

            ctd.BaseTypes.Add(baseType);

        }

        if (interfaces != null)

        {

            ctd.BaseTypes.AddRange(interfaces);

        }

        return ctd;

    }

 

    private enum TypeType

    {

        Class,

        Interface,

        Struct,

        Enum

    }

}

After working with CodeDom for a while, I must say that the API was poorly designed. Although those utility methods helped a bit, but any program using this API still looks cumbersome and hard to maintain.

I'm hoping that I can have a fluent interface so that I can write like this:

DeclearType.Class.Attribute(...).Public.Virtual.Name("MyClass").Extends(baseType).Implements(...);

Utility methods for easier and better looking CodeDom program

Friday, August 15, 2008

Spring.Net object name for Web Services

With the great experience that I have with Spring for Java for many years, I naturally started our .Net project with Spring.Net as the dependency injection framework. Overall this is another great experience. With that being said, we did encountered some difficulties here and there. This one is about the object name of the Web Service client. Let's say we have IHelloWorld and its implementation HelloWorld, thus we have the object defined in the spring.config as

<object id="HelloWorld" type="Example.Service.HelloWorld, Example.Service"/>

Of course, the HelloWorld ASP.NET page is working perfectly well with the injected IHelloWorld. Now we need to expose it as Web Service at HelloWorld.asmx. In order to do this, I must define the object name as 'HelloWorld'. There is no other way to expose the service as HelloWorld.asmx. This is bad. I obviously want to define the object id as HelloWorld.asmx like below:

  <object id="HelloWorld.asmx" type="WebServiceExporter">

    <property name="TargetName" value="HelloWorld"/>

    <property name="Namespace" value="http://imm.bn-corp.com/"/>

    <property name="Description" value="My exported Person web service"/>

    <property name="Name" value="HelloWorld"/>

  </object>

But this didn't work. The WebServiceHandlerFactory refused to pick it up. Solution? That's why I love the open source! Hey, we have the source code and all we did was to change the code in the factory class to the following. Everything started working happily.

string pageName = WebUtils.GetPageName(url);

IApplicationContext appContext = WebApplicationContext.Current;

 

string serviceName = pageName + ".asmx";

bool found = appContext.ContainsObjectDefinition(serviceName);

if (!found)

{

    serviceName = pageName;

    found = appContext.ContainsObjectDefinition(serviceName);

}

Saturday, May 31, 2008

Raising Events in NMock 2.0

Neil Bourgeois posted an excellent article on this. I find myself often need EventHandler<TEventArgs> in addition to EventHandler, and also I would like to write Will(Hookup.Event(mockEvent)) because it reads more natually so easier to understand:
   1:MockEvent mockEvent = new MockEvent();
   2:Expect.Once.On(mockProvider).EventAdd("EffectiveUserChanged", Is.Anything)
   3:    .Will(Hookup.Event(mockEvent));
   4:mockEvent.Raise(mockProvider, EventArgs.Empty);
Here I added a static class Hookup and generic versions of Neil's classes.
   1:    /// <summary>
   2:    /// http://blog.gravityfree.ca/2007/03/raising-events-in-nmock-20.html
   3:    /// </summary>
   4:    /// <author>Neil Bourgeois</author>
   5:    /// <author>Kenneth Xu</author>
   6:    /// <seealso cref="MockEvent{TEventArgs}"/>
   7:    /// <seealso cref="Hookup"/>
   8:    public class MockEvent
   9:    {
  10:        private EventHandler _handler;
  11:
  12:        public void Initialize(EventHandler handler)
  13:        {
  14:            this._handler = handler;
  15:        }
  16:
  17:        public void Raise()
  18:        {
  19:            Raise(this, EventArgs.Empty);
  20:        }
  21:
  22:        public void Raise(object source, EventArgs e)
  23:        {
  24:            _handler.Invoke(source, e);
  25:        }
  26:
  27:    }
  28:
  29:    /// <summary>
  30:    /// http://blog.gravityfree.ca/2007/03/raising-events-in-nmock-20.html
  31:    /// </summary>
  32:    /// <author>Neil Bourgeois</author>
  33:    /// <author>Kenneth Xu</author>
  34:    /// <seealso cref="MockEvent"/>
  35:    /// <seealso cref="Hookup"/>
  36:    public class MockEvent<TEventArgs> where TEventArgs : EventArgs
  37:    {
  38:        private EventHandler<TEventArgs> _handler;
  39:
  40:        public void Initialize(EventHandler<TEventArgs> handler)
  41:        {
  42:            this._handler = handler;
  43:        }
  44:
  45:        public void Raise(object source, TEventArgs e)
  46:        {
  47:            _handler.Invoke(source, e);
  48:        }
  49:
  50:    }
  51:
  52:    /// <summary>
  53:    /// See BusinessContextTest for an example.
  54:    /// </summary>
  55:    /// <author>Kenneth Xu</author>
  56:    /// <seealso cref="MockEvent"/>
  57:    /// <seealso cref="MockEvent{TEventArgs}"/>
  58:    public static class Hookup
  59:    {
  60:        public static IAction Event(MockEvent mockEvent)
  61:        {
  62:            return new MockEventHookup(mockEvent);
  63:        }
  64:
  65:        public static IAction Event<TEventArgs>(MockEvent<TEventArgs> mockEvent)
  66:            where TEventArgs : EventArgs
  67:        {
  68:            return new MockEventHookup<TEventArgs>(mockEvent);
  69:        }
  70:
  71:        /// <summary>
  72:        /// http://blog.gravityfree.ca/2007/03/raising-events-in-nmock-20.html
  73:        /// </summary>
  74:        /// <author>Neil Bourgeois</author>
  75:        /// <seealso cref="MockEvent"/>
  76:        /// <seealso cref="MockEventHookup{TEventArgs}"/>
  77:        private class MockEventHookup : IAction
  78:        {
  79:            private readonly MockEvent mockEvent;
  80:
  81:            public MockEventHookup(MockEvent mockEvent)
  82:            {
  83:                this.mockEvent = mockEvent;
  84:            }
  85:
  86:            public void Invoke(Invocation invocation)
  87:            {
  88:                EventHandler handler = invocation.Parameters[0] as EventHandler;
  89:                if (handler == null)
  90:                    throw new Exception("Unknown event handler type: " + invocation.Parameters[0].GetType());
  91:                mockEvent.Initialize(handler);
  92:            }
  93:
  94:            public void DescribeTo(TextWriter writer)
  95:            {
  96:                // do nothing
  97:            }
  98:        }
  99:
 100:        /// <summary>
 101:        /// http://blog.gravityfree.ca/2007/03/raising-events-in-nmock-20.html
 102:        /// </summary>
 103:        /// <author>Neil Bourgeois</author>
 104:        /// <seealso cref="MockEvent{TEventArgs}"/>
 105:        /// <seealso cref="MockEventHookup"/>
 106:        private class MockEventHookup<TEventArgs> : IAction
 107:            where TEventArgs : EventArgs
 108:        {
 109:            private readonly MockEvent<TEventArgs> mockEvent;
 110:
 111:            public MockEventHookup(MockEvent<TEventArgs> mockEvent)
 112:            {
 113:                this.mockEvent = mockEvent;
 114:            }
 115:
 116:            public void Invoke(Invocation invocation)
 117:            {
 118:                EventHandler<TEventArgs> handler = invocation.Parameters[0] as EventHandler<TEventArgs>;
 119:                if (handler == null)
 120:                    throw new Exception("Unknown event handler type: " + invocation.Parameters[0].GetType());
 121:                mockEvent.Initialize(handler);
 122:            }
 123:
 124:            public void DescribeTo(TextWriter writer)
 125:            {
 126:                // do nothing
 127:            }
 128:        }
 129:    }
 130:

Thursday, May 08, 2008

Logging the SQL generated by NHibernate

Having used Hibernate for many years but relatively new to NHibernate, I though logging SQL executed by NHibernate would be a simple log4net configuration task, but only to find out it is not as straightforward as I thought. The problem is mainly due to a specious solution on Internet here (Update: Dave correct the post, Thanks!) and here. The correct logger to use is NHibernate.SQL, NOT NHibernate.Loader.Loader. And you need DEBUG level instead of INFO. What's confusing was that the NHibernate.Loader.Loader does log all the SELECT statements but not for UPDATE statement. The XML section below works for me.
  <log4net>
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date - %message%newline"/>
      </layout>
    </appender>
    <logger name="NHibernate.SQL" additivity="false">
      <level value="DEBUG" />
      <appender-ref ref="ConsoleAppender" />
    </logger>
  </log4net>

Find the mouse position in WndProc

I had a need to know the mouse position when subclassing the ComboBox (will blog this part later). Googled for it and the best I can get is a post in VB.NET news group. I was hoping a method somewhere in the .Net can easily do this, but I couldn't find it. For now, I have this method below to do the work.
        /// <summary>
        /// Extract the mouse position from mouse related
        /// <see cref="Message">.
        /// </summary>
        /// <param name="mouseMessage"></param>
        /// <returns>Position of the mouse</returns>
        public static Point GetMousePosition(Message mouseMessage)
        {
            int lParam = mouseMessage.LParam.ToInt32();
            int x = (lParam & 0xFFFF);
            int y = (int)(lParam & 0xFFFF0000 >> 16);
            return new Point(x, y);

        }