Namespaces


With some perspective on the design goals and guidelines of the .NET Framework, let's now look at the functionality offered by many of the Framework Class Library's namespaces. Throughout this section, simple applications are provided to demonstrate the use of the libraries.

System

The System namespace is the root namespace; it offers common functionality that is needed by a wide variety of application types. It includes common base classes, types, and utility classes that will be required in nearly any application. Although you have encountered many of the common base classes earlier in this book, a short overview of them is provided here for completeness:

  • System.Object is the ultimate base class for all types in the system. It defines the base set of services that any type in the system is able to provide. Not surprisingly, Object provides default implementations for all of these services.

  • System.ValueType is a reference type that serves as the base class for all value types. It customizes the implementations of the virtual methods on Object so that they are more appropriate for value types.

  • System.Enum , a reference type that derives from System.ValueType , is the base class for all enumerations in the system. It further customizes the virtual methods from System.ValueType so that they deal with exactly one integral field of instance data. System.Enum also offers utility methods for formatting and parsing enumeration values.

  • System.Exception is the ultimate base class for all exceptions in the system.

  • All custom attributes derive from the System.Attribute base class, which contains utility methods for reading custom attributes off of reflection elements. (Reflection is discussed in detail in Chapter 3.)

Table 7.1 summarizes the base data types that are found in the System namespace. These types are so commonly used that languages typically use keywords as aliases for them. The System namespace classes represent those types and provide formatting and parsing, comparing, and coercion support for them.

Other classes provide services including supervision of managed and unmanaged applications, mathematics, remote and local program invocation, data type conversion, and application environment management.

Listing 7.3 demonstrates the use of some of the common base data types. This command-line tool returns the date plus or minus n days from today. The first thing it does is set up a try/catch block ”a lazy way to validate parameters. If any error occurs, the program displays a message indicating that the problem probably arose because invalid values were passed on the command line. If there is no argument, the program prints today's date. If there is an argument, it is parsed with Convert.ToInt32 and a TimeSpan representing that many days is created. This TimeSpan is then added to today's date and displayed to the user .

Listing 7.3 Using base data types
 using System; public class Today {   const string Usage = "Usage: Today [[+/-]offset]";   static void Main(string[] args)   {   try     {       if (args.Length == 0)       {         Console.WriteLine ("{0:dddd, MMMM dd, yyyy}",                             DateTime.Now);         return;       }       if (args.Length == 1)       {         int offset = Convert.ToInt32(args[0]);         TimeSpan t = new TimeSpan (offset,0,0,0);         Console.WriteLine ("{0:dddd, MMMM dd, yyyy}",                             DateTime.Now+t);         return;       }       Console.WriteLine (Usage);     }     catch (Exception)     {       Console.WriteLine (Usage);     }   } } 

Listing 7.3 produces output like the following:

 C:\Project7\Demos\Today>Today.exe  Tuesday, July 23, 2002 C:\Project7\Demos\Today>Today.exe +6 Monday, July 29, 2002 C:\Project7\Demos\Today>Today.exe -100 Sunday, April 14, 2002 

System.Collections

The System.Collections namespace contains classes and interfaces for in-memory data storage and manipulation. It provides three main types.

The first set of types in the System.Collections namespace consists of concrete implementations of commonly used collection classes such as ArrayList (a dynamic array), Hashtable , Stack , and Queue . Application developers use these classes as a convenient way to store and retrieve in-memory data.

The second set of types in this namespace is a set of interfaces to define a formal contract between developers who are creating new collections and developers who are consuming collections. For example, the IEnumerable interface defines the contract for collections that can offer sequential access via an enumerator. The ASP.NET application model supports the data binding of controls to data sources that honor the IEnumerable contract; thus any new collection that implements this interface can automatically be bound as data in the ASP.NET model.

Table 7.1. .NET Framework Built-in Value Types

Category

Class Name

Description

Visual Basic Data Type

C# Data Type

Managed Extensions for C++ Data Type

Integer

Byte

An 8-bit unsigned integer

Byte

byte

char

 

SByte

An 8-bit signed integer; not CLS compliant

SByte No built-in type

sbyte

signed char

 

Int16

A 16-bit signed integer

Short

short

short

 

Int32

A 32-bit signed integer

Integer

int

int or long

 

Int64

A 64-bit signed integer

Long

long

__int64

 

UInt16

A 16-bit unsigned integer; not CLS compliant

UInt16 No built-in type

ushort

unsigned short

 

UInt32

A 32-bit unsigned integer; not CLS compliant

UInt32 No built-in type

uint

unsigned int or unsigned long

 

UInt64

A 64-bit unsigned integer; not CLS compliant

UInt64 No built-in type

ulong

unsigned __int64

Floating point

Single

A single-precision (32-bit) floating-point number

Single

float

float

 

Double

A double-precision (64-bit) floating-point number

Double

double

double

Logical

Boolean

A Boolean value (true or false)

Boolean

bool

bool

Other

Char

A Unicode (16-bit) character

Char

char

__wchar_t

 

Decimal

A 96-bit decimal value

Decimal

decimal

Decimal

 

IntPtr

A signed integer ”that is, a 32-bit value on a 32-bit platform and a 64-bit value on a 64-bit platform

IntPtr No built-in type

IntPtr No built-in type

IntPtr No built-in type

 

UIntPtr

A native- sized unsigned integer; not CLS compliant

UIntPtr No built-in type

UIntPtr No built-in type

UIntPtr No built-in type

The third set of types in the System.Collections namespace supports the creation of strongly typed collections. The CollectionBase class offers data storage that is convenient for developers who are creating their own strongly typed collections. Library developers, for example, frequently want to do something such as expose an EmployeeCollection that has its Add and Remove methods typed to take an Employee instance. By implementing the EmployeeCollection to derive from CollectionBase , the developer avoids much of the tedious work involved in rewriting the collection from scratch.

Listing 7.4 illustrates how to use some of the basic collections. In this program, three different collection classes are used: ArrayList , Stack , and an array of String s. The method PrintItems can display the contents of all three collections, however, as it accesses each collection via a shared interface, ICollection .

Listing 7.4 Using collections
 using System; using System.Collections; namespace Samples {   public class Collections2   {     static void Main(string[] args)     {       Console.WriteLine("From an ArrayList");       ArrayList l = new ArrayList ();       l.Add ("Damien");       l.Add ("Mark");       l.Add ("Brad");       PrintItems (l);       Console.WriteLine("From a stack");       Stack s = new Stack();       s.Push(4.5);       s.Push(12.3);       s.Push(66.2);       PrintItems (s);       Console.WriteLine("From an array");       PrintItems (new string[] {"monkey","cat","dog"});     }     static void PrintItems (ICollection c)     {       int ct=0;       foreach (object o in c)       {         Console.WriteLine ("\t{1}:{0}", o,ct++);       }     }   } } 

Listing 7.4 would produce the following output: [3]

[3] This example in no way implies that Damien resembles a 66-year-old monkey, Mark a 12-year-old cat, or Brad a 4-year-old dog!

 From an ArrayList          0:Damien         1:Mark         2:Brad From a stack         0:66.2         1:12.3         2:4.5 From an array         0:monkey         1:cat         2:dog 

System.Data

The System.Data namespace is the home for the ADO.NET programming model. Over the last few years , it has become clear that classic ADO does not address key requirements for building scalable server-side applications. System.Data was designed specifically for the Web, with scalability, statelessness, and XML considerations in mind.

If you already work with ADO, many of the concepts underlying ADO.NET will look familiar, such as the Connection and Command objects. ADO.NET also introduces some new objects and types, including DataSet , DataReader , and DataAdapter .

The DataSet class points to an important distinction between ADO.NET and other data architectures, in that it offers a disconnected view of the data as separate and distinct from any data stores. As a consequence, a DataSet functions as a stand-alone entity. You can think of a DataSet as an always disconnected recordset that knows nothing about the source or destination of the data it contains. Inside a DataSet , much like in a database, you can find tables, columns , relationships, constraints, views, and so forth.

The DataSet class offers built-in support for XML. It allows you to read in data as XML and populate a database or, as in Listing 7.5, read data from a database and output it into XML.

Listing 7.5 Using the DataSet class to read data from a database and output XML
 using System; using System.Data; using System.Xml; using System.Data.SqlClient; namespace Samples {   public class DataSetSample   {     static void Main(string[] args)     {       SqlConnection myConn = new SqlConnection("server=...;database=...;uid=...;password=...");       SqlDataAdapter da = new SqlDataAdapter("select * from customers", myConn);       DataSet ds = new DataSet();       da.Fill(ds, "Customers");       ds.WriteXml("Customers.xml");     }   } } 

Listing 7.5 produces the following output:

 mydata.xml contains:  <?xml version="1.0" standalone="yes"?> <NewDataSet>   <Employees>     <CustomerID>ALFKI</CustomerID>     <CompanyName>Alfreds Futterkiste</CompanyName>     <ContactName>Maria Anders</ContactName>     <ContactTitle>Sales Representative</ContactTitle>     <Address>Obere Str. 57</Address>     <City>Berlin</City>     <PostalCode>12209</PostalCode>     <Country>Germany</Country>     <Phone>030-0074321</Phone>     <Fax>030-0076545</Fax>   </Employees>   <Employees>     ...   </Employees> </NewDataSet> 

System.Globalization

The System.Globalization namespace contains classes that define culture- related information, including the language, country/region, calendars in use, sort order for strings, and format patterns for dates, currency, and numbers .

The .NET Framework introduces a new distinction to the globalization world. The concept that was previously referred to as "locale" has been split into two separate types that allow much more flexibility: who you are (culture) and where you are (region). The locale is represented as two different types in the .NET world: CultureInfo and RegionInfo . CultureInfo gives information about the user's culture, such as what language is spoken, how numbers should be formatted, and which calendar is used. The RegionInfo class gives information about where a person physically is, what currency symbol is used, and whether the metric system is employed.

Listing 7.6 demonstrates how to print out the date in different cultures. By default, the date is printed in the current UI culture: Thread.CurrentCulture . However, it is customizable as shown in the example.

Listing 7.6 Printing culture-based dates
 using System; using System.Globalization; namespace Samples {   public class GlobalizationExample   {    static void Main(string[] args)    {      CultureInfo c = new CultureInfo("en-US");      string s = string.Format (c,"{0,-25}: {1:D}",                           c.DisplayName, DateTime.Now);      Console.WriteLine(s);      c = new CultureInfo("fr-CA");      s = string.Format (c,"{0,-25}: {1:D}",                           c.DisplayName,DateTime.Now);       Console.WriteLine(s);       c = new CultureInfo("de-AT");       s = string.Format (c,"{0,-25}: {1:D}",                           c.DisplayName,DateTime.Now);       Console.WriteLine(s);     }   } } 

Listing 7.6 produces output like the following:

 English (United States)  : Tuesday, July 23, 2002  French (Canada)          : 23 juillet, 2002 German (Austria)         : Dienstag, 23. Juli 2002 

System.Resources

Creating resources can help you develop robust, culture-aware programs without having to recompile your application just because the resources have changed. Resources are an application-building feature that allows you to place culture-specific data inside satellite data files (called resource files), rather than directly in the main application. The main assembly does not strongly bind to these satellite data files, which offers you the flexibility of deploying them in different phases. When building your application, you can identify culture-specific aspects such as user-visible strings and graphics, and then put these characteristics in a different resource file for each culture where your application may be used. At runtime, the appropriate set of resources will be loaded based on the user's culture settings. The setting used is the CurrentUICulture for the main thread of execution, which the user can set programmatically.

The ResourceManager class provides the user with the ability to access and control resources stored in the main assembly or in resource satellite assemblies. Use the ResourceManager.GetObject and ResourceManager.GetString methods to retrieve culture-specific objects and strings.

Listing 7.7 creates a resource manager bound to the localizable resource file associated with this assembly. In the body of the program, the code pulls the string with the key "Hello" out of the resource file that is the best match for the current culture.

Listing 7.7 Creating a resource manager and matching resources and cultures
 using System; using System.Reflection; using System.Globalization; using System.Resources; namespace Sample {   class ResourcesClass   {     static ResourceManager rm =         new ResourceManager("strings",                    Assembly.GetExecutingAssembly());     static void Main(string[] args)     {       CultureInfo ci = CultureInfo.CurrentUICulture;       Console.Write ("In '{0}': ", ci.DisplayName);       Console.WriteLine (rm.GetString ("Hello",ci));       ci = new CultureInfo("en-NZ");       Console.Write ("In '{0}': ", ci.DisplayName);       Console.WriteLine (rm.GetString ("Hello",ci));     }   } } strings.txt Hello="Hello" En-NZ\ strings.en-nz.txt Hello="Hello from the land of the long white cloud" Makefile: all: Resources.cs      resgen strings.txt      csc /debug /res:strings.resources Resources.cs      cd en-NZ      resgen strings.En-NZ.txt      al /embed:strings.en-NZ.resources /culture:en-NZ \         /out:resources.resources.dll      cd .. 

Listing 7.7 produces output like the following:

 In 'English (United States)': "Hello"  In 'English (New Zealand)': "Hello from the land of the long white cloud" 

System.IO

The System.IO namespace contains the infrastructure pieces you need to work with input/output (I/O). It includes a model for working with streams of bytes, higher-level readers and writers that consume those bytes, interesting implementations of streams such as FileStream and MemoryStream , and a set of utility classes for working with files and directories.

System.Stream is the base class for all streams in the system. It provides a model for byte-level access to a backing store, which can be a file on disk, memory, network, or another stream. Implementations of the stream class can support reading, writing, and/or seeking. Predicates ( CanSeek , CanRead , CanWrite ) on the stream class advertise this support to consumers. In addition, the System.Stream class provides a model for both synchronous and asynchronous access to the backing store ”functionality important for the loosely coupled , disconnected world of the Internet.

The .NET Framework offers stream implementations for backing stores such as the file system, the network, and memory; it also provides streams for composition such as a buffering stream and an encrypting stream. As is the pattern throughout the framework, the stream classes are designed to provide sensible default behavior so that the developer does not have to implement a large amount of boilerplate code. For example, the FileStream class is buffered by default, eliminating the need to wrap it in a BufferedStream . Although this feature makes for a less purely object-oriented model, it does enhance developer productivity.

The Reader and Writer classes are adapters that consume streams so as to provide higher-level views of the data. For example, the TextReader and TextWriter classes associate a stream of bytes with an encoding to present a textual view of the data. Although streams allow you only to read bytes, a TextReader will consume a stream and allow you to read strings of data from the stream.

Listing 7.8 outputs text to the console from a variety of sources. Because these sources share the same stream model, the code remains fairly simple.

Listing 7.8 Outputting text from a variety of sources that share a stream model
 namespace Samples {   using System;   using System.IO;   public class EntryPoint   {     static void Main(string[] args)     {       Console.WriteLine ("File from Disk");       PrintText (new FileStream ("hello.txt",FileMode.Open));       Console.WriteLine ("From memory");       byte[] ba = new byte []          {0x48,0x65,0x6c,0x6c,0x6f,0x20,          0x57,0x6f,0x72,0x6c,0x64,0x21,00};       PrintText (new MemoryStream(ba));     }     static void PrintText (Stream s)     {       TextReader r = new StreamReader (s);       string str;       int ct = 0;       while ((str = r.ReadLine()) != null)       {         Console.WriteLine ("{0:00}: {1}",ct++,str);       }     }   } } 

Listing 7.8 produces the following output:

 File from Disk  00: Hello from the file... 01: Hope you enjoy the book! From memory 00: Hello World! 

System.Net

The classes in the System.Net namespace support the creation of applications that use Internet protocols to send and receive data. Using a layered approach, the .NET classes enable applications to access the network with varying levels of control, depending on the needs of the application. The spectrum covered by these levels includes nearly every scenario on the Internet today ”from fine-grained control over sockets to a generic request/response model. Furthermore, the model can be extended so that it will continue to work with an application as the Internet evolves.

Listing 7.9 reads some HTML from an Internet site and displays it to the console. First, it creates a WebRequest to the URL being accessed. This activity is merely setup work, as the result does not actually hit the network. When the program is ready to make the request, it does so synchronously (i.e., the program blocks until the entire request returns from the Web). You might find it interesting to use the asynchronous Begin and End methods on WebResponse , instead. Finally, the program receives the raw HTML from the server and prints it to the console. Listing 7.9 uses C#'s using construct to ensure that the request is closed when the program is finished with it.

Listing 7.9 Sending data to and receiving data from the Internet
 using System; using System.Net; using System.IO; using System.Text; namespace Samples {   class NetSample   {     static void Main(string[] args)     {       WebRequest wReq = WebRequest.Create("http://www.GotDotNet.com");       WebResponse wResp = wReq.GetResponse();       using (StreamReader sr = new StreamReader(wResp.GetResponseStream(), Encoding.ASCII))       {         string line = null;         while ((line = sr.ReadLine()) != null)         {           Console.WriteLine(line);         }       }       Console.ReadLine();     }   } } 

Listing 7.9 produces the following output:

 <TITLE>Welcome to the GotDotNet Home Page!</TITLE>  <HTML>   <HEAD>     <! Nav control will supply title below >     <TITLE>     </TITLE>     <link rel="stylesheet" type="text/css"       href="/includes/gotdotnet_stylenew.css">   </HEAD>   <BODY background="/images/expando.gif"       TOPMARGIN="0" LEFTMARGIN="0"       MARGINHEIGHT="0" MARGINWIDTH="0">     ... </BODY> </HTML> 

System.Reflection

As discussed in Chapter 3, metadata is a core concept for the .NET Framework's functionality. The System.Reflection namespace provides ways to read that information and emit it in a logical programming model. The reading portion of reflection APIs is used primarily in two scenarios: browsing and late-bound invocation.

Browsing Support

The browsing support is rooted on the System.Type class. The Type class represents the metadata for a class: the class's name, its visibility, the members that it defines, and so forth. System.Type instances are obtained from three places:

  • The nonvirtual GetType method

  • The typeof() operator

  • Fully qualified string names

You can call the nonvirtual GetType method on any object type. For every object, this method returns its type. For example:

 string s = "hello";  Type t = s.GetType (); Console.WriteLine (t.FullName); 

The output from the program would look like this:

 System.String 

The typeof() operator is found in many languages. Given a valid reference to a type, this method returns the Type instance. For example:

 Type t = typeof(string[]);  Console.WriteLine(t.FullName); 

The output from the program would look like this:

 System.String[] 

Finally, returning to the roots of the late-bound nature of reflection, you can access the type object from a fully qualified string name of the type. Commonly, the Type.GetType() static method is used to provide this information. For example:

 Type t = Type.GetType ("System.Int32, mscorlib");  Console.WriteLine (t.FullName); 

The output from the program would look like this:

 System.Int32 

Notice that the arguments include both the full name (" System.Int32 ") and the assembly where that type is defined (" mscorlib "). Both pieces of information are required because namespaces are not unique in the .NET world; uniqueness is relative to the assembly boundary.

Once you have the type, you can find out other information, such as all the members of the class. As an example, Listing 7.10 prints out all the members of a type.

Listing 7.10 Printing the members of a type
 using System; using System.Reflection; namespace Samples {   class ReflectionSample   {     public static void PrintType (Type t)     {       Console.WriteLine (t);       foreach (MemberInfo m in t.GetMembers())       {         if (m is ConstructorInfo)         {           Console.WriteLine ("Constructor: {0}", m);         }         else if (m is MethodInfo)         {           Console.WriteLine ("Method: {0}", m);         }         else if (m is PropertyInfo)         {           Console.WriteLine ("Property: {0}", m);         }         else if (m is EventInfo)         {           Console.WriteLine ("Event: {0}", m);         }       }     }     static void Main(string[] args)     {       PrintType(typeof(string));       PrintType(typeof(object));     }   } } 

Listing 7.10 produces the following output:

 System.String  Method: System.String ToString(System.IFormatProvider) Method: System.TypeCode GetTypeCode() Method: System.Object Clone() Method: Int32 CompareTo(System.Object) Method: Int32 GetHashCode() Method: Boolean Equals(System.Object) Method: System.String ToString() Method: System.String Join(System.String, System.String[]) Method: System.String Join(System.String, System.String[], Int32, Int32) Method: Boolean Equals(System.String) Method: Boolean Equals(System.String, System.String) Method: Boolean op_Equality(System.String, System.String) Method: Boolean op_Inequality(System.String, System.String) Method: Char get_Chars(Int32) ... Method: System.String Intern(System.String) Method: System.String IsInterned(System.String) Method: System.CharEnumerator GetEnumerator() Method: System.Type GetType() Constructor: Void .ctor(Char*) Constructor: Void .ctor(Char*, Int32, Int32) Constructor: Void .ctor(SByte*) Constructor: Void .ctor(SByte*, Int32, Int32) Constructor: Void .ctor(SByte*, Int32, Int32, System.Text.Encoding) Constructor: Void .ctor(Char[], Int32, Int32) Constructor: Void .ctor(Char[]) Constructor: Void .ctor(Char, Int32) Property: Char Chars [Int32] Property: Int32 Length System.Object Method: Int32 GetHashCode() Method: Boolean Equals(System.Object) Method: System.String ToString() Method: Boolean Equals(System.Object, System.Object) Method: Boolean ReferenceEquals(System.Object, System.Object) Method: System.Type GetType() Constructor: Void .ctor() 
Invocation Support

The invocation support available through the reflection feature allows for late-bound invocation of code. It is an important consideration in script scenarios or other instances where you know only at runtime which methods to call. There are two main entry points for dynamic invocation:

  • Type.InvokeMember()

  • The Invoke method applied off the various subclasses of MemberInfo

You use the Invoke member when you have little context for the type of member being called. As an example, consider the following Visual Basic code: [4]

[4] Strict type checking must be turned off for this code to work in VB.NET. Put the command Option Strict Off at the top of your program to test for this condition.

 Dim x as Object  x = new Foo() Console.WriteLine (x.Bar) 

Because x is statically typed to be an Object , the compiler can't look up exactly what Bar is and emit the specific call at compile time. Instead, it must emit a late-bound call. Ah, but to what? To the member Bar on the instance x . From the syntax, it appears that Bar may be a field, property, or zero-argument method. Ultimately, the Type.InvokeMember function is called for this kind of construct.

To handle this problem, Listing 7.11 first creates an instance of the target class. It then invokes the Bar member via Type 's InvokeMember method. It specifies to search for members of that name that are public and either static or instance. Based on the syntax (it is on the right-hand side), the instance must be a property getter, a field get, or a method call, so the code lists those choices. It turns out to be a property getter. Because that fact isn't known at the call site, however, the code must pass all the alternatives. No custom binding is specified and no parameters are passed, so those values can be null. As a result, the Bar property on Foo is called and returns "Hello"; the code then displays that result to the console.

Listing 7.11 Using late-bound invocation
 using System; using System.Reflection; namespace Samples {   public class Foo   {     public string Bar     {       get       {         return "Hello";       }     }   }   class ReflectionSample   {     static void Main(string[] args)     {       object t = new Foo();       object res = typeof(Foo).InvokeMember("Bar",                         BindingFlags.Public                          BindingFlags.Instance                          BindingFlags.Static                          BindingFlags.GetProperty                          BindingFlags.GetField                          BindingFlags.InvokeMethod,                         null, t, null);       Console.WriteLine (res);     }   } } 

Listing 7.11 produces the following output:

 Hello 

Invocation off the various subclasses of MemberInfo works in a very similar way, except that it is much more efficient because the member resolution (which member to call) has already taken place. Listing 7.12 demonstrates this technique.

Listing 7.12 Using invocation off subclasses
 using System; using System.Reflection; namespace Samples {   public class Foo   {     public string Bar     {       get       {         return "Hello";       }     }     public string GetName (int empId)     {       switch (empId)       {         case 0: return "Judy";         case 1: return "Frank";         case 2: return "Tamara";         case 3: return "Lisa";         case 4: return "Cam";         case 5: return "Emma";         default: return "unknown";       }     }     class ReflectionSample     {       static void Main(string[] args)       {         object t = new Foo();         MethodInfo m = typeof (Foo).GetMethod ("GetName");         object res = m.Invoke (t, new object[] {1});         Console.WriteLine (res);       }     }   } } 

Listing 7.12 produces the following output:

 Frank 
Reflection Emit

Reflection Emit is used to dynamically emit new types at runtime. It is primarily targeted at compiler authors and higher-end tools that need to emit executable code on the fly. A classic example of a tool that uses Reflection Emit is the .NET Framework regular expression engine. It has a mode where the regular expression passed by the user is compiled into an assembly, allowing you to achieve the speed of JIT-compiled code at execution time. Of course, you must pay a start-up penalty to "compile" the regular expression, but that cost is amortized away for a sufficiently large number of uses of the resulting regular expression.

In the example in Listing 7.13, the goal is to create an assembly, execute a method on it while the assembly remains in memory, and then write it out to disk.

Listing 7.13 Using Reflection Emit
 using System; using System.Reflection; using System.Reflection.Emit; using System.Threading; namespace Samples {   public class DynamicType   {     public static int Main(string[] args)     {       string g = "out";       AssemblyName asmname = new AssemblyName();       asmname.Name = g;       AssemblyBuilder asmbuild =         Thread.GetDomain().DefineDynamicAssembly(asmname,                         AssemblyBuilderAccess.RunAndSave);       ModuleBuilder mod = asmbuild.DefineDynamicModule("Mod1", asmname.Name + ".exe");       TypeBuilder tb = mod.DefineType("Class1");       MethodBuilder mb1 = tb.DefineMethod("Main",                         MethodAttributes.Public                          MethodAttributes.Static,               null,new Type[]{typeof(System.String[])});       ILGenerator ilg1 = mb1.GetILGenerator();       ilg1.Emit(OpCodes.Ldstr, "Hello World");       ilg1.Emit(OpCodes.Call,              typeof(System.Console).GetMethod("WriteLine",              new Type[]{typeof(String)}));       ilg1.Emit(OpCodes.Ret);       Type t = tb.CreateType();       //Invoke the Main method just for fun       t.GetMethod("Main").Invoke(null,new object[] {new string[]{}});       //Now emit it to disk       asmbuild.SetEntryPoint(mb1);       asmbuild.Save(asmname.Name + ".exe");       Console.WriteLine ("assembly written: {0}",                  asmname.Name + ".exe");       return 0;     }   } } 

First, the program sets up the name of the assembly; then, it creates the assembly within the context of the current application domain. It specifies that this assembly should run from memory and be saved. If you didn't want to save the assembly, Reflection Emit would optimize it differently for in memory-only access.

Next , the code defines a module within the new assembly. Many .NET languages perform this task transparently for developers; at the lowest levels of the runtime environment, you as the developer have to handle this bit of housekeeping yourself.

The next step is to define a type within the new assembly, as with the name "Class1". A main method is then created: It is public, is static, and takes an array of strings.

Now you are ready to start writing IL. To get an idea of what IL instructions do, you can use the ILDASM tool to look at code written with a higher-level language. You also may need to reference the IL instruction set specification in the SDK documentation. The instructions in Listing 7.13 are simple: Load a const string on the stack, and then invoke the WriteLine() method off the console. You must be exact here and specify the WriteLine overload that takes a string.

After the instructions are executed, the code creates the type, which "bakes" the type at runtime. This Type object is now as good as any other type, whether built-in or user-defined. You can do anything with it that you can do with any type, including late-bound invocation and browsing. Just to prove the point, the code in Listing 7.13 invokes the main method and writes the assembly to an executable file on disk. Before writing the assembly out, it sets the entry point explicitly to be the main method.

Listing 7.13 produces the following output:

 Hello  assembly written: out.exe 

Figure 7.2 shows the result of running the ILDASM tool over the assembly created in Listing 7.13. Figure 7.3 shows the IL code emitted for the method created in Listing 7.13 when viewed with ILDASM. Notice the correlation between the three "emit" statements in Listing 7.13 and the IL code displayed.

Figure 7.2. ILDASM tool running over the user-defined assembly

graphics/07fig02.gif

Figure 7.3. IL code produced by Listing 7.13

graphics/07fig03.gif

System.Security

The System.Security namespace contains the infrastructure for working with a permissions-based security system ”specifically, code access security. A permissions-based system protects resources (such as the user's file system or network) by checking whether the caller is granted a particular permission (or set of permissions) represented by classes that implement IPermission . A user could have the permission because he or she has taken on a particular role (role-based security), such as "user" or "administrator." Alternatively, in the case of code access security, the user might be granted the permission because of evidence from the actual code that is executing on the stack. This evidence includes where the code is running from (a local machine is more highly trusted than one on the Internet) and who published the code (an administrator could choose to trust code from Microsoft more highly than that from FlyByNight.com). This system can be extended by the user. For example, when you create libraries that access some resource, you can create your own permission that can be administratively granted to code based on evidence.

Listing 7.14 shows part of the code from the implementation of the FileStream class. First, it gets the fully qualified path name to canonicalize the path . Next, it creates a new FileIOPermission with the appropriate flags. Then, it calls the Demand method. If the demand operation fails, the code throws a SecurityException ; otherwise , execution continues and Win32 is called to read the file (not shown).

Listing 7.14 Using code access security
 public FileStream(string fileName) {   string fullPath = Directory.GetFullPathInternal(fileName);   new FileIOPermission(FileIOPermissionAccess.Read,                        fullPath).Demand();  //[... read the specified file at behest of caller(s) ...]  } 

System.Text

The System.Text namespace contains classes representing ASCII, Unicode, UTF-7, and UTF-8 character encodings; abstract base classes for converting blocks of characters to and from blocks of bytes; and a helper class that manipulates and formats String objects without creating intermediate instances of String .

Listing 7.15 demonstrates the conversion of a Unicode string into a byte array in a given code page encoding. This kind of transformation is useful for dealing with legacy data formats that predate the Unicode standard now used through the .NET Framework. By working with the encoding abstraction, you can write a single routine that works for a wide range of encodings.

Listing 7.15 Converting blocks of characters to and from blocks of bytes
 using System; using System.IO; using System.Globalization; using System.Text; namespace Samples {   public class Encoding_UnicodeToCodePage   {     public static void Main()     {       // Simple ASCII characters       PrintCodePageBytes("Hello, World!",1252);       PrintCodePageBytes("Hello, World!",932);       // Japanese characters       PrintCodePageBytes("\ub,\u308b,\u305a,\u3042,\ud",1252);       PrintCodePageBytes("\ub,\u308b,\u305a,\u3042,\ud",932);       // GB18030       PrintCodePageBytes("\u00A4",54936);     }     public static void PrintCodePageBytes(string str,                                           int codePage)     {       Encoding targetEncoding;       byte[] encodedChars;       // Get the requested encoding       targetEncoding = Encoding.GetEncoding(codePage);       // Get the byte representation       encodedChars = targetEncoding.GetBytes(str);       // Print the bytes       Console.WriteLine ("Byte representation of '{0}' in CodePage '{1}':",           str,codePage);       for (int i = 0; i < encodedChars.Length; i++)         Console.WriteLine("Byte {0}: {1}",                           i,encodedChars[i]);     }   } } 

Listing 7.15 produces the following output:

 Byte representation of 'Hello, World!' in CodePage '1252':  Byte 0: 72 Byte 1: 101 ... Byte 12: 33 Byte representation of 'Hello, World!' in CodePage '932': Byte 0: 72 ... Byte 12: 33 Byte representation of '?,?,?,?,?' in CodePage '1252': Byte 0: 63 ... Byte 8: 63 Byte representation of '?,?,?,?,?' in CodePage '932': Byte 0: 130 ... Byte 13: 203 Byte representation of '?' in CodePage '54936': Byte 0: 161 Byte 1: 232 

Within the System.Text namespace, the System.Text.RegularExpressions namespace contains classes that provide access to the .NET Framework regular expression engine. Regular expressions allow for easy parsing and matching of strings to a specific pattern. Using the objects available in the RegularExpressions namespace, you can compare a string with a given pattern, replace a string pattern with another string, or retrieve only portions of a formatted string.

One of the most impressive things about this support is that you can compile the regular expressions into in-memory IL using the Reflection Emit functionality described earlier. This facility allows regular expressions to be executed at the same speed as native code, although it does take some overhead to set them up. For this reason, you should use this option only when working with regular expressions that are frequently used and rarely change.

Consider the simplest case, in which regular expressions are used to compare strings with a particular pattern. You might allow only strings in a particular length range, especially when accepting passwords. Listing 7.16 demonstrates the process of creating a regular expression ( Regex ) and testing whether a string matches the specified pattern with the IsMatch method.

Listing 7.16 Using regular expressions to search for string patterns
 using System; using System.Text.RegularExpressions; namespace Samples {   class RegExExample   {     static void Main(string[] args)     {       Regex emailregex =                  new Regex("(?<user>[^@]+)@(?<host>.+)");       bool ismatch = emailregex.IsMatch(args[0]);       string not = "";       if (!ismatch) not="not ";       Console.WriteLine("'{0}' is {1}in e-mail address format",                 args[0],not);     }   } } 

Listing 7.16 produces the following output:

 C:\> isEmail darb@smarba.com  'darb@flybynight.com' is in e-mail address format C:\> isEmail Eat.At.Eds 'Eat.At.Eds' is not in e-mail address format 

Regular expressions are often useful when you are trying to retrieve small portions of text from a large document or result set, or when you are filtering a stream. The MatchCollection object contains all valid Match objects for a given regular expression after a successful match occurs. Listing 7.17 demonstrates this use of regular expressions.

Listing 7.17 Using regular expressions to retrieve strings
 using System; using System.Text.RegularExpressions; namespace Samples {   class RegExExample   {     static void Main(string[] args)     {       Regex digitregex = new Regex("(?<number>\d+)");       string s = "the8bl5ind1mou5se8ra8nin3side";       MatchCollection ms = digitregex.Matches(s);       foreach (Match m in ms)       {         Console.WriteLine ("'{0}' matches", m.Value);       }     }   } } 

Listing 7.17 produces the following output:

 '8' matches  '5' matches '1' matches '5' matches '8' matches '8' matches '3' matches 

Functions from the regular expressions library can often minimize the time needed to generate your own string replacement functions. By specifying a pattern of strings to be replaced , you do not have to search for every possible variation of a string. Once you have created a Regex object that matches every possible string to be replaced, you can use the Replace method to generate a result. For example, you can pass in the source string and the replacement string, and the Replace method will return the results as a String (see Listing 7.18).

Listing 7.18 Using regular expressions to replace strings
 using System; using System.Text.RegularExpressions; namespace Samples {   class RegExExample   {     static void Main(string[] args)     {       Regex digitregex = new Regex("(?<digit>[0-9])");       string before =       "Here is so4848me te88xt with emb4493edded numbers.";       string after = digitregex.Replace(before, "");       Console.WriteLine (after);     }   } } 

Listing 7.18 produces the following output:

 Here is some text with embedded numbers. 

System.Threading

The .NET Framework provides a rich set of functionality to support multithreaded programming. Functionality in the System.Threading namespace allows you to write programs that perform more than one task at the same time. The Thread class represents a task, an isolated flow of execution in a program. By default, when you program starts, it receives a single thread of execution. To create an additional thread, you create a new Thread object and pass it a pointer to a method in your class via the ThreadStartDelegate . Listing 7.19 creates a new thread on the Run method.

Listing 7.19 Creating a new thread
 using System; using System.Threading; namespace Samples {   class ThreadSample   {     static void Run()     {       Console.WriteLine("This is the secondary thread running.");     }     static void Main()     {       Console.WriteLine("This is the primary thread running.");       Thread t = new Thread(new ThreadStart(Run));       t.Start();       t.Join();       Console.WriteLine("The secondary thread has terminated.");     }   } } 

Listing 7.19 produces the following output:

 This is the primary thread running.  This is the secondary thread running. The secondary thread has terminated. 

Be careful when dealing with multithreaded code, as each thread has access to the same state and the various threads' reads and writes to it can be interleaved.

System.Runtime.InteropServices

The InteropServices namespace contains functionality to allow .NET objects to access unmanaged code and unmanaged code to call into .NET objects. The two primary features in this namespace are Platform Invocation Services (PInvoke), which allows managed code to call unmanaged functions that are implemented in a DLL, and COM Interoperability (COM Interop), which allows COM objects to be accessed from .NET objects, and vice versa. COM+ services, such as transactions and object pooling, are exposed directly in the .NET Framework via the System.EnterpriseServices namespace, which is outside the scope of this book.

PInvoke

PInvoke takes care of finding and invoking the correct function, as well as marshaling its managed arguments to and from their unmanaged counterparts (such as integers, strings, arrays, and structures). PInvoke is intended primarily to allow managed code to call existing unmanaged code, typically written in C. A good example is the several thousand functions that constitute the Win32 API.

Listing 7.20 demonstrates calling the Win32 API CreateFontIndirect from managed code. In the Win32API class, a PInvoke stub is added that points to the CreateFontIndirect entry point in gdi32.dll . The code then specifies how the string should be marshaled at runtime as well as how the lplf parameter should be marshaled. Next, a LOGFONT class is created on the managed side that exactly mirrors the unmanaged version. The runtime environment lays it out sequentially and a specific size is given for the lfFaceName string. After this setup work is complete, you can call the CreateFontIndirect method just like any other method.

Listing 7.20 Calling a Win32 API from managed code
 using System; using System.Runtime.InteropServices; class Win32API {     [DllImport("gdi32.dll", CharSet=CharSet.Auto)]     public static extern int CreateFontIndirect([In, MarshalAs(UnmanagedType.LPStruct)]         LOGFONT lplf   // characteristics);     public static void Main()     {         LOGFONT lf = new LOGFONT();         lf.lfHeight = 9;         lf.lfFaceName = "Arial";         int i = CreateFontIndirect(lf);         Console.WriteLine("{0:X}", i);     } } [StructLayout(LayoutKind.Sequential)] public class LOGFONT {     public const int LF_FACESIZE = 32;     public int lfHeight;     public int lfWidth;     public int lfEscapement;     public int lfOrientation;     public int lfWeight;     public byte lfItalic;     public byte lfUnderline;     public byte lfStrikeOut;     public byte lfCharSet;     public byte lfOutPrecision;     public byte lfClipPrecision;     public byte lfQuality;     public byte lfPitchAndFamily;     [MarshalAs(UnmanagedType.ByValTStr,        SizeConst=LF_FACESIZE)]     public string lfFaceName; } 

Listing 7.20 produces the following output:

 C0A0996 
COM Interop

In many ways, interoperating with COM is more seamless than interoperating with PInvoke. COM already has many of the basic constructs, such as objects, classes, methods, and properties. The CLR offers bidirectional COM interoperability.

You can readily use COM objects from managed code. The Type Library Importer ( Tlbimp.exe ) is a command-line tool that converts the coclass es and interface s contained in a COM type library to metadata. It creates an assembly and namespace for the type information automatically. After the metadata of a class becomes available, managed clients can create instances of the COM type and call its methods, just as if it were a .NET instance. The Type Library Importer converts an entire type library to metadata at once, but it cannot generate type information for a subset of the types defined in a type library.

To generate an assembly from a type library, use the following command to produce the dx7vb.dll assembly in the DxVBLib namespace:

 c:\>tlbimp dx7vb.dll 

Adding the /out: switch, as shown below, produces an assembly with an altered name ”in this case, DirectXWrapper . Altering the COM Interop assembly name can help distinguish it from the original COM DLL.

 c:\>tlbimp dxvb7.dll /out: DirectXWrapper.dll 

Similarly, you can expose any .NET object to COM. Of course, only those constructs that COM already recognizes can be exposed; thus static methods and parameterized constructors are not exposed, for example. For this reason and because of some version issues, the .NET library designer must decide at development time whether user-defined objects should be exposed to COM. If so, the designer can apply the ComVisible() attribute to these types. Types without this attribute are not exposed to COM. The Type Library Exporter ( Tlbexp.exe ) is a command-line tool that converts the classes and interfaces contained in an assembly to a COM type library. Once the type information of the class becomes available, COM clients can create an instance of the .NET class and call its methods, just as if it were a COM object. Tlbexp.exe converts an entire assembly at one time.

The following command generates a type library with the same name as the assembly found in myTest.dll :

 tlbexp myTest.dll 

The following command generates a type library with the name clipper.tlb :

 tlbexp myTest.dll /out:clipper.tlb 

System.Windows.Forms

Windows Forms is a framework for building "smart" client applications. Applications within this framework can be written in any language that supports the CLR. Windows Forms offers a programming model for developing Windows applications that combines the simplicity of the Visual Basic 6 programming model with the full power and flexibility of the CLR and the .NET Framework.

To illustrate some basic concepts of Windows Forms, let's look at a very simple application that keeps track of "to do" items. It leverages the extensive set of common controls that Windows Forms offers to perform the heavy lifting and the richness of the framework to handle some of the details. The application allows you to add items to your list and check them off when they are done (see Figure 7.4). It uses System.Xml to preserve the state of the application data when you start up and shut down the application, so no items ever fall off your list. Listing 7.21 provides the code for this example.

Figure 7.4. Simple application using Windows Forms

graphics/07fig04.gif

Listing 7.21 Using Windows Forms
 using System; using System.Xml; using System.IO; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace WinFormsExample {   public class Form1 : System.Windows.Forms.Form   {     private System.Windows.Forms.Button button1;     private System.Windows.Forms.CheckedListBox                 checkedListBox1;     private System.Windows.Forms.TextBox textBox1;     private System.ComponentModel.Container components =                                                     null;     public Form1()     {       InitializeComponent();     }     protected override void Dispose(bool disposing)     {       using (StreamWriter s = new StreamWriter("todo.xml"))       {         XmlTextWriter t = new XmlTextWriter (s);         t.WriteStartElement("items");         for (int i = 0; i < checkedListBox1.Items.Count;                                                      i++)         {           t.WriteStartElement ("Item");           t.WriteAttributeString ("Name",           checkedListBox1.Items[i].ToString());           t.WriteAttributeString("State",                checkedListBox1.GetItemCheckState(i).                   ToString());           t.WriteEndElement();         }         t.WriteEndElement();       }       if(disposing)       {         if (components != null)         {           components.Dispose();         }       }       base.Dispose(disposing);     }     #region Windows Form Designer generated code     private void InitializeComponent()     {       this.button1 = new System.Windows.Forms.Button();       this.checkedListBox1 =           new System.Windows.Forms.CheckedListBox();       this.textBox1 = new System.Windows.Forms.TextBox();       this.SuspendLayout();       // button1       this.button1.Location =           new System.Drawing.Point(176, 24);       this.button1.Name = "button1";       this.button1.TabIndex = 0;       this.button1.Text = "&Add";       this.button1.Click +=           new System.EventHandler(this.button1_Click);       // checkedListBox1       this.checkedListBox1.Location =           new System.Drawing.Point(24, 56);       this.checkedListBox1.Name = "checkedListBox1";       this.checkedListBox1.Size =           new System.Drawing.Size(224, 154);       this.checkedListBox1.TabIndex = 1;       // textBox1       this.textBox1.Location =           new System.Drawing.Point(24, 24);       this.textBox1.Name = "textBox1";       this.textBox1.Size =           new System.Drawing.Size(144, 20);       this.textBox1.TabIndex = 2;       this.textBox1.Text = "";       // Form1       this.AutoScaleBaseSize =           new System.Drawing.Size(5, 13);       this.ClientSize = new System.Drawing.Size(292, 273);       this.Controls.AddRange(new System.Windows.Forms.Control[] {               this.textBox1,               this.checkedListBox1, this.button1});       this.Name = "Form1";       this.Text = "ToDo List";       this.Load +=           new System.EventHandler(this.Form1_Load);       this.ResumeLayout(false);     }     #endregion     [STAThread]     static void Main()     {       Application.Run(new Form1());     }     private void Form1_Load(object sender,                             System.EventArgs e)     {       if (File.Exists ("todo.xml"))       {         using (StreamReader s =                           new StreamReader("todo.xml"))         {           XmlTextReader t = new XmlTextReader (s);           int ct = 0;           while(t.Read())           {              string n = t.GetAttribute ("Name");              if (n != null)              {                this.checkedListBox1.Items.Add(n);                CheckState v = (CheckState)Enum.Parse(typeof(CheckState),                               t.GetAttribute("State"));                this.checkedListBox1.SetItemCheckState (ct,v);                ct++;              }           }         }       }     }     private void button1_Click(object sender,                                System.EventArgs e)     {       this.checkedListBox1.Items.Add(this.textBox1.Text);       textBox1.Text = String.Empty;     }   } } 

To load the form, the program calls the Form_Load method. This method reads the data file from disk and populates the checked list box.

The program calls the Dispose method when the application is shut down; it gives you a chance to do any necessary cleanup before the application exits. The disposing parameter is true if this method was explicitly called or false if it was called as part of the garbage collection operation that cleans up the object before it is collected. In either case, the current state is saved.

System.Web

The System.Web namespace encapsulates the functionality of ASP.NET, a server-side programming model whereby the UI is generated on the server and pushed down to clients. The UI is commonly written in HTML, but it can also follow an XML, WAP, or other format. This model enables lower-level clients to access your application with just an HTML 3.2 browser.

The System.Web namespace consists of two main parts :

  • Web Application services: Infrastructure dealing with security, configuration, or caching

  • Web Forms: A user interface model that uses the same drag-and-drop application development paradigm that Windows Forms and Visual Basic 6 use, but creates server-side applications that push HTML (or other content) to the client

In keeping with the history of ASP, you build ASP.NET applications by creating pages that contain HTML markup and embedded script that executes on the server.

Figure 7.5 depicts a very trivial ASP.NET page. When this page is requested for the first time, ASP.NET parses it (see Listing 7.22). The script code is written to a file and compiled into a managed assembly. As ASP.NET is building the response (in this case, in HTML), it calls out to the library when it needs to perform some action. In this example, the action is to process a button click. When the processing on the server is complete, the server sends an HTML response back to the client. Notice that the compilation and loading of the code needs to happen only on the first hit.

Figure 7.5. An ASP.NET page

graphics/07fig05.gif

Listing 7.22 Using ASP.NET
 <%@ Page language="c#" %> <HTML>   <HEAD>     <title>WebForm1</title>   </HEAD>   <script language="C#" runat=server>     void Button1_Click(Object sender, EventArgs e)     {       this.Label1.Text = String.Format("<h2> {0} Loves .NET </h2>",           this.TextBox1.Text);       }   </script>   <body>     <form id="Form1" method="post" runat="server">       <H2>Hello!&nbsp; Please enter your name.</H2>       <P>         <asp:TextBox id="TextBox1" runat="server">         </asp:TextBox>         <asp:Button id="Button1" OnClick="Button1_Click"                     runat="server" Text="Go">         </asp:Button>       </P>       <P>         <asp:Label id="Label1" runat="server">         </asp:Label>       </P>     </form>   </body>   </HTML> 

To run the program in Listing 7.22, copy the code to a file called WebForm1.aspx and put it in a directory that has been mapped to a VRoot (for example, something under Inetpub\ wwwroot \ ).

System.Web.Services

Web Forms is a great way to build Web applications with which a user interacts , but it does not address the problem of communicating programmatically via the Web. On many occasions, developers want to build applications that pull data from and post data to a Web site. Today, the only way to do so is to "screen scrap" or to use proprietary protocols. With screen scrapping, a developer makes an HTTP request and gets back HTML. Instead of displaying this HTML to the user, the application sniffs out and uses only the desired parts. Unfortunately, this approach is extremely error prone: Make one small change to the site, and the clients are broken. Also, content owners often don't appreciate this repackaging of their data. The alternative is not particularly attractive, either: Custom protocols are not easily usable by different vendors and are often blocked by firewalls.

What is needed is an open standard for programmatically communicating across the Internet. Enter SOAP. SOAP offers a way to package information in the well-known XML format and send it via HTTP across the Internet. It is supported across a wide range of computing environments by many vendors, including IBM and Microsoft.

The System.Web.Services namespace allows you to build SOAP-based Web services and conveniently access them. This functionality turns XML requests into .NET classes and instances, giving developers a well- understood way to interact with them. It can also turn .NET instances into standard XML that can be read by consumers on any platform.

Creation of a Web Service

To define a Web service, you write a .NET class and decorate it with the System.Web.Services.WebMethodAttribute . This attribute tells the .NET Framework to expose this method on the network as a Web service. Listing 7.23 gives an example of this process. To run this program, place the code in a file called Grades.asmx in a Vroot (typically off Intnet\wwwroot\ ).

Listing 7.23 Defining a Web service
 <%@ WebService Language="csharp" Class="Grades" %> using System; using System.Collections; using System.Web.Services; public class Grades {   private Hashtable table;   public Grades()   {     //Really should pull this out of a database     table = new Hashtable();     table.Add ("Steve",new int [] {100,12,99,98});     table.Add ("Ken",new int [] {98,79,52,88});     table.Add ("Dawn",new int [] {87,86,95,98});     table.Add ("Dusty",new int [] {100,100,99,98});     table.Add ("Jill",new int [] {53,100,100,100});     table.Add ("Ed",new int [] {97,83,71,77});     table.Add ("Jeff",new int [] {99,88,70,100});   }   [WebMethod]   public double GetAverage (string studentName)   {     ICollection grades = (ICollection)table[studentName];     if (grades == null) return 0.0;     int sum = 0;     foreach (int i in grades)     {       sum+=i;     }     return sum/grades.Count;   } } 

The first line tells the ASP.NET infrastructure information such as the language in which the Web service is written and the class to expose. Each time a request comes in, the program creates the grades class and runs its constructor. The code then marshals the arguments passed in from XML into .NET types and invokes the method. Next, it marshals the return value from a .NET type into XML format. To test a Web service, simply navigate to the asmx page in your browser, where you will see a form that allows you to test each of these services.

Use of a Web Service

After creating a Web service with .NET, you may wonder how to use one. Listing 7.24 gives a very simple program to consume the GetAverage Web service defined in Listing 7.23. The command-line program takes the name of the student as an argument and returns his or her average grade. Notice how the .NET Framework automatically handles all the complexity involved in marshalling the arguments to XML format and back again.

Listing 7.24 Consuming a Web service
 using System; public class Client {   public static void Main (string [] args)   {     Grades g = new Grades();     double d = g.GetAverage (args[0]);     Console.WriteLine (d);   } } 

This code creates an instance of the proxy class for the Grades Web service, then invokes the GetAverage Web service by passing in the name given at the command line. This process is where a Web request and response occur, so in some cases it might be better to use the asynchronous methods also available on the proxy class. The code then prints out the results.

The Grades class is automatically created by the WSDL tool that ships with the .NET Framework SDK. This tool takes an XML scheme that describes a service (WSDL) and creates a client proxy for the service. To create the Grades class, you simply run the WSDL tool and compile the code:

 all:     client  client:  GetAverage.cs   wsdl http://localhost/Demos/WebService/Grades.asmx   csc GetAverage.cs Grades.cs 

Of course, the Visual Studio.NET tool makes it very simple to produce and consume Web services. The command-line tools are shown here simply for explanatory purposes.

System.Xml

If you have not heard of XML yet, you must be living under a rock! This meta-markup language provides a format for describing structured data, thereby supporting the creation of a new generation of Web-based data viewing and manipulation applications. XML, which is the universal language for data on the Web, gives developers the power to deliver structured data from a wide variety of applications to the desktop for local computation and presentation.

Although XML support is spread across the entire .NET Framework, the System.Xml namespace provides key XML processing functionality. This namespace contains a comprehensive set of XML classes for parsing, validation, and manipulation of XML data using readers, writers, and World Wide Web Consortium (W3C) DOM-compliant components. It also covers XML Path Language (XPath) queries and eXtensible Stylesheet Language Transformations (XSLT).

The main classes in the XML namespace are as follows :

  • The XmlTextReader class provides fast, noncached, forward-only, read access to XML data.

  • The XmlNodeReader class provides an XmlReader over the given DOM node subtree .

  • The XmlValidatingReader class provides DTD, XDR, and XSD schema validation.

  • The XmlTextWriter class provides a fast, forward-only way of generating XML.

  • The XmlDocument class implements the W3C DOM Level 1 Core and the Core DOM Level 2.

  • The XmlDataDocument class provides an implementation of an XmlDocument that can be associated with a DataSet . You can view and manipulate structured XML simultaneously through the DataSet 's relational representation or the XmlDataDocument 's tree representation.

  • The XPathDocument class provides a very high-performance, read-only cache for XML document processing for XSLT.

  • The XPathNavigator class provides a W3C XPath 1.0 data model over a store with a cursor-style model for navigation.

  • The XslTransform class is a W3C XSLT 1.0-compliant XSLT processor for transforming XML documents.

  • The XmlSchema object model classes provide a navigable set of classes that directly reflect the W3C XSD specification. They provide the ability to programmatically create an XSD schema.

  • The XmlSchemaCollection class provides a library of XDR and XSD schemas. These schemas, which are cached in memory, provide fast, parse-time validation for the XmlValidatingReader .

Listing 7.25 demonstrates the functionality in the System.XML namespace by opening an XML document containing information about a number of book titles and then processing the document to produce text output describing those titles.

Listing 7.25 Processing an XML document using System.XML -based functionality
 using System; using System.Xml; using System.Xml.Serialization; using System.IO; class EntryPoint {   static void Main(string[] args)   {     XmlTextReader r = new XmlTextReader ("books.xml");     {       while (r.Read())       {         switch (r.NodeType)         {           case XmlNodeType.Text:             Console.WriteLine (r.Value);             break;           case XmlNodeType.Element:             if (r.Name == "book")             {               Console.WriteLine ("-");               string genre = r["genre"];               string pub = r["publicationdate"];               string isbn = r["ISBN"];               Console.WriteLine ("{0} {1} {2}",                                  genre,pub,isbn);             }             break;         }       }     }   } } 

Listing 7.25 produces the following output:

 -  autobiography 1981 1-861003-11-0 The Autobiography of Benjamin Franklin Benjamin Franklin 8.99 - novel 1967 0-201-63361-2 The Confidence Man Herman Melville 11.99 - philosophy 1991 1-861001-57-6 The Gorgias Sidas Plato 9.99 


Programming in the .NET Environment
Programming in the .NET Environment
ISBN: 0201770180
EAN: 2147483647
Year: 2002
Pages: 146

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net