asp tutorials, asp.net tutorials, sample code, and Microsoft news from 15Seconds
Data Access  |   Troubleshooting  |   Security  |   Performance  |   ADSI  |   Upload  |   Email  |   Control Building  |   Component Building  |   Forms  |   XML  |   Web Services  |   ASP.NET  |   .NET Features  |   .NET 2.0  |   App Development  |   App Architecture  |   IIS  |   Wireless
 
Pioneering Active Server
 Power Search





Active News
15 Seconds Weekly Newsletter
• Complete Coverage
• Site Updates
• Upcoming Features

More Free Newsletters
Reference
News
Articles
Archive
Writers
Code Samples
Components
Tools
FAQ
Feedback
Books
Links
DL Archives
Community
Messageboard
List Servers
Mailing List
WebHosts
Consultants
Tech Jobs
15 Seconds
Home
Site Map
Press
Legal
Privacy Policy
internet.commerce














internet.com
IT
Developer
Internet News
Small Business
Personal Technology

Search internet.com
Advertise
Corporate Info
Newsletters
Tech Jobs
E-mail Offers

HardwareCentral
Compare products, prices, and stores at Hardware Central!

Microsoft .NET CodeDom Technology - Part 3
By Brian J. Korzeniowski
Rating: 4.2 out of 5
Rate this article


  • email this article to a colleague
  • suggest an article

    Summary


    This article introduces Web Services and demonstrates how to implement Web Services in your own Code Generation applications. You will program a Web Service that returns the C# source code for a custom attribute. To keep this article focused, an overview of Web Services is presented; however, several Web Services topics will not be covered - These include Web Services Security, Web Services Design and Web Services Architecture. For more information on Web Services, please visit the Microsoft MSDN Developer website at http://msdn.microsoft.com/webservices/understanding/default.aspx.

    Requirements

    1. Visual Studio.NET Professional, or Enterprise Architect Edition
    2. VB.NET or C#.NET Programming Knowledge
    3. Read Article, "Microsoft .NET CodeDom Technology - Part 1"
    4. Read Article, ""Microsoft .NET CodeDom Technology - Part 2"

    Contents

    Introduction
    Web Services Overview
    Web Services Example - Listing 3.1
    Web Services Example Explanation - Listing 3.1
    Class Object Example - Listing 3.2
    Class Object Example Explanation - Listing 3.2
    Web Services Output Source Code - Listing 3.3
    Conclusion
    About the Author

    Introduction

    In this article, you will build a Web Service that generates C# source code for creating custom attributes. We will name our Web Service the "CodeDom Attribute Factory." The AttributeFactory Web Service may be invoked by remote clients through a firewall and will generate C# source code. If you want to generate source code in a different language other than C#, modify the listings presented here to do that. This article will focus on building a tool, and will utilize our line-by-line discussion approach to reinforce the concepts presented.

    Web Services Overview

    Web Services are compact applications, written in XML, that allow communication between applications, devices, and data sources. Web Services communicate using industry standard, firewall friendly protocols such as HTTP, SOAP and UDDI. The ultimate goal of Web Services development is to allow applications to be built using different vendor tools, running on different vendor platforms and devices, using different programming languages. Each Web Service can be developed and deployed independently. You can create Web Services using any text editor; however, Microsoft Visual Studio .NET is the best tool for developing .NET projects.

    Web Services are different than traditional Remote Procedure Call (RPC) applications. First, Web Services use SOAP Messages. There are two main parts to any SOAP Message: The SOAP Envelope and the SOAP Body. The SOAP Envelope contains information about namespaces and templated information, while the SOAP Body contains XML data to be included within the SOAP Message. This XML is sometimes referred to as the SOAP "Payload".

    Second, although HTTP is the most common transport protocol used in transmitting SOAP Messages, you are not bound to exclusively use the HTTP transport protocol. Web Services SOAP Messages can contain optional header elements containing special instructions for controlling SOAP Request/Response behaviors. SOAP Messages may also specify Intermediaries. Intermediaries are transport nodes located at specific points along the delivery route of the final SOAP Message. By using Intermediaries, the entire SOAP Message does not need to be transmitted in a single transmission hop.

    Third, Web Services are self-describing. They include information about the messages they consume, the messages they produce, the transport protocols used to transmit the SOAP messages back and forth, and how to invoke the Web Service.

    Web Services contain behaviors that the consumer invokes. These behaviors are described using an XML vocabulary called the Web Services Description Language (WDSL). WSDL describes the specific behaviors that are able to be invoked and the protocol to use for invoking them. For more information on XML Web Services, see the following link to Microsoft's MSDN Developer Website: http://msdn.microsoft.com/webservices/understanding/default.aspx.

    Web Services Example - Listing 3.1

    Listing 3.1 contains the source code for creating a Web Service that generates C# source code for a Custom Attribute. A custom attribute is a class that derives from the System.Attribute namespace. The source code listing below functions as a wrapper for code generation. The actual C# source code generation is done in a business object named "CAttributeFactory." The method listed below is NOT meant to depict an ideal architecture; however, it does give insight into the flexibility of your .NET Code Generation Applications.

    Listing 3.1 Web Services Source Code

    
    1:     using System;
    2:     using System.CodeDom;
    3:     using System.CodeDom.Compiler;
    4:     using System.Collections;
    5:     using System.Collections.Specialized;
    6:     using System.ComponentModel;
    7:     using System.Data;
    8:     using System.Diagnostics;
    9:     using System.IO;
    10:   using System.Text;
    11:   using System.Web;
    12:   using System.Web.Services;
    13:   using Microsoft.CSharp;
    14:   using Factory;
    15:
    16:   namespace Factory
    17:   {
    18:	/// <summary>
    19:	/// The wbSvcAttributeFactory Web Service creates custom attributes as 
    source code or CodeDom Source.
    20:	/// </summary>
    21:	[WebService(Description="Returns the CodeDom or Source Code custom 
    attribute representation.",
    22:		     Namespace="http://www.mydomain.com/services")]
    23:	public class wbSvcAttributeFactory : WebService
    24:	{
    25:		//Member Fields
    26:		//private string m_ClassName = "defaultClass";
    27:
    28:		public wbSvcAttributeFactory()
    29:		{
    30:		        //CODEGEN: This call is required by the ASP.NET Web Services 
    Designer
    31:		        InitializeComponent();
    32:		}
    33:				
    34:		[WebMethod(Description="Create custom attribute as C# Source code.",
    35:			     BufferResponse=true,
    36:			     EnableSession=false)]
    37:
    38:		public byte[] CreateAsSourceCode(string strClassName,
    39:			                                    ArrayList 
    aryPosParamsScopes,
    40:			                                    ArrayList 
    aryPosParamsDataTypes,
    41:			                                    ArrayList aryPosParams,
    42:					       ArrayList aryConstructArgs,
    43:			                                    ArrayList 
    aryConstructArgsDataTypes)
    44:		{
    45:
    46:			byte[] myBytes;
    47:			
    48:			CAttributeFactory objAttribute = new CAttributeFactory();
    49:			myBytes = objAttribute.CreateAsSourceCode(strClassName, 
    aryPosParamsScopes,
    50:					aryPosParamsDataTypes, aryPosParams, 
    aryConstructArgs, 
    51:                                                                   aryConstructArgsDataTypes);
    52:
    53:			//Return source code stream to calling routine
    54:         			return myBytes;			
    55:		}
    56:
    57:		#region Component Designer generated code
    58:		
    59:		//Required by the Web Services Designer 
    60:		private IContainer components = null;
    61:				
    62:		/// <summary>
    63:		/// Required method for Designer support - do not modify
    64:		/// the contents of this method with the code editor.
    65:		/// </summary>
    66:		private void InitializeComponent()
    67:		{
    68:		}
    69:
    70:		/// <summary>
    71:		/// Clean up any resources being used.
    72:		/// </summary>
    73:		protected override void Dispose( bool disposing )
    74:		{
    75:			if(disposing && components != null)
    76:			{
    77:				components.Dispose();
    78:			}
    79:			base.Dispose(disposing);		
    80:		}
    81:		
    82:		#endregion
    83:	
    84:	}
    85:   }
    
    

    Web Services Example Explanation - Listing 3.1

    
    1:     using System;
    2:     using System.CodeDom;
    3:     using System.CodeDom.Compiler;
    4:     using System.Collections;
    5:     using System.Collections.Specialized;
    6:     using System.ComponentModel;
    7:     using System.Data;
    8:     using System.Diagnostics;
    9:     using System.IO;
    10:   using System.Text;
    11:   using System.Web;
    12:   using System.Web.Services;
    13:   using Microsoft.CSharp;
    14:   using Factory;
    
    
    The lines above reference the namespaces and classes needed in your Web Service. Lines #2 and #3 reference the CodeDom namespaces containing the CodeDom objects required for our Web Service. Notice that we are using our Web Service as a wrapper for a C# business object. This object is contained within the namespace listed on line #14.

    The source code of our custom attribute will be directly written to a memory stream. A stream is an object that performs IO operations using specialized types of stream manipulator objects. Each stream manipulator object performs IO on an underlying data store type. For example, in our Web Service we are using the memory as a storage area; therefore, you would manipulate a memory store using a MemoryStream object. You will learn about a MemoryStream a little later in this article.

    
    16:   namespace Factory
    17:   {
    
    
    When you create a new Web Service Project in Visual Studio .NET, the project creates a namespace having the same name as the project itself. In our Web Service, we changed the default namespace to "Factory". Line #16 shows the change to the project's default namespace property. You can change the default namespace of a project by right-clicking the project in Visual Studio .NET, selecting the "properties" menu item. Look for the field titled "Default Namespace." Now, enter the new name of the default namespace and click the "OK" command button. From the point the default namespace is modified, all project item types added to your project will be created using the new default namespace value. For example, if we added a class object to our Web Service, the class will automatically be attached and grouped using the "Factory" namespace.
    
    18:	/// <summary>
    19:	/// The wbSvcAttributeFactory Web Service creates custom attributes as 
    source code or CodeDom Source.
    20:	/// </summary>
    
    
    Lines #18 - Line #20 illustrates the source code commenting features of the C# Language. To document your C# source code, simply type the "/" character three times successively. The text in lines #18-#20 above will instantly appear, along with a default text summary description. Try experimenting with the various built-in documenting features of C# when you can It will be time well spent.
    
    21:	[WebService(Description="Returns the CodeDom or Source Code custom 
    attribute representation.",
    22:		     Namespace="http://www.mydomain.com/services")]
    
    
    In Part 2 of this article series, you learned how to create custom attributes. You learned there are two types of attributes: built-in attributes, intrinsic to the .NET Framework, and custom attributes, which are user-defined. Line #21 and line #22 illustrate using the intrinsic "[WebService"] attribute. The "[WebService]" Attribute is applied to any class derived from the WebService class. Its purpose is to provide two things: a short description of the functionality of the Web Service and the namespace and UDDI directories it uses to locate and consume Web Services. When setting a web reference to your web service, the URL is used to locate your service and add it to a Visual Studio .NET Project.
    
    23:	public class wbSvcAttributeFactory : WebService
    24:	{
    
    
    The Microsoft .NET Framework provides a base class for implementing Web Services. This base class is called the "WebService" class, located in the System.Web.Services namespace. All classes designed to be Web Services will inherit functionality from the "WebService" class. In our application, we are developing a Web Service class named "wbSvcAttributeFactory". Line #23 declares a class named "wbSvcAttributeFactory". Since our class will be implemented as a Web Service, we inherited our "wbSvcAttributeFactory" class from the "WebService" base class. When creating your own Web Service projects, any class that are implemented as Web Services will always inherit from the "WebService" base class.
    
    28:		public wbSvcAttributeFactory()
    29:		{
    30:		        //CODEGEN: This call is required by the ASP.NET Web Services 
    Designer
    31:		        InitializeComponent();
    32:		}
    
    
    Each class in your application must include a constructor as part of its definition. The constructor will have the same name as the class being implemented. In our example, we are creating a class named "wbSvcAttributeFactory"; therefore, the constructor will be named "wbSvcAttributeFactory" as well. Line #28 shows the constructor for our Web Service class. Notice it is named the same as our class. Our example class contains a parameter-less constructor; however, constructors can accept zero or more parameters. In line #32, the call to the "InitializeComponent" method performs.NET Framework housekeeping activities and should not be deleted.
    
    34:	[WebMethod(Description="Create custom attribute as C# Source code.",
    35:			    BufferResponse=true,
    36:			    EnableSession=false)]
    
    
    Attributes are pieces of meta-data that clarify the usage or structure of the element they are applied to. The Microsoft .NET Framework makes liberal use of intrinsic and custom-defined attributes. When manipulating attributes, you will encounter the terms "consumer" and "producer". When an attribute is applied to an object, the object is an attribute consumer. When an attribute is end-user created, the end user plays the role of an attribute producer. When creating your own custom attributes, you derive your attribute class from the System.Attribute class, located in the System namespace.

    Line #34 introduces the .NET Framework's "[WebMethod]" attribute. The "WebMethod" attribute allows the attribute consumer to be callable by remote clients and devices. Methods of your Web Service that do not consume the "WebMethod" attribute are not publicly visible to external clients and devices. A general rule when developing Web Services is to expose the smallest possible public interface to your Web Service consumer; therefore, you will not want to publicly expose all the member objects of your Web Service, unless it makes sense for your specific application.

    The "WebMethod" attribute exposes many parameterized properties to a consumer. Among the most notable are the ones implemented in lines #34 - line #36 - the Description, BufferResponse, and EnableSession properties. The "Description" property allows you to describe the functionality of your web method. The "BufferResponse" property instructs Internet Information Server (IIS) to buffer the response before it is streamed to the client. The EnableSession property allows you to store session state for the web service. Session state is stored using an instance of the HttpSessionState object, in the System.Web.SessionState namespace. In order for the HttpSessionState object to properly store session state, the EnableSession property must be set to true.

    
    38:		public byte[] CreateAsSourceCode(string strClassName,
    39:			                                    ArrayList 
    aryPosParamsScopes,
    40:			                                    ArrayList 
    aryPosParamsDataTypes,
    41:			                                    ArrayList aryPosParams,
    42:					       ArrayList aryConstructArgs,
    43:			                                    ArrayList 
    aryConstructArgsDataTypes)
    44:		{
    
    
    Notice that the method return type of the method in line #38 is a byte array, which contains the serialized version of the source code used to represent our custom attribute. The paramaters of the method defined in line #38 are as follows:
    1. aryPosParamsScopes    This is an ArrayList of positional parameter scopes
    2. aryPosParamsDataTypes    This is an Array List of positional parameter data types
    3. aryPosParams    This is an Array List of positional parameter names.
    4. aryConstructArgs    This is an Array List of constructor arguments
    5. aryConstructArgsDataTypes    This is an Array List of constructor argument data types
    
    46:			byte[] myBytes;
    47:			
    48:			CAttributeFactory objAttribute = new CAttributeFactory();
    49:			myBytes = objAttribute.CreateAsSourceCode(strClassName, 
    aryPosParamsScopes,
    50:					aryPosParamsDataTypes, aryPosParams, 
    aryConstructArgs, 
    51:                                                                   
    aryConstructArgsDataTypes);
    52:
    53:			//Return source code stream to calling routine
    54:         			return myBytes;			
    55:		}
    
    
    Line #46 defines a byte array for holding the serialized C# source code of our new custom attribute class. Line #48 creates an instance of the business object that will utilize the CodeDom to create our attribute class. We pass the parameters mentioned in line #38-43 above into the business object method named "CreateAsSourceCode", listed on line #49 above. This method returns a serialized byte array representing the source code of our custom attribute class. In line #54, we return the serialized representation of our source code to the calling routine.
    
    57:		#region Component Designer generated code
    58:		
    59:		//Required by the Web Services Designer 
    60:		private IContainer components = null;
    61:				
    62:		/// <summary>
    63:		/// Required method for Designer support - do not modify
    64:		/// the contents of this method with the code editor.
    65:		/// </summary>
    66:		private void InitializeComponent()
    67:		{
    68:		}
    69:
    70:		/// <summary>
    71:		/// Clean up any resources being used.
    72:		/// </summary>
    73:		protected override void Dispose( bool disposing )
    74:		{
    75:			if(disposing && components != null)
    76:			{
    77:				components.Dispose();
    78:			}
    79:			base.Dispose(disposing);		
    80:		}
    81:		
    82:		#endregion
    
    
    The lines above show the common code that is called when a designer is used to create Web Services or components in .NET. Of interest to us, is lines #73-#80. These lines help to properly destroy resources and free up memory used by our processes.

    Class Object Example - Listing 3.2

    Listing 3.2 Class Object Source Code

    
    1:     using System;
    2:     using System.Runtime.Serialization;
    3:     using System.Runtime.Serialization.Formatters.Binary;
    4:     using System.Collections;
    5:     using System.Collections.Specialized;
    6:     using System.CodeDom;
    7:     using System.CodeDom.Compiler;
    8:     using System.Diagnostics;
    9:     using System.IO;
    10:   using System.Text;
    11:   using Microsoft.CSharp;
    12:   using Microsoft.VisualBasic;
    13:   using System.Reflection;
    14:
    15:   namespace Factory
    16:   {	
    17:	public class CAttributeFactory
    18:	{
    19:		public CAttributeFactory()
    20:		{
    21:		}
    22:
    23:		public byte[] CreateAsSourceCode(string strClassName, ArrayList 
    aryPosParamsScopes,
    24:				ArrayList aryPosParamsDataTypes, ArrayList aryPosParams,
    25:			    ArrayList aryConstructArgs, ArrayList 
    aryConstructArgsDataTypes)
    26:		{
    27:			//Used for looping array lists
    28:			int idx = 0;
    29:
    30:			//Initialize CodeDom variables
    31:			//Stream s = File.Open("c:\\" + m_ClassName + ".cs", 
    FileMode.Create);
    32:	
    33:			MemoryStream ms = new MemoryStream();
    34:			//StreamWriter sw = new StreamWriter(s);
    35:			StreamWriter sw = new StreamWriter(ms);
    36:			
    37:			CSharpCodeProvider cscProvider = new CSharpCodeProvider();
    38:			ICodeGenerator cscg = cscProvider.CreateGenerator(sw);
    39:			CodeGeneratorOptions cop = new CodeGeneratorOptions();
    40:			cop.BracingStyle = "C";
    41:			
    42:			//Generate [AttributeUsage] Attribute
    43:			CodeSnippetCompileUnit cdAttributeTarget = 
    this.CreateAttributeUsage("Class");
    44:
    45:			//Write [AttributeUsage] attribute to memory stream
    46:			cscg.GenerateCodeFromCompileUnit(cdAttributeTarget, sw, cop);
    47:
    48:			//Generate Class Definition			
    49:			CodeTypeDeclaration theClass = new CodeTypeDeclaration();
    50:			theClass.IsClass = true;
    51:			theClass.Name = strClassName;
    52:			theClass.BaseTypes.Add("Attribute");
    53:			theClass.TypeAttributes = TypeAttributes.Public;
    54:
    55:			//Generate Positional Parameter Fields
    56:			for(idx = 0; idx <= aryPosParamsScopes.Count - 1; idx++)
    57:			{
    58:				string strParamScope = (string)aryPosParamsScopes[idx];
    59:				string srtParamDataType = 
    (string)aryPosParamsDataTypes[idx];
    60:				string strParamName = (string)aryPosParams[idx];
    61:
    62:				//Retrieve positional parameter definition
    63:				CodeMemberField cmf = new 
    CodeMemberField(srtParamDataType, 
    64:										strParamName);
    65:				cmf.Attributes = MemberAttributes.Private;
    66:				theClass.Members.Add(cmf);
    67:			}
    68:
    68:			//Generate Comment
    69:			CodeCommentStatement ccs2 = new 
    CodeCommentStatement("Constructor");
    70:
    71:			//Generate Constructor
    72:			CodeConstructor ccon = new CodeConstructor();
    73:			ccon.Attributes = MemberAttributes.Public;	
    74:			ccon.Comments.Add(ccs2);
    75:
    76:			//Generate Constructor Arguments
    77:			for(idx = 0; idx <= aryConstructArgs.Count - 1; idx++)
    78:			{
    79:				string strConArg = (string)aryConstructArgs[idx];
    80:				string strConArgDataType = 
    (string)aryConstructArgsDataTypes[idx];
    81:
    82:				//Define Constructor Argument
    83:				CodeParameterDeclarationExpression cpd = new 
    84:                                                                   
    CodeParameterDeclarationExpression(strConArgDataType, strConArg);
    85:			
    86:				ccon.Parameters.Add(cpd);
    87:			}
    88:
    89:			//Generate Source Code File
    90:			theClass.Members.Add(ccon);
    91:			cscg.GenerateCodeFromType(theClass, sw, cop);
    92:
    93:			sw.Close();
    94:			
    95:			//Return source code as byte array
    96:			return ms.ToArray();
    97:		}
    98:
    99:		private CodeSnippetCompileUnit CreateAttributeUsage(string 
    strAttributeTarget)
    100:		{
    101:			//Return CodeDom Representation
    102:			CodeSnippetCompileUnit cdSnippet1 = new
    103:				CodeSnippetCompileUnit("[AttributeUsage(AttributeTargets." + 
    strAttributeTarget 
    104:                                                                        + ")]");
    105:			return cdSnippet1;
    106:		}
    107:	     }
    108:     }
    
    

    Class Object Example Explanation - Listing 3.2

    
    1:     using System;
    2:     using System.Runtime.Serialization;
    3:     using System.Runtime.Serialization.Formatters.Binary;
    4:     using System.Collections;
    5:     using System.Collections.Specialized;
    6:     using System.CodeDom;
    7:     using System.CodeDom.Compiler;
    8:     using System.Diagnostics;
    9:     using System.IO;
    10:   using System.Text;
    11:   using Microsoft.CSharp;
    12:   using Microsoft.VisualBasic;
    13:   using System.Reflection;
    
    
    Lines #1-#13 simple declare the namespace we use in our class object. Since our business object will be using CodeDom, we have referenced the proper namespaces in lines #6-#7.
    
    15:   namespace Factory
    16:   {	
    17:	public class CAttributeFactory
    18:	{
    19:		public CAttributeFactory()
    20:		{
    21:		}
    
    
    Namespaces in the Microsoft .NET Framework allow us to group code with similar functionality, and then refer to that group of code by a logical name. Just by looking at the title of the namespace, you can usually infer the contents of the functionality behind the namespace. You will see namespaces everywhere in .NET. You will see them when you reference the .NET Framework Base Class Libraries (BCL), when you create XML files, when you create XSL files, and other places as well, so learn how to group your code using namespaces. Line #15 declares the namespace we use to group our source code - the "Factory" namespace.
    
    23:		public byte[] CreateAsSourceCode(string strClassName, ArrayList 
    aryPosParamsScopes,
    24:				ArrayList aryPosParamsDataTypes, ArrayList aryPosParams,
    25:			    ArrayList aryConstructArgs, ArrayList 
    aryConstructArgsDataTypes)
    26:		{
    
    
    Lines#23-26 define the public interface of our business object. It accepts the parameters defined in the discussion of lines #38-#44 of Listing 3.1 above.
    
    27:			//Used for looping array lists
    28:			int idx = 0;
    
    
    Our parameters to the "CreateAsSourceCode" method are Array Lists. The indexes of the arrays are synchronized. We will need an index to keep the indexes in sync. The "idx" variable in line #28 does just that - it keeps our arrays in sync. We will discuss that in more detail later.
    
    33:			MemoryStream ms = new MemoryStream();
    34:			//StreamWriter sw = new StreamWriter(s);
    35:			StreamWriter sw = new StreamWriter(ms);
    
    
    Line #33 introduces an object you may not have used before - the MemoryStream object. Streams are quite common programming elements. A stream manipulates some type of underlying data storage type. Examples of streams include physical disk files and physical memory inside your computer. You manipulate stream by using a specific stream object that is custom-designed to manipulate the underlying stream type. For example, to read and write file streams, you use the StreamWriter. To read and write data to memory, you use the Memory stream object. Because we do not want to be creating and destroying files with every method call, we chose to use a MemoryStream, which will write out our source code to the memory of the server. The memory contents are then returned to the calling routine as a serialized byte array.

    In line #35, the StreamWriter object operates on a memory storage area. To manipulate the memory storage area we create a StreamWriter object. The state of the MemoryStream object is directly affected by calls made to the StreamWriter object.

    
    37:			CSharpCodeProvider cscProvider = new CSharpCodeProvider();
    38:			ICodeGenerator cscg = cscProvider.CreateGenerator(sw);
    39:			CodeGeneratorOptions cop = new CodeGeneratorOptions();
    40:			cop.BracingStyle = "C";
    
    
    Line #37: This line creates an instance of the CSharpCodeProvider class. If you want to use the CodeDom classes in your own projects, you must point the CodeDom namespaces to a class that conforms to the CodeDom specification and that provides the functionality to generate source code in the language you select. In our case, Line 37 above tells us that the CSharpCodeProvider class contains the necessary functionality to dynamically create source code in C# using CodeDom. The CSharpCodeProvider class contains the necessary plumbing to support code generation that targets the C# programming language using CodeDom.

    Line #38: This line declares an instance of the ICodeGenerator interface which is responsible for logically creating the CodeDom tree structures and CompileUnits as in-memory structures.

    Line #39: This line creates a CodeGeneratorOptions object. This object is responsible for setting configuration properties for structuring the physical source code file on disk. Common properties that are set using the CodeGeneratorOptions Class include how many characters to indent, the indent character and what style of bracing to use. In our case, since we did not specify any configuration properties, and since we are targeting C# as our default language, the defaults for the CodeGeneratorOptions Class were used - "C" Style bracing, and tab character indentations.

    
    42:			//Generate [AttributeUsage] Attribute
    43:			CodeSnippetCompileUnit cdAttributeTarget = 
    this.CreateAttributeUsage("Class");
    
    99:		private CodeSnippetCompileUnit CreateAttributeUsage(string 
    strAttributeTarget)
    100:		{
    101:			//Return CodeDom Representation
    102:			CodeSnippetCompileUnit cdSnippet1 = new
    103:				CodeSnippetCompileUnit("[AttributeUsage(AttributeTargets." + 
    strAttributeTarget 
    104:                                                                + ")]");
    105:			return cdSnippet1;
    106:		}
    
    
    Line #43 creates a CodeSnippetCompileUnit object which will holds the definition of the "[AttributeUsage'} attribute for our custom attribute class. Line #43 calls the "CreateAttributeClass" method, which builds the CodeSnippetCompileUnit representation of the "[AttributeUsage]" attribute. Lines #99-#106 builds the CodeSnippetCompileUnit representation of the "[AttributeUsage]" attribute object and returns a CodeSnippetCompileUnit object to the calling routine. The "[AttributeUsage]" attribute must inform the consumer of your attribute how it can be consumed. This is done by setting the "AttributeTargets" enumeration to one or more values of the "AttributeTargets" enumeration, located in the System.Attribute namespace.
    
    45:			//Write [AttributeUsage] attribute to memory stream
    46:			cscg.GenerateCodeFromCompileUnit(cdAttributeTarget, sw, cop);
    
    
    Now that we created the definition of our "[AttributeUsage]" attribute, we need a mechanism that generates C# source code. Line #46 implements a method of the GenerateCodeFromCompileUnit object to generate our C# source code. This method takes three parameters: the CodeSnippetCompileUnit object containing the CodeDom parse tree, the StreamWriter object for writing the source code to disk, and the CodeGeneratorOptions object, providing source code formatting options - including indentation and bracing styles. After executing line #46, a single line of C# source code containing the source "[AttributeUsage....]" is written to the specified source code file on disk.
    
    48:			//Generate Class Definition			
    49:			CodeTypeDeclaration theClass = new CodeTypeDeclaration();
    50:			theClass.IsClass = true;
    51:			theClass.Name = strClassName;
    52:			theClass.BaseTypes.Add("Attribute");
    53:			theClass.TypeAttributes = TypeAttributes.Public;
    
    
    Custom attributes are implemented using a class. A class is a "type" of programming element. A "type" is simple a certain classification of object. Class objects are represented in the CodeDom by the CodeTypeDeclaration object. For example, there are various "types" of cars - Ford, Honda, Chrysler, and others.

    Line #50 creates the CodeDom object for our attribute type. Line #51 sets the name of the class. Line #52 links to the System.Attribute base class using inheritance. Under object-oriented terminology, a base class is the parent, or superclass, of its immediate child. Therefore, when we assign the "BaseType" for our custom attribute class to point to the "Attribute" class, we are setting up an inheritance relationship in the generated source code later on. Line #53 sets the scope of our custom attribute class to public. Why set the scope to public? If we used a private scope, our custom attribute would only be visible from within the class or assembly in which it is defined.

    
    55:			//Generate Positional Parameter Fields
    56:			for(idx = 0; idx <= aryPosParamsScopes.Count - 1; idx++)
    57:			{
    58:				string strParamScope = (string)aryPosParamsScopes[idx];
    59:				string srtParamDataType = 
    (string)aryPosParamsDataTypes[idx];
    60:				string strParamName = (string)aryPosParams[idx];
    61:
    62:				//Retrieve positional parameter definition
    63:				CodeMemberField cmf = new 
    CodeMemberField(srtParamDataType, 
    64:                                                    strParamName);
    65:				cmf.Attributes = MemberAttributes.Private;
    66:				theClass.Members.Add(cmf);
    67:			}
    
    
    Line #56 uses a loop to iterate through all the positional parameters to be included in our custom attribute class. Line #58 retrieves the first scope of the first positional parameter from the aryPosParamsScope array list method argument. Line #59 retrieves the first positional parameter data type from the aryPosParamsDataTypes array list method argument. Line #60 retrieves the first positional parameter name from the aryPosParams array list method argument. Inside the loop, we access the arrays in parallel. Element zero in aryPosParamsScopes contains the scope of the positional parameter. Element zero in aryPosParamsDataTypes contains the data type of the positional parameter. Element zero in aryPosParams contains the name of the positional parameter. By accessing the arrays this way, we have the complete definition of our positional parameter easily accessible.

    Line #63 creates a CodeMemberField object, which is initialized with the data type and name of the positional parameter. Line #65 takes the CodeMemberField object and sets the scope of the positional parameter. Now that the CodeMemberField has been defined and initialized with data, line #66 adds the new CodeDom CodeMemberField object to the CodeDom tree.

    
    68:			//Generate Comment
    69:			CodeCommentStatement ccs2 = new 
    CodeCommentStatement("Constructor");
    
    
    The CodeDom stores comments using a CodeCommentStatement object. Line #69 creates a comment for our custom attribute class. When designing your CodeDom source code, be sure to liberally include comments. This will help you understand the source code be more understandable after it is generated by the Source Code Generator.
    
    71:			//Generate Constructor
    72:			CodeConstructor ccon = new CodeConstructor();
    73:			ccon.Attributes = MemberAttributes.Public;	
    74:			ccon.Comments.Add(ccs2);
    
    
    All classes in the Microsoft .NET Framework contain constructors. A constructor is a special method that every class contains. The constructor is called when an object is created from the class definition template. All code contained within the constructor is run each time an instance of the class is created. Constructors typically have the same name as the class that contains it and accepts parameters. Constructor parameters usually initialize internal member fields and private data of the class.

    The CodeDom represents constructors using the CodeConstructor class. Line #72 creates a new CodeConstructor class that will be used to generate our constructor source code later on. Line #73 sets the scope of the constructor to public. After the CodeConstructor object has been initialized with data, it is added to the CodeDom tree in line #74. The last step in creating our custom attribute class is to generate the arguments to our new class constructor.

    
    76:			//Generate Constructor Arguments
    77:			for(idx = 0; idx <= aryConstructArgs.Count - 1; idx++)
    78:			{
    79:				string strConArg = (string)aryConstructArgs[idx];
    80:				string strConArgDataType = 
    (string)aryConstructArgsDataTypes[idx];
    81:
    82:				//Define Constructor Argument
    83:				CodeParameterDeclarationExpression cpd = new 
    84:                                                                   
    CodeParameterDeclarationExpression(strConArgDataType, strConArg);
    85:			
    86:				ccon.Parameters.Add(cpd);
    87:			}
    
    
    Line #76 iterates through the constructor arguments to be included in our constructor. Line #79 retrieves the first constructor name argument from the aryConstructArgs array list method argument. Line #80 retrieves the first constructor name data type argument from the aryConstructArgsDataTypes array list method argument. Line #60 retrieves the first positional parameter name from the aryPosParams array list method argument. Notice that we are accessing the arrays in parallel. Element zero in aryConstructArgs contains the scope of the constructor parameter. Element zero in aryConstructArgsDataTypes contains the data type of the constructor parameter. By accessing the arrays in this way, we have the definition of our constructor arguments easily accessible.

    Now that the CodeParamaterDeclarationExpression object has been defined and initialized with data, line #86 adds the CodeParamaterDeclarationExpression object to the CodeDom tree.

    
    89:			//Generate Source Code File
    90:			theClass.Members.Add(ccon);
    91:			cscg.GenerateCodeFromType(theClass, sw, cop);
    92:
    93:			sw.Close();
    
    95:			//Return source code as byte array
    96:			return ms.ToArray();
    97:		}
    
    
    Line #90 adds the constructor object to our CodeDom tree. Now that we are finished creating the CodeDom representation of our custom attribute, we need to generate the C# source code for our custom attribute class. Line #91 uses the CodeDom tree built so far to generate our source code using the StreamWriter object defined in line #35. The output is written to memory, using the StreamWriter object which redirects to the memory of the server. Line #93 closes the StreamWriter object and disallows any further use of the memory stream object. Line #96 returns the source code to the calling routine as a byte array. The byte array is a serialized representation of the C# source code of our custom attribute class.

    Web Services Output Source Code - Listing 3.3

    Listing 3.3 Web Services Output Source Code

    
    1:   [AttributeUsage(AttributeTargets.Class)]
    2:   public class MyAttribute : Attribute
    3:   {
    4:    
    5:       private int MyPositionalParameter1;
    6:    
    7:       private string MyPositionalParameter2;
    8:    
    9:       // Constructor
    10:     public MyAttribute(int ConstructorArg1, string ConstructorArg2)
    11:     {
    12:      }
    13:   }
    
    

    Conclusion

    In this article, you were introduced to Web Services. You learned that Web Services are compact XML applications that are firewall friendly. You learned that Web Services communicate using industry standard, firewall-friendly protocols such as HTTP, SOAP and UDDI. You learned that SOAP Messages contain Envelopes and Body elements. Also, you saw that Web Services are self-describing and contain methods callable by remote clients. Web Services are described using an XML vocabulary called WSDL, or the Web Services Description Language. WSDL describes the specific behaviors that are able to be invoked and the protocol to use for invoking them.

    You learned how to create your own Web-based code generation utilities using Web Services, and you learned about the possibilities Web Services have for CodeDom projects.

    On a personal note, thank you being a loyal reader of my articles. I have received a lot of positive feedback regarding the structure, content and presentation of the material covered. In the next series, "Microsoft .NET Enterprise Template Technology", I will be changing the structure of the articles a little bit. As you complete the exercises in the next article series, you will have the knowledge, skills and experience to build your own Enterprise Template projects.

    I look forward to sharing with you how you can build your own Enterprise Template projects using Visual Studio .NET. Thanks again for your loyal readership.

    About the Author

    Brian has been working in the IT Industry since 1995. He currently lives in Denver, Colorado. His specialties include Software Design, Systems Analysis and Design, and Backend Server Development using VB, COM, MTS, ADO, SQL, T-SQL, and SQL Server 2000. He has worked for Microsoft as an ASP.NET Support Engineer during the worldwide launch of the Visual Studio.NET product. He has used Visual Studio.NET since Beta 0 and has become specialized in VB.NET/C# and the System.CodeDom and System.CodeDom.Compiler Namespaces of the Microsoft.NET Framework. He holds a B.S, Computer Science Degree from Evangel University. His research interests lie in language development for the .NET Framework, and IDE Productivity Add-Ins and Windows Services for .NET. Brian can be reached at VBAnswerGuy@adelphia.net. He is in the process of developing an academic textbook for teaching Visual Basic.NET to college students. If interested in his book, please contact him at: VBAnswerGuy@adelphia.net. When not burning up the power supply in Colorado, Brian enjoys road trips, hiking, fishing and spending time with his family in the mountains of Colorado.

  • Rate This Article
    Not HelpfulMost Helpful
    1 2 3 4 5
    Other Articles
    Jul 7, 2005 - Hosting Indigo Web Services
    In the second article of his series on Indigo web services, Chris Peiris explains how to host an Indigo web service and examines the IIS, self hosting, and Windows Activation Service hosting options. He then provides step-by-step instructions and sample code for an IIS-hosted and self-hosted Indigo web service.
    [Read This Article]  [Top]
    Jun 8, 2005 - Indigo Programming Model
    In the first part of his series on Microsoft Indigo, Chris Peiris examines the basics of SOA, explains how Indigo fits into the picture and the problems it solves. He then introduces Indigo's programming model and finishes by building a sample Indigo web service using the Microsoft .Net Framework 2.0.
    [Read This Article]  [Top]
    Nov 10, 2004 - Business Intelligence with Microsoft SQL Server Reporting Services - Part 3
    Adnan Masood concludes his discussion of Microsoft SQL Server Analysis services and Microsoft SQL Server Reporting services. In the final part, he discusses Reporting Server web services and using custom code in reports.
    [Read This Article]  [Top]
    Jul 8, 2004 - Using IE's Web Service Behavior To Create Rich ASP.NET Applications
    This article explains the features of the IE Web service behavior and shows how to asynchronously communicate with an ASP.NET Web service directly from the client.
    [Read This Article]  [Top]
    Jul 6, 2004 - Using .NET and Excel 2003 To Validate E-Mails
    Calvin Luttrell shows how to validate e-mail addresses stored in Excel 2003 and provides a special function for solving that pesky problem Yahoo! mail servers cause.
    [Read This Article]  [Top]
    Jun 9, 2004 - Modifying Web Services Documentation
    This short article describes a quick and easy way to provide some security to an ASP.NET Web service by modifying its associated documentation file.
    [Read This Article]  [Top]
    Jun 2, 2004 - Kerberos Authentication with Web Services Enhancements 2.0
    Kerberos authentication is the cornerstone of Windows operating system authentication architecture. Web Services Enhancement 2.0 (WSE 2.0) extends Kerberos support to ASP.NET Web services. Chris Peiris explains the support for this new feature in WSE 2.0.
    [Read This Article]  [Top]
    Dec 15, 2003 - Realizing a Service-Oriented Architecture with .NET
    Chip Irek examines the architectural issues and component design issues of building a .NET application in a service-oriented architecture.
    [Read This Article]  [Top]
    Nov 24, 2003 - Consuming Asynchronous Web Services
    Thiru Thangarathinam shows how to use asynchronous Web services, Windows Service applications, server-based timer components and .NET XML API classes to create high-performance, scalable, and flexible applications.
    [Read This Article]  [Top]
    Nov 12, 2003 - Implementing Paging and XSLT Extensions Using XSLT in .NET - Part 2
    Part one showed how to transform XML data into HTML by using an XSL stylesheet from within a .NET application. This part explains how to make use of XSLT Extension objects and invoke a C# class method from an XSL stylesheet.
    [Read This Article]  [Top]
    Mailing List
    Want to receive email when the next article is published? Just Click Here to sign up.

    Support the Active Server Industry

    internet.commediabistro.comJusttechjobs.comGraphics.com

    Search:

    WebMediaBrands Corporate Info

    Legal Notices, Licensing, Reprints, Permissions, Privacy Policy.
    Advertise | Newsletters | Shopping | E-mail Offers | Freelance Jobs