Getting Started with NGWS Frameworks and Runtime

If you are new to the NGWS frameworks and runtime, here are some topics that will provide a good hands-on introduction and overview.

Section | Description
ASP+ Quick Start | Contains a set of ASP+ samples, and supporting commentary, designed to quickly acquant you with the syntax, architecture, and power of the ASP+ web programming framework.
Win Forms Quick Start | Provides samples code and commentary designed to teach you how to make the most of the Win Forms programming framework.
Developing Applications using the NGWS SDK: An Introduction | Demonstrates how to create NGWS applications and components using the tools included with the NGWS SDK.

© 2000 Microsoft Corporation. All rights reserved.


ASP+ QuickStart

The ASP+ QuickStart is a series of ASP+ samples and supporting commentary designed to quickly acquaint developers with the syntax, architecture, and power of the ASP+ web programming framework. The QuickStart samples are designed to be short, easy-to-understand illustrations of ASP+ features. By the time you have completed the QuickStart tutorial, you will be familiar with:

  • ASP+ Syntax. While some of the ASP+ syntax elements will be familiar to veteran ASP developers, several are unique to the new framework. The QuickStart samples cover each element in detail.
  • ASP+ Architecture and Features. The QuickStart introduces the features of ASP+ that enable developers to build interactive, world-class applications with much less time and effort than ever before.
  • Best Practices. The QuickStart samples discuss the best ways to exercise the power of ASP+ while avoiding potential pitfalls along the way.

What Level of Expertise Is Assumed in the QuickStart?

If you've never developed Web pages before, then the QuickStart is not for you. You should be fluent in HTML and general Web development terminology. You do not need previous ASP experience, but you should be familiar with the concepts behind interactive Web pages, including forms, scripts, and data access.

Installing and Using the ASP+ QuickStart

To install and run the QuickStarts - as well as the other samples - see the StartSamples.htm file in the …\Samples folder of the NGWS SDK installation directory.


WinForms QuickStart

The Win Forms QuickStart is a series of Win Forms samples and supporting commentary designed to quickly acquaint you with the programming model, architecture, and power of the Win Forms framework. The QuickStart samples are designed to be short, easy-to-understand illustrations of many of the features of Win Forms. By the time you have completed the QuickStart tutorial you will be able to build fully functioning Win Forms applications.

What Level of Expertise Is Assumed in the QuickStart?

The Win Forms QuickStart assumes that you are familiar with object oriented programming techniques. It also assumes that you are familiar with concepts such as Windows, Controls, Menus and so on. You should have experience building applications using Microsoft Windows(TM) or a similar Graphical User Interface (GUI) programming system. If you have experience building applications using the Windows Win32 API, MFC or the Visual Basic forms package then you will quickly get to grips with Win Forms.

Installing and Using the Win Forms QuickStart

To install and run the QuickStarts - as well as the other samples - see the StartSamples.htm file in the …\Samples folder of the NGWS SDK installation directory.


Inside the NGWS Frameworks and Runtime

This section describes several key features of the NGWS frameworks and runtime. The following topics are provided:

Section | Description
What is the NGWS Runtime? | Explains what the NGWS runtime is, what it does, and the benefits it provides.
Understanding Managed Execution | Describes what happens during managed execution, including how the runtime uses assemblies and application domains.
The Type System | Identifies the types supported by the runtime.
The NGWS Frameworks | Introduces the NGWS Frameworks, which expedites and optimizes the development process and gives you access to system functionality.
Cross-Language Interoperability | Explains how you can ensure that NGWS objects written in different languages can interact with each other.
NGWS Runtime Security | Describes mechanisms for protecting resources and code from unauthorized code and users.


What is the NGWS Runtime?

NGWS provides a run-time environment called the NGWS runtime, which manages the execution of code and provides services that make programming easier. Compilers and tools expose the runtime's functionality and enable you to write code that benefits from this managed execution environment. Code that you develop with a language compiler that targets the runtime is called managed code; it benefits from features such as cross-language integration, cross-language exception handling, enhanced security, versioning and deployment support, a simplified model for component interaction, and debugging and profiling services.

To enable the runtime to provide services to managed code, language compilers are required to emit metadata, which means that they provide information that describes the types, members, and references in your code. Metadata is stored along with the code: every loadable NGWS runtime image contains metadata. The runtime uses metadata to locate and load classes, lay out instances in memory, resolve method invocations, generate native code, enforce security, and set up run time context boundaries.

The runtime automatically handles object layout and manages references to objects, releasing them when they are no longer being used. Objects whose lifetimes are managed in this way by the runtime are called managed data. Automatic memory management helps eliminate memory leaks as well as some other common programming errors, such as attempts to reference memory that has already been released. If your code is managed, you can use managed data; however, you can instead use unmanaged data if you wish, or you can use both managed and unmanaged data in your NGWS application. Because language compilers supply their own types, such as primitive types, you might not always know (or need to know) whether your data is being managed.

The NGWS runtime makes it easy to design components and applications whose objects interact across languages. Objects written in different languages can communicate with each other, and their behaviors can be tightly integrated. For example, you can derive a class from a class written in another language, and you can pass an instance of a class written in one language to a method on a class written in a different language. This cross-language integration is possible because language compilers and tools that target the runtime use a common type system defined by the runtime, and they follow the runtime's rules for defining new types, as well as creating, using, persisting, and binding to types.

As part of their metadata, all managed components carry information about the components and resources they were compiled against. At deployment time, this information can be configured to control the behavior of applications. The runtime uses this information to ensure that your component or application has the specified versions of everything it needs; as a result, your code is less likely to break due to some dependency not being met. Registration information and state data are no longer stored in the registry where it can be difficult to establish and maintain; instead, information about the types you define (and their dependencies) is stored with the code as metadata, making the tasks of component replication and removal much less complicated.

Language compilers and tools expose the runtime’s functionality in ways that are intended to be useful and intuitive to their developers. This means that some features of the runtime might be more noticeable in one environment than in another. Therefore, how you experience the runtime depends on which language compilers or tools you use. For example, if you are a Visual Basic developer, you might notice that with the NGWS runtime, the Visual Basic language has more object-oriented features than before. The following benefits of the runtime might be particularly interesting to you:

  • Performance improvements.
  • The ability to easily use components developed in other languages.
  • Extensible classes provided by a class library.
  • A broad set of language features.

If you use Visual C++, you can write managed code using managed extensions to C++, which provide the benefits of a managed execution environment while giving you access to powerful capabilities and expressive data types that you are familiar with. You might find the following runtime features especially compelling:

  • Cross-language integration, especially cross-language inheritance.
  • Automatic memory management, which manages object lifetime so that reference counting is unnecessary.
  • Self-describing objects, which make using IDL (Interface Definition Language) unnecessary.
  • The ability to compile once and run on any CPU and operating system that supports the runtime.

For more detailed information about the runtime, see Understanding Managed Execution.


Understanding Managed Execution

The first step in the managed execution process is designing the source code. If you want your application to have the benefits provided by the NGWS runtime, you must use one or more language compilers that target the runtime, such as Visual Basic, C#, Visual C++, or a Perl or COBOL compiler.

Because the runtime is a multi-language execution environment, it supports a wide variety of data types and language features. The language compiler you are using determines what subset of the runtime's functionality is available, and you design your code using the features available in your compiler. If your component is required to be completely usable by components written in other languages, you must use only language features that are included in the Common Language Specification (CLS) in your component's exported types. The syntax you use in your code is determined by the compiler, not by the runtime.

Once your code is written, you compile it, and the compiler translates it to Intermediate Language (IL) and supplies the required metadata. When you are ready to execute your code, the IL gets compiled into native code by a Just In Time (JIT) compiler. If security policy requires the code to be type-safe, the IL is checked for type-safety as part of the JIT compilation process, and the code will not be allowed to execute unless the code is determined to be type-safe. During execution, the runtime provides services that include automatic memory management, debugging support, enhanced security, and interoperability with unmanaged code, such as legacy COM components.

The following topics provide more detailed information about the managed execution process:

  • Intermediate Language (IL)
  • JIT Compilation
  • Execution
  • Assemblies
  • Application Domains
  • Runtime Hosts


Intermediate Language (IL)

If you compile your source code to managed code, the compiler translates your source code into Intermediate Language (IL), which is a CPU-independent set of instructions that includes instructions for loading, storing, initializing, and calling methods on objects. Along with IL, the compiler produces metadata, which describes the types in your code, including the definition of each type, the signatures of each type's members, the members that your code references, and other data that the runtime uses at execution time.

The IL and metadata are contained in a portable executable (PE) file that is based on and extends the published Microsoft Portable Executable (PE) and Common Object File Format (COFF) used historically for .obj, .dll, and .exe files. This file format, which accommodates IL or native code as well as metadata, enables the operating system to recognize NGWS runtime images. The presence of metadata in the file along with the IL enables your code to describe itself, which means that there is no need for type libraries or IDL. The runtime locates and extracts the metadata from the file as necessary during execution. Before execution, IL must be converted to CPU-specific code by a JIT compiler.


JIT Compilation

Before IL can be executed, it must be converted by an NGWS Just In Time (JIT) compiler to native code, which is CPU-specific code that runs on the same computer architecture that the JIT compiler is running on. Because the runtime supplies one or more JIT compilers for each CPU architecture that the runtime operates on, developers can write a set of IL that can be JIT-compiled and executed on computers with different architectures. (If your managed code calls platform-specific native APIs or a class libary that is platform-specific, your code is limited to running on a specific operating system.)

The idea behind JIT compilation recognizes the fact that some code may never get called during execution; therefore, rather than using time and memory to convert all of the IL in a PE (portable executable) file to native code, it makes sense to convert the IL as it is needed during execution and store the resulting native code so that it is accessible for subsequent calls. The loader creates and attaches a stub to each of the type's methods when the type is loaded; on the initial call to the method, the stub passes control to the JIT compiler, which converts the IL for that method into native code and modifies the stub to direct execution to the location of the native code. Subsequent calls of the JITted method proceed directly to the native code that was previously generated, reducing the time it takes to JIT compile and execute the code.

As part of compiling IL to native code, the code must pass a verification process (unless an administrator has established a security policy that allows the code to bypass verification). Verification examines IL and metadata to see whether the code can be determined to be type-safe, which means that it is known to access only the memory locations it is authorized to access. Type safety is necessary to ensure that objects are safely isolated from each other and therefore safe from inadvertent or malicious corruption. It also provides assurance that security restrictions on code can be reliably enforced.

For code that is verifiably type-safe, the runtime can rely on the following statements being true:

  • A reference to a type is strictly compatible with the type being referenced.
  • Only appropriately defined operations are invoked on an object.
  • Identities are what they claim to be.

During the verification process, IL code and metadata are examined in an attempt to confirm that the code can access memory locations and call methods only through properly defined types. For example, code cannot allow an object's fields to be accessed in a manner that allows memory locations to be overrun. Additionally, verification inspects code to see whether the IL itself has been correctly generated, since incorrect IL could lead to a violation of the type safety rules. The verification process passes a well-defined set of type-safe code, and it passes only code that is type-safe. However, some type-safe code might not pass verification due to limitations of the verification process, and some languages by design do not produce verifiably type-safe code.


JIT Compilation Options

The runtime supplies two kinds of Just In Time (JIT) compilers, each of which is designed for specific situations, as the following table indicates:

JIT compiler | Description
JIT | The regular JIT compiler. Converts one method at a time to native code, as described above. Used when small, fast, platform-specific code is required and the time required for optimization is not an issue.
Fast-JIT | A very fast JIT compiler. Converts IL to platform-specific native code very quickly, performing no optimizations. Supports a mechanism called code pitching, which frees memory by throwing away native code for methods that haven't recently been executed. Used when there are constraints on CPU time available for JIT compiling and when the native code doesn't have to be efficient.

The runtime supplies a third mode of compilation called "pre-JIT", which occurs at installation time instead of at execution time. Pre-JIT performs the task of converting IL to native code just as the regular JIT compiler does, but it converts larger units of code at a time, storing the resulting native code for use when the assembly is subsequently loaded and executed. When using the pre-JIT option, the entire assembly that is being installed is converted into native code, taking into account what is known about other assemblies that are already installed. The resulting pre-JITted file loads and starts more quickly than it would have if it were being converted to native code "just-in-time" for execution.


Execution

As each method is called for the first time, the method is JIT-compiled, then executed. The next time that method is executed, the existing JIT compiled native code is executed. The process of JIT compiling and then executing the code is repeated until execution is complete. During execution, managed code receives services such as automatic memory management, security, interoperability with legacy unmanaged code, cross-language debugging support, and enhanced deployment and versioning support. For more information about these services, see the following topics:

  • Code Access Security
  • Role-based Security
  • Interoperating with Unmanaged Code
  • Versioning, Packaging, and Deployment


Assemblies

An assembly is the functional unit of sharing and reuse in the NGWS runtime. It is a reusable, versionable, and self-describing building block of an NGWS application. An assembly forms the boundary for security, class deployment, and type resolution. The assembly is also the smallest unit that can be versioned. An NGWS application consists of one or more assemblies.

In designing your source code today, you make explicit decisions about how to partition the functionality of your application into one or more files. In designing NGWS code, you'll make similar decisions about how to partition the functionality into one or more assemblies. An assembly is a collection of types and resources that are built to work together and form a logical unit of functionality, a “logical” dll.

In physical terms, an assembly is a collection of physical files that are owned by the assembly. These physical files can include NGWS types (interfaces and classes) as well as resources for the assembly (bitmaps, JPEG files, resource files, etc.). Also included in every assembly is a collection of metadata that describes how the elements in an assembly relate. This metadata is contained in a manifest.

An assembly manifest contains all the metadata needed to specify the version requirements, security identity, and all information needed to define the scope of the assembly and resolve references to resources and classes.

The same development tools you have always used to create DLLs or EXEs are used to create assemblies. You can also use additional tools provided in the NGWS SDK to create assemblies from modules you created from other development environments.

Note: An assembly does not form a physical entity but a logical one. Files are linked together through the manifest and are treated by the runtime as a unit. The files themselves are not physically linked.

For more information on assemblies, see The Minimum You Need to Know About Assemblies.


The Minimum You Need to Know About Assemblies

As a developer, you can think of an assembly as a “logical” dll. Here is a list of things you should remember about an assembly:

  • It contains code that the runtime will execute. IL code in a PE file will not be executed if it does not have an assembly manifest associated with it. Note too that each assembly can have only one entry point (i.e. DllMain, WinMain, or Main).
  • It forms a security boundary – An assembly is the unit at which permissions are requested and granted. For more information on security boundaries as they apply to assemblies, see Assemblies and Security Considerations.
  • It forms a type boundary – Every type has as part of its identity the assembly in which it resides. Therefore, a type MyType loaded in the scope of one assembly is not the same as type MyType loaded in the scope of another assembly. For more information on assemblies and type boundaries, see Assemblies and Type Identity.
  • It forms a reference scope boundary – The assembly’s manifest contains the rules for resolving types and resources. It specifies what types and resources are exposed outside the assembly. The manifest also enumerates other assemblies on which it is dependent. For more information on scoping, see Scoping Application Behavior in an Application Domain.
  • It forms a convenient unit for “private” assemblies, such as utilities, debug code, and exceptions. For more information on how to best group code modules into assemblies, see Considerations when Creating Assemblies and Packaging NGWS Applications.
  • It forms a version boundary – The assembly is the smallest versionable unit in the NGWS runtime; all types and resources in the same assembly are versioned as a unit. The assembly’s manifest describes the version dependencies you specify for any dependent assemblies. For more information on versioning, see Assemblies and Versioning.
  • It determines the unit of sharing if you create a shared assembly. For more information on sharing assemblies, see Shared Assemblies.
  • It forms a deployment unit – An application when launched only requires those assemblies it initially calls. All assemblies of an application do not have to be present at run time; other assemblies, such as localization resources, can be retrieved on demand. This allows downloaded applications to be kept simple and thin. For more information on deploying assemblies, see Packaging Components for Distribution.
  • It is the unit at which side-by-side execution is supported. For more information on running multiple versions of the same assembly, see Side by Side Execution.

Some of the information in an assembly manifest can be overridden by an application or machine-wide configuration file. For more information on configuration files, see Creating Configuration Files.


Application Domains

The NGWS runtime provides a lightweight, versatile unit of processing called an application domain. Since the NGWS runtime is type safe, application domains can provide fault tolerance at a much smaller resource cost than with a traditional Win32 process. In fact, multiple application domains can run in a single Win32 process.

Operating systems and runtime environments typically provide some form of isolation between applications running on the system. This isolation is necessary to ensure that code running in one application cannot adversely affect other unrelated applications.

Typically, isolation means

  • Faults in one application cannot affect other applications by bringing down the entire process.
  • Applications can be independently stopped and debugged. Note that application domain debugging is not available in this release.
  • Code running in one application cannot directly access code or resources from another application. Doing so could introduce a security hole.
  • The behavior of running code is scoped by the application it runs in.

In Windows, this isolation has historically been achieved using process boundaries. A process runs exactly one application and that process scopes the resources that are available for that process to use. For example, memory addresses in Win32 are process relative---a pointer in one process is meaningless in the context of another process.

The NGWS runtime also provides isolation, but there are many scenarios where the process boundary is unnecessarily resource intensive. Additionally, the runtime can rely on the fact that user code is type safe to help provide isolation at a lower cost than process isolation that relies on processes switches or hardware ring transitions. In addition, fault isolation is achieved at this lower cost in resources.

The NGWS runtime relies on the fact that code is type safe and verifiable to provide fault isolation between domains. By relying on the type safe nature of the code, application domains provide fault isolation at a much lower cost than the process isolation used in Win32. Because isolation is based on static type verification, there is no need for hardware ring transitions or process switches.

The NGWS runtime allows multiple applications to be run in a single Win32 process. A construct called an application domain is used to isolate those applications from one another. In many respects, application domains are the NGWS runtime equivalent of a Win32 process.

In the NGWS runtime, one application domain serves one application. An application can have multiple application domains running on its behalf. An application domain is the unit of code loading and unloading. Note that you can have multiple application domains in one process. Each type is loaded into the same application domain as its caller, or a caller can request that a type be loaded into a new application domain. There are no direct calls between objects in different application domains; instead, a proxy is used. Application domains can be debugged independently.


References Between Application Domains

The code loaded in one application domain cannot directly access code or resources from another application in another application domain. The NGWS runtime enforces this by not allowing direct calls between objects in different application domains.

Isolation between domains is also required for security reasons. If a caller in one domain could compromise code in another domain, the potential exists to maliciously breech security.

Objects that pass between domains are either copied or accessed via proxy. The mechanism for determining how objects are accessed across domains is determined by the object. There are three cases:

  • Unbound objects are marshaled by value across application domains. That is, a copy is made of the object and the receiver manipulates the copy, not the original object.
  • Application-domain bound objects are marshaled by reference across application domains. As such, cross application domain access is always through proxies.
  • Context-bound objects are also marshaled by reference across application domains. They are marshaled by reference between contexts in the same domain as well.


Unloading and Threads

Application domains represent the unit of code unloading in the NGWS runtime. The runtime does not unload code at a more granular level than an application domain, such as by class or by assembly. Each application domain is a process that can be unloaded independently of the other application domains using the Unload method on System.AppDomain.

A new application domain runs on the same thread as the code that created it. There is not a one to one correspondence between threads and application domains. Web server scenarios can require hundreds of application domains per process, whereby the ideal number of threads is some small multiple of the number of processors on the machine.

At any given time, every thread is executing in one application domain. The NGWS runtime keeps track of which threads are running in which application domains. This information, coupled with the fact that direct references are not allowed between domains, allows the runtime to provide clean shutdown of application domains. When a shutdown request is made, the runtime unwinds the thread until it gets to the application domain boundary. At this point, it can "detach" all proxies to objects in other application domains.

Application domains also can each be debugged independently. Stopping at a breakpoint in one application domain will not affect the other application domains in a process. The NGWS debuggers provide a list of currently running application domains and allow the developer to choose which one to debug.


Runtime Hosts

The runtime is typically started and managed by environments like ASP+, IE or the Windows Shell. These hosting environments run managed code on behalf of the user and take advantage of the application isolation features provided by application domains. In fact it is the host that determines where the application domain boundaries lie and in what application domain user code is run in. The NGWS runtime provides a set of classes and interfaces used by hosts to create and manage Application Domains.

There are five NGWS runtime hosts:

  • ASP+ - ASP+ creates application domains to run user code. Application domains are created per application as defined by the web server.
  • Internet Explorer - IE creates an application domain per site.
  • Windows Shell EXE - Each application that is launched from the command line runs in a separate application domain.
  • VBA - VBA runs the script code contained in an Office document in an application domain.
  • WinForms Designer - The WinForms Designer places each form the user is building in a separate application domain. When the user edits the form and rebuilds, WinForms shuts down the old application domain, recompiles the code and runs it in a new application domain.


The Type System

The runtime's Virtual Object System (VOS) describes the types supported by the runtime and specifies how those types can interact with each other and how they are persisted in metadata. A type defines allowable values and the operations supported by those values. The types in the runtime's type system include classes, interfaces, and value types. Types can have methods that describe the operations on the type as well as other members such as fields, properties, and events.

The type system is an important part of the runtime's support for cross-language integration because it provides the rules that language compilers follow with respect to defining, referencing, using, and storing types. The fact that types are created and used consistently by language compilers supplies the basis for ensuring that objects written in different languages can interact with each other.

The NGWS Frameworks are built on the type system that the runtime defines. It supplies built in primitive types as well as other types that you can use or derive from when building your component or application.


Classes

If you are familiar with object-oriented programming, you know that a class defines the operations the object can perform (methods, events, or properties) and defines variables that hold the state of the object (fields). A class generally includes both definition and implementation, but a class can have one or more members that have no implementation. Class members that have no implementation are abstract members. An abstract class that has one or more abstract members is itself abstract and cannot be instantiated. Some language compilers that target the runtime allow you to mark a class as abstract even if none of its members are abstract. You can use an abstract class when you need to encapsulate a basic set of functionality that derived classes can inherit or override when appropriate.

An instance of an NGWS class is an object. You get access to an object's functionality by calling its methods and accessing its properties, events, and fields. Each language compiler chooses its own syntax for creating instances of classes.

A class can implement any number of interfaces, but it can inherit from exactly one base class. The runtime allows you to pass an instance of a class as a parameter. All non-abstract classes are required to have at least one constructor, which initializes new instances of the class.

Each language compiler that supports the runtime chooses its own syntax for indicating to the compiler that the class or class member has specific characteristics. You just use the syntax required by the language compiler and, when your code is compiled to IL, the compiler ensures that the characteristics of the class and its members are stored (as metadata) along with the implementation of the class.

The following table provides a description of some of the characteristics that the runtime allows a class to have. (Additional characteristics that are available through Attribute classes are not included in this list.) Your compiler might not make all of these characteristics available:

Class characteristic | Meaning
sealed | You cannot derive another class from this one.
implements | The class fulfills the contract specified by one or more interfaces.
abstract | You cannot create an instance of the class. If you want to use it, you must derive another class from it.
inherits | Indicates that instances of the class can be used anywhere the base class is specified. A derived class that inherits from a base class can use the implementation of any virtual methods provided by the base class, or the derived class can override them with its own implementation.
exported or not exported | Indicates whether a class is visible outside the assembly.

The runtime allows you to define members of your class, such as properties, methods, fields, and events. Again, some language compilers might choose not to support all of these member types. The runtime allows class members to have the following characteristics (but language compilers are not required to support all of these characteristics):

Member characteristic | Meaning
abstract | This virtual method has no implementation. If you inherit from the class that it is a member of, you must implement this member if you want to instantiate the derived class.
private, family, assembly, family and assembly, family or assembly, or public. | Defines the accessibility of the member: private: accessible only from within the same class as the member or within a nested class. family: accessible from within the same class as the member and from subtypes that inherit from it. assembly: accessible only from within the assembly that contains the member's implementation. NGWS applications are partitioned into one or more assemblies, which establish the visibility scope for types at execution time. (For more information, see Assemblies.) family or assembly: accessible only from classes that qualify for either family or assembly access. family and assembly: accessible only from classes that qualify for both family and assembly access. public: accessible from any class.
final | You cannot override this virtual method in a derived class.
overrides | The virtual method's implementation replaces the implementation supplied by a member of the class from which it derives.
static | The member belongs to the class it is defined on, not to a particular instance of the class; the member exists even if the class is not instantiated, and it is shared among all instances of the class.
overloads | The method has the same name as another member defined in the same type, but it differs in some way from the other method(s) with the same name; for example, its parameter types, the order of parameter types, or the calling convention might be different.
virtual | The method can be implemented by a subclass, and can be invoked either statically or dynamically. If dynamic invocation is used, the type of the instance that is used to make the call at run time determines which implementation of the method to call, instead of the type known at compile time.
synchronized | The runtime ensures that only one thread of execution at a time can access the implementation. This characteristic can be applied to static methods.


Interfaces

Interfaces define a set of functionality that classes can implement; however, interfaces contain no implementation, except for static methods and static fields. An interface specifies a contract that a class implementing the interface must follow. Interfaces can contain static or virtual methods, static fields, properties, and events. All interface members must be public. Interfaces cannot define constructors. The runtime allows one interface to require that any class that implements it must also implement one or more other interfaces.

To simplify the definition of interfaces, some languages do not require interface characteristics to be applied explicitly because those characteristics already apply implicitly. For example, all interfaces are implicitly abstract, and every member of the interface is implicitly abstract.


Value Types

Most programming languages provide built-in data types, like integers and floating point numbers, that are copied when they are passed as arguments. This differs from the way objects are treated: they are always passed by reference. The NGWS runtime generalizes the idea of built-in data types to allow for user-defined data types that are passed by value. These types are called value types.

Value types are stored as efficiently as primitive types, yet you can call methods on them, including the virtual methods defined on the System.Object class as well as any non-static methods defined on the value type itself. The NGWS Frameworks define built-in value types such System.Int32 and System.Boolean, which correspond to primitive data types used by language compilers; you can use them interchangeably with your language compiler's primitive types. Value types can be instantiated, passed as parameters, stored as local variables, or stored in a field of another value type or object.

You can define your own value types by deriving a type from System.ValueType or by using special syntax that your language compiler requires. Value types do not have the overhead associated with storing an instance of a class. If you want to define a type representing a value that is small, such as a complex number (using two floating point numbers), you might choose to define it as a value type because you can pass the value type efficiently by value. If the type you are defining would be more efficiently passed by reference, you should define it as a class instead.

For each value type, the runtime supplies a corresponding boxed type, which has the base type specified in the class definition. Some language compilers require you to use special syntax when the boxed type is required; others automatically use the boxed type when it is needed. When you define a value type, you are defining both the unboxed type and the boxed type that is associated with the value type.

Value types can have fields, properties, and events. They can also have static and non-static methods. When they are boxed, they inherit the virtual methods from System.ValueType, and they can implement zero or more interfaces. Constructors are not required on value types.

Value types are sealed, meaning that no other type can be derived from them. However, you can define virtual methods directly on the value type, and these can be called on either the boxed or unboxed form of the value type. Even though you cannot derive another type from a value type, you might define virtual methods on a value type when you are using a language in which it is more convenient to work with virtual methods than non-virtual or static methods.

Value types do not require a constructor to be called when an instance is created. The contents of a value type are guaranteed to be initially zero (null for object fields).


Enumerations

  • An enumeration (enum) is a special form of value type, which inherits from System.Enum and supplies an alternate name for an underlying primitive type. An enum type has a name, an underlying type, and a set of fields. The underlying type must be one of the built-in signed or unsigned integer types (such as Int16, Int32, or Int64). The fields are static literal fields, each of which represents a constant. Each field is assigned a specific value of the underlying type by the language compiler. Multiple fields can be assigned the same value. When this occurs, the compiler marks exactly one of the enum values as a “primary” enum value for the value, for the purposes of reflection and string conversion.

You can assign a value of the underlying type to an enum and vice versa (no cast is requiredby the runtime). You can create an instance of an enum, and you can call the methods of System.Enum as well as any methods defined on the enum's underlying type. However, some language compilers might not allow you to pass an enum as a parameter when an instance of the underlying type is required (or vice versa).

The following additional restrictions apply to enumerations:

  • They cannot define their own methods.
  • They cannot implement interfaces.
  • They cannot define properties or events.


Delegates

The runtime provides support for delegates, which are type-safe, object-oriented function pointers. Delegates are used for event handling and callbacks in the NGWS Frameworks.

All delegates are derived from the System.Delegate class. All delegates have a constructor and an Invoke method.They can also have two optional methods, BeginInvoke and EndInvoke, which are used for asynchronous calls.

Each instance of a delegate forwards calls to its Invoke method to a method on a particular object. The object and method are chosen when the delegate instance is created. Therefore, the definition of a delegate is simply the signature of the method to which it forwards its calls. The implementations of the methods on a delegate are provided by the runtime, not by user code. Developers cannot specify additional members on a delegate.


The NGWS Frameworks

You don't have to design all your NGWS types from the ground up. NGWS includes libraries of classes, interfaces, and value types -- called the NGWS Frameworks -- that help expedite and optimize the development process and gives you access to system functionality. To facilitate interoperability between languages, the NGWS Frameworks are CLS-compliant and can be used from any language compiler that targets the runtime.

The NGWS Frameworks include classes that encapsulate data structures, perform I/O, give you access to information about a loaded class, and provide a way to invoke security checks, as well as classes that encapsulate exceptions, and other helpful functionality such as data access, server side UI projections and rich GUI generation. The NGWS Frameworks are designed to be the foundation on which NGWS applications, components, and controls are built.

The NGWS Frameworks provide both abstract base classes and class implementations derived from those base classes. You can use these derived classes "as is" or derive your own classes from them. Also provided are interfaces and default implementations of those interfaces. To get the interface's functionality, you can either implement the interface yourself or you can use or derive a class from one of the runtime-based classes that implements the interface.

Frameworks classes are named using a dot-syntax naming scheme that connotes a naming hierarchy. This technique is used to logically group related classes together so that they can be searched and referenced more easily. For example, the System.Reflection.FieldInfo class is related to other classes that also use a System.Reflection.x naming pattern: all of the classes named with a System.Reflection prefix can be used to discover information about types at runtime. The part of the name up to the last dot (e.g., System.Reflection) is often referred to as the namespace name and the last part (e.g., FieldInfo) as the class name. The use of naming patterns to group related classes into namespaces is a very useful way to build and document class libraries. This naming syntax has no effect on visibility, member access, inheritance, or binding. A logical namespace may be partitioned across multiple assemblies and a single assembly may contain classes from multiple namespaces. It is the assembly that provides the formal structure for visibility and versioning in the runtime.

The root namespace for the NGWS Frameworks is the System namespace. In this namespace are the classes that represent the base data types used by every application: Object (the root of the inheritance hierarchy), Byte, Char, Array, Int32, String, etc. Many of these types correspond to the primitive data types that your language compiler uses. When you write code using NGWS Frameworks types, you can, if you wish, use your language's corresponding primitive type when a runtime-based primitive type is expected. The following table lists the NGWS Frameworks types that represent primitive types, briefly describes each type, and indicates the corresponding type in Visual Basic, C#, and managed extensions to C++.

 | Class name | Description | Visual Basic data type | C# data type | Managed extensions to C++ data type
Integral | Byte | 1-byte unsigned integer | Byte | byte | unsigned char
 | SByte | 1-byte signed integer | Not applicable | sbyte | char
 | Int16 | 2-byte signed integer | Short | short | short
 | Int32 | 4-byte signed integer | Integer | int | int
 | Int64 | 8-byte signed integer | Long | long | __int64
 | UInt16 | 2-byte unsigned integer | Not applicable | ushort | unsigned short
 | UInt32 | 4-byte unsigned integer | Not applicable | uint | unsigned int
 | UInt64 | 8-byte unsigned integer | Not applicable | ulong | unsigned __int64
Floating Point | Single | 4-byte floating point number | Single | float | float
 | Double | 8-byte floating point number | Double | double | double
Other | Char | A Unicode character, which is a 2-byte unsigned integer | Char | char | __wchar_t
Logical | Boolean | true or false | Boolean | bool | bool

Along with the base data types, there are almost 100 classes directly in the System namespace ranging from classes for handling exceptions and forming delegates to dealing with core runtime concepts such as application domains and the automatic memory manager.

The System namespace also contains 24 second-level namespaces. The following list shows the categories of functionality that are covered, and the namespaces in each category:

Data: System.Data, System.XML
Component Model: System.CodeDOM, System.ComponentModel, System.Core
Configuration: System.Configuration
Frameworks Services: System.Diagnostics, System.DirectoryServices, System.ServiceProcess, System.Messaging, System.Timers
Globalization: System.Globalization, System.Resources
Net: System.Net
Programming Basics: System.Collection, System.IO, System.Text, System.Threading
Reflection: System.Reflection
Rich Client-Side GUI: System.Drawing, System.Winforms
Runtime Infrastructure Services: System.Runtime
Security: System.Security
Web Services: System.Web

The reference documentation for the NGWS Frameworks provides an overview of each class along with a formal description of its members (including parameters and return values). For more detailed information about the Frameworks, see the NGWS Frameworks reference documentation.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Metadata and Self Describing Components

[To be supplied.]


Cross-Language Interoperability

Before the NGWS runtime was available, objects compiled by different language compilers could communicate only if they followed an established binary standard. NGWS objects automatically have the ability to communicate and interact with each other, even if they are written in different languages. Your objects can call methods on other objects, inherit implementation from other objects, and pass instances of a class to another class's methods, regardless of the language they are implemented in. In addition, the runtime’s language interoperability enables one debugger to understand your multi-language application and step through it. Exception handling reveals another aspect of language interoperability: the NGWS runtime enables exceptions to be handled the same way across languages. Your code can throw an exception in one language and that exception can be caught and understood by an object written in another language.

In some scenarios, language interoperability is particularly desirable. For example, if you are writing components for a distributed Web application, it would be helpful to know that no matter what language you choose to write your components in, they can interact closely with each other and with components supplied by other developers.

Or, suppose you have developed several components for your server application in one language, but you realize that another language is better suited for implementing the functionality that the remaining component provides. Or, maybe an existing corporate code base is in one language and you are asked to enhance it, but you are much more comfortable using a different programming language or you would like to leverage your development staff's experience with another language. It would be convenient to be able to implement the remaining components in the language that is best suited for the job and/or most efficient for you to use.

Language interoperability is also significant with respect to class library development. Because the runtime enables and supports language interoperability, you can develop class libraries that can be called from any language.

However, there are situations in which you might not be concerned about language interoperability. For example, if you are designing components for an internal application within your company, and you know that your code will only ever use objects written in a particular language, it doesn't matter whether your components are guaranteed to interoperate with components written in other languages.


What is the Common Language Specification?

For objects to be able to understand each other no matter what language compiler they are compiled with, they must expose to callers only those data types and features that are common to all the languages they need to interoperate with. For this reason, the runtime has identified a set of language features called the Common Language Specification (CLS), which includes the basic language features needed by many applications. If your component uses only CLS features in the API that it exposes to other code, including subclasses, the component is guaranteed to be accessible from any object compiled by a language compiler that supports the CLS. Components that adhere to the CLS rules and use only the features included in the CLS are said to be CLS-compliant components.

The following list summarizes the types and features that are in the CLS. This list is intended to be informative, but not comprehensive. The primitive types listed are the types in the NGWS Frameworks, with a description of each type in parentheses.

 | CLS Feature Description
Primitive types: |
 | System.Boolean (true or false)
 | System.Char (2-byte unsigned integer)
 | System.Byte (1-byte unsigned integer)
 | System.Int16 (2-byte signed integer)
 | System.Int32 (4-byte signed integer)
 | System.Int64 (8-byte signed integer)
 | System.Single (4-byte floating point number)
 | System.Double (8-byte floating point number)
 | System.String (A string of zero or more characters; null is allowed.)
 | System.Object (The root of all class inheritance hierarchies)
Arrays: |
 | Can have known type, known ( >= 1) dimension (rank), with zero lower bound.
 | Element type must be a CLS type.
Types: |
 | Can be abstract or sealed (but not both).
 | Can implement zero or more interfaces; different interfaces can have methods with the same name and signature.
 | Can be derived from exactly one type and override or hide members provided by that type.
 | Can have zero or more members, which are fields, methods, events, or types.
 | Can have zero or more constructors.
 | Can have public or assembly visibility, but only public members are considered part of the "public" interface of the type.
 | Value types must inherit from System.ValueType, unless they are enumerations, in which case they inherit from System.Enum.
Type members: |
 | Members can override or hide other members in another type.
 | Argument types and return types must be CLS-compliant types.
 | Constructors, methods, and properties can be overloaded.
 | Members can be abstract, but not if the type is sealed.
 | Can have public. private, family, assembly, FamilyAndAssembly, or FamilyOrAssembly visibility, but only Public, Family and FamilyOrAssembly are considered part of the "public" interface of the type.
Methods: |
 | Can be one of virtual, instance, or static.
 | Virtual and instance methods can be abstract or concrete (static methods must be concrete).
 | Virtual methods can be final (or not).
Fields: |
 | Can be static or non-static.
 | Static fields can be initonly or literal.
Properties: |
 | Can be exposed as Get and Set methods instead of using property syntax.
 | Return type of the Getter and the first argument of the setter must be identical; this is the "property type."
 | Cannot differ by the "property type" alone.
 | If an X property is defined, you cannot define Get_X and Set_X methods in the same class.
 | Can be indexed.
 | Must follow this naming pattern: get_<PropName>, set_<PropName>.
Enumerations: |
 | Underlying type must be Byte, Int16, Int32, or Int64.
 | Each member is a static literal field of the enum's type.
 | Cannot implement any interfaces.
 | Multiple fields can be assigned the same value.
 | Must inherit from System.Enum.
Exceptions: |
 | Can be thrown and caught.
 | Must inherit from System.Exception.
Interfaces: |
 | Can require implementation of other interfaces.
 | Can define properties, events, and virtual methods.
Event: |
 | Add and remove methods must be either both provided or both absent; each of these methods take one parameter, which is a class derived from System.Delegate.
 | Must following this naming pattern: add_<EventName>, remove_<EventName>, and raise_<EventName>.
Custom Attributes: |
 | Can use only the following types: Type, String, Char, Boolean, Byte, Int16, Int32, Int64, Single, Double, Enum (of a CLS type), Object.
Delegates: |
 | Can be created and invoked.
Identifiers |
 | The first character must come from a restricted set.
 | Case cannot be used to distinguish between identifiers within a single name scope (i.e, types within assemblies, members within types).


Writing CLS-Compliant Code

If you want your component to be CLS-compliant, you must use only CLS features in the following places:

  • in the definitions of your public classes,
  • in the definitions of the public members of public classes, and in the definitions of members accessible to subclasses.
  • in the parameters of public methods of public classes, and in the parameters of methods accessible to subclasses.

However, it doesn't matter what features you use in the definitions of your private classes, in the definitions of private methods on public classes, and in local variables. Also, in the code that implements your class, you can use any language features you want and still have a CLS-compliant component.

Many language compilers targeting the runtime have agreed to support the data types and features in the CLS. These language compilers simplify CLS-compliance by making the CLS data types and features available to you so that you can create components using them. Language compilers that are able to produce CLS-compliant components are called CLS-compliant producer tools. Some language compilers can understand all of the CLS types and features, but do not support them to the extent of allowing you to create components that use those features. These language compilers or tools, which can consume CLS-compliant components but cannot produce them, are called CLS-compliant consumer tools.

When you design CLS-compliant components, it is helpful to use a language compiler that has the ability to produce CLS-compliant components. In some cases, these language compilers will assist you by providing a "switch" that you can throw to tell the compiler that you want your code to be CLS-compliant; the compiler will let you know when your code uses functionality that is not supported by the CLS. You could write CLS-compliant components without using a CLS-compliant language compiler, but it will probably be more difficult for two reasons: 1) you must keep track of CLS features yourself and make sure that you use only those features and follow CLS rules; 2) the compiler might not offer all the CLS features you would like to use.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Automatic Memory Management

[To be supplied.]


NGWS Runtime Security

The NGWS runtime provides two mechanisms for protecting resources and code from unauthorized code and users:

  • Code access security uses permissions to control the access code has to protected resources and operations. It helps protect computer systems from malicious mobile code and provides a way to allow mobile code to run safely.
  • Role-based security provides information needed to make decisions about what a user is allowed to do. These decisions can be based on either the user's identity or role membership or both.

The following topics describe NGWS runtime security:

  • Code Access Security
  • Role-Based Security
  • Security Policy Management
  • Security Tools


Building NGWS Applications

This section describes how to perform the tasks involved in building NGWS applications. The following topics are provided:

Section | Description
Creating ASP+ Web Applications | Describes how to use ASP+ to build enterprise-class Web applications.
Creating Win Forms Applications | Provides information about using classes that enable you to develop Windows applications.
Accessing Data | Explains how to access databases from ASP+ applications and how to access data represented as either relational data or XML data.
Accessing XML that Contains Relational Data | Shows how to work with XML data that has some relational content.
Securing Your Application | Describes NGWS code access security, role-based security, security policy, and security tools.
Developing Controls and Components | Provides information needed by developers of custom controls.
Accessing the Internet | Shows how to use Internet access classes to implement both Web- and Internet-based applications.
Manipulating Strings | Describes how to use regular expressions to process text.
Discovering Type Information at Runtime | Explains how to get access to type information at run time using reflection.
Interoperating with Unmanaged Code | Describes COM and COM+ 1.0 interoperability.
Working with Transactions | Discusses support for transactions.
Working with I/O | Explains how you can perform synchronous and asynchronous file and data stream access, and how to use to isolated storage.
Incorporating Messaging Into Applications | Describes support for incorporating message-based communication into your applications.


Creating ASP+ Web Applications

Microsoft® Active Server Pages Plus (ASP+) offers developers a powerful new platform to create Web applications.

For detailed information on ASP+, see the following topics:

Introduction to ASP+

ASP+ Web Forms

Using ASP+ Web Services

Browser-hosted ASP+ Applications

ASP+ Applications

ASP+ State Management

ASP+ Web Application Security

ASP+ Application Deployment

Accessing Data with ASP+

ASP+ Configuration Concepts

ASP+ Caching Features

HTTP Runtime Support

Optimizing ASP+ Applications

See Also

Building NGWS Applications


Introduction to ASP+

ASP+ is more than the next version of Active Server Pages (ASP); it is a unified Web development platform that provides the services necessary for developers to build enterprise-class Web applications. While ASP+ provides backward syntax-compatibility with ASP, it also provides a new programming model and infrastructure that enables a powerful new class of applications. It is also possible to augment ASP applications by incrementally adding ASP+ functionality to existing applications.

ASP+ is a compiled NGWS-based environment; developers can author applications in any NGWS compatible language, including Microsoft® Visual Basic®, Microsoft® Managed Extensions for C++, Microsoft® C# and JScript. Additionally, the entire NGWS runtime platform is available to any ASP+ application. Developers can easily access the benefits of these technologies, which include a managed runtime environment, type safety, inheritance, and so on.

ASP+ has been designed to work seamlessly with WYSIWYG HTML editors and other programming tools, including Microsoft® Visual Studio® version 7.0. Not only does this make Web development easier, but it also provides all the benefits that these tools have to offer, including a GUI that developers can use to drop server controls onto a Web page, as well as fully integrated debugging support.

Developers can choose from three programming models when creating an ASP+ application, or combine these in any way they see fit.

  • Web Forms allows developers to build powerful powerful forms-based web pages that take advantage of server controls for common UI elements or tasks. Server controls allow developers to rapidly build up a Web Form out of reusable built-in or custom components, simplifying the code of a page. For more information, see ASP+ Web Forms.
  • A Web service is a way to access server functionality remotely. Using services, businesses can expose programmatic interfaces to their data or business logic, which in turn can be obtained and manipulated by client and server applications. Web services enable the exchange of data in client-server or server-server scenarios, using standards like HTTP and XML messaging to move data across firewalls. Web services are not tied to a particular component technology or object-calling convention. As a result, programs written in any language, using any component model, and running on any operating system can access Web services. For more information, see Using ASP+ Web Services.
  • The Personal Tier offers developers the option of creating Web applications that run in the browser process on the client, meaning that you do not need a Web server to run ASP+ applications. Developers can create entire applications that run on the browser, or only a select number of Web pages, such as a downloadable catalog page. Personal Tier uses the same programming model as ASP+. For more information, see Browser-hosted ASP+ Applications.

Each of these options can take full advantage of all ASP+ features, as well as the power of the NGWS framework and runtime.

  • ASP+ not only takes advantage of performance enhancements found in the NGWS framework and runtime, it has also been designed to offer significant performance improvements over ASP and other Web development platforms. All ASP+ code is compiled rather than interpreted, which allows early binding, strong typing, and just-in-time (JIT) compiling to native code, to name only a few of its benefits. ASP+ is also easily factorable, meaning that developers can remove modules (a session module, for instance) that are not relevant to the application being developed. ASP+ also provides extensive caching services, both built-in and caching APIs. ASP+ also ships with Performance Counters that developers and system administrators can monitor to test new applications and gather metrics on existing ones. For more information, see ASP+ Caching Features and Optimizing ASP+ Applications.
  • With ASP+, installing, updating or replacing applications is as easy as copying files to the appropriate application directories. There is no need for assembly registration or starting and stopping your Web server. For more information, see Deploying ASP+ Applications.
  • ASP+ configuration settings are stored in XML-based files, which are human readable and writable. Each of your applications can have a distinct configuration file and you can extend the configuration scheme to suit your requirements. For more information, see ASP+ Configuration Concepts.
  • ASP+ provides easy-to-use Application and Session state facilities that are familiar to ASP developers and are readily compatible with all other NGWS APIs. For more information, see ASP+ State Management.
  • The NGWS framework and ASP+ provide default authorization and authentication schemes for Web applications. You can easily remove, add to, or replace these schemes depending upon the needs of your application. For more information, see ASP+ Web Application Security.
  • Intended as a logical replacement for the ISAPI Extension and Filter APIs exposed on versions of the Microsoft® Internet Information Services (IIS) Web Server, the HTTP Runtime provides a low-level HTTP request processing API for NGWS classes. Developers can program directly against this API for ultimate flexibility, or utilize higher-level Web programming abstractions built on top of it—like the Web Forms Page Framework—when building Web applications. For more information, see HTTP Runtime Support.
  • Accessing databases from ASP+ applications is an often-used technique for displaying data to Web site visitors. ASP+ makes it easier than ever to access databases for this purpose - and provides for managing the data in the database. For more information, see Accessing Data with ASP+.
  • ASP+ provides a simple framework that enables Web developers to write logic that runs at the application level. Developers can write this code in either the global.asax text file or in a compiled class deployed as an assembly. This logic can include application-level events, but developers can easily extend this framework to suit the needs of their Web application. ASP application code, written in the global.asa file, is completely supported in ASP+. Web developers simply rename global.asa to global.asax when upgrading from ASP. For more information, see Defining Application-level Logic.
  • ASP+ and NGWS ship with the NGWS SDK Debugger, which supports local debugging. Microsoft will also ship a second debugger for ASP+ applications with the Visual Studio 7.0 release. The Visual Studio Debugger will support debugging of ASP+ code on a remote computer, a feature that allows auto-attachment to the ASP+ runtime, and the ability to edit source files within the debugger source window. ASP+ also provides profiling and tracing capabilities, a built-in system for collecting runtime information about a series of application requests, as well as a set of APIs that developers can use to output custom performance information. For more information, see Debugging ASP+ Applications.
  • ASP+ offers complete syntax and processing compatibility with ASP applications. Developers simply need to change file extensions from .asp to .aspx to migrate their files to the ASP+ framework. They can also easily add ASP+ functionality to their applications with ease, sometimes by simply adding just a few lines of code to their ASP files. For more information, see Migrating ASP Applications to ASP+.

See Also

Creating ASP+ Web Applications


ASP+ Web Forms

The following topics detail the Web Forms technology of ASP+ that allows developers to create programmable Web pages easily. For detailed information about Web Forms, see the following topics:

Introduction to Web Forms

Windows DNA and Web Applications

Web Forms Code Model

Web Forms Page Processing Stages

Web Forms Page State Management

Web Forms Event Model

Web Forms Server Controls

Validation Controls

Pagelet Controls

ASP+ Syntax

Web Forms Control Framework

Trace Functionality

See Also

Introduction to ASP+ | Creating ASP+ Web Applications


Introduction to Web Forms

Web Forms are an ASP+ technology that you use to create programmable Web pages. They can present information, using any markup language, to the user in any browser and use code on the server to implement application logic.

Web Forms:

  • Can run on any browser and automatically render the correct, browser-compliant HTML for features such as styles, layout, and so on. Alternatively, you can design your Web form to run on a specific browser such as Microsoft® Internet Explorer 5 and take advantage of the features of a rich browser client.
  • Can be programmed in any NGWS runtime and frameworks language, including Microsoft® Visual Basic, Microsoft® C#, Microsoft® Managed Extensions for C++, and Microsoft® JScript.
  • Are built on NGWS runtime and provide all the benefits of those technologies, including a managed execution environment, type safety, inheritance, and dynamic compilation for improved performance.
  • Support WYSIWYG editing tools and powerful RAD dvelopment tools, such as Microsoft® Visual Studio®, for designing and programming your forms.
  • Support a rich set of controls that allows developers to cleanly encapsulate page logic into reusable components and declaratively handle page events.
  • Allow for separation between code and content on a page, eliminating the "spaghetti-code" often found in ASP pages.
  • Provide a set of state management features that preserve the view state of a page between requests.
  • Are extensible with user-created and third-party controls.

Components of Web Forms

Web Forms divide the Web applications user interface into two pieces: the visual component and the user interface logic. If you have worked with rapid application deployment tools, like Microsoft® Visual Basic® and Microsoft® Visual C, in the past, you will recognize this distinction between the visible portion of a form and the code that interacts with the form.

The user interface for Web Forms pages consists of a file containing markup and Web-Forms–specific elements. This file is referred to as the page. The page works as a container for the text and controls you want to display. Using any HTML editor plus Web Forms Server Controls, you can lay out the form as you like. The page is a file with the extension ".aspx."

User interface logic for the Web form consists of code that you create to interact with the form. You can choose that the programming logic reside in the .aspx file, or in a separate file (referred to as the "code-behind" file), written in Visual Basic or C#. When you run the form, the code-behind class file runs and dynamically produces the output for your page.

For more details about how Web Forms are constructed, see Web Forms Code Model.

What Web Forms Help You Accomplish

Web application programming presents challenges that do not typically arise when programming traditional client-based applications. Among the challenges are these:

  • Rich user interface. A user interface with a large amount of content, a complex layout, and rich user interaction can be difficult and tedious to create and program using basic HTML facilities. It is especially hard to create a rich user interface for applications likely to run in many different browsers.
  • Separation of client and server. In a Web application, the client (browser) and server are different programs often running on different computers (and even on different operating systems). Consequently, the two halves of the application share very little information; they can communicate, but typically only exchange small chunks of simple information.
  • Stateless execution. When a Web server receives a request for a page, it finds the page, processes it, sends it to the browser, and then, effectively, discards all page information. If the user requests the same page again, the server repeats the entire sequence, reprocessing the page from scratch. Put another way, servers have no memory of pages that they have processed. Therefore, if an application needs to maintain information about a page, this becomes a problem that has to be solved in application code.
  • Unknown client capabilities. In many cases, Web applications are accessible to many users using different browsers. Each of these browsers has different capabilities, making it difficult to create an application that will run equally well on all of them.
  • Data access. Reading from and writing to a data source in traditional Web applications can be complicated and resource-intensive.

Meeting these challenges for Web applications can require substantial time and effort. Web Forms address these challenges in the following ways:

  • Browser-independent applications. Web Forms provide a framework for creating all application logic on the server, eliminating the need to explicitly code for differences in browsers. However, it still allows you to automatically take advantage of browser-specific features to provide improved performance and a richer client experience.
  • Event-based programming model. Web Forms bring to Web applications the model of writing event-handling methods for events that occur in either the client or server. The Web Forms framework abstracts this model in such a way that the underlying mechanism of capturing an event on the client, transmitting it to the server, and calling the appropriate handler is all automatic and invisible. The result is a clear, easily written code structure.
  • Abstract, intuitive, consistent object model. The Web Forms framework presents an object model that allows you to think of your forms as a unit, not as separate client and server pieces. In the Web Forms model, you can program the form in a much more intuitive way than in traditional Web applications, including the ability to set properties for form elements and respond to events. In addition, Web Forms controls are an abstraction from the physical contents of an HTML page and from the direct interaction between browser and server. In general, you can use Web Forms controls the way you might work with controls in a client application and not have to think about how to create the HTML to present and process the controls and their contents.
  • State management. The Web Forms framework automatically handles the task of maintaining the state of your form and its controls, and provides you with explicit ways to maintain the state of application-specific information. This is accomplished without heavy use of server resources and without sending cookies to the browser, two traditional means for storing state.
  • Scalable server performance. The Web Forms framework allows you to scale your application from one computer with a single processor to a multi-computer Web farm cleanly and without complicated changes to the application's logic.

See Also

Creating ASP+ Web Applications


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Windows DNA and Web Applications

[To be supplied.]


Web Forms Code Model

A Web Form consists of two components, the visual elements and the code. These components can reside in the same file; in most situations, however, you should keep each of these components is in a separate file. The visual elements are created in an .aspx file that acts as a container for HTML elements and Web Forms controls. The code can be stored in a separate class file, or within in the .aspx file. In most situations you will want to use the former.

Page Class

Although you create Web Forms from separate components, they form a unit. When the Web Form is compiled, ASP+ parses the page and its code, generates a new class dynamically, and then compiles the new class. The dynamically generated class is derived from the ASP+ Page class, but is extended with controls, your code, and the static HTML text in the .aspx file.

This new derived Page class becomes a single executable file that is executed on the server whenever the Web Forms page is requested. At run time, the Page class processes incoming requests and responds by dynamically creating HTML and streaming it back to the browser. If the page contains Web controls (as it typically would), the derived Page class acts as container for the controls, and instances of the controls are created at runtime and likewise render HTML text to the stream.

For developers who are used to ASP, this model represents something new. In ASP, the page consisted of static HTML interspersed with executable code. The ASP processor read the page, extracted and ran only the code (interpreted, rather than compiled), and then fitted the results back into the static HTML before sending the results to the browser. The traditional ASP model is therefore one of HTML with code added.

In the Page class, the opposite is true. The entire Web Form is, in effect, an executable program whose output is HTML. The processing model is much more like any callable component in that the page executes as code when invoked. The page goes through a series of processing stages analogous with those of other components — initialize, process, and dispose — with a few differences.

One difference is that, like Web components generally, the Page class performs these stages each time the page is called; that is, the page is initialized, processed, and disposed every time a round trip to the server occurs. (In fact, for efficiency the information required to recreate the page is cached, but this is independent of its lifecycle.) A second difference is that the Page class has a distinct stage — render, during which HTML is generated — which is unique to Page classes.

Deriving from the Page Class

When you create the page and class files for a Web Form, it generates code that derives the page from the base Page class. For example, if you create a new Web Form and name it WebPage1, you will be creating a new class WebPage1 derived from Page. In the generated code, you would see a line such as the following:

[Visual Basic]
Inherits Page

The page (.aspx file) is in turn derived from the derived class, that is, from WebPage1. The relationship of the base Page class, the derived class file, and the .aspx file is illustrated in the following diagram.

Because the .aspx file is not a module in the traditional sense, its relationship to the class file is established with directives at the top of the page. Specifically, the @ Page directive's Inherits attribute is used to specify the class file from which the .aspx file is derived. A typical directive might look like this:

<%@ PAGE Inherits="Project1.WebPage1" ... %>

See Also

ASP+ Web Forms |


Web Forms Page Processing Stages

The life cycle for a Web Forms page is, generally speaking, similar to that of any Web process that runs on the server. Certain characteristics of Web processing — information passed via HTTP protocol, the stateless nature of Web pages, and so on — apply to Web Forms just as they do to other Web applications.

However, the Web Forms framework performs many Web application services for you. As one example, the Web Forms framework captures the information posted with the form, extracts the relevant values, and makes it available to you in object properties.

Even so, it is helpful to understand the sequence of events that occurs when a Web Forms page is processed. This knowledge will help you know how you can program your Web Forms pages effectively.

The Life Cycle of a Web Forms Page

Before you examine the details of what goes on inside a Web Forms page when it is processed, you might find it helpful to understand some fundamental characteristics of how Web Forms work as Web applications.

Round Trips

One of the most important things to understand is the division of labor in a Web Forms page. The browser presents the user with a form, and the user interacts with the form. However, all meaningful processing occurs on the server. This means that for each action that requires processing, the form must be sent (posted) to the server, processed, and returned to the browser. This sequence is referred to as a round trip.

For example, if the user enters an order and you want to confirm sufficient inventory for the order, your application posts the page to the server at an appropriate point in the user's order entry. A server process examines the order, performs an inventory lookup, perhaps takes some action (such as modifying the page to indicate an error), and then returns the page to the browser for the user to continue.

In Web Forms, most user actions such as clicking a button result in a round trip. For that reason, the events available in Web Forms controls are limited. Most controls expose a click event, for example — an event that requires an explicit user gesture. By the same token, controls do not expose high-frequency events such as onmouseover, because each time the event is raised, it would require another round trip to the server, which would considerably affect response time in the form.

Recreating the Page (View State and State Management)

In any Web processing scenario, pages are created from scratch with every round trip. In effect, as soon as the server finishes processing a page and sends it to the browser, it discards the page information. The next time the page is posted, the server starts all over in creating and processing it. For this reason, Web pages are said to be stateless; that is, the values of a page's variables and controls are not preserved on the server.

Note In fact, the server might cache page information to optimize the pages, but for purposes of application programming, it is clearest to think of them as being disposed of as soon as the server has finished processing them.

In a traditional Web application, the only information that the server has about a form is the information that the user has filled into controls, because that information is sent to the server when the form is posted.

The Web Forms framework works around these limitations in several ways. First, the Web Forms framework saves page and control properties between round trips, which is referred to as saving the controls' view state. (The framework can detect when a form is requested for the first time versus when the form is posted, which allows you to program accordingly.) In addition, the Web Forms page framework provides state management facilities so you can store other application-specific information (not just the values of controls) between round trips or to pass between pages.

Linear Processing

In client-side forms, the form is initialized and displayed along with its controls. Users then interact with the form, which causes events to be fired that in turn call event-handling methods.

In traditional server-based Web forms, because user actions cause the form to be posted to the server, and because the server must recreate the page with each round trip, processing is more linear than in a client-side form. Effectively, after being recreated, the page is processed in the same top-to-bottom sequence each time.

The Web Forms framework overcomes this strict linear processing in various ways. For example, the framework allows you to set up event-handling methods in server code for events that occur in the browser. When the user clicks a Button Web control, for example, the button raises an event that is transmitted via a form post to the server. There, the Web Forms framework interprets the post information and as part of its normal processing calls the appropriate process in your code to handle the event. For more details about event handling in Web Forms, see Web Forms Event Model.

Stages in Web Forms Processing

During Web Forms processing, the Web Forms page goes through a number of distinct stages. At each stage, the Web Forms processor calls a corresponding page processing method and runs any code in that method. These page-processing methods provide you with entry points — hooks — that you can use to work with the contents of the form.

The following table lists the most commonly used stages of page processing, when they occur, and what you can do at each stage. These stages are repeated at each round trip — that is, each time the form is requested or posted. The IsPostBack property allows you to test whether the page is being processed for the first time.

Note There are several more stages of page processing than are listed in the following table. However, they are not used for ordinary page processing. Instead, they are primarily used by controls on the page to initialize themselves, render themselves, and so on. If you are intending to write your own Web Forms controls, you will need to understand more about these additional stages. For details about the complete set of page processing stages, see Writing a Custom Web Forms Control.
Stage Meaning Typical uses
Configuration

(Page_Load Event)

 Page and control view state is restored, and then the page OnLoad event is fired. In general, read and restore values stored previously, including the following.
  • Check (using the IsPostBack page property) whether this is the first time the page is being processed.
  • Perform data binding the first time the page is processed, or reevaluate data binding expressions on subsequent round trips.
  • Read and update control properties.
  • Restore state saved from a previous client request during the Save stage.

For more details, see Web Forms Data Binding.
Event handling If the page was called in response to a form event, the corresponding event-handling method in the page is called during this stage. In general, perform your application-specific processing, such as the following:
  • Handle the specific event.
Note Events are not raised in a particular order, except that cached Web control events (as specified by the control's AutoPostBack property) are always processed before the posting event.
  • If the page contains validation controls, check the IsValid property for the page or for individual validation controls.
  • Manually save the state of global page variables that you are maintaining yourself and of controls you dynamically added to the page.
Cleanup

(Page_Unload event)

 The page has finished rendering and is ready to be discarded. Perform final cleanup work, such as:
  • Closing files.
  • Closing database connections.
  • Discarding objects.

Note It is important that expensive resources (like database connections) be explicitly closed. Otherwise, they will remain open until the next garbage collection occurs. On a heavily loaded server too many open resources can exhaust memory.

See Also

ASP+ Web Forms |


Web Forms Page State Management

Web Forms pages, like other Web files processed on the server, make a round trip to the server each time processing is required. Ordinarily, this would mean that all information associated with the form would be lost each time.

However, the Web Forms framework can save the state of the page and its controls. The framework provides these options:

  • The view state — the state of the page and its current properties, and the state of controls and their base properties — is saved automatically with each round trip.
  • Any values stored in the state bag during page processing are automatically saved with the page and restored at the next round trip.
  • You can explicitly store information in the Session object. For details, see Session State.

This means that for most ordinary form processing, you do not need to worry about saving and restoring values with each round trip. It also means that if you have some application-specific values that you want to preserve (values not associated with controls), you can do so by taking advantage of the page's automatic facilities.

State Bags

The state bag is a data structure maintained by the page for retaining values between round trips to the server. By storing a value in the page's state bag, you automatically preserve it between round trips.

The following example illustrates how you might maintain the state of an application value. The code defines a public member called UserType, which stores a numeric value indicating what type of customer is using the form. The UserType member includes get and set functions, so it can be used like a property. Each time the value of the member is changed, its value is stored in the page's state bag. When the value of the member is read, the member's get function extracts the value from the state bag. Because the state bag is saved automatically as part of form processing, the state of this member is preserved between round trips.

[C#]
public int UserType
{
    get{
      if (State["UserType"] != null)
         return (int)State["UserType"];
      return 0;
    }
    set{
       State["UserType"] = value;
    }
}

See Also

ASP+ Web Forms |


Web Forms Event Model

Events in Web Forms work somewhat differently than events in traditional client forms or in client-based Web applications. The difference arises primarily because of the separation of the event itself from where the event is handled.

In client-based applications, events are raised and handled on the client. In Web Forms, on the other hand, most events are raised on the client but handled on the Web server. (The page raises events on the server, as do some controls, such as the Calendar and Ad Rotator Web controls.)

For events raised on the client, the Web Forms event model requires that the event information be captured on the client and an event message transmitted to the server. Because of the way the Web works, this must happen via an HTTP post. The Web Forms framework must interpret the post to determine what event occurred, and then call the appropriate method in your code to handle the event.

The Web Forms framework handles virtually all of the mechanics of capturing, transmitting, and interpreting the event. When you create event handlers in a Web Forms page, you can do so without thinking about the mechanics of how the event information is captured and made available to you. Instead, you can create event handlers in much the same way you would do so in a traditional client form.

Even so, there are some aspects of event handling in Web Forms that you should be aware of.

Intrinsic Event Set

Because most Web Forms events require a round trip to the server for processing, they can affect the performance of a form. As a consequence, controls in Web Forms offer a limited set of intrinsic events, usually limited to click-type events. Some controls support a special version of a change event, which is raised when the control's value changes. For example, the CheckBox control raises a change event when the user clicks the box. Events that occur often (and can be raised without the user knowing it), such as a mouseover event, are not supported for Web Forms controls.

Note Some controls support a set of higher-level events. For example, the Calendar control supports an OnSelectionChanged event that is a more abstract version of a click event.

Event Arguments

Web and HTML control events follow a standard NGWS pattern for event methods. All events pass two arguments: an object representing the object that raised the event, and an event object containing event-specific information, if any. The second argument is usually of type EventArgs, but for some controls is of a type specific to that control. For example, for an ImageButton Web control, the second argument is of the type ImageClickEventArgs, which includes information about the coordinates where the user clicked.

Postback and Non-Postback Events in Web Controls

In Web controls (not HTML controls), by default, only click events cause the form to be posted back to the server. Change events are captured, but do not immediately cause a post. Instead, they are cached by the control until the next time that a post occurs. Then, when the page is processed on the server again, all the pending events are raised and processed. During server page processing, all change events are processed first, in no particular order. When all change events have been processed, the click event that caused the form to be posted is processed.

Note You should not create application logic that relies on the change events being raised in a specific order.

You can specify that a change event causes a form post. Controls that support a change event include an AutoPostBack property. When this property is true, the control's change event causes the form to post immediately, without waiting for a click event.

Bubbled Events

Controls such as the Repeater, DataList, DataGrid Web controls can contain child controls that themselves raise events. For example, each row in a DataGrid Web control can contain one or more buttons created dynamically by templates.

Rather than each button raising an event individually, events from the nested controls are "bubbled" — that is, they're sent to the container. The container in turn raises a generic event called OnItemCommand with parameters that allow you to discover which individual control raised the original event. By responding to this single event, you can avoid having to write individual event handlers for child controls.

The OnItemCommand event includes the two standard event arguments, an object referencing the source of the event and a second event object containing event-specific information.

Note The DataGrid and DataList Web controls support additional events, such as OnEditCommand and OnDeleteCommand, which are special cases of bubbled events.

Responding to Both Client and Server Events in HTML Controls

Web controls support only server-side events. However, the core set of HTML controls (buttons and text boxes) allow you to create event-handling methods for either client or server events. As with events for Web Forms controls, the server-side events or HTML controls are usually limited to a single event such as click or change. The remaining events are available for you to handle in client script.

For example, you might have an HTML image button element that you converted to an HTML control. Typically, in a Web Forms page you would handle the image button's click event in server code. However, you might also want to use client code to change the image when the user moves the mouse over it. You can do that by creating a client script for the image button's onmouseover event. (In this case, the assumption is that you are working with a browser that supports HTML 4.0, such as Microsoft® Internet Explorer 4.0 or later.)

You cannot create event-handling methods in client and server code for the same event. For example, you cannot create an OnServerClick event for an HTML button and also a client-side onclick event-handling method for the same button. When you create a server-side event handling method for an HTML control, the Web Forms framework overrides the corresponding client event in order to transmit the event information to the server.

Application and Session Events

In addition to page and control events, the ASP+ framework provides higher-level events:

  • Application events An OnApplicationStart event is raised when your Web application first starts. This allows you to initialize resources that will be used throughout the application lifetime. A corresponding event, OnApplicationEnd, provides you with an opportunity to close or otherwise discard resources.
  • Session events Session events are similar to Application events (an OnSessionStart and OnSessionEnd event), but are raised with each unique session in the application. (A session begins when a user requests a page for the first time from your application and ends either when your application explicitly closes the session or when the session times out.)
  • For more details about these events, see ASP+ State Management.

See Also

ASP+ Web Forms |


Web Forms Server Controls

Web Forms controls are specifically designed to work with Web Forms. They differ from controls for Windows applications because they work within the ASP+ page framework. Because of this, Web Forms controls feature unique design considerations.

When you create Web Forms, you can use these types of controls:

  • HTML controls HTML elements exposed to the server so you can program them. HTML controls expose an object model that maps very closely to the HTML elements that they render.
  • Web controls Controls with more built-in features than HTML controls. Web controls include not only form-type controls such as buttons and text boxes, but also special-purpose controls such as a calendar. Web controls are more abstract than HTML controls, in that their object model does not necessarily reflect HTML syntax.

You can use both types of controls on the same form. When you are working with Web Forms, you can also use these other special purpose controls:

  • Validation controls check the contents of an HTML control or Web control against a static or dynamic value, against another control, against a pattern, and so on.
  • Pagelet Controlsare Web Forms pages that you can reuse as a control in other Web Forms. These new files are defined by the .aspc file extension.

ASP+ also offers you the powerful option of creating your own Web Forms controls. For more information, see Developing Web Forms Controls.

These following topics provide more information about Web Forms controls.

HTML Controls

Web Controls

Web Forms Control Recommendations

Web Forms Controls by Function

Web Forms Controls and CSS Styles

Creating Web Control Templates

Web Forms Data Binding

Programming Web Forms Controls

See Also

ASP+ Web Forms | Developing Web Forms Controls


HTML Controls

HTML controls offer Web developers the power of the Web Forms page framework while retaining the familiarity and ease of use of HTML elements. These controls look exactly like HTML, save that they have a runat="server" attribute/value pair in the opening tag of an HTML element. For example, the following HTML would not only create a text box on a Web page, but would also create an instance of the HtmlInputText server control:

<input id="MyTextBox" type="text" runat="server">

Without runat="server", this line of HTML would be parsed into a standard text box. This model allows developers to import an HTML page authored by a Web designer and program against selected elements on the page. In addition, once an element is converted to a control, a NGWS class is created for the element and its attributes are exposed as properties on the HTML control. For information about these classes, see the System.Web.UI.HtmlControls Namespace reference.

To enable programmatic referencing of the control, developers can include a unique id attribute. In the example above, the id="MyTextBox" defines this programmatic ID, allowing developers to manipulate the contents of this text box with events and other code that they write.

ASP+ offers direct object model support for the most commonly used HTML elements. For those that are not directly supported, there is the HtmlGenericControl, which supports the <span>, <div>, <body>, and <font> elements, among others.

In addition, HTML controls offer the following features:

  • A set of events for which you can write handlers in much the same way you would in a client-based form, except that the event is handled in server code.
  • The ability to handle events in client script.
  • None of them emit ECMAScript to the client when responding to a request.
  • Automatic management of the values of the form's controls. If the form makes a round trip to the server, HTML controls are automatically populated with the values they had when the form was submitted to the server.
  • Interaction with validation controls so you easily verify that a user has entered appropriate information into a control.
  • Databinding to a single field, property, method, or expression (simple data binding) or a collection (list databinding).
  • Support for HTML 4.0 styles.
  • Pass-through of custom attributes. You can add any attributes you need to an HTML control and the Web Forms framework will read them and render them without any change in functionality. This allows you to add browser-specific attributes to your controls.

General Rules for Using HTML Controls

  • All controls that post back events must be nested within an HtmlForm control. For example, the following input elements would be parsed correctly as HTML server controls:
<form runat="server" id="myForm">
   <input type="text" id="myTextBox" runat="server">
   <input type="submit" id="mySubmitButton" onServerClick="SubmitBtn_Click" runat="server">
</form>
  • All HTML controls must be well formed and must not overlap. Unless otherwise noted, elements must be closed, either with an ending slash within the tag, or with a closing tag. For example, <span id="mySpan" runat="server"/> or <span id="mySpan" runat="server">Span text to be displayed here</span>.

See Also

Web Forms Server Controls | Web Controls | Web Forms Controls by Function | Validation Controls | System.Web.UI.HtmlControls Namespace


Web Controls

Web controls are a second set of controls designed with a different emphasis. They do not map one-to-one to HTML controls. Instead, they are defined as abstract controls in which the actual HTML rendered by the control can be quite different from the model that you program against. For example, a RadioButtonList control might be rendered in a table or inline with other HTML.

Web controls include traditional form controls such as buttons and text boxes as well as complex controls such as tables. They also include controls that provide commonly used form functionality such as displaying data in a grid, choosing dates, and so on.

Web controls offer all of the features described above for HTML controls (except one-to-one mapping to HTML elements) and these additional features:

  • A rich object model that provides type-safe programming capabilities.
  • Automatic browser detection. The controls can detect browser capabilities and create appropriate output for both basic and rich (HTML 4.0) browsers.
  • For some controls, the ability to define your own look for the control using templates.
  • For some controls, the ability to specify whether a control's event causes immediate posting to the server or is instead cached and raised when the form is submitted.
  • Ability to pass events from a nested control (such as a button in a table) to the container control.

At design time in HTML view, the controls appear in your page in a format such as:

<asp:button attributes>

The attributes in this case are not those of HTML elements. Instead, they are properties for the Web Forms control.

When the Web Forms page runs, the Web Forms control is rendered on the page using appropriate HTML, which often depends not only on the browser type but also on settings that you have made for the control. For example, a Button control might render as an <INPUT> tag or a <BUTTON> element, depending on its properties.

Detailed how-to and reference documentation is available for each control separately.

See Also

Web Forms Server Controls | HTML Controls | Web Forms Controls by Function | Validation Controls | System.Web.UI.WebControls Namespace


Web Forms Control Recommendations

When creating Web Forms, you can use different classes of controls, as described in Web Forms Server Controls. This topic will help you decide when each type of control is appropriate.

You can mix control types on the same page. For example, your Web Forms page might include a form made up of Web controls plus an HTML control converted from an HTML <SPAN> element.

The following table summarizes when to use Web controls and when to use HTML controls.

Type of control Use it when
Web control
  • You prefer a Visual Basic-like programming model.
  • You are writing a Web Forms page that might be used by both HTML 3.2 and HTML 4.0 browsers.
  • You need specific functionality such as a calendar or ad rotator that is available only as a Web Forms control.
  • You are creating applications with nested controls and want to be able to catch events at the container level.
HTML control
  • You prefer an HTML-like object model.
  • You are working with existing HTML pages and want to quickly add Web Forms functionality. Because HTML controls map exactly to HTML elements, they can be supported by any HTML design environment.
  • The control will also interact with client script.

More on Choosing Web Forms Controls

As a general rule, Web controls are more capable and have a richer object model than HTML controls. If you are writing your Web Forms page so that all processing occurs on the server, Web controls are a good choice.

Web Controls

Web controls are designed to provide a quick, easy way to add functionality — such as displaying data or picking dates — to a Web page. They are also designed for applications that work the same no matter what type of browser the user has.

The advantages of Web controls over HTML controls are:

  • Web controls feature a typed object model for cleaner programming and better error checking.
  • They automatically generate correct HTML for both downlevel (HTML 3.2) and later (HTML 4.0) browsers.
  • They can be used to build more complex server controls (such as Calendar and DataGrid).

Web controls have the disadvantage that you have less direct control over how a Web control is rendered on the page sent to the browser. For example, you cannot add HTML attributes to the controls. Instead, the HTML element and its attributes are created automatically based on how you program the control.

HTML Controls

HTML controls are useful for situations in which you will be scripting a control both on the server and on the client, because the control will be identical in both run-time environments. This makes it easier to write client script for the control.

When a Web Forms page is processed on the server, an instance of each HTML control is created using the information from the underlying HTML element. Attributes recognized by the control class become properties. Unknown attributes are ignored and passed through, so that they appear in the browser as they were created in the page. (The unknown attributes are accessible to server code via the AttributeCollection class, but they are not made into control properties.)

Using HTML controls is also an easy way to convert an existing HTML or ASP page to a Web Form. By converting individual HTML elements on the page to HTML controls, you can quickly add Web Forms functionality to the page without affecting the rest of the page.

HTML controls have the following disadvantages:

  • All values are strings, so there is no type safety.
  • Support for rich browsers is available, but you must program it yourself using extended styles. There is no automatic detection of a browser's capabilities for styles.

See Also

Web Forms Server Controls | HTML Controls | Web Controls | Web Forms Controls by Function


Web Forms Controls by Function

The Web Forms controls are listed here according to general function. The following sections individually provide details about Web controls and HTML controls.

Note For more about the differences between Web and HTML controls, see Web Forms Server Controls.

Web Controls

Web controls include traditional user-entry controls as well as special-purpose controls such as a calendar. All Web controls can be bound to a data source. To find properties that are available to all Web controls, see Base Web Control Properties. To find style properties that are supported by some Web controls, see Style Object Properties.

Function | Control | Description
Text display (read only) | Label | Displays text that users can't directly edit.
Text edit TextBox Displays text entered at design time that can be edited by users at run time or changed programmatically.

Note Although other controls allow users to edit text (for example, DropDownList), their primary purpose is not usually text editing.
Selection from a list | DropDownList | Allows users to either select from a list or enter text.
ListBox | Displays a list of choices. Optionally, the list can allow multiple selections.
Graphics display | Image | Displays an image.
AdRotator | Displays a sequence (pre-defined or random) of images.
Value setting | CheckBox | Displays a box that users can click to turn on and off.
CheckBoxList | Creates a grouping of check boxes. The list control makes it easy to create check boxes using data binding.
RadioButton | Displays a single button that can be turned on or off.
RadioButtonList | Creates a grouping of radio buttons. Inside the group, only one button can be selected.
Date setting | Calendar | Displays a graphic calendar to allow users to select a date.
Commands
Note These controls cause the form to be posted to the server for processing.
 Button Used to perform a task.
LinkButton | Like a Button control, but has the appearance of a hyperlink.
ImageButton | Like a Button control, but incorporates an image instead of text.
Navigation controls | Hyperlink | Creates Web navigation links.
Table controls | Table | Creates a table.
TableCell | Creates an individual cell within a table row.
TableRow | Creates an individual row within a table.
Grouping other controls | CheckBoxList | Creates a collection of check boxes.
Panel | Creates a borderless division on the form that serves as a container for other controls.
RadioButtonList | Creates a grouping of radio buttons. Inside the group, only one button can be selected.
List controls | Repeater | Displays information from a data set using a set of HTML elements and controls you specify, repeating the elements once for each record in the data set.
DataList | Like the Repeater control, but with more formatting and layout options, including the ability to display information in a table. The DataList control also allows you to specify editing behavior.
DataGrid | Displays information, usually data-bound, in tabular form with columns. Provides mechanisms to allow editing and sorting.

HTML Controls

HTML controls map directly to HTML elements. The controls listed in this section are pre-defined controls. However, any HTML element can be made into a control; in that case, the control supports a generic set properties and methods. All HTML controls can be bound to a data source. To find properties that are shared by all HTML controls, see

Function | Control | Description
Forms | HtmlForm | Defines an HTML form. The values of controls within the form are posted to the server when the form is submitted.
Text edit | HtmlInputText | Can be blank or display text entered at design time that can be edited by users at run time, or changed programmatically. This control can also be used to create password boxes that display their values as asterisks (*).
HtmlTextArea | Displays large quantities of text. Used for multi-line text entry and display.
Commands
Note These controls cause the form to be posted to the server for processing.
 HtmlAnchor Creates Web navigation.
HtmlButton | Performs a task. This control can contain any arbitrary HTML, and therefore is very flexible in look and feel. However, it is not compatible with all browsers.
HtmlInputButton | Performs a task. This button is supported on all browsers.
HtmlInputImage | Like a button, but displays a graphic.
Selection from a list | HtmlSelect | Displays a list of text and graphical items (icons).
Graphics display | HtmlImage | Displays an image.
Information storage | HtmlInputHidden | Stores state information for a form (information that needs to be available with each round trip to the server).
Navigation | HtmlAnchor | Creates a Web navigation link.
Value setting | HtmlInputCheckBox | Creates a box that users can click to turn on and off. The CheckBox control includes a label.
HtmlInputRadioButton | Displays a button that can be turned on or off. Radio buttons are typically used to allow the user to select one item from a short list of fixed options.
Table manipulation | HtmlTable | Creates a table.
HtmlTableRow | Creates an individual row within a table.
HtmlTableCell | Creates an individual cell within a row within a table row.
File transfer | HtmlInputFile | Allows users to specify files to be uploaded to a server. (The server must allow uploads.)
Other functions | HtmlGenericControl | Creates a basic object model (properties, methods, events) for any HTML element converted to a control.

Validation Controls

Validation controls provide a way to ensure that users enter appropriate data into a Web Forms page. You can apply one or more of the validation controls listed in the following table to any Web control or HTML control.

Function | Control | Description
Required entry | RequiredFieldValidator | Ensures that the user does not skip an entry.
Comparison to a value (including database fields) | CompareValidator | Compares a user's entry against a constant value or a property value of another control using a comparison operator (less than, equal, greater than, and so on). To check against a database field, for the comparison value you specify an expression that gets the data to compare against.
Range checking | RangeValidator | Checks that a user's entry is between specified lower and upper boundaries. You can check ranges within pairs of numbers, alphabetic characters, and dates. Boundaries can be expressed as constants or as values derived from another control.
Pattern matching | RegularExpressionValidator | Checks that the entry matches a pattern defined by a regular expression. This type of validation allows you to check for predictable sequences of characters, such as those in social security numbers, e-mail addresses, telephone numbers, postal codes, and so on.
User-defined | CustomValidator | Checks the user's entry using validation logic that you code yourself. This type of validation allows you to check for values derived at run time.

See Also

Web Forms Server Controls |


Base Web Control Properties

The properties listed in the following table apply to all Web controls.

Properties

AccessKey | The control's keyboard accelerator key. Specify a single letter or number that the user should use while pressing ALT. For example, specify "K" if the user should press ALT+K to access the control. Access keys are not supported on all browsers.
Attributes The complete set of attributes for the control's persistence format.

This property is only used when programming; it cannot be set when declaring the control.
BackColor The color behind the text of the control. In the ASP+ element, the color value can be set using standard HTML color identifiers: the name of a color ("black" or "red") or an RGB value expressed in hex ("#ffffff").

When programming, this property is set using [to be supplied]

BorderWidth | The size of the control's border, if any, in pixels. This property may not work in downlevel browsers.
BorderStyle The control's border style, if any. Possible values are:
  • NotSet
  • None
  • Dotted
  • Dashed
  • Solid
  • Double
  • Groove
  • Ridge
  • Inset
  • Outset
  • When programming, this property is set using [to be supplied]
CSSClass | The CSS class to assign to the control.
CSSStyle A collection of text attributes that will be rendered as a css style attribute on the outer tag of the control.

This property is only used when programming; it cannot be set when declaring the control.

Note Any style values set via style properties (for example, BackColor) will automatically override a corresponding value in this collection. Values set using this property are not automatically reflected in the strongly-typed style properties.

Some controls support style objects that allow you to apply style properties to individual elements of the control. Those properties override any settings you make using the CSSStyle property.
Enabled | Enables the control if set to true (the default) or disables it if set to false.
Font-Bold True if the control's text should be bold, false othewise. The default is false.

When programming, this property is set using the Font object and [to be supplied].
Font-Italic True if the control's text should be italicized, false othewise. The default is false.

When programming, this property is set using the Font object and [to be supplied].
Font-Name The font in which the text of the control is displayed. Some browsers do not support font settings for individual controls.

When programming, this property is set using the Font object and [to be supplied].
Font-Names A list of font faces, in order of preference. If a browser cannot render a particular font, it selects the next font in the list. Some browsers do not support font settings for individual controls.

When programming, this property is set using the Font object and [to be supplied].
Font-Overline True if the control's text should have a line above it, false othewise. The default is false.

When programming, this property is set using the Font object and [to be supplied].
Font-Size The size of the font, from 1 to 7 (in HTML sizes.) Some browsers do not support font settings for individual controls.

When programming, this property is set using the Font object and the FontUnit class.
Font-Strikeout True if the control's text should have a line through it, false othewise. The default is false.

When programming, this property is set using the Font object and [to be supplied].
Font-Underline True if the control's text should be underlined, false othewise. The default is false.

When programming, this property is set using the Font object and [to be supplied].
ForeColor | The control's text color. This property may not work in some downlevel browsers.
Height | The control's height in pixels. This property may not work in some downlevel browsers.
TabIndex | The order of the control when the user tabs between controls. If this property is not set, the control's index is zero. Controls with the same tab index will be tabbed to in the order they are declared in the content. This property works in Microsoft Internet Explorer 4.0 and later.
ToolTip | The text that appears when the user leaves the mouse pointer over the control. Tooltips do not work in all browsers.
Width | The fixed width of the control. The default unit is characters. You can also express width as pixels (eg "100px") or percentage (eg "20%"). All browsers support character width, but not all support other units.

See Also

Web Forms Controls by Function |


Style Object Properties

Some controls support style objects that allow you to set the appearance, such as color and font, of individual elements within the control. To change the appearance of an element, you set properties of the appropriate style object.

For example, to set the appearance of the selected day in a Calendar Web control, you set style properties of the SelectedDayStyle style object. To give the selected day a blue background and bold red text in the Arial font, you would use the following syntax:

<asp:Calendar id="Calendar1" runat="server"
   SelectedDayStyle-BackColor="Blue"
   SelectedDayStyle-ForeColor="Red"
   SelectedDayStyle-Font-Bold="True"
   SelectedDayStyle-Font-Name="Arial">
</asp:Calendar>

ASP+ Syntax

<asp:control id=value  runat="SERVER"
   StyleObject-property="value"
>
</asp:control>

Style Properties

All style objects support the base properties defined in the base Style class. Other style classes derive from the Style class and support additional, class-specific properties.

Style class | ASP+ syntax | Data type
Style (Base style properties) | styleobject-BackColor | Color
 | styleobject-BorderColor | Color
 | styleobject-BorderStyle | BorderStyle (Dashed, Dotted, Double, Groove, Inset, None, NotSet, Outset, Ridge, Solid)
 | styleobject-BorderWidth | Unit
 | styleobject-CssClass | String
 | styleobject-Font-Bold | Boolean
 | styleobject-Font-Italic | Boolean
 | styleobject-Font-Name | String
 | styleobject-Font-Names When programming, this property is a collection of strings. | String
 | styleobject-Font-Overline | Boolean
 | styleobject-Font-Size | FontUnit
 | styleobject-Font-Strikeout | Boolean
 | styleobject-Font-Underline | Boolean
 | styleobject-ForeColor | Color
 | styleobject-Height | Unit
 | styleobject-Width | Unit
 | styleobject.IsEmpty (Programming only) | Boolean
TableItemStyle | styleobject-HorizontalAlign | HorizontalAlign (Center, Justify, Left, NotSet, Right)
 | styleobject-VerticalAlign | VerticalAlign (Bottom, Middle, NotSet, Top)
 | styleobject-Wrap | Boolean
DataGridPagerStyle | PagerStyle-Mode | PagerMode (NextPrev, NumericPages)
 | PagerStyle-NextPageText | String
 | PagerStyle-PageButtonCount | Integer
 | PagerStyle-Position | PagerPosition (Bottom, Top, TopAndBottom)
 | PagerStyle-PrevPageText | String
 | PagerStyle-Visible | Boolean

See Also

Web Forms Controls by Function |


AdRotator Web Control

The AdRotator Web control displays ad banners on Web pages. It changes the displayed ad each time the Web page is refreshed

ASP+ Syntax

Required properties are noted in boldface type.

<asp:AdRotator
   id=value
   runat="SERVER"
   AdvertisementFile="AdvertisementFile"
   propertyname=propertyvalue
   OnAdCreated="OnAdCreatedMethod"
>
</asp:AdRotator>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
AdvertisementFile | The path to a well-formed XML file containing advertisement information. For information about the format of this file, see "Advertisement File Format" below.
KeywordFilter Indicates a category filter to pass to the source of ads, as follows:
  • If the ad source is specified in the AdvertisementFile property and the KeywordFilter property is set, an ad with a matching keyword will be selected.
  • If the ad source is specified in the AdvertisementFile property and the KeywordFilter property is set but no match exists, the AdRotator control will render as a blank image and a trace warning will be generated.
  • If the KeywordFilter property is empty, keyword filtering will not be used to select the ad.
Target | Specifies the name of the browser window or frame in which to display the advertisement. This parameter can also be one of the HTML frame-keywords, such as _TOP, _NEW, _CHILD, _SELF, _PARENT, or _BLANK.

Events

Event (and paramters) Description
OnAdCreated(Object sender, AdCreatedEventArgs e) Raised on the server once per round trip, after control creation and immediately before the page is rendered. If an AdvertisementFile is provided, OnAdCreated is raised after an ad has been selected from the file.

Properties of the e events argument object:

  • e.AdProperties (IDictionary) [???]
  • e.AlternateText (string)
  • e.ImageUrl (string)
  • e.NavigateUrl (string)
  • For descriptions, see "Advertisement File Format" below.

Advertisement File Format

<?xml xmlns=”Ad Rotator Schedule” ?>
<Advertisements>
   <Ad>
      <ImageUrl>URL of image to display</ImageUrl>
      <TargetUrl> URL of page to link to </TargetUrl>
      <AlternateText>
         Text to show as ToolTip
       </AlternateText>
      <Keyword>keyword used to filter</Keyword>
      <Impressions>relative weighting of ad</Impressions>
   </Ad>
</Advertisements>

Note the following about creating an advertisement file:

  • The XML must be well formed.
  • Only the first <Advertisements> element will in the file will be parsed. All others within the file will be ignored.
  • ImageUrl (Required) Contains an absolute or relative URL to an image file.
  • TargetUrl (Optional) Contains the URL of a page to link to if the user clicks the ad. If this is not set, the HREF property is not rendered on the anchor tag and the ad is not "live."
  • AlternateText (Optional) The ALT attribute of the image. In some browsers, this appears as a ToolTip for the ad. In all browsers, the text is displayed if the image specified in ImageUrl is not accessible.
  • Keyword (Optional) Specifies a category for the ad (for example "computers") that you can filter by.
  • Impressions (Optional) A number that indicates the weight of the ad in the schedule of rotation relative to the other ads in the file. The larger the number, the more often the ad will be displayed. The total of all impressions values in the XML file may not exceed 2,048,000,000 - 1. If it does, the Ad Rotator raises a runtime exception.

Remarks

The AdRotator control renders as an image within an anchor. At runtime, AdRotator will use <asp:Hyperlink> and <asp:Image> controls to perform the rendering.

The source image will be sized by the browser to the dimensions of the AdRotator control, regardless of its actual size.

If the AdvertisementFile property is set, an ad is selected based on impressions weighting from the file, the event arguments are set, and the OnAdCreated event is raised. If the AdvertisementFile property is not set, the event arguments are empty when the OnAdCreated event is raised. The event arguments are used to render the AdRotator control, so you can modify the values passed to the event from the advertisement file, or set them with values you generate yourself. A common scenario is to populate the event arguments with values pulled from a database.

You can use the OnAdCreated event to select ads in your own code or to modify the rendering of an ad selected from the advertisement file. If an advertisement file is set, the arguments to the AdCreated event will already be set to the selected ad when the event is called. Whether or not the values are already set, you can modify the values in the ImageUrl, NavigateUrl, and AlternateText properties to modify the rendering of the AdRotator control.

Example

The following shows a sample declaration for an AdRotator control in an .aspx file.

<asp:AdRotator id="ar1" runat=server
   AdvertisementFile="Ads.xml"
   BorderWidth="1"
/>

The following example shows how you can create ad content at run time.

[Visual Basic]
Protected Sub AdRotator1_AdCreated(  s as object, _
       e as AdCreatedEventArgs)
   If Not IsEmpty(e.AdProperties("LabelText")) Then
      ' Set the caption label for the ad
      CaptionLabel.Text = e.AdProperties("LabelText")
   End If
End Sub

See Also

Web Forms Controls by Function |


Button Web Control

The Button Web control provides a generic pushbutton-style control that causes the Web Forms page to be posted back to the server.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:Button id="MyButton" runat="server"
   Text="label"
   Command="command"
   CommandArgument="commandArgument"
   OnClick="OnClickMethod"
/>
Note Because the <asp:Button> element has no content, you can close it with />.

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
CommandArgument | Optional command argument used in conjunction with the value of the Command property. Contains a value that should be bubbled when a button is clicked. For example, if the Command property is set to "Sort," the CommandArgument property might be set to "Ascending" or "Descending."
Command | Command that should be bubbled when a button embedded in a container control is clicked. The value set in this property is available as an argument of the OnItemCommand event of the container control. For example, if the Button control is in a DataList control, the Command might be set to "Sort." You can pass additional information using the CommandArgument property.
Text | The button caption.

Events

Event (and paramters) | Description
OnClick(Object sender, EventArgs e) | Raised when the user clicks the button. This event always causes the page to be posted to the server. The e events argument object has no properties.

Example

The following shows a sample declaration for a Button control in an .aspx file. The button is embedded in a Repeater control; its OnClick event will be bubbled to the container control, where it will raise an OnItemCommand event. The CommandArgument property is set using a data-binding expression that gets the value of a field called Ticker.

[C#]

<asp:Button id=btnBuy runat="server"
   Text="Buy"
   Command="buy"
   CommandArgument="<%# ((PositionData)Container.DataItem).Ticker %>" />

The following shows an event-handling method on the Repeater control that gets the button click and displays the information passed from the button from its Command and CommandArgument properties.

[C#]
protected void Repeater1_ItemCommand(object sender,
   RepeaterCommandEventArgs e)
   {
      string cmd = ((Button)e.CommandSource).Command;
      string arg = ((Button)e.CommandSource).CommandArgument;
      lblResult.Text = "<u>Command</u> is: <b>" + cmd + "</b>,
           <u>CommandArgument</u> is: <b>" + arg + "</b>";
   }

See Also

Web Forms Controls by Function |


Calendar Web Control

The Calendar Web control displays a one-month calendar that the user can use to view dates or select a specific day, week, or month.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:Calendar id="Calendar1" runat="server"
   CellPadding="pixels"
   CellSpacing="pixels"
   DayNameFormat="FirstLetter|FirstTwoLetters|Full|Short"
   FirstDayOfWeek="Default|Monday|Tuesday|Wednesday|
      Thursday|Friday|Saturday|Sunday"
   NextMonthText="HTML text"
   NextPrevFormat="ShortMonth|FullMonth|CustomText"
   PrevMonthText="HTML text"
   SelectedDate="date"
   SelectionMode="None|Day|DayWeek|DayWeekMonth"
   SelectMonthText="HTML text"
   SelectWeekText="HTML text"
   ShowDayHeader="True|False"
   ShowGridLines="True|False"
   ShowNextPrevMonth="True|False"
   ShowTitle="True|False"
   TitleFormat="Month|MonthYear"
   TodaysDate="date"
   VisibleDate="date"
   TodayDayStyle-property="value"
   DayHeaderStyle-property="value"
   DayStyle-property="value"
   NextPrevStyle-property="value"
   OtherMonthDayStyle-property="value"
   SelectedDayStyle-property="value"
   SelectorStyle-property="value"
   StyeDayHeaderStyle-property="value"
   TitleStyle-property="value"
   TodayDayStyle-property="value"
   WeekendDayStyle-property="value"
/>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
CellPadding | The amount of space, in pixels, between cells. When programming, this property is set using Units.
CellSpacing | The amount of space, in pixels, between the contents of a cell and the cell's border. When programming, this property is set using Units.
DayNameFormat | A value that determines the format of the day names. Day names are derived from the server's locale settings. The default is Short. When programming, this property is set using the DayNameFormat enumeration.
FirstDayOfWeek | A value for the day of the week that will be displayed in the calendar's first column. The default is Default, which uses the server's locale settings to determine the day. When programming, this property is set using the FirstDayOfWeek enumeration.
NextMonthText | The HTML text shown for the next month navigation hyperlink if ShowNextPrevMonth is true. If set, this value overrides the default (the month name).
NextPrevFormat | A value that specifies the format of the next and previous month hyperlinks in the title. To create your own link text, specify Custom and set the NextMonthText and PrevMonthText properties. The default is Custom. When programming, this property is set using the NextPrevFormat enumeration.
PrevMonthText | The HTML text shown for the previous month navigation hyperlink if ShowNextPrevMonth is true. If set, this value overrides the default (the month name).
SelectedDate | A date that specifies the highlighted date. The default is the value of TodaysDate. When programming, this property is set using a DateTime data type.
SelectedDates | (Read only) A collection of DateTime objects representing days highlighted on the calendar. If SelectionMode is set to anything but Day, the collection can contain multiple dates. This property is only used when programming.
SelectionMode | A value that specifies whether the user can select a day, week, or month. The default is Day. To disable date selection, set this property to None. When programming, this property is set using a CalendarSelectionMode enumeration.
SelectMonthText | The HTML text shown for month selection in the selector column if SelectionMode is DayWeekMonth.
SelectWeekText | The HTML text shown for week selection in the selector column if SelectionMode is DayWeek or DayWeekMonth.
ShowDayHeader | A value that determines whether the names of the days of the week are displayed.
ShowGridLines | A value that specifies whether days are displayed with lines around them. Not all browsers can display gridlines.
ShowNextPrevMonth | A value that specifies whether the calendar displays next and previous month hyperlinks in the title.
ShowTitle | A value that specidies whether the calendar's title is displayed. Hiding the calendar hides the month name and month selection hyperlinks.
TitleFormat | A value that specifies how the month name is formatted in the title bar. The default is Month. When programming, this property is set using the TitleFormat enumeration.
TodaysDate | The date to use as the current date. (This date might have to be adjusted to accommodate users who are in a another time zone.) TodaysDate is displayed on the calendar only if you have set properties of the TodayDayStyle object. When programming, this property is set using a DateTime data type.
VisibleDate | A date that specifies what month to display. The date can be any day within the month. The VisibleDate property is updated after the VisibleMonthChanged event is raised. When programming, this property is set using a DateTime data type.

Style Objects

For information about the properties supported for each style class, see Style Object Properties.

Style object | Description | Style class
DayHeaderStyle | Days of the current month. Weekends, the current date, and the selected day can have different styles. | TableItemStyle
DayStyle | Row above the calendar where the names of the days appear. | TableItemStyle
NextPrevStyle | Sections at the left and right ends of the title bar where the month navigation LinkButtons are. | TableItemStyle
OtherMonthDayStyle | Days from the previous and next month that appear on the current month view. | TableItemStyle
SelectedDayStyle | Day selected by the user. | TableItemStyle
SelectorStyle | Column to the left containing links for selecting a week or the whole month. | TableItemStyle
TitleStyle | Title bar at the top of the calendar containing the month name and month navigation links. If NextPrevStyle is set, it overrides the extreme ends of the title bar. | Style
TodayDayStyle | The current date. | TableItemStyle
WeekendDayStyle | Weekend days. | TableItemStyle

Events

Event (and paramters) | Description
OnDayRender(Object sender, DayRenderEventArgs e) | Raised on the server as each day cell is created. The following are properties of the e events argument object: e.Cell (TableCell) An object representing a table cell into which the day is rendered. e.Cell.RowSpan (Integer) e.Cell.ColumnSpan (Integer) e.Cell.HorizontalAlign (HorizontalAlign) e.Cell.VerticalAlign (VerticalAlign) e.Cell.Wrap (Boolean) e.Day (CalendarDay) An object representing the day being rendered. e.Day.Date (DateTime) The date being rendered. e.Day.DayNumberText (String) The number of the day (for example, "20") as a string. e.Day.IsOtherMonth (Boolean) e.Day.IsSelectable (Boolean) e.Day.IsSelected (Boolean) e.Day.IsToday (Boolean) e.Day.IsWeekend (Boolean)
OnVisibleMonthChanged(Object sender, MonthChangedEventArgs e) | Raised on the server when the user clicks a button for the next or previous month in the Calendar's title. The following are properties of the e events argument object: e.NewDate (DateTime) e.PreviousDate (DateTime)
OnSelectionChanged(Object sender, EventArgs e) | Raised on the server when the user selects a day, week, or month selector. This event always causes the page to be posted to the server. The e events argument object has no properties.

Example

The following shows a sample declaration for a Calendar control in an .aspx file. The declaration includes a number of style object properties and establishes the method Date_Selected as the handler for the OnSelectionChanged event.

<asp:Calendar id=Calendar2 runat="server"
   OnSelectionChanged="Date_Selected"
   DayNameFormat="FirstTwoLetters"
   SelectionMode="DayWeekMonth"
   Font-Name="Verdana;Arial" Font-Size="12px"
   Height="180px" Width="230px"
   TodayDayStyle-Font-Bold="True"
   DayHeaderStyle-Font-Bold="True"
   OtherMonthDayStyle-ForeColor="gray"
   TitleStyle-BackColor="#3366ff"
   TitleStyle-ForeColor="white"
   TitleStyle-Font-Bold="True"
   SelectedDayStyle-BackColor="#ffcc66"
   SelectedDayStyle-Font-Bold="True"
   NextPrevFormat="ShortMonth"
   NextPrevStyle-ForeColor="white"
   NextPrevStyle-Font-Size="10px"
   SelectorStyle-BackColor="#99ccff"
   SelectorStyle-ForeColor="navy"
   SelectorStyle-Font-Size="9px"
   SelectWeekText = "week"
   SelectMonthText = "month"
   />

The following example shows how an event-handling method for a Calendar control's OnSelectionChanged event. By querying the s events argument object, the code can determine how many days were selected, and therefore whether the user selected a day, week, or month. Information about the selection is displayed in a Label Web control.

[C#]
protected void Date_Selected(object s, EventArgs e)
{
   switch (((Calendar)s).SelectedDates.Count)
   {
       case (0):   //None
       {
           Label1.Text = "No dates are currently selected";
           break;
       }
       case (1):   //Day
       {
           Label1.Text = "The selected date is " +
              ((Calendar)s).SelectedDate.ToShortDateString();
           break;
       }
       case (7):   //Week
       {
           Label1.Text = "The selection is a week beginning " +
             ((Calendar)s).SelectedDate.ToShortDateString();
           break;
       }
       default:    //Month
           Label1.Text = "The selection is a month beginning " +
             ((Calendar)s).SelectedDate.ToShortDateString();
   }
}

See Also

Web Forms Controls by Function | Using the Calendar Web Control


CheckBox Web Control

The CheckBox Web control enables users to set true-or-false values in the page.

Note You can also use the CheckBoxList control. The CheckBoxList control is easier for data binding, while the individual CheckBox control gives you greater control over the layout of the controls.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:CheckBox id="CheckBox1" runat="server"
   AutoPostBack="True|False"
   Text="label"
   TextAlign="Right|Left"
   Checked="True|False"
   OnCheckedChanged="OnCheckedChangedMethod"
/>
Note Because the <asp:CheckBox> element has no content, you can close it with />.

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
AutoPostBack | True if client-side changes in the check box automatically cause a postback to the server; false otherwise. The default is false.
Checked | True if the check box is checked, false otherwise. The default is false.
TextAlign | The position of the caption. Possible values are Right and Left The default is Right. When programming, you set this property using the TextAlign enumeration.
Text | The check box caption.

Events

Event (and paramters) | Description
OnCheckedChanged(Object sender, EventArgs e) | Raised when the user clicks the checkbox. This event does not cause the Web Forms page to be posted to the server unless the AutoPostBack property is set to true. The e events argument object has no properties.

Example

The following shows a sample declaration for a CheckBox control in an .aspx file. The check box is set to immediately post the form when checked.

<asp:CheckBox id=Check1 runat="server"
   Text="CheckBox 1"
   AutoPostBack="True"
/>

The following example shows an event-hanlding method for the CheckBox control. Because the control posted the page immediately to the server, the event is handled with the Page object's Page_Load event-handling method.

[C#]
protected void Page_Load(Object Sender, EventArgs e)
{
   Label1.Text = Check1.Checked ? "Check1 is checked" :
       "Check1 is not checked";
}

See Also

Web Forms Controls by Function


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

CompareValidator Control

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

CustomValidator Control

[To be supplied.]


CheckBoxList Web Control

The CheckBoxList Web control provides a multi-selection check box group that can be dynamically generated using data binding.

Note You can also use the CheckBox control. The CheckBoxList control is easier for data binding, while the individual CheckBox control gives you greater control over the layout of the controls.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:CheckBoxList id="CheckBoxList1" runat="server"
   AutoPostBack="True|False"
   CellPadding="Pixels"
   DataSource='<% databindingexpression  %>'
   DataTextField="DataSourceField"
   DataValueField="DataSourceField"
   RepeatColumns="ColumnCount"
   RepeatDirection="Vertical|Horizontal"
   RepeatLayout="Flow|Table"
   TextAlign="Right|Left"
   OnSelectedIndexChanged="OnSelectedIndexChangedMethod"
/>
   <asp:ListItem text="label" value="value" selected="Truae|False" />
</asp:CheckBoxList>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
AutoPostBack | True if client-side changes in the control automatically cause a postback to the server; false otherwise. The default is false.
CellPadding The amount of space, in pixels, between check boxes.

When programming, this property is set using Units.
DataSource A data-binding expression that references any object that supports the ICollection interface.
Note In ASP+ syntax, it is preferable to include the data-binding expression in single quotation marks to accommodate quoted expressions.
DataTextField | The field or property of the object in DataSource that will be the source of data for the Text property of individual list items.
DataValueField | The field or property of the object in DataSource that will be the source of data for the Value property of individual list items.
Items A collection of ListItem objects representing individual items within the list of check boxes.

This property is only used when programming. At design time, you set this property by declaring <asp:ListItem> items.
RepeatColumns | The number of columns to use to display the checkboxes. The default is 1.
RepeatDirection The direction in which check boxes are displayed in the list. If Vertical, the check boxes are ordered top to bottom, like text in a newspaper. If Horizontal, check boxes are ordered left-to-right, like days in a calendar. This property has no effect if RepeatColumns is set to 1. The default is Vertical.

When programming, this property is set using the RepeatDirection enumeration.
RepeatLayout Indicates whether control renders in-line (Flow) or in a table (Table). The default is Table.

When programming, this property is set using the RepeatLayout enumeration.
SelectedIndeces A collection of index values for check boxes that are selected. If only one check box is selected, the collection contains the same information as SelectedIndex.

This property is only used when programming. At design time, you set this property by including a Selected property in any ListItem elements that should be checked.
SelectedIndex Ordinal index of currently selected check box in the list.

This property is only used when programming. At design time, you set this property by including a Selected property in a ListItem element.
SelectedItem Reference to the Value property of the currently selected check box in the list.

This property is only used when programming. At design time, you set this property by including a Selected property in a ListItem element.
SelectedItems (Read-only) References to a collection of selected check boxes.

This property is only used when programming. At design time, you set this property by including a Selected property in the ListItem elements to be selected.
TextAlign The position of the caption with respect to the check box. The default is Right.

When programming, you set this property using the TextAlign enumeration.

Events

Event (and paramters) | Description
OnSelectedIndexChanged(Object sender, EventArgs e) | Raised on the server when a check box in the list is selected. This event does not cause the Web Forms page to be posted to the server unless the AutoPostBack property is set to true. The e events argument object has no properties.

Example

The following shows a sample declaration for a CheckBoxList control in an .aspx file. The list contains six items that are not mutually exclusive. When a user checks one of the boxes, the page is not immediately posted. Instead, the page is not posted until some other gesture occurs, such as a Button control click). No OnSelectedIndexChanged will be called, because no method is declared for that event.

<asp:CheckBoxList id=Check1 runat="server"
   RepeatLayout="flow"
>
   <asp:ListItem>Item 1</asp:ListItem>
   <asp:ListItem>Item 2</asp:ListItem>
   <asp:ListItem>Item 3</asp:ListItem>
   <asp:ListItem>Item 4</asp:ListItem>
   <asp:ListItem>Item 5</asp:ListItem>
   <asp:ListItem>Item 6</asp:ListItem>
</asp:CheckBoxList>

The following example shows how you can determine which check boxes in a CheckBoxList control are checked. The code loops through the control's Items collection, and for each item, tests the item's Selected property.

[C#]
protected void Button1_OnClick(object Source, EventArgs e)
{
   string s;
   s = "Selected items:<br>";
   for (int i=0; i < Check1.Items.Count; i++)
      {
      if ( Check1.Items[ i ].Selected )
         {
            s = s + Check1.Items[i].Text;
            s = s + "<br>";
         }
      }
      Label1.Text = s;
}

See Also

Web Forms Controls by Function |


DataGrid Web Control

The DataGrid Web control is multi-column, data-bound grid. The control allows you to define various types of columns, both to lay out the contents of the grid (template columns) and to add specific functionality (edit button columns, hyperlink columns, and so on). The control also supports a variety of options for including paging.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:DataGrid id="DataGrid1" runat=server
   DataSource='<%# DataBindingExpression %>'
   AllowPaging="True|False"
   AllowSorting="True|False"
   AutoGenerateColumns="True|False"
   BackImageUrl="url"
   CellPadding="pixels"
   CellSpacing="pixels"
   DataKeyField="DataSourceKeyField"
   GridLines="None|Columns|Rows|Both"
   HorizontalAlign="Center|Justify|Left|NotSet|Right"
   PagedDataSource
   PageSize="ItemCount"
   ShowFooter="True|False"
   ShowHeader="True|False"
   VirtualItemCount="ItemCount"

   AlternatingItemStyle-property="value"
   EditItemStyle-property="value"
   FooterStyle-property="value"
   HeaderStyle-property="value"
   ItemStyle-property="value"
   PagerStyle-property="value"
   SelectedItemStyle-property="value"

   OnCancelCommand="OnCancelCommandMethod"
   OnDeleteCommand="OnDeleteCommandMethod"
   OnEditCommand="OnEditCommandMethod"
   OnItemCommand="OnItemCommandMethod"
   OnItemCreated="OnItemCreatedMethod"
   OnPageIndexChanged="OnPageIndexChangedMethod"
   OnSortCommand="OnSortCommandMethod"
   OnUpdateCommand="OnUpdateCommandMethod"
</asp:DataGrid>

or

<asp:DataGrid id="DataGrid1" runat=server
   DataSource='<%# DataBindingExpression %>'
   AutoGenerateColumns="False"
   (other properties)

   <property name="Columns">
      <asp:BoundColumn
         DataField="DataSourceField"
         DataFormatString="FormatString"
         FooterText="FooterText"
         HeaderImageUrl="url"
         HeaderText="HeaderText"
         ReadOnly="True|False"
         SortField="DataSourceFieldToSortBy"
         Visible="True|False"
         FooterStyle-property="value"
         HeaderStyle-property="value"
         ItemStyle-property="value"
      />

      <asp:ButtonColumn
         ButtonType="LinkButton|PushButton"
         Command="BubbleText"
         DataTextField="DataSourceField"
         DataTextFormatString="FormatString"
         FooterText="FooterText"
         HeaderImageUrl="url"
         HeaderText="HeaderText"
         ReadOnly="True|False"
         SortField="DataSourceFieldToSortBy"
         Text="ButtonCaption"
         Visible="True|False"
      />

      <asp:EditCommandColumn
         ButtonType="LinkButton|PushButton"
         CancelText="CancelButtonCaption"
         EditText="EditButtonCaption"
         FooterText="FooterText"
         HeaderImageUrl="url"
         HeaderText="HeaderText"
         ReadOnly="True|False"
         SortField="DataSourceFieldToSortBy"
         UpdateText="UpdateButtonCaption"
         Visible="True|False"
        />

      <asp:HyperlinkColumn
         DataNavigateUrlField="DataSourceField"
         DataNavigateUrlFormatString="FormatExpression"
         DataTextField="DataSourceField"
         DataTextFormatString="FormatExpression"
         FooterText="FooterText"
         HeaderImageUrl="url"
         HeaderText="HeaderText"
         NavigateUrl="url"
         ReadOnly="True|False"
         SortField="DataSourceFieldToSortBy"
         Target="window"
         Text="HyperlinkText"
         Visible="True|False"
       />

      <asp:TemplateColumn>
         FooterText="FooterText"
         HeaderImageUrl="url"
         HeaderText="HeaderText"
         ReadOnly="True|False"
         SortField="DataSourceFieldToSortBy"
         Visible="True|False"
         <template name="HeaderTemplate">
             Header template HTML
         </template>
         <template name="ItemTemplate">
             ItemTemplate HTML
         </template>
         <template name="EditItemTemplate">
             EditItem template HTML
         </template>
         <template name="FooterTemplate">
             Footer template HTML
         </template>
      </asp:TemplateColumn>
    </property>
</asp:DataGrid>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
AllowCustomPaging | True if the control does not calcuate a starting position in the data model based on PageIndex. Instead, the DataGrid control displays rows 0 through PageSize. This allows you to implement a "chunked" paging behavior. The default is true.
AllowPaging | True if paging is enabled. The default is true.
AllowSorting | True if sorting is enabled on the DataGrid, false otherwise. The default is false. If true, the headers of columns whose SortExpression is set will be rendered as LinkButton controls with the command sort.
AutoGenerateColumns | True if columns will be generated for each bound data field, false otherwise. If other columns are explicitly declared, the auto-generated columns will be appended to the end of the columns collection. The default is true.
BackImageUrl | The URL of an image to display behind the control.
CellPadding The amount of space, in pixels, between cells.

When programming, this property is set using Units.
CellSpacing The amount of space, in pixels, between the contents of a cell and the cell's border.

When programming, this property is set using Units.
Columns (Read-only) A collection of the columns in the control.

This property is only used when programming; it is not used when declaring the control.
CurrentPageIndex The current page number.

This property is only used when programming; it cannot be set when declaring the control.
DataKeyField | The name of the field in the data source referenced by DataSource that holds the primary key.
DataKeys A collection of DataKey objects created by evaluating the DataKeyField expression against the data source. One DataKey object is stored per item in the Items collection.

This property is only used when programming; it cannot be set when declaring the control.
DataSource A data-binding expression that references any object that supports the ICollection interface.
Note In ASP+ syntax, it is preferable to include the data-binding expression in single quotation marks to accommodate quoted expressions.
EditItemIndex The ordinal index of item to be edited (zero-based). Setting this property to -1 cancels editing.

This property is only used when programming; it cannot be set when declaring the control.
GridLines How grid lines are displayed between items. The default is None. This property has no effect unless RepeatLayout is set to Table.
Note Gridlines can be displayed only in browsers that support CSS stylesheets.

When programming, this property is set using the GridLinesRule enumeration.
HorizontalAlign The alignment of the rows with respect to surrounding text.

When programming, you set this property using the HorizontalAlign enumeration.
Items A collection of DataListItem objects representing individual items within the control. The Items collection contains only items bound to the data model; header, footer and separators are not available via the collection.

This property is only used when programming. At design time, you set this property by declaring ListItem elements.
PageCount (Read-only) The total number of pages to be displayed, calculated as a function of the PageSize and the total number of records to be displayed.

This property is only used when programming.
PagedDataSource | To be supplied.
PageSize | Number of items (records) to display per page.
SelectedIndex The ordinal index of currently selected row (referenced by the SelectedItem property) in the control (zero-based).

This property is only used when programming.
SelectedItem A reference to the currently selected row in the list.

This property is only used when programming.
ShowFooter | true if the footer should be rendered, false otherwise.
ShowHeader | true if the header should be rendered, false otherwise.
VirtualItemCount A user-settable count of the number of rows to display (rather than an actual count based on the rows in the data source). If AllowCustomPaging is true, this property determines the number of pages that will be displayed. In that case, the DataGrid control does not calcuate a starting position based on PageIndex. Instead, the control displays rows 0 through PageSize of the data, while its Pager object reports the current position as page PageIndex of (VirtualItemCount+PageSize-1)/PageSize.

If AllowCustomPaging is false, the control will calculate the total page count from the data.

DataGrid Style Objects

For information about the properties supported for each style class, see Style Object Properties.

Style object | Description | Style class
AlternatingItemStyle | Every other item. | TableItemStyle
EditItemStyle | The item currently being edited. | TableItemStyle
FooterStyle | The footer at the end of the list (if any) | TableItemStyle
HeaderStyle | The header at the top of the list (if any). | TableItemStyle
ItemStyle | Individual items. | Style
PagerStyle | The pager buttons. | DataGridPagerStyle
SelectedItemStyle | The currently-selected item. | TableItemStyle

DataGridColumns

The following tables provide information about the columns you can declare for the DataGrid control.

Column Base Properties

These properties are available on all DataGrid column types.

Property | Description
FooterText | Text to display at the bottom of the column.
HeaderImageUrl | The URL of an image that will render instead of HeaderText text in the column header.
HeaderText | Text to display at the top of the column.
Owner | (Read-only) A reference to the DataGrid to which the bound column belongs. This property is only used when programming.
SortField | The name of the field to sort the data source by when the user specifies sorting by the contents of this column.
State | (Read-only) A reference to a StateBag object for this column. This property is only used when programming.
Visible | true if the column should be displayed, false otherwise.

Column Base Style Objects

All style objects for DataGrid columns are of type TableItemStyle. For details, see Style Object Properties.

Style object | Description
FooterStyle | To be supplied.
HeaderStyle | To be supplied.
ItemStyle | To be supplied.

BoundColumn Properties

Property | Description
DataField | The field or property of the object in DataSource that will be the source of data for the column.
DataFormatString | A format expression.
ReadOnly | True if the column cannot be edited when the row is put into edit mode, false otherwise.

ButtonColumn Properties

Property | Description
ButtonType | What type of button to render. The default is LinkButton. When programming, this property is set using the ButtonColumnType enumeration.
Command | A string indicating the command that should be bubbled when a button in the column is clicked. This string is available in the event-handling method via the Command property of the e events argument object.
DataTextField | The field name from DataSource that is bound to the Text property of the button in this column.
DataTextFormatString | A format expression.
Text | The caption to display on the face of the button in this column. If DataTextField is set, it overrides this value.

TemplateColumn Properties

Property | Description
EditItemTemplate | The HTML elements and controls that define the column when it is in edit mode.
FooterTemplate | The HTML elements and controls that define the column footing.
HeaderTemplate | The HTML elements and controls that define the column heading.
ItemTemplate | The HTML elements and controls that define the column when it is being displayed.

HyperlinkColumn Properties

Property | Description
DataNavigateUrlField | The field or property of the object in DataSource that will provide the URL of the page to go to.
DataNavigateUrlFormatString | A format expression to be used with the Text property.
DataTextField | The field or property of the object in DataSource that will be the source of data for the Text property of the columns.
DataTextFormatString |
NavigateUrl | The URL of the page to go to. If DataNavigateUrlField is set, it overrides this property.
Target | The target window in which the hyperlink will display the page. This can be the name of a window or a frame reference such as _TOP.
Text | The text of the hyperlink.

EditCommandColumn Properties

Property | Description
ButtonType | What type of button to render. The default is LinkButton. When programming, this property is set using the ButtonColumnType enumeration.
EditText | The string to display on the face of the edit button.
UpdateText | The string to display on the face of the update button.
CancelText | The string to display on the face of the cancel button.

Events

Event (and paramters) Description
OnCancelCommand(Object sender, DataGridCommandEventArgs e) Raised when an event is bubbled to the DataGrid control that was raised by a control whose Command property is cancel. Typically, this is a Button or LinkButton control declared in the EditItemTemplate.

The following are properties of the e events argument object:

  • e.CommandSource (Object)
  • e.Item (DataGridItem)
OnDeleteCommand(Object sender, DataGridCommandEventArgs e) Raised when an event is bubbled to the DataGrid control that was raised by a control whose Command property is delete. Typically, this is a Button or LinkButton control declared in the ItemTemplate.

For details about the properties of the event argument e, see OnCancelCommand above.
OnEditCommand(Object sender, DataGridCommandEventArgs e) Raised when an event is bubbled to the DataGrid control that was raised by a control whose Command property is edit. Typically, this is a Button or LinkButton control declared in the ItemTemplate.

For details about the properties of the event argument e, see OnCancelCommand above.
OnItemCommand(Object sender, DataGridCommandEventArgs e) Raised in the DataGrid control when an embedded control raises an event that is not already covered by edit, cancel, delete, or update.

For details about the properties of the event argument e, see OnCancelCommand above.
OnItemCreated(Object sender, DataGridItemCreatedEventArgs e) Raised on the server each time a control is created.

The following are properties of the e events argument object:

  • e.Item (DataGridItem)
OnPageIndexChanged(Object sender, DataGridPageChangedEventArgs e) Raised when

The following are properties of the e events argument object:

  • e.CommandSource (Object)
  • e.NewPageIndex (Integer)
  • e.OldPageIndex (Integer)
OnSortCommand(Object sender, DataGridSortCommandEventArgs e) Raised when

The following are properties of the e events argument object:

  • e.CommandSource (Object)
  • e.SortField (String)
OnUpdateCommand(Object sender, DataGridCommandEventArgs e) Raised when an event is bubbled to the DataGrid control that was raised by a control whose Command property is update. Typically, this is a Button or LinkButton control declared in the EditItemTemplate.

For details about the properties of the event argument e, see OnCancelCommand above.

Remarks

The DataGrid control allows you to define the following types of columns:

Column type | Description
EditCommandColumn | Encapsulates the most common Edit commands (edit, update, cancel) into a predefined column type.
HyperlinkColumn | Creates a column with a set of hyperlinks that are bound to the value of a data field. For example, a grid that displays a list of orders could include a hyperlink column where the OrderID is rendered as a hyperlink to a page that displays the details for the order.
BoundColumn | A column bound to a field in the data source and rendered into a table cell using styles. This is the default column type for the DataGrid control.
TemplatedColumn | Allows you to define the layout of controls using templates for custom HTML elements and controls.
Note By default, the DataGrid control recreates the data set each time the user navigates to a new page and then calculates which rows to display for the requested page. If this is not efficient in your application, you can turn off automatic paging. To do this, set the AllowCustomPaging property to true. The grid will now only display the rows provided in the data set. If you are using built-in navigation controls, the grid will still set the CurrentPageIndex property.
If you haven't already, you will need to provide built-in or custom navigation controls, and in the event handling method for these controls, request a page worth of rows from the data source. The number of rows to retrieve is set by the grid's PageSize property and the page number to go to the value of the CurrentPageIndex property. You can calculate the starting row number this way:

[C#]

StartRowIndex=(MyDataGrid.CurrentPageIndex*MyDataGrid.PageSize);

.

Example

To be supplied.

See Also

Web Forms Controls by Function |


DataList Web Control

The DataList control displays a data-bound list. You can manipulate the control's layout by defining templates.

Note The DataList control differs from the Repeater control by providing supporting directional rendering (via the RepeatColumns and RepeatDirection properties) and the option to render within an HTML table.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:DataList id="DataList1" runat="server"
   CellPadding="pixels"
   CellSpacing="pixels"
   DataKeyField="DataSourceKeyField"
   DataSource='<% databindingexpression  %>'
   ExtractTemplateRows="To be supplied"
   GridLines="None|Columns|Rows|Both"
   RepeatColumns="ColumnCount"
   RepeatDirection="Vertical|Horizontal"
   RepeatLayout="Flow|Table"
   ShowFooter="True|False"
   ShowHeader="True|False"

   OnCancelCommand="OnCancelCommandMethod"
   OnDeleteCommand="OnDeleteCommandMethod"
   OnEditCommand="OnEditCommandMethod"
   OnItemCommand="OnItemCommandMethod"
   OnItemCreated="OnItemCreatedMethod"
   OnUpdateCommand="OnUpdateCommandMethod"

   <template name="Header">
      Header template HTML
   </template >
   <template name="Item">
      Item template HTML
   </template >
   <template name="AlternatingItem">
      Alternating item template HTML
   </template >
   <template name="EditItem">
      Edited item template HTML
   </template >
   <template name="SelectedItem">
      Selected item template HTML
   </template >
   <template name="Separator">
      Separator template HTML
   </template >
   <template name="Footer">
      Footer template HTML
   </template >

   AlternatingItemStyle-property="value"
   EditItemStyle-property="value"
   FooterStyle-property="value"
   HeaderStyle-property="value"
   ItemStyle-property="value"
   SelectedItemStyle-property="value"
   SeparatorStyle-property="value"
/>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
CellPadding The amount of space, in pixels, between cells. This property has no effect unless RepeatLayout is set to Table.

When programming, this property is set using Units.
CellSpacing The amount of space, in pixels, between the contents of a cell and the cell's border. This property has no effect unless RepeatLayout is set to Table.

When programming, this property is set using Units.
DataKeyField | The name of the field in the data source referenced by DataSource that holds the primary key.
DataKeys A collection of DataKey objects created by evaluating the DataKeyField expression against the data source. One DataKey object is stored per item in the Items collection.

This property is only used when programming; it cannot be set when declaring the control.
DataSource A data-binding expression that references any object that supports the ICollection interface.
Note In ASP+ syntax, it is preferable to include the data-binding expression in single quotation marks to accommodate quoted expressions.
EditItemIndex The ordinal index of item to be edited (zero-based). Setting this property to -1 cancels editing.

This property is only used when programming; it cannot be set when declaring the control.
ExtractTemplateRows | True if [to be supplied]; otherwise false.
GridLines How grid lines are displayed between items. The default is None. This property has no effect unless RepeatLayout is set to Table.
Note Gridlines can be displayed only in browsers that support CSS stylesheets.

When programming, this property is set using the GridLinesRule enumeration.
Items A collection of DataListItem objects representing individual items within the control. The Items collection contains only items bound to the data model; header, footer and separators are not available via the collection.

This property is only used when programming. At design time, you set this property by declaring ListItem elements.
RepeatColumns | The number of columns to use to display the items. The default is 1.
RepeatDirection The direction in which items are displayed in the list. If Vertical, the itme are ordered top to bottom, like text in a newspaper. If Horizontal, items are ordered left-to-right, like days in a calendar. This property has no effect if RepeatColumns is set to 1. The default is Vertical.

When programming, this property is set using the RepeatDirection enumeration.
RepeatLayout Indicates whether the control renders in-line (Flow) or in a table (Table). The default is Table.

When programming, this property is set using the RepeatLayout enumeration.
SelectedIndex The ordinal index of currently selected row (referenced by the SelectedItem property) in the control (zero-based).

This property is only used when programming.
SelectedItem A reference to the currently-selected row in the list.

This property is only used when programming.
ShowFooter | True if the footer should be rendered, false otherwise.
ShowHeader | True if the header should be rendered, false otherwise.

Templates

AlternatingItemTemplate | Like the ItemTemplate element, but rendered for every other row in the DataList control. You can specify a different look for the AlternatingItemTemplate element by setting its style properties.
EditItemTemplate | The layout of an item when it has been set into edit mode. This template typically contains editing controls (such as TextBox controls). The EditItemTemplate is invoked for a row in the DataList control when the EditItemIndex is set to the ordinal number of that row.
FooterTemplate | The text and controls to render after all items have been rendered. The FooterTemplate item cannot be data bound.
HeaderTemplate | The text and controls to render before all items have been rendered. The HeaderTemplate item cannot be data bound.
ItemTemplate | The elements to render once for each row in the data source.
SelectedItemTemplate | The elements to render when the user selects an item in the DataList control. Typical uses are to expand the number of data fields beign displayed and to visually mark the row using a highlight.
SeparatorTemplate | The elements to render between each item. The SeparatorTemplate item cannot be data bound.

Style Objects

For information about the properties supported for each style class, see Style Object Properties.

Style object | Description | Style class
AlternatingItemStyle | Every other item. | TableItemStyle
EditItemStyle | The item currently being edited. | TableItemStyle
FooterStyle | The footer at the end of the list (if any) | TableItemStyle
HeaderStyle | The header at the top of the list (if any). | TableItemStyle
ItemStyle | Individual items. | Style
SelectedItemStyle | The currently-selected item. | TableItemStyle
SeparatorStyle | The elements between each item. | TableItemStyle

Events

Event (and paramters) Description
OnCancelCommand(Object sender, DataListCommandEventArgs e) Raised when an event is bubbled to the DataList control that was raised by a control whose Command property is cancel. Typically, this is a Button or LinkButton control declared in the EditItemTemplate.

The following are properties of the e events argument object:

  • e.CommandSource (Object)
  • e.Item (DataListItem)
OnDeleteCommand(Object sender, DataListCommandEventArgs e) Raised when an event is bubbled to the DataListt control that was raised by a control whose Command property is delete. Typically, this is a Button or LinkButton control declared in the ItemTemplate.

For details about the properties of the event argument e, see OnCancelCommand above.
OnEditCommand(Object sender, DataListCommandEventArgs e) Raised when an event is bubbled to the DataList control that was raised by a control whose Command property is edit. Typically, this is a Button or LinkButton control declared in the ItemTemplate.

For details about the properties of the event argument e, see OnCancelCommand above.
OnItemCommand(Object sender, DataListCommandEventArgs e) Raised in the DataList control when an embedded control raises an event that is not already covered by edit, cancel, delete, or update.

For details about the properties of the event argument e, see OnCancelCommand above.
OnItemCreated(Object sender, DataListItemCreatedEventArgs e) Raised on the server each time a control is created.

The following are properties of the e events argument object:

  • e.Item (DataListItem)
OnUpdateCommand(Object sender, DataListCommandEventArgs e) Raised when an event is bubbled to the DataList control that was raised by a control whose Command property is update. Typically, this is a Button or LinkButton control declared in the EditItemTemplate.

For details about the properties of the event argument e, see OnCancelCommand above.

Remarks

The Items collection contains the databound members of the DataList control. The collection is populated when the Bind method is called on the DataList control. The Header (if any) is added first, then one Item object for each data row. If a SeparatorTemplate exists, Separators are created and added between each item, but these are not added to the Items collection.

After all items have been created for the rows in the DataSource, the Footer is added to the control (but not to the Items collection). Finally, the control raises the ItemCreated event for each item, including the header, footer, and separators. Unlike most collections, the Items collection does not expose any add or remove methods. However, you can modify the contents within an Item by handling the OnItemCreated event.

Example

To be supplied.

See Also

Web Forms Controls by Function |


DropDownList Web Control

The DropDownList control enables users to select from a single-selection drop-down list. The drop-down portion can contain any number of list items.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:DropDownList id="DropDownList1" runat="server"
   DataSource="<% databindingexpression  %>"
   DataTextField="DataSourceField"
   DataValueField="DataSourceField"
   AutoPostBack="True|False"
   OnSelectedIndexChanged="OnSelectedIndexChangedMethod"
/>
   <asp:Listitem value="value" selected="True|False">
         Text
   </asp:Listitem>
</asp:DropDownList>

DropDownList Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
AutoPostBack | True if client-side changes in the control automatically cause a postback to the server; false otherwise. The default is false.
DataTextField | The field or property of the object in DataSource that will be the source of data for the Text property of individual list items.
DataValueField | The field or property of the object in DataSource that will be the source of data for the Value property of individual list items.
DataSource | A data-binding expression that references any object that supports the ICollection interface.
Items | A collection of ListItem objects representing individual items within the drop-down list. This property is only used when programming. At design time, you set this property by declaring <asp:ListItem> items.
SelectedIndex | The ordinal index of the currently-selected item in the drop-down list. This property is only used when programming. At design time, you set this property by including a Selected property in a ListItem element.
SelectedItem | A reference to the Value property of the currently selected item in the list. This property is only used when programming. At design time, you set this property by including a Selected property in a ListItem element.

ListItem Properties

Property | Description
Selected | true if the item is the one selected in the drop-down list and that appears in the text box; false otherwise. Only one item can be declared as selected.
Text | The text that appears in the list. This property is primarily for display; the Value property contains the actual selection.
Value | The value of the item selected by the user, which can be different from the text displayed in the list. This property is the one available in the SelectedItem property of the DropDownList control.

Events

Event (and paramters) | Description
OnSelectedIndexChanged(Object sender, EventArgs e) | Raised on the server when the selection of the DropDownList control changes. This event does not cause the Web Forms page to be posted to the server unless the AutoPostBack property is set to true. The e events argument object has no properties.

Example

To be supplied.

See Also

Web Forms Controls by Function |


HtmlAnchor Control

The HtmlAnchor control allows developers to program against the HTML <a> element. This control must have an opening and closing tag. Developers can use this control to dynamically modify the attributes and properties of the <a> element. They can also write handlers for the OnServerClick event to customize how a Web browser responds to users clicking on a link.

Target values must begin with a letter in the range A-Z or a-z, with the exception of the following special values that begin with an underscore: _blank, _self, _parent, and _top.

ASP+ Syntax

Required properties are noted in boldface type.

<a
   runat=server
   id="programmaticID"
   href="linkurl"
   name="bookmarkname"
   OnServerClick="onserverclickhandler"
   target="linkedcontentframeorwindow"
   title="titledisplayedbybrowser"
>
linktext
</a>

Tasks

Writing Event Handlers for Server Controls

Setting Properties on Server Controls

See Also

Web Forms Controls by Function |


HtmlButton Control

The HtmlButton control allows developers to program against the HTML <button> element. Developers can use this control to customize the appearance of the buttons that they place in .aspx pages. The HTML 4.0 <button> element enables web developers to create UI form buttons composed from embedded HTML elements (and even other server controls).

Important The <button> element is defined in the HTML 4.0 specification. Therefore the HtmlButton control is only supported in Microsoft Internet Explorer versions 4.0 and above.

ASP+ Syntax

Required properties are noted in boldface type.

<button
   runat="server"
   id="programmaticID"
   OnServerClick="onserverclickhandler"
>
buttontextorimage
</button>

Tasks

Modifying the Appearance of a Button

Writing Event Handlers for HTML Server Controls

See Also

Web Forms Controls by Function |


HtmlForm Control

The HtmlForm control allows Web developers to program against the HTML <form> element. All server controls, both HTML and custom, are required to be nested between well-formed opening and closing tags of the HtmlForm control.

By default, the HtmlForm control's METHOD attribute is set to POST, and its ACTION attribute is set to the URL of the source page. Developers can customize these attributes to suit their needs, but doing so can break the built-in state and postback services provided by the Web Forms Page Framework.

ASP+ Syntax

Required properties are noted in boldface type.

<form
   runat=server
   id="programmaticID"
   method=POST | GET
   action="srcpageURL"
>
Other controls, input forms, and so on.
</form>

Tasks

Setting the Encoding Type for Form Data Posted to the Server

Redirecting a Form POST to Another Browser Window or Frame

Overriding the Action Attribute for a Form POST

See Also

Web Forms Controls by Function |


HtmlGenericControl

The HtmlGenericControl is created on the server in response to tags that include the runat="server" attribute/value pair in elements which do not map directly to a specific HTML control. These include the <span>, <body>, <div>, and <font> elements among others. The control maps the tag name of the particular element that is to be used as an HTML control to the page framework through a TagName property. This control inherits functionality from the HtmlContainerControl class, which allows developers to dynamically change inner content of HTML control tags.

ASP+ Syntax

Required properties are noted in boldface type.

<span | body | div | font | others
   runat="server"
   id="programmaticID"
>
Contentbetweentags
</span | body | div | font | others>

Tasks

Generating Text Dynamically Using the <span> Element

See Also

Web Forms Controls by Function |


HtmlImage Control

The HtmlImage control allows developers to program against the HTML <img> element. This control allows developers to dynamically set and retrieve image attributes, including the src, width, height, border, alt, and align attributes.

Note This control does not require a closing tag.

ASP+ Syntax

Required properties are noted in boldface type.

<img
   id="programmaticID"
   runat=server
   alt="alttext"
   align= top | middle | bottom | left | right
   border="borderwidth"
   height="imageheight"
   src="imageURI"
   width="imagewidth"
>

Tasks

Aligning an Image with Surrounding Text

See Also

Web Forms Controls by Function |


HtmlInputButton Control

The HtmlInputButton control allows developers to program against the HTML <input type=button>, <input type=submit>, and <input type=reset> elements. When a user clicks on an HTML Input Button control, input from the form that the control is embedded in is posted to the server, processed there, and a response sent back to the requesting browser.

Used in conjunction with the HtmlInputText and HtmlTextArea controls, developers can create user input or authentication pages that can be processed on the server.

Note This control does not require a closing tag.

ASP+ Syntax

Required properties are noted in boldface type.

<input
   type= button | submit | reset
   runat=server
   id="programmaticID"
   OnServerClick="onserverclickhandler"
   >

Tasks

Writing Event Handlers for Server Controls

See Also

Web Forms Controls by Function |


HtmlInputCheckBox Control

The HtmlInputCheckBox control allows developers to program against the HTML <input type=checkbox> element. Developers can use this control with the HtmlInputButton control and events to determine whether a check box is selected. This is done using the Checked property on this control.

Note This control does not require a closing tag.

ASP+ Syntax

Required properties are noted in boldface type.

<input
   type=checkbox
   runat=server
   id="programmaticID"
   checked
>

Tasks

Writing Event Handlers for Server Controls

Tracking Changes to a Check Box

See Also

Web Forms Controls by Function |


HtmlInputFile Control

The HtmlInputFile control allows developers to program against the HTML <input type=file> element. With this control, Web developers can allow the upload of binary or text files from a browser to the server. File-upload is enabled in all HTML 3.2 and above web browsers.

Note This control does not require an end tag.

ASP+ Syntax

Required properties are noted in boldface type.

<input
   type=file
   runat=server
   id="programmaticID"
   accept="MIMEencodings"
   maxlength="maxfilepathlength"
   size="widthoffilepathtextbox"
   postedfile="uploadedfile"
>

Tasks

Uploading a File to the Server

See Also

Web Forms Controls by Function |


HTML Input Hidden Control

NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

HtmlInputHidden Control

The HtmlInputHidden control allows developers to program against the HTML <input type=hidden> element. Developers can use this control in conjunction with the HtmlInputButton and HtmlInputText controls to create, store, and verify information about a specific user, or a number of other scenarios.

Note This control does not require a closing tag.

ASP+ Syntax

Required properties are noted in boldface type.

<input
   type="hidden"
   runat="server"
   id="programmaticID"
   value="contentsofhiddenfield"
>

Tasks

Updating User Information Using Hidden Text

See Also

Web Forms Controls by Function |


HtmlInputImage Control

The HtmlInputImage control allows developers to program against the HTML <input type=image> element. Developers can use this control in conjunction with the HtmlInputText, HtmlTextArea, and other controls to construct user input forms. For button customization, this control offers greater support for down-level browsers than the HtmlButton control.

Note This control does not require a closing tag.

ASP+ Syntax

<input
   type=image
   runat=server
   id="programmaticID"
   src="imagepath"
   align="imagealignment"
   alt="alttext"
OnServerClick="onserverclickhandler"
   width="borderwidthinpixels"
>

Tasks

Writing Event Handlers for Server Controls

Aligning an Image Button with Surrounding Text

See Also

Web Forms Controls by Function |


HtmlInputRadioButton Control

The HtmlInputRadioButton control allows developers to program against the HTML <input type=radio> element. Developers can group these controls together by specifying a name property common to all <input type=radio> elements within the group.

Note This control does not require a closing tag.

ASP+ Syntax

Required properties are noted in boldface type.

<input
   type=radio
   runat=server
   id="programmaticID"
   checked
name="radiobuttongroup"
>

Tasks

Tracking Changes to a Radio Button

See Also

Web Forms Controls by Function | HtmlInputRadioButton Class | System.Web.UI.HtmlControls Namespace


HtmlInputText Control

The HtmlInputText control allows developers to run server code against the HTML <input type=text> and <input type=password> elements. As with standard HTML, these controls can be used to enter user names and passwords into HTML forms.

Developers can use this control in conjunction with the HtmlInputButton, HtmlInputImage, or HtmlButton control to process user input on the server. Developers can retrieve or assign values the HtmlInputText MaxLength, Size, and Value properties.

Note This control does not require a closing tag.

ASP+ Syntax

Required properties are noted in boldface type.

<input
   type=text | password
   runat=server
   id="programmaticID"
   maxlength="max#ofcharacters"
   size="widthoftextbox"
   value="defaulttextboxcontents"
>

Tasks

Checking the Contents of a Text Box

See Also

Web Forms Controls by Function | HtmlInputText Class | HtmlInputButton Control | HtmlInputControl Class


HtmlSelect Control

The HtmlSelect control allows developers to program against the HTML <select> element. Developers must use this control to access HTML <option> elements that they want to program against.

ASP+ Syntax

Required properties are noted in boldface type.

<select
   runat=server
   id="programmaticID"
   OnServerChange="onserverchangehandler"
   DataSource="databindingsource"
   DataTextField="fieldtodatabindoptionttext"
   DataValueField="fieldtodatabindoptionvalue"
   Multiple
   Items="collectionofoptionelements"
   SelectedIndex="indexofcurrentlyselecteditem"
   Size="#ofvisibleitems"
   Value="currentitemvalue"
>
<option>value1</option>
<option>value2</option>
</select>

Tasks

See Also

Web Forms Controls by Function | HtmlSelect Class | HtmlForm Control | System.Web.UI.HtmlControls Namespace


HtmlTable Control

The HtmlTable control allows developers to program against the HTML <table> element. Developers can databind to a table control, and dynamically add rows and cells to the table using the methods provided by the HtmlTableRowCollection and HtmlTableCellCollection.

ASP+ Syntax

Required properties are noted in boldface type.

<table
   runat="server"
   id="programmaticID"
   align=left | center | right
   bgcolor="bgcolor"
   border="borderwidthinpixels"
   bordercolor="bordercolor"
   cellpadding="spacingwithincellsinpixels"
   cellspacing="spacingbetweencellsinpixels"
   height="tableheight"
   rows="collectionofrows"
   width="tablewidth"
>
<tr><td><td></tr>
<tr><td><td></tr>
</table>

Tasks

See Also

Web Forms Controls by Function | HtmlTable Class | HtmlTableCell Control | HtmlTableRow Control | HtmlForm Control | System.Web.UI.HtmlControls Namespace


HtmlTableCell Control

The HtmlTableCell control allows developers to program against the HTML <td> and <th> elements.

ASP+ Syntax

Required properties are noted in boldface type.

<td or th
   runat=server
   id="programmaticID"
   align="alignmentofcontentincell"
   bgcolor="bgcolor"
   bordercolor="bordercolor"
   colspan="#ofcolscellspans"
   height="tableheight"
   nowrap="True | False"
   rowspan="#ofrowscellspans"
   valign="vertalignmentofcellcontent"
   width="cellwidth"
>CellContent
</td or /th>

Tasks

See Also

Web Forms Controls by Function | HtmlTableCell Class | HtmlTable Class


HtmlTableRow Control

The HtmlTableRow control allows developers to program against the HTML <tr> element.

ASP+ Syntax

Required properties are noted in boldface type.

<tr
   runat=server
   id="programmaticID"
   align="tablecontentalignment"
   bgcolor="tablebgcolor"
   bordercolor="bordercolor"
   height="tableheight"
   cells="collectionoftablecells"
   valign="verticalalignmentofrowcontent"
>
<td>cellcontent</td>
<td>cellcontent</td>
<td>cellcontent</td>
</tr>

Tasks

See Also

Web Forms Controls by Function | HtmlTableRow Class | HtmlTableCell Control | HtmlTable Control | System.Web.UI.HtmlControls Namespace


HtmlTextArea Control

The HtmlTextArea control allows developers to program against the HTML <textarea> element.

ASP+ Syntax

Required properties are noted in boldface type.

<textarea
   runat="server"
   id="programmaticID"
   cols="numberofcolsintextarea"
   name="namepassedtobrowser"
   rows="numberofrowsintextarea"
   onserverchange="onserverchangehandler"
>
textareacontent
</textarea>

Tasks

See Also

Web Forms Controls by Function |


Hyperlink Web Control

The Hyperlink control creates links on the page that users can click to go to another page or bookmark.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:Hyperlink id="Hyperlink1" runat="server"
   NavigateUrl="url"
   Text="HyperlinkText"
   ImageUrl="url"
   Target="window"
/>
or
<asp:Hyperlink id="Hyperlink1" runat="server"
   NavigateUrl="url"
   ImageUrl="url"
   Target="window"
/>
Text
</asp:Hyperlink>
Note Because the <asp:Hyperlink> element has no content, you can close it with />.

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
ImageUrl The URL of an image that is an alternative to Text. (ImageURL takes precedence.) If specified, an image is rendered that users click to navigate.
Note You can create a button (rather than a hyperlink) with an image using the ImageButton control.
NavigateUrl | The URL of the page or bookmark to navigate to.
Target | The target window in which the hyperlink will display the page. This can be the name of a window or a frame reference such as _TOP.
Text | The hyperlink text.

Example

To be supplied.

See Also

Web Forms Controls by Function |


Image Web Control

The Image control displays a Web-compatible image.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:Image id="Image1" runat="server"
   ImageUrl="string"
   AlternateText="string"
   ImageAlign="NotSet|AbsBottom|AbsMiddle|BaseLine|Bottom|Left|Middle|
       Right|TextTop|Top"
/>
Note Because the <asp:Image> element has no content, you can close it with />.

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
AlternateText | Text to display if the image referenced by ImageURL is not available. In some browsers, the alternate text is displayed as a ToolTip when the user holds the mouse pointer over the image.
ImageAlign | The alignment of the image with surrounding text. The default is NotSet. When programming, you set this property using the ImageAlign enumeration.
ImageUrl | The URL of the image to display.

Example

To be supplied.

See Also

Web Forms Controls by Function |


ImageButton Web Control

The ImageButton control enables you to to trap user clicks within an image, which gives you an image map-like facility.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:ImageButton id="ImageButton1" runat="server"
   ImageUrl="string"
   Command="command"
   CommandArgument="commandArgument"
   OnClick="OnClickMethod"
/>
Note Because the <asp:ImageButton> element has no content, you can close it with />.

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
ImageUrl | The URL of the image to display.
CommandArgument | Optional command argument used in conjunction with the value of the Command property. Contains a value that should be bubbled when the image button is clicked. For example, if the Command property is set to "Sort," the CommandArgument property might be set to "Ascending" or "Descending."
Command | Command that should be bubbled when a button embedded in a container control is clicked. The value set in this property is available as an argument of the OnItemCommand event of the container control. For example, if the ImageButton control is in a DataList control, the Command might be set to "Sort." You can pass additional information using the CommandArgument property.

Events

Event (and paramters) Description
OnClick(Object sender, ImageClickEventArgs e) Raised when the user clicks the button. This event always causes the page to be posted to the server.

Properties of the e event argument object:

  • e.x (integer) X coordinate where click took place.
  • e.y (integer) Y coordinate where click took place.

Example

To be supplied.

See Also

Web Forms Controls by Function |


Label Web Control

The Label control allows you to display static text on the page and manipulate it programmatically.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:Label id="Label1" runat="server"
   Text="label"
/>
or
<asp:Label id="Label1" runat="server">
Text
</asp:Label>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
Text | The text to display.

Example

To be supplied.

See Also

Web Forms Controls by Function |


LinkButton Web Control

The LinkButton control creates hyperlink-style buttons that post back to the same page on which they origionated.

Note The LinkButton control requires some client-side script support in order to function.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:LinkButton id="LinkButton1" runat="server"
   Text="label"
   Command="command"
   CommandArgument="commandArgument"
   OnClick="OnClickMethod"
/>
or
<asp:LinkButton id="LinkButton1" runat="server"
   Command="command"
   CommandArgument="commandArgument"
   OnClick="OnClickMethod"
/>
   Text
</asp:LinkButton>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
CommandArgument | Optional command argument used in conjunction with the value of the Command property. Contains a value that should be bubbled when a button is clicked. For example, if the Command property is set to "Sort," the CommandArgument property might be set to "Ascending" or "Descending."
Command | Command that should be bubbled when a button embedded in a container control is clicked. The value set in this property is available as an argument of the OnItemCommand event of the container control. For example, if the LinkButton control is in a DataList control, the Command might be set to "Sort." You can pass additional information using the CommandArgument property.
Text | The button caption.

Events

Event (and paramters) | Description
OnClick(Object sender, EventArgs e) | Raised when the user clicks the button. This event always causes the page to be posted to the server. The e events argument object has no properties.

Example

To be supplied.

See Also

Web Forms Controls by Function |


ListBox Web Control

The ListBox control allows you to display a single-select or multi-select listbox.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:ListBox id="Listbox1" runat="server"
   DataSource="<% databindingexpression  %>"
   DataTextField="DataSourceField"
   DataValueField="DataSourceField"
   AutoPostBack="True|False"
   Rows="rowcount"
   SelectionMode="Single|Multiple"
   OnSelectedIndexChanged="OnSelectedIndexChangedMethod"
/>
   <asp:Listitem value="value" selected="True|False">
         Text
   </asp:Listitem>
</SP:ListBox>

ListBox Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
AutoPostBack | True if client-side changes in the control automatically cause a postback to the server; false otherwise. The default is false.
DataTextField | The field or property of the object in DataSource that will be the source of data for the Text property of individual list items.
DataValueField | The field or property of the object in DataSource that will be the source of data for the Value property of individual list items.
DataSource A data-binding expression that references any object that supports the ICollection interface.
Note In ASP+ syntax, it is preferable to include the data-binding expression in single quotation marks to accommodate quoted expressions.
Items A collection of ListItem objects representing individual items within the drop-down list.

This property is only used when programming. At design time, you set this property by declaring <asp:ListItem> items.
SelectedIndex The ordinal index of the currently selected item in the drop-down list.

This property is only used when programming. At design time, you set this property by including a Selected property in a ListItem element.
SelectedItem A reference to the Value property of the currently-selected item in the list.

This property is only used when programming. At design time, you set this property by including a Selected property in a ListItem element.
SelectedItems The collection of ListItem objects currently selected. If SelectionMode is set to Single, this collection contains the same item as SelectedItem.

This property is only used when programming. At design time, you set this property by including a Selected property in a ListItem element.
Rows | The number of rows to display.
SelectionMode Single if the user can select only one item; Multiple if the user can hold down SHIFT or CTRL while clicking to select multiple items.

When programming, you set this property using the ListBoxSelectionMode enumeration.

ListItem Properties

Property | Description
Selected | true if the item is the one selected in the drop-down list and that appears in the text box; false otherwise. Only one item can be declared as selected.
Text | The text that appears in the list. This property is for display; the Value property contains the actual selection.
Value | The value of the item selected by the user, which can be different from the text displayed in the list. This property is the one available in the SelectedItem property of the DropDownList control.

Events

Event (and paramters) | Description
OnSelectedIndexChanged(Object sender, EventArgs e) | Raised on the server when the selection of the ListBox control changes. This event does not cause the Web Forms page to be posted to the server unless the AutoPostBack property is set to true. The e events argument object has no properties.

Example

To be supplied.

See Also

Web Forms Controls by Function |


Panel Web Control

The Panel control provides a container for other controls. This control is rendered as an HTML <DIV> element.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:Panel id="Panel1" runat="server"
   BackImageUrl="url"
   HorizontalAlign="Center|Justify|Left|NotSet|Right"
   Wrap="True|False"
/>
   (Other controls declared here)
</asp:Panel>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
BackImageUrl | The URL of an image to display behind the control.
HorizontalAlign | The alignment of the panel with respect to surrounding text. When programming, you set this property using the HorizontalAlign enumeration.
Wrap | True if content should be wrapped within the panel; false othewise. The default is true.

Example

To be supplied.

See Also

Web Forms Controls by Function |


RadioButton Web Control

The RadioButton control creates an individual radio button on the page. You can group multiple radio buttons to present mutually exclusive choices.

Note You can also use the RadioButtonList control. The RadioButtonList control is easier for data binding, while the individual RadioButton control gives you greater control over the layout of the controls.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:RadioButton id="RadioButton1" runat="server"
   AutoPostBack="True|False"
   Checked="True|False"
   GroupName="GroupName"
   Text="label"
   TextAlign="Right|Left"
   OnCheckedChanged="OnCheckedChangedMethod"
/>
Note Because the <asp:RadioButton> element has no content, you can close it with />.

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
AutoPostBack | true if client-side changes in the control automatically cause a postback to the server; false otherwise. The default is false.
Checked | true if the radio button is checked, false otherwise. The default is false.
GroupName | The name of a group to which the radio button belongs. Radio buttons with the same group name are mutually exclusive.
TextAlign | The position of the caption. Possible values are Right and Left" The default is Right. When programming, you set this property using the TextAlign enumeration.
Text | The radio button caption.

Events

Event (and paramters) | Description
OnCheckedChanged(Object sender, EventArgs e) | Raised when the user clicks the radio button. This event does not cause the Web Forms page to be posted to the server unless the AutoPostBack property is set to true. The e events argument object has no properties.

Example

To be supplied.

See Also

Web Forms Controls by Function |


RadioButtonList Web Control

The RadioButtonList control provides a radio button group that can be dynamically generated via databinding. The radio buttons in the list are mutually exclusive.

Note You can also use the RadioButton control. The RadioButtonList control is easier for data binding, while the individual RadioButton control gives you greater control over the layout of the controls.

ASP+ Syntax

A RadioButtonList control can include individual list items that declare the check boxes to include in the list. You can declare as many list items as you like.

Required properties are noted in boldface type.

<asp:RadioButtonList id="RadioButtonList1" runat="server"
   AutoPostBack="True|False"
   CellPadding="Pixels"
   DataSource="<% databindingexpression  %>"
   DataTextField="DataSourceField"
   DataValueField="DataSourceField"
   RepeatColumns="ColumnCount"
   RepeatDirection="Vertical|Horizontal"
   RepeatLayout="Flow|Table"
   TextAlign="Right|Left"
   OnSelectedIndexChanged="OnSelectedIndexChangedMethod"
/>
   <asp:ListItem text="label" value="value" selected="True|False" />
</asp:RadioButtonList>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
AutoPostBack | true if client-side changes in the control automatically cause a postback to the server; false otherwise. The default is false.
CellPadding The amount of space, in pixels, between radio buttons.

When programming, this property is set using Units.
DataSource A data-binding expression that references any object that supports the ICollection interface.
Note In ASP+ syntax, it is preferable to include the data-binding expression in single quotation marks to accommodate quoted expressions.
DataTextField | The field or property of the object in DataSource that will be the source of data for the Text property of individual list items.
DataValueField | The field or property of the object in DataSource that will be the source of data for the Value property of individual list items.
Items A collection of ListItem objects representing individual items within the list of radio buttons.

This property is only used when programming. At design time, you set this property by declaring <asp:ListItem> items.
RepeatColumns | The number of columns to use to display the radio buttons. The default is 1.
RepeatDirection The direction in which radio button are displayed in the list. If Vertical, the radio buttons are ordered top to bottom, like text in a newspaper. If Horizontal, radio buttons are ordered left-to-right, like days in a calendar. This property has no effect if RepeatColumns is set to 1. The default is Vertical.

When programming, this property is set using the RepeatDirection enumeration.
RepeatLayout Indicates whether control renders in-line (Flow) or in a table (Table). The default is Table.

When programming, this property is set using the RepeatLayout enumeration.
SelectedIndex Ordinal index of currently selected radio button in the list.

This property is only used when programming. At design time, you set this property by including a Selected property in a ListItem element.
SelectedItem Reference to the Value property of the currently-selected radio button in the list.

This property is only used when programming. At design time, you set this property by including a Selected property in a ListItem element.
TextAlign The position of the caption with respect to the radio button. The default is Right.

When programming, you set this property using the TextAlign enumeration.

Events

Event (and paramters) | Description
OnSelectedIndexChanged(Object sender, EventArgs e) | Raised on the server when a check box in the list is selected. This event does not cause the Web Forms page to be posted to the server unless the AutoPostBack property is set to true. The e events argument object has no properties.

Example

To be supplied.

See Also

Web Forms Controls by Function |


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

RegularExpressionValidator Control

[To be supplied.]


Repeater Web Control

The Repeater control is a data-bound list that renders a row for every row in the data source. You define the appearance of the Repeater rows using templates.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:Repeater id="Repeater1" runat=server
   DataSource="% databindingexpression  %>"
>

   <template name="Header">
      Header template HTML
   </template>
   <template name="Item">
      Item template HTML
   </template >
   <template name="AlternatingItem">
      Alternating item template HTML
   </template >
   <template name="Separator">
      Separator template HTML
   </template >
   <template name="Footer">
      Footer template HTML
   </template >
<asp:Repeater>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
DataSource A data-binding expression that references any object that supports the ICollection interface.
Note In ASP+ syntax, it is preferable to include the data-binding expression in single quotation marks to accommodate quoted expressions.
Items A collection of RepeaterItem objects representing individual rows in the control.

This property is only used when programming.
DataItem | This property is only used when programming.
ItemIndex Returns a value indicating what row from the DataSource is being rendered.

This property is only used when programming.
ItemType | This property is only used when programming.

Templates

AlternatingItemTemplate Like the ItemTemplate element, but rendered for every other row in the Repeater control. You can specify a different look for the AlternatingItemTemplate element by setting its style properties.
FooterTemplate Elements to render once when all data-bound rows have been rendered. A typical use is to close an element opened in the HeaderTemplate item (with a tag such as </TABLE>).

The FooterTemplate item cannot be data bound.
HeaderTemplate Elements to render once before any data-bound rows have been rendered. A typical use is to begin a container element such as a table.

The HeaderTemplate item cannot be data bound.
ItemTemplate Elements that are rendered once for each row in the data source. To display data in the ItemTemplate, declare one or more Web controls and set their data-binding expressions to evaluate to a field in the Repeater control's (that is, the container control's) DataSource:
First Name:
<asp:Label runat="server"
   Text="<%# Container.DataItem.FirstName %>" />
SeparatorTemplate Elements to render between each row, typically line breaks (<BR> tags), lines (<HR> tags), and so on.

The SeparatorTemplate item cannot be data bound.

Events

Event (and paramters) Description
OnItemCreated(Object sender, RepeaterItemCreatedEventArgs e) Raised in the Repeater control each time a new row is created.

The following are properties of the e events argument object:

  • e.Item (RepeaterItem)
  • e.Item.DataItem (Object)
  • e.Item.ItemIndex (Integer) The "row" of the Repeater control which is being created.
  • e.Item.ItemType (ListItemType) A value indicating the template type of the row being created.
OnItemCommand(Object sender, RepeaterItemEventArgs e) Raised in the Repeater control when an embedded control raises an event.

Properties of the e events argument object:

  • e.Item (RepeaterItem)
  • e.Item.DataItem (Object)
  • e.Item.ItemIndex (Integer) The "row" of the Repeater control in which the embedded control is located.
  • e.Item.ItemType (ListItemType) A value indicating what template type the embedded control is in.

Remarks

The Repeater control has no built-in layout or styles; you must explicitly declare all HTML layout, formatting, and style tags within the control's templates. For example, to create a list within an HTML table, you can declare the <TABLE> tag in the HeaderTemplate, a table row (<TR> tags, <TD> tags and data-bound items) in the ItemTemplate, and the </TABLE> tag in the FooterTemplate.

The Repeater control has no built-in selection or editing support. You can create a handler for the control's OnItemCommand event to process control events that bubble from the templates to the control.

The control binds its Item and AlternatingItem templates to a data structure referenced in the control's DataSource property. (The Header, Footer, and Separator templates cannot be bound to data.) If the Repeater control's DataSource is set but no data is returned, the control renders the Header and Footer templates, but no items. If DataSource is not set, the Repeater control is not rendered at all.

Example

To be supplied.

See Also

Web Forms Controls by Function |


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

RequiredFieldValidator Control

[To be supplied.]


Table Web Control

The Table control allows you to declare a table and manipulate it programmatically. A Table control can include individual TableRow and TableCell items that declare the contents of the table. You can declare as many table rows and cells as you like.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:Table id="Table1" runat="server"
   BackImageUrl="url"
   CellSpacing="cellspacing"
   CellPadding="cellpadding"
   GridLines="None|Columns|Rows|Both"
   HorizontalAlign="Center|Justify|Left|NotSet|Right"
/>
   <asp:TableRow>
      <asp:TableCell>
         Cell text
      </asp:TableCell>
   </asp:TableRow>
</asp:Table>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
BackImageUrl | The URL of an image to display.
CellPadding The amount of space, in pixels, between the contents of a cell and the cell's border.

When programming, this property is set using Units.

CellSpacing The amount of space, in pixels, between cells.

When programming, this property is set using Units.
GridLines Whether grid lines are displayed in the table. The default is None.
Note Gridlines can be displayed only in browsers that support CSS stylesheets.

When programming, this property is set using the GridLinesRule enumeration.
HorizontalAlign The alignment of the table with respect to surrounding text.

When programming, you set this property using the HorizontalAlign enumeration.
Rows Collection of TableRow objects representing individual rows in the table.

This property is only used when programming. At design time, you set this property by declaring TableRow controls.

Example

[To be supplied.]

See Also

Web Forms Controls by Function |


TableRow Web Control

The TableRow control allows you to declare a table row and manipulate it programmatically.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:TableRow id="eableRow1" runat="server"
   HorizontalAlign="Center|Justify|Left|NotSet|Right"
   VerticalAlign="Bottom|Middle|NotSet|Top"
/>
   <asp:TableCell>
         Cell text
   </asp:TableCell>
</asp:TableRow>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
Cells | Collection of TableCellCollection objects representing individual cells within the row. This property is only used when programming. At design time, you set this property by declaring <asp:TableCell> items.
HorizontalAlign | The default horizontal alignment of the cells in the table row. When programming, you set this property using the HorizontalAlign enumeration.
VerticalAlign | The default vertical alignment of the cells in the table row. When programming, you set this property using the HorizontalAlign enumeration.

Example

To be supplied.

See Also

Web Forms Controls by Function |


TableCell Web Control

The TableCell control allows you to declare a table cell and manipulate it programmatically.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:TableCell id=" " runat="server"
   ColumnSpan="colcount"
   RowSpan="rowcount"
   HorizontalAlign="Center|Justify|Left|NotSet|Right"
   VerticalAlign="Bottom|Middle|NotSet|Top"
   Wrap="True|False"
/>
   Cell text
</asp:TableCell>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
ColumnSpan | The number of columns that this is cell should occupy horizontally.
HorizontalAlign | The horizontal alignment of the contents of the cell. When programming, you set this property using the HorizontalAlign enumeration.
RowSpan | The number of rows that this is cell should occupy vertically.
VerticalAlign | The vertical alignment of the contents of the cell. When programming, you set this property using the HorizontalAlign enumeration.
Wrap | Indicates whether text should wrap within the cell.

Example

[To be supplied.]

See Also

Web Forms Controls by Function |


TextBox Web Control

The TextBox control generates single and multiline text boxes.

ASP+ Syntax

Required properties are noted in boldface type.

<asp:TextBox id=value runat="SERVER"
   AutoPostBack="True|False"
   Colums="characters"
   MaxLength="characters"
   Rows="rows"
   Text="text"
   TextMode="Single | Multiline | Password"
   Wrap="True|False"
   OnTextChanged="OnTextChangedMethod"
>
</asp:TextBox>

Properties

Property | Description
(Base control properties) | The properties defined in Base Web Control Properties.
AutoPostBack | True if client-side changes in the control automatically cause a postback to the server; false otherwise. The default is false.
Columns | The width of the control in characters. This property differs from the Width, which sets the absolute width of the control independent of the character spacing.
MaxLength | The maximum number of characters allowed within text box. This property has no effect unless the TextMode property is set to SingleLine or Password.
Rows | Number of rows within the text box. This property has no effect unless the TextMode property is set to MultiLine.
Text | The text that the user has entered into the box.
TextMode | Indicates whether the text box is in single-line, multiline, or password mode. Possible values are Single, MultiLine, and Password.
Wrap | Indicates whether text should wrap around as users type text into a multiline control. This property has no effect unless the TextMode property is set to MultiLine.

Events

Event (and paramters) | Description
OnTextChanged(Object sender, EventArgs e) | Raised on the server when the contents of the text box change. This event does not cause the Web Forms page to be posted to the server unless the AutoPostBack property is set to true. The e events argument object has no properties.

Example

The following shows a sample declaration for a TextBox control in an .aspx file. The control is multiline with a maximum of 10 lines displayed. The control will display in Arial font if the browser supports fonts for controls. The method txtLogin_Changed is bound to the control's TextChanged method.

<asp:TextBox runat=server
      id="txtLogin"
      Text=""
      Font_Face="Arial" Font_Size="3"
      BackColor="lightblue"
      TextMode="MultiLine"
      Heigth=10
      OnTextChanged="txtLogin_Changed()"
/>

See Also

Web Forms Controls by Function | Applying Styles to Web Forms Controls | Setting Fonts in Web Forms Controls | Creating Event-Handling Methods for Web Forms Controls | Creating Web Forms Controls Programmatically


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

ValidationSummary Control

[To be supplied.]


Web Forms Controls and CSS Styles

You can control the look of controls by setting various appearance properties such as ForeColor, BackColor, Height, and Width. In addition, some controls support style objects that expose additional style-related settings.

The sections below provide information about how style information is rendered to the browser and how to work with styles both at design time and programmatically.

Rendering Appearance Properties to the Browser

When the page runs, appearance properties are rendered according to the capabilities of the user's browser. If the user's browser supports CSS stylesheets, the appearance properties are rendered as style attributes of the HTML elements that make up the control. For example, if you create a Textbox Web control and set its ForeColor property to red, and if the user's browser supports stylesheets, the control is rendered as the following:

<INPUT name="TextBox1" type="Text" id="TextBox1" style="color:red">

On the other hand, if the user's browser does not support styles, the control is rendered using other means, such as a <FONT> element. The following shows the rendering for the example from above, but for a browser that doesn't support styles:

<INPUT name="TextBox1" type="Text" id="TextBox1">

Other examples of properties that are rendered differently depending on the browser are BorderWidth and BorderColor.

Some appearance properties, such as BorderStyle cannot be rendered without using styles. These properties are therefore ignored in downlevel browsers.

Control Style Objects

In addition to simple appearance properties such as ForeColor and BackColor, controls expose one ore more style objects that encapsulate additional appearance properties. One example is the Font style property, which exposes an object of type FontInfo containing individual properties pertaining to the font, such as Size, Name, Bold, and so on.

Some controls expose style objects you can use to set the appearance of specific portions of the control. For example, the Calendar Web control contains style objects such as DayStyle (individual days), SelectedDayStyle (a day, week, or month selected by the user), and WeekendDayStyle. Using the SelectedDayStyle style object, for example, you can set the BackColor and ForeColor properties of the day selected by the user.

Most style objects are of type TableItemStyle, because they are setting the attributes of table cells. The Font style object is of style FontStyle.

Precedence and Inheritance of Style Objects

In complex controls, style objects often inherit characteristics from other style objects. For example, in the Calendar Web control, the SelectedDayStyle object is based on the DayStyle object. If you don't explicitly set any properties for SelectedDayStyle, it inherits its characteristics from DayStyle.

This inheritance means that style-object properties you set have precedence. For example, the following list shows the precedence of style-object properties for the Calendar Web control, with the highest precedence given to the settings on the object last on the list.

  1. Appearance properties of the base Calendar control
  2. DayStyle style object
  3. WeekendDayStyle style object
  4. OtherMonthDayStyle style object
  5. TodayStyle style object
  6. SelectedDayStyle style object

Controlling CSS Styles and Classes Directly

In addition to the appearance properties and style objects, controls expose two properties that allow you to manipulate CSS styles more directly: CSSStyle and CSSClass. By setting the CSSStyle property, you can set a string of style attributes to be written as-is to the control. Using the CSSStyle property allows you to set style attributes that are not exposed via other properties.

Settings you make in the CSSStyle property are not reflected in the corresponding individual appearance property. For example, if you set a string background-color:red in the CSSStyle property, the BackColor property is not also set to red. Furthermore, if you set both appearance properties and the CSSStyle property, the individual appearance properties take precedence over the CSSStyle property.

The CSSClass property allows you to assign a stylesheet class to the control.

See Also

Web Forms Server Controls


Web Forms Controls Templates

All Web Forms controls have a default look and layout, which you can manipulate by setting properties or by using styles. Some Web controls also allow you to customize their look by using templates.

A template is a set of HTML elements an controls that make up the layout for a particular portion of a control. For example, in the DataList Web control you can use a combination of HTML elements and controls to create the layout for each row of the list.

has a default look for rows in the grid. However, you can customize the grid's look by defining different templates for individual rows, alternating rows, selected rows, and so on.

Note Templates differ from styles. Styles specify the appearance of elements such as color, font, and so on within a control's layout. You can use styles without using templates, if all you want to do is change the appearance of the default layout of a control. You can also use styles with a template to control the appearance of elements that you define within your templates.

Templates consist of HTML and even embedded Web Forms controls. When the control runs in the Web Forms page, the control framework renders the contents of the template in place of the default HTML for the control.

Which Controls Support Templates?

Not all Web Forms controls support templates. For the most part, templates are supported by complex controls. In Visual Studio, this includes the DataGrid, DataList, and Repeater Web controls. (In fact, the Repeater control not only supports templates, but requires that you create a template to define its output.) Controls that you acquire from other sources might also support templates.

Each control supports a slightly different set of templates that specify layouts for different portions of the control, such as the header, footer, item, and selected item. You can specify a template for any or all of these, depending on which ones you want to customize. In the DataGrid control, you can specify templates for columns (rather than rows).

The following table summarizes the Web Forms controls that support templates

Control Templates
Repeater
  • HeaderTemplate
  • FooterTemplate
  • ItemTemplate
  • AlternatingItemTemplate
  • SeparatorTemplate
DataList
  • ItemTemplate
  • AlternatingItemTemplate
  • SeparatorTemplate
  • SelectedItemTemplate
  • EditItemTemplate
  • HeaderTemplate
  • FooterTemplate
DataGrid
  • HeaderTemplate
  • FooterTemplate
  • Pager
  • ItemTemplate (column)
  • EditItemTemplate (column)

Creating Templates

You can create templates directly in the .aspx file. Templates are created as XML declarations. A special element— the <template> element— contains the information to be rendered for the template. The template type (for example, ItemTemplate) is provided as the NAME attribute of the template element.

The following example shows how you can display a list of employee names, phone numbers, and email addresses using templates in the DataList control. The layout of the employee information is specified in the ItemTemplate template using data-bound controls.

<asp:DataList ID="DataList1" runat="Server">
  <template name="HeaderTemplate">
    Employee List
  </template>
  <template name="ItemTemplate">
    <asp:Label runat=server
      Text="<%# Container.DataItem.EmployeeName %>" />
    <asp:Label runat=server
       Text="<%# Container.DataItem.PhoneNumber %>" />
    <asp:HyperLink runat=server
       Text="<%# Container.DataItem.Email %>"
       NavigateURL="<%# Container.DataItem.Link %>" />)
   </template>
</asp:DataGrid>

See Also

Web Forms Server Controls | Web Forms Controls and CSS Styles | Web Forms Data Binding


Creating Web Control Templates

Some Web controls allow you to specify templates, which are HTML elements that define the layout for a particular portion of a control. For example, in the DataGrid Web control, you can define templates for items, selected items, alternating items, and so on, so that each of these elements can have a custom look.

Note For background information about templates, see Web Forms Controls Templates.

The procedure for creating templates is the same for all controls that support templates.

You can create templates by editing the control directly in the .aspx file.

Note For details about what templates are supported in individual controls, see [xref].

To create a Web control template using ASP+ syntax

  1. In the .aspx file, insert a <template> element inside the control, using the NAME attribute of the element to identify what template you are creating:
    <asp:DataList id="DataList1" runat="server">
   <template name="ItemTemplate">
   </template>
</asp:DataList>
  2. Inside the <template> element, add HTML text and other controls as the template's content. Include property and data-binding values for the embedded controls using normal syntax:
    <template name="ItemTemplate">
   Name: <asp:Label runat="server"
          Text="<%# Container.DataListItem.Name %>"/>
</template>
  3. Repeat Steps 1 and 2 for each template you want to create.

    The following example shows a complete declaration for a DataList Web control with simple templates declared for the Header, Item, and Separator templates:

    <asp:DataList id="DataList1" runat=server
   datasource="<%# MyCollection %>" >
   <template name="Header">
      Items matching your query:
   </template>
   <template name="Item">
      Name: <asp:Label runat="server"
             Text="<%# Container.DataListItem.Name %>" />
   </template>
   < template name="Separator"
      <BR><HR>
   </ template >
</asp:DataList>

See Also

Web Forms Controls Templates | Web Forms Server Controls


Web Forms Data Binding

The Web Forms page framework provides an easy-to-use, flexible means of binding controls to information in a data store. The approach used in Web Forms allows you to bind any control property to information in almost any kind of data store, from the simplest to the most sophisticated, and gives you virtually complete control over how data moves from the data store to the page and back again.

Data binding in Web Forms works somewhat differently than it does in Windows Applications and differently than in previous versions of products such as Visual Basic®. This topic provides background information that you will find helpful when designing data binding for your Web Forms pages.

Data Classes in Web Forms

Data is made available to Web Forms pages in the form of classes that expose the raw data as properties and that typically define methods for updating the underlying data store. These classes incorporate a data adapter to interact with a database, the data sets themselves, properties to retrieve and set the data in the data set, and methods to write the data back to a database. You can create instances of the resulting data classes and use their members to retrieve, navigate, and update the data.

In fact, Web Forms pages can take advantage of virtually any publicly available information. Simple controls — those that bind to a single value — can bind to public property on the page, whether from another control or the page itself.

Complex controls that include embedded controls (such as the Repeater, DataList, and DataGrid Web controls) can bind to any structure that implements the ICollection interface. This interface guarantees that the data class provides a basic means of data access and navigation. You can use a structure as simple as an array by implementing it using a class that incorporates the ICollection interface, such as the ArrayList class.

The concept of what "data" means for data binding in the Web Forms page framework is therefore very broad. This gives you great flexibility in what you can bind to as data. Of course, some data classes provide considerably more functionality than others, which you can take advantage of in your own code. But the basic functionality of data binding in Web Forms is not tied to a specific level of data class functionality. The result is a clean distinction between data and data binding, and as a consequence, flexibility for you.

Binding Controls to Data

You establish data binding for controls with these basic steps:

  • For complex list controls, set the control's DataSource property to reference a data class.
  • For all controls, bind a control property using a data binding expression. When the expression is evaluated, it provides a value that is loaded into the bound property. The expression typically references a data property. However, it can refer to any public property or field on the page.

The syntax of a data binding expression is simple. In the Web Forms page (.aspx) file, the expression is embedded as part of the property in the control declaration. A data binding expression used to set the Text property of a Label Web control might look like the following:

<asp:Label runat=server Text='<%# CustList(0).FirstName %>'/>

The data binding expression itself is delimited with <%# and %> tags. Within the tags, the expression refers to a public source of information. If the expression references a member of a data class, the expression must specify as much of the class hierarchy as necessary to evaluate to a single data item. You can optionally provide formatting information to specify an appropriate string format for the data being bound.

In complex controls such as the Repeater, DataList, and DataGrid controls, the control can act as a container for other controls that actually display data. For example, a Repeater control might contain a template with a Label control to display customer names. When the Repeater runs, it repeats the contents of its template once for every record in the customer data class, and each Label then displays the next customer name.

The data binding expression in that case is similar to that for single controls. The following example shows the complete definition of the Repeater control, including the template with the Label control.

<asp:repeater id="MyRepeater" DataSource="CustomersClass" runat=server>
   <template name="itemtemplate">
      <asp:Label ID="Label1" runat="server"
          Text="<%#Container.CurrentCustomer.Name%>">
      </asp:Label>
   </template>
</asp:repeater>

Performing Data Binding

The Web Forms page framework does not automatically evaluate data binding expressions. Instead, the expressions are evaluated only when the DataBind method is called. This method is supported by each control and by the page as a whole; when the method is called for the page, it is cascaded through the page hierarchy, calling the DataBind method for each control.

The advantage of having an explicit method to evaluate the data binding expression is that you can control when the controls are loaded with data. Typically, you do this at two times:

  • At the Page_Load stage of page processing, immediately after creating instances of the data classes. At this stage, all the controls have been created and you can access their properties. You can call the DataBind method for the page as a whole or for a specific control, such as a complex control.
  • In event handlers that have caused a change to the data set. In this case, you want to refresh the controls with the updated data. You might call the DataBind method either for a single control or for the entire page, depending on whether you need to refresh one or all controls.

Updating Data Sets

A majority of Web application data scenarios do not involve data update. Instead, the Web page displays data for information only or displays it with hyperlinks. The binding performed with data binding expressions is therefore one-way — it reads data from a data class and loads it into control properties. If you want to write data back to a data store, you must do so yourself.

A common scenario is to update the data store in an event handler. You extract the values from the controls and then update the data store. If the data source is an ADO+ data set, you update the current record and then call an ADO+ update method, which causes the data to be written through to the underlying database.

See Also

Web Forms Server Controls


Using Web Forms Controls

The following topics detail how to use write code for Web Forms controls.

Writing Event Handlers for Web Forms Controls

Databinding to Web Forms Controls

Adding a Web Forms Control to a Page

Setting Properties on Web Forms Controls

See Also

ASP+ Web Forms


Writing Event Handlers for Web Forms Controls

You can create event-handling methods and server control events to them using ASP+ syntax. In order to do so, you must know:

  • The name or names of the events supported by the controls you are working with.
  • The appropriate event-handling signature for the event you want to handle. Most events have the same signature. However, the handler for some events requires a specific event argument.

To create event handlers using ASP+ syntax

  1. In the declarative tag for the control, assign a method name to the name of the event:
    <asp:Button id="Button1" runat="SERVER" OnClick="MyHandler" />
    Note Remember that any control must be between opening and closing tags of an HtmlForm control, <form runat="server"></form>.
  2. Create an event-handling method in your page code. The method can be named anything, but must have a signature similar to the following:
    [Visual Basic]
Private Sub methodname(sender As Object, e as EventArgs)

    In C#, it must have a signature such as the following:

    [C#]
protected void methodname (object sender, EventArgs e)
    Note The type of the second argument depends on the control for which you are creating a handler. For example, the OnVisibleMonthChanged event of the Calendar Web control requires an argument derived from the MonthChangedEventArgs class. For details, refer to the reference documentation for the control you are working with.

The following example shows a simple but complete file that contains a button and a label. The button's OnClick event is bound to the Button1_OnClick method, which displays text in the label.

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Collections" %>
<HTML>
<HEAD>
    <SCRIPT RUNAT="SERVER">
    protected void Button1_OnClick(object Source, EventArgs e){
        Label1.Text="You clicked the button";
    }
    </SCRIPT>
</HEAD>
<BODY>
    <H3>Simple &lt;asp:Button&gt; Sample</H3>
    <FORM RUNAT=SERVER>
        <asp:Button id=Button1 runat="server"
           Text="Button1"
           onclick="Button1_OnClick" />
        &nbsp;&nbsp;
        <asp:Label id=Label1 runat=server />
    </FORM>
</BODY>
</HTML>

See Also

Web Forms Server Controls |


Databinding to Web Forms Controls

ASP+ provides hierarchical databinding that allows page developers to declaratively associate databinding expressions between server control properties and data sources. Web Forms databinding allows you to bind a server control property to any public field or property on the page.

ASP+ databinding expressions are read-only.

Examples

The following example shows how a text box server control can be databound to a list of values contained in an ArrayList object:

<html>
   <script language="C#" runat=server>
      public void Page_Load(Object sender, EventArgs e) {
         ArrayList myList = new ArrayList();
         myList.Add("DataItem One");
         myList.Add("DataItem Two");
         myList.Add("DataItem Three");
         MyRepeater.DataSource = myList;
         MyRepeater.DataBind();
      }
   </script>
   <body>
      <asp:repeater id="MyRepeater" runat=server>
         <template name="itemtemplate">
            <input type=text value="<%#Container.DataItem%>" runat=server>
         </template>
      </asp:repeater>
   </body>
</html>

In the code above, the Page_Load method creates and populates an ArrayList object named myList with three strings. The ArrayList object is assigned to be the datasource for the Repeater server control named MyRepeater:

MyRepeater.DataSource = myList;

The MyRepeater object's itemtemplate contains a textbox server control that will display the ArrayList values. The value attribute of the textbox is a databinding expression:

<input type=text value="<%#Container.DataItem%>" runat=server>

In the databinding expression, Container refers to the first parent control in the immediate control hierarchy that supports the INamingContainer Interface interface. In this case this is the RepeaterItem object. As RepeaterItem iterates through its ArrayList datasource, each value of the ArrayList is obtained by reference to the DataItem property.

Databinding Expression Evaluation and the DataBind Method

In the example above, the Page_Load method calls the DataBind method on the MyRepeater object after assigning its DataSource:

MyRepeater.DataSource = myList;
MyRepeater.DataBind();

The page developer must explicitly call the DataBind method on a databound server control to invoke proper runtime evaluation of any databinding expressions contained in that control's child controls. In the example, the MyRepeater control has one child control, the textbox. In a more complex scenario, the databound server control might contain several child controls. Depending on the scenario, you may need to call the DataBind method more than once.

See Also

Web Forms Server Controls | Web Forms Data Binding


Adding a Web Forms Control to a Page

Whether creating new server controls or adding Web Forms functionality to a previously designed Web page, adding server controls to a page is simple.

The following example shows how one can declaratively add three instances of the <asp:label> control to a Web Forms page.

<html>
   <body>
      <h3><font face="Verdana">Declaring Server Controls</font></h3>
      <p>The following are instances of the Web Forms Label Web control.
      <hr>
      <asp:label id="Message1" font-size="16" font-bold="true" forecolor="red" runat=server>This is Message One</asp:label>
      <br>
      <asp:label id="Message2" font-size="20" font-italic="true" forecolor="blue" runat=server>This is Message Two</asp:label>
      <br>
      <asp:label id="Message3" font-size="24" font-underline="true" forecolor="green" runat=server>This is Message Three</asp:label>
   <body>
</html>

Each instance of the control has different values for the properties declared on them. Notice that these properties are declared in the same way that standard HTML attributes are.

Any Web Form control can be added to a page by simply declaring them directly in a text file, or an HTML designer such as Microsoft® FrontPage.

See Also

Server Controls | Adding a Server Control to a Page Programmatically | Setting Properties on Server Controls


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Adding a Web Forms Control to a Page Programmatically

[To be supplied.]


Setting Properties on Web Forms Controls

Setting properties on server controls is as simple as declaring attributes when writing standard HTML. In fact, for HTML server controls, all attributes supported for an HTML element are treated as properties on a control.

The Web Forms page framework defines properties for specific controls. These can be declared in the same way as HTML attributes. These can also be changed dynamically when a page is loaded or in response to user input.

In the following example, the font-size, font-bold, and forecolor properties of the Label control are set declaratively.

<asp:label id="Message1" font-size="16" font-bold="true" forecolor="red" runat=server>This is Message One</asp:label>

This declarative method of setting property values also works for HTML Controls. In the following example, the src, alt, and align properties are set on this HtmlImage control.

<img id="image1" src="blank.jpg" alt="This is a blank image." align="right" runat="server">

See Also

Web Forms Server Controls |


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Setting HTML Control Properties Programmatically

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Setting Web Forms Controls Style Properties

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Setting Web Forms Controls Style Properties Programmatically

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Generating Text Dynamically Using the <span> Element

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Modifying the Appearance of a Button

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Setting the Encoding Type for Form Data Posted to the Server

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Redirecting a Form POST to Another Browser Window or Frame

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Overriding the Action Attribute for a Form POST

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Aligning an Image with Surrounding Text

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Tracking Changes to a Check Box

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Checking the Contents of a Text Box

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Uploading a File to the Server

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Updating User Information Using Hidden Text

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Aligning an Image Button with Surrounding Text

To be supplied.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Tracking Changes to a Radio Button

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Databinding to an HtmlSelect Control

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Adding <option> Elements to a Select List

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Managing <option> Elements at Runtime

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Setting the Selected <option> Element at Runtime

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Setting the HtmlSelect Control for Multi-selected <option> Items

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Databinding to the HtmlTable Control

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Adding a Cell to a Table

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Getting and Setting Table Cell Values

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Adding a Row to a Table

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Tracking Changes to a Text Area

[To be supplied.]


Using the DataList Web Control

The DataList Web control provides you with a flexible, full-featured control for displaying information from data sources.

The following topics provide information that you will find useful in learning to work with the DataList control.

For information about | See
Background information on the DataList control, including what you can do with it and an overview of its features. | Introduction to the DataList Web Control
How to add a DataList Web control to a Web Forms page and set the required properties to make it work. | Adding a DataList to a Web Forms Page
How to display the list in either flow or table format. | Specifying List Format
How to control how many columns the list has and how the items are ordered within columns. | Specifying List Layout
How to provide a Select button which users can click to highlight an item. | Allowing Users to Select Items
How to add editing features to items in the list. | Allowing Users to Edit Items
How to allow users to delete either individual items or a batch of items. | Allowing Users to Delete Items
How to add buttons to individual items that invoke custom functionality for list items. | Responding to Button Events in DataList Items
How to dynamically control the contents and appearance of items. | Customizing DataList Items at Run Time

See Also

ASP+ Web Forms | Web Forms Server Controls


Introduction to the DataList Web Control

The DataList control displays database information in a format that you can control very precisely using templates and styles. The DataList control is useful for displaying rows of database information. Optionally, you can configure the DataList control to allow users to edit or delete information. You can also customize the control to support add other functionality such as selecting rows.

Note To display read-only database information, you can also use the Repeater Web control. If youe want to include paging and sorting automatically, you can use the DataGrid Web control.

The following sections introduce you to the features of the DataList control.

Data Binding

The DataList control must be bound to a data source. If the control is not bound, the control will not be rendered on the page. The DataList control can use as a data source any class that supports the IEnumerable interface. In practical terms, this means that you can use any type of data source that you can create using the Visual Studio data designers. You can also use data sources that you can add to your page from the Toolbox, such as the SQLDatSetCommand and ADODataSetCommand classes. You can also bind to simpler structures, such as an array of type ArrayList.

When data binding, you specify a data source for the DataList control as a whole. You can then bind individual controls within the list — for example, labels or text boxes in list items — to the DataList control as the container.

For more information, see Web Forms Data Binding.

Templates for Control Layout

In the DataList control, you define the layout of your information using templates. The control supports the following templates:

  • ItemTemplate The elements to render once for each row in the data source.
  • AlternatingItemTemplate Like the ItemTemplate element, but rendered for every other row in the DataList control. If you use this template, you typically create a different look for it, such as a different background color than that of the ItemTemplate.
  • SelectedItemTemplate The elements to render when the user selects an item in the DataList control. Typical uses are to visually mark the row using a background or font color. You can also expand the item by displaying additional fields from the data source.
  • EditItemTemplate The layout of an item when it is edit mode. This template typically contains editing controls, such as TextBox controls.
  • HeaderTemplate and FooterTemplate The text and controls to render at the beginning and end of the list.
  • SeparatorTemplate The elements to render between each item. A typical example might be a line (using an <HR> element).

As a minimum, you must define an ItemTemplate layout. To support editing, you would add an EditItemTemplate. Other templates are optional.

For more information, see Web Forms Controls Templates.

Styles

To specify the appearance of items in a template, you can set its styles. For example, you might specify that:

  • Items are black text on a white background
  • Alternating items are black text on a light gray background
  • The selected item is bold black text on a yellow background
  • Items being edited are black text on a light blue background

Each template supports its own style object whose properties you can set both at design time and at run time.

For more information, see Web Forms Controls and CSS Styles.

Item Layout

Not only can you use templates to specify the layout of controls and text within individual items, but you can specify how the items are laid out with respect to one another. The DataList control offers you these options:

  • Flow versus table In flow layout, list items are rendered inline, in the fashion of a word-processing document. Flow layout uses HTML <SPAN> elements and is suitable for uncomplicated lists. However, some browsers do not support setting properties for <SPAN> elements.

    In table layout, the items are rendered into an HTML table, which gives you more facilities for specifying the look of items, because it allows you to set properties for table cells, such as gridlines.

  • Vertical versus horizontal ordering By default, items in a DataList control are displayed in a single vertical column. However, you can specify that the control contain more than one column. If so, you can further specify whether the items are ordered vertically (like newspaper columns) or horizontally (like days in a calendar).

Events

The DataList control supports six events. One of them, the OnItemCreated event, gives you a way to customize the item-creation process. For example, you might conditionally set style object properties for new items as they are created.

The remaining events are raised in response to button clicks in list items. They are designed to help you respond to the most commonly used functionality for the DataList control: editing items, deleting them, updating the edited items, and canceling edits.

When a button is clicked in an item, its event is bubbled to the container — the DataList control. The exact event it raises depends on the Command property of the button that was clicked. For example, if the Command property of a button is edit, the button causes an OnEditCommand event to be raised. If the Command property is delete, the button causes an OnDeleteCommand event to be raised, and so on.

The DataList control also supports a generic OnItemCommand event that is raised when any other button click occurs in an item. You can use this event for custom functionality by setting a button's Command property to a value you need and then testing for it in the OnItemCommand event-handling method. (For example, selecting an item, as documented in Allowing Users to Select Items.)

For more information, see Web Forms Event Model.

Editing and Selecting Items

You can allow users to edit individual items in the control. The general strategy is to create an EditItemTemplate that provides the appropriate layout and controls for editing. You must also provide a way for users to indicate that they want to edit the item. The most common way is to include a button in the item template (and AlternatingItemTemplate, if you are using that) and set the button's Command argument to edit. Then when the button is clicked, the DataList control automatically raises the OnEditCommand event. In the event-handling method for that event, you set the item to edit mode, which displays the EditItemTemplate.

The EditItemTemplate usually includes buttons to allow the user to save the changes or discard them (for example, Update and Cancel buttons). These buttons work similarly to the edit button — they send a predefined command message (update and cancel) to the DataList control, which raises OnUpdateCommand and OnCancelCommand events that you can respond to appropriately.

The process for selecting an item is similar, with one exception: there is no specific event corresponding to a selection. You still create a button and set its Command property to something like select. However, because there is no selection event, you write an event-handling method for the generic OnItemCommand event. In that event-handling method, you must test the command, and if it is select, put the item into selection mode.

See Also

Using the DataList Web Control


Adding a DataList to a Web Forms Page

To add a DataList Web control to a page requires several steps. The following procedure describes the minimum that you must do to create a working DataList control.

To add a DataList Web control to a Web Forms page

  1. In Design view, drag a DataList control from the Toolbox onto the page.
  2. Set the control's DataSource and DataKeyField properties.
  3. In Design view, right-click the DataList control, choose Edit Templates, and then choose Item Templates.

    The control is displayed in template-editing mode.

  4. Add HTML text and other controls (Web controls or HTML controls) to the item template. Optionally, define other templates as well.

See Also

  1. Adding Controls to a Page


Specifying List Format

The DataList control allows you to lay out the items in the control in a variety of ways. You can specify that the control renders items in either flow mode (in <SPAN> elements separated by spaces) or in table mode (in an HTML table). Flow mode is suitable for simple layouts. Table mode gives you more precise control over layout and allows you to use table attributes (such as CellPadding).

To specify flow or table format

  • Set the DataList control's RepeatLayout property to Flow or Table. If you are setting the property in code, use the RepeatLayout enumeration, as shown in the following example:
    [Visual Basic]
DataGrid1.RepeatLayout = RepeatLayout.Flow

See Also

Introduction to the DataList Web Control | Specifying List Layout


Specifying List Layout

By default, the DataList control displays a single column. However, you can specify any number of columns. Within the columns, you can specify how the items are ordered: vertically (the default) or horizontally. Vertical layout in three columns results in a layout such as the following:

A    D    G
B    E     H
C    F

Horizontal layout for the same items results in the following layout:

A    B    C
D    E     F
G    H

To specify column count and ordering:

  1. Set the DataList control's RepeatColumns property to the number of columns you want.
  2. Set the RepeatDirection property to Vertical or Horizontal. If you are setting the property in code, use the RepeatDirection enumeration, as shown in the following example:
    [Visual Basic]
DataGrid1.RepeatDirection = RepeatDirection.Horizontal

See Also

Introduction to the DataList Web Control | Specifying List Format


Allowing Users to Select Items

You can specify that users can select individual items in the DataList Web control. Typically, selecting an item highlights it visually. In addition, you might display different information for a selected item.

To allow users to select items in a DataList control

  1. Create a SelectedItemTemplate for the control to include appropriate text and controls, and set the template's styles. For details, see Creating Web Control Templates.
  2. In the ItemTemplate (and AlternatingItemTemplate, if you are using that), add a Button or LinkButton Web control and set its Text property "Select" or something similar.
  3. Set the Command property of the button from Step 2 to select (case-sensitive).
  4. Create an event-handling method for the control's OnItemCommand event. For details, see Writing Event Handlers for Web Forms Controls. In the event-handling method, do the following:
    1. Check the Command property of the event-argument object's CommandSource to see if the term select was passed.
    2. If so, then set the DataList control's SelectedIndex property to the index of the item to be selected. You can get that index value as a property of the event-argument object.
    3. Call the control's DataBind method to refresh the information in the control.
  5. To cancel the selection, set the control's SelectedIndex property to -1.

    The following example shows a complete DataList control declaration. It includes an ItemTemplate that displays a Select button (a LinkButton control) and a product name. The declaration also includes a SelectedItemTemplate that displays the product name and a product order date. The BackColor property for the SelectedItemStyle object is set to yellow. Finally, the OnItemCommand event is bound to the DataList1_OnItemCommand method.

    [C#]
<asp:DataList id="DataList1" runat="server"
   DataSource="[need datasource reference here]"
   SelectedItemStyle-BackColor="yellow"
   OnItemCommand="DataList1_OnItemCommand"
   >
      <template name="ItemTemplate">
         <asp:LinkButton id="button1" runat="server"
         Text="Select"
         Command="select"
      />
      <%# DataBinder.Eval(Container, "DataItem.ProductName") %>
     </template>
     <template name="SelectedItemTemplate">
         Item:
         <%# ((DataRowView)Container.DataItem)["ProductName"]  %>
         <br>
         Order Date:
         <%# DataBinder.Eval(Container, "DataItem.OrderDate",
            "{0:d}") %>
         <br>
     </template>
</asp:DataList>

    The following example shows the event-handling method for the DataList control's OnItemCommand event. It checks whether the user clicked the Select button defined in the control. If so, it sets the control's SelectedIndex property to the index of the item clicked, which causes the control to display the SelectedItemTemplate for that item.

    protected void DataList1_ItemCommand(object Source,
      DataListCommandEventArgs e)
{
   // See if the "select" button was clicked
   string cmd = ((LinkButton)e.CommandSource).Command;
   if (cmd == "select"){
      // Set the SelectedIndex property to the index of the item
      // where the user clicked.
         DataList1.SelectedIndex = e.Item.ItemIndex;}
   DataList1.DataBind();
   }
}

See Also

Introduction to the DataList Web Control | Allowing Users to Delete Items | Allowing Users to Edit Items | Responding to Button Events in DataList Items


Allowing Users to Edit Items

You can specify that users can edit individual items in the DataList Web control. When an individual item is put into edit mode, its editable values are usually displayed in text boxes or other controls that the user can change values in.

To allow users to select items in a DataList control

  1. Create an EditItemTemplate for the control to include appropriate text and controls, and set the template's styles. For details, see Creating Web Control Templates and Customizing Control Appearance Using Styles.
  2. In the ItemTemplate (and AlternatingItemTemplate, if you are using that), add a Button or LinkButton Web control and set its Text property to "Edit" or something similar. Set the Command property for this button to edit (case-sensitive).
  3. Create an EditItemTemplate for the DataList control that includes the following:
    • Controls for all the values that the user can change. For example, include TextBox controls for all character and numeric data.
    • A Button or LinkButton control with the text "Update" whose Command property is set to update (case-sensitive).
    • A Button or LinkButton control with the text "Cancel" whose Command property is set to cancel (case-sensitive).

    The Update button will allow users to specify that they are finished editing. The Cancel button will allow them to quit editing without saving changes.

  4. Create an event-handling method for the control's OnEditCommand event. In the event-handling method, do the following:
    • Set the DataList control's EditItemIndex property to the index value of the item to put into edit mode. The index of the item the user clicked is available via the Item object's ItemIndex property.
    • Bind the control to its data source.

    The event-handling method might look like this:

    [C#]
protected void DataList1_OnEditCommand(object Source,
   DataListCommandEventArgs e)
{
   DataList1.EditItemIndex = (int)e.Item.ItemIndex;
   DataList1.DataBind();
}
  5. Create a method for the OnUpdateCommand event that reads the values of the editing controls in the current item and writes them back to the data source. To get the value of a specific control in the item, use the FindControl method of the Item event-argument object. After updating the data source, switch the item back out of editing mode by setting the EditItemIndex property to -1, and then rebind to the data source:
    [C#]
protected void DataList1_OnUpdateCommand(object Source,
   DataListCommandEventArgs e)
{
   string name = ((TextBox)e.Item.FindControl("txtName")).Text;
   string qty = ((TextBox)e.Item.FindControl("txtQty")).Text;
   string price = ((TextBox)e.Item.FindControl("txtPrice")).Text;

   // Update data source here

   DataList1.EditItemIndex = -1; // Switch out of edit mode
   DataList1.DataBind();
    }
  6. Create a method for the OnCancelCommand event that sets the EditItemIndex property to -1, and then rebind to the data source:
    protected void DataList1_OnCancelCommand(object Source,
   DataListCommandEventArgs e)
{
   DataList1.EditItemIndex = -1; // Switch out of edit mode
   DataList1.DataBind();
}

See Also

Introduction to the DataList Web Control | Allowing Users to Delete Items | Allowing Users to Select Items | Responding to Button Events in DataList Items


Allowing Users to Delete Items

You can allow users to delete items in a DataList control in a variety of ways. One way is to include a Delete button in an item and to delete that item immediately when the user clicks it.

Another way is to include a checkbox in individual items. Users can then check all the items they want and then click a separate Delete button to delete them in a batch. This is a style used in products such as Microsoft Hotmail.

To allow users to delete items individually

  1. In the ItemTemplate (and AlternatingItemTemplate, if you are using that), add a Button or LinkButton Web control and set its Text property to "Delete" or something similar. Set the Command property for this button to delete (case-sensitive).
  2. Create a method for the OnDeleteCommand event. In the method:
    • Delete the item from the data source and then rebind the DataList control. The index of the item the user clicked is available via the Item object's ItemIndex property. To get the value of individual controls, use the FindControl method of the Item event-argument object.
    • Rebind the control to its data source.

    The event-handling method might look like this:

    [C#]
protected void DataList1_OnDeleteCommand(object Source,
   DataListCommandEventArgs e)
{
   string ID = ((TextBox)e.Item.FindControl("CustomerID")).Text;

   // Delete item from data source here using ID extracted earlier

   DataList1.DataBind(); // Rebind to data source
}

To allow users to delete multiple items in a batch

  1. In the ItemTemplate (and AlternatingItemTemplate, if you are using that), add a CheckBox Web control and set its ID property. Make sure that the AutoPostBack property of the CheckBox control is not true.
  2. Somewhere other than in the DataList control, include a button or similar control to act as the Delete button.
  3. Create a method for the OnClick event of the Delete method. In the method:
    1. Loop through the Items collection of the DataList control, extracting each item in turn.
    2. Within the item, use the item's FindControl method to get the CheckBox control from Step 1 and test its Checked property.
    3. If the box is checked, delete the corresponding item from the data source.
    4. Rebind the DataList control to its data source.

    The following example shows how you can create an event-handling method for a Delete button that deletes items in a batch using the procedure outlined above. The example assumes that you are working with a DataList control called DataList1 and that the CheckBox control in each item is called Check1.

    protected void btnDelete_Click(object sender, System.EventArgs e)
{
   int i;
   for(i=0; i< DataList1.Items.Count; i++){
      DataListItem NextItem = DataList1.Items[i];
      Boolean DelFlag = ((CheckBox)NextItem.FindControl("Check1")).Checked;
      if(DelFlag == true){

         // Delete item from data source here

      }
      DataList1.DataBind(); // Rebind to data source
   }
}

See Also

Introduction to the DataList Web Control | Allowing Users to Edit Items | Allowing Users to Select Items | Responding to Button Events in DataList Items


Responding to Button Events in DataList Items

If your DataList control templates include Button, LinkButton, or ImageButton Web controls, then these buttons can bubble their click events to the container control — that is, to the DataList control. This allows you to include buttons for functions not already defined for the DataList control (edit, delete, update, and cancel) and define your own functionality for the list.

Note Only controls that support an OnCommand event can bubble their events to the DataList control. These include the ones listed above.

To respond to button events in DataList items

  1. Include a Button, LinkButton, or ImageButton in a DataList template. Set the button's Command property to a string that identifies its function, such as "sort" or "copy".
  2. Create a method for the OnCommandEvent event of the DataList control. In the method, do the following:
    • Check the Command property of the event-argument object's CommandSource to see what string was passed. If you are testing for a number of possible strings, use a Select Case (Visual Basic) or switch (C#) statement or equivalent.
    • Perform the appropriate logic for the button that the user clicked.

    The following example shows how you can respond to a button click in a DataList control. In the example, the ItemTemplate contains an ImageButton control that displays a shopping cart. The button sends the command AddToCart. The OnItemCommand handler determines what button was clicked, and — if it was the shopping cart button — performs the appropriate logic. The following shows the declaration of the DataList commmand in HTML view, including the ItemTemplate with the ImageButton control:

    <asp:DataList
      id="DataList1"
      runat=server
      OnItemCommand="DataList1_OnItemCommand"
      >
    <template name="ItemTemplate">
         <%# DataBinder.Eval(Container.DataItem, "ItemName")  %>
         &nbsp;
         <asp:ImageButton runat="server"
            id="btnShopCart"
            Command="AddToCart"
            ImageURL="shopcart.gif"
         />
    </template>
    </asp:datalist>

    The following shows the event-handling method for the OnItemCommand method:

    [C#]
protected void DataList1_OnItemCommand(object Source,
   DataListCommandEventArgs e)
{
   string cmd = ((ImageButton)e.CommandSource).Command;
   if(cmd == "AddToCart"){
      int RowIndex = (int)e.Item.ItemIndex;

      // Logic here to add item to shopping cart
   }
}

See Also

Introduction to the DataList Web Control | Allowing Users to Delete Items | Allowing Users to Edit Items | Allowing Users to Select Items


Customizing DataList Items at Run Time

During page processing, the DataList control creates individual items that it will render to the page. To allow you to customize the items as they are being created, the DataList control raises an event. By responding to this event, you can change the items. A typical use is to change the appearance of the item based on the data being displayed in it. For example, if a numeric value you read from a database is negative, you might set the item's background color to red.

To customize items at run time

  1. Create an event-handling method for the DataList control's OnItemCreated event, using a method signature similar to the following:
    [Visual Basic]
Private Sub DataList1_ItemCreated(ByVal sender as Object, _
   ByVal e as DataListItemCreatedEventArgs)
[C#]
protected void DataList1_OnItemCreated(Object sender,
   DataListItemCreatedEventArgs e)
    Note The OnItemCreated event is raised for all items in the list, including the header and footer. (The event is raised for these items even if you did not create templates for them, in case you want to create them dynamically.) If you do not want to handle the event for certain item types, you can test for those types, as explained in the next step.
  2. In the event-handling method, access the current item using the Item object from the e event-argument object. The index of the current item is available via the Item object's ItemIndex property, and its type is available via its ItemType property (which is defined using the ListItemType enumeration).
    Note The ItemIndex property returns a positive value only for Item, AlternatingItem, and SelectedItem objects only. The index value for header, footer, and separator items is -1.

    The following example shows how you can modify the background color of an item conditionally. The example uses the ItemType property to make sure that the template being rendered is not a header, footer, or separator. It then determines what item number is being rendered. If it is an even-numbered item (that is, if the modulus of the item index and 2 is zero), the method sets the item's background color to yellow. To show its progress, the method displays a message in a Label Web control for every item it creates.

    [C#]
protected void DataList_OnItemCreated(object Source, DataListItemCreatedEventArgs e)
{
   if( e.Item.ItemType == ListItemType.Item ||
         e.Item.ItemType == ListItemType.AlternatingItem){
      if ( (e.Item.ItemIndex % 2) == 0 ){
         e.Item.BackColor = Color.LightGoldenrodYellow;}
      Label1.Text = Label1.Text + "Creating item " + e.Item.ItemIndex
         + " (" + e.Item.ItemType + ")" + "<BR>";
   }
}

At the time that the OnItemCreated event is raised, the individual controls in the templates have not yet been bound to data. You therefore cannot base a condition on the bound data for a control. However, the data is available, and you can use a data-binding expression to extract the data that will be bound to the control and base a comparison on that.

The following example shows how you can make a conditional test based on data. The example extracts a field — the Quantity field — from the current record from the data source. If the quantity is less than zero, the item in the DataList control is colored red.

 [C#]
protected void DataList_OnItemCreated(object Source,
   DataListItemCreatedEventArgs e)
{
   if( ((Int32)DataBinder.Eval(e.Item.DataItem, "Quantity")) < 0){
      e.Item.BackColor = Color.Red;
   }
}

See Also

Introduction to the DataList Web Control


Using the Calendar Web Control

The Calendar Web control displays a traditional one-month calendar on your Web Forms pages. Users can use the calendar to view and select dates.

The following topics provide information about the Calendar Web control and how to use it.

For information about | See this topic
What the Calendar control is and what you can do with it. | Introduction to the Calendar Web Control
Changing the fonts, colors, size, and other appearance features of the calendar as a whole. | Customizing Calendar Web Control Appearance
Customizing the look of specific areas of the calendar, such as today's date, weekend days, the title bar, and so on. | Formatting Calendar Elements Using Styles
Specifying whether users can select a day, a week, or the whole month. | Controlling User Date Selection
Specifying whether users can move from month to month. | Controlling Month Navigation
Changing the content or look of individual days programmtically, such as to create appointment information. | Customizing Individual Days
Running code in response to the user selecting one or more dates. | Responding to Date Selection
Adjusting the current date to accommodate different timezones on the server and client. | Setting Today's Date Programmatically
Setting and clearing selected dates in code. | Selecting Dates Programmatically

See Also

Web Forms Server Controls


Introduction to the Calendar Web Control

The Calendar Web control can serve these purposes:

  • Display and select dates The control displays a calendar that users can navigate in to see any date in any year. The current date is highlighted. Optionally, users can move to arbitrary dates by clicking a day and moving from month to month. The calendar can be configured to allow users to select multiple dates, either an entire week or an entire month.

    Typical uses are to graphically display the current date on a daily news Web page, or to allow users to select dates in scheduling applications such as ordering airline reservations, concert tickets, and so on.

  • Display appointment or other information in a calendar grid The Calendar Web control can display day-specific details for individual days, such as a to-do list, a schedule of events, or similar information. This feature allows you to display day information from a database.

The Calendar Web control is based around the NGWS DateTime object, and therefore supports the full range of dates allowed by that object. Effectively, you can display virtually any date between the years 0 and 9999 A.D.

The Calendar control is rendered as an HTML table when the Web Forms page runs. Therefore, a number of the control's properties pertain to various aspects of table formatting. A few of these properties are not fully supported in some older browsers, so some formatting features will not be available.

Developing with the Calendar Web Control

The following sections contain details about how the Calendar Web control works and an overview of tasks you can accomplish with it.

Displaying and Selecting Dates

The Calendar Web control displays a month of dates at a time, with a total of six weeks appearing at once. Within the control, there are several types of dates that you can work with:

  • Today's date By default, the current date on the server. However, you can adjust it so that the date appears correctly in a different locale.
  • Visible date This date determines what month appears in the calendar. The user can move from month to month on the calendar, thereby changing the visible date without affecting today's date. You can navigate between months by setting this date programmatically.
  • Selected date or dates This is the date or date range that the user chooses in the control. In the control, the user can choose a single day, week, or month, but can only select contiguous dates. You can also programmatically set the selected dates; in that case, you can set non-contiguous selected dates.

Enabling Date Selection

By default, the calendar allows users to click an individual date to select it. If you are using the control as a read-only calendar, you can disable all date selection.

If day selection is enabled, each day of the calendar contains a LinkButton Web control that raises an event when clicked. If you enable week or month selection, a column of links is added to the left side of the calendar to enable the user to specify what week to select.

Customizing the Calendar's Appearance

You can set calendar properties to change the colors, size, text, and other visual features of the calendar. There are a number of ways to do this:

  • Setting properties You can set properties to specify whether the calendar shows grid lines, change what day is displayed as the first day of the week, and change the appearance of the month and day names.
  • Setting extended style properties You can use properties derived from the Style object to set the appearance of particular elements in the calendar, such as today's date, the title bar containing the month and navigation links, and so on. These style properties are supported in browsers that can use cascading style sheets; a reduced set of appearance styles is supported for earlier browsers.
  • Customizing the rendering of individual days As the control renders individual days, it raises an event you can handle to modify the stream being rendered to the browser. This is useful not just for changing the appearance of days, but for including custom content on each day as well.

Capturing User Interaction with the Calendar Web Control

The Calendar Web control raises an event when the user selects an individual date or range of dates, or when navigating to a new month. By creating handlers for these events, you can determine what day or dates the user has selected and respond appropriately.

By creating custom day output, you can include arbitrary HTML in the calendar, including other controls.

Displaying Information from a Database in the Calendar Control

A common scenario is to display information from a database in the calendar. For example, an events calendar is often based out of information kept in a database.

The Calendar control does not directly support databinding — that is, you do not bind the calendar as a whole to a data source. Instead, you create a handler for the control's OnDayRender event, which is raised as each day in the current calendar month (view) is being rendered. In the handler for this event, you can extract information from a data source and add it to the stream being sent to the browser.

See Also

Using the Calendar Web Control


Customizing Calendar Web Control Appearance

Because the Calendar Web control is a complex control made up out of many individual elements, it offers you a wide variety of options for customizing its appearance. In this topic, you can learn about the following customization options:

  • Changing General Calendar Appearance
  • Changing Day-of-Week Display
  • Changing the Month Display and Navigation Links

Changing General Calendar Appearance

You can change the overall appearance of the Calendar control, which establishes a default look for the calendar on the page. If you set appearance properties for individual calendar elements, such as today's date, the selected date, and so on, the individual settings are based on the default settings you make for the calendar as a whole. You can also select from predefined Calendar formats.

Note By default, there is no special formatting to indicate today's date on the Calendar control. For details about how to set this format, see Formatting Calendar Elements Using Styles.

To change the general appearance of the Calendar control

  • Set properties as listed in the following table.
    To affect | Set this property
    Font, text and background color | Font, ForeColor, BackColor
    Overall control size | Height, Width
    Margins and spacing around individual days | CellPadding, CellSpacing
    Color, size, and style (eg, dotted) of border and grid lines
    • BorderColor (set to a color)
    • BorderStyle (set to a BorderStyle enumeration value: Dotted, Dashed, Solid, and so on).
    • ShowGridLines (true or false)
    • GridLineColor

Changing Day-of-Week Display

By default, the names of the days of the week are displayed in a row at the top of the calendar. You can control the format of this row.

To change the day of week display

  • Set properties as listed in the following table.
    To | Set this property
    Enable or disable the row with days of the week | ShowDayHeader (true or false)
    Specify the format of day names (for example, full day name or abbreviation) | DayNameFormat (use a value in the DayNameFormat enumeration: Full, Short, FirstLetter, FirstTwoLetters).
    Specify which day starts the week (default is Sunday) | FirstDayOfWeek (use a day defined in the FirstDayOfWeek enumeration). If you don't explicitly set this property, the first day of the week is Sunday. [Or is it locale specific?].
    Set color, font, border style and width, and so on | DayHeaderStyle, which incorporates style information. For details, see Formatting Calendar Elements Using Styles.

Changing the Month Display and Navigation Links

The title bar by default displays the name of the current month and a set of navigation links (LinkButton Web controls) that allow users to move to the next and previous months.

To change month display and navigation links

  • Set properties as listed in the following table.
    To | Set this property
    Remove the title bar altogether (which disables month navigation). | ShowTitle (true or false)
    Specify the format of the name of the month (for example, full name or abbreviation) and optionally add a year.
    • MonthNameFormat (use a value in the MonthNameFormat enumeration: Full or Short).
    • TitleFormat (set to a value in the TitleFormat enumeration: Month or MonthYear).
    Specify the format and text of month navigation links
    • NextPrevFormat (use a value in the NextPrevFormat enumeration: Month or CustomText).
    • NextMonthText and PrevMonthText (set to strings [can these be HTML?)
    Set color, font, border style and width, and so on TitleStyle, which incorporates style information. For details, see Formatting Calendar Elements Using Styles.
    Note You can format the navigation links separately using the NextPrevStyle property.

See Also

Formatting Calendar Elements Using Styles | Controlling User Date Selection


Formatting Calendar Elements Using Styles

The Calendar Web control, like all Web controls, allows you to use styles to specify the appearance of the control, including aspects such as font, color, borders, and so on. For general information about using styles with Web controls, see Customizing Control Appearance Using Styles.

Note Styles are not fully supported in all browsers.

To allow you flexibility in customizing the appearance of the calendar output, the Calendar Web control supports styles for a number of discrete elements that make up the calendar grid. The following diagram of a calendar with all optional elements displayed.

The following table describes each of the elements that you can control using styles.

Calendar Element | Description
DayStyle | Days of the current month. Weekends, today, and the selected day can have different styles (see below).
DayHeaderStyle | Row above the calendar where the names of the days appear.
NextPrevStyle | Sections at the left and right ends of the title bar where the month navigation LinkButtons are.
OtherMonthDayStyle | Days from the previous and next month that appear on the current month view.
SelectedDayStyle | The day selected by the user.
SelectorStyle | The column to the left containing links for selecting a week or the whole month.
TitleStyle | Titlebar at the top of the calendar containing the month name and month navigation links. If NextPrevStyle is set, it overrides the extreme ends of the title bar.
TodayDayStyle | Today's date.
WeekendDayStyle | Weekend days.

To set calendar element styles in the .aspx file

  • In the Calendar Web controls element (<asp:WebCalendar>), include the style information using the following syntax:
    StyleElementName-Style="value"

    The following examples illustrate style settings for the DayStyle and TitleStyle elements:

    <asp:WebCalendar id="Calendar1" runat="server" SelectionMode="DayWeek"
   DayStyle-BackColor="white" TitleStyle-ForeColor="black" >
</asp:WebCalendar>
  1. You can also set style elements at run time in code.

To set calendar element styles programmatically

  • Set properties of individual style elements using the following syntax:
    Calendar1.StyleName.Property = setting

    The following examples illustrate some typical style settings for the Calendar control:

     [VB]
Calendar1.DayStyle.FontSize = new FontUnit(10)
Calendar1.TitleStyle.ForeColor = new Color("red")
    Note For details about setting properties, see Getting and Setting Web Form Control Values.

See Also

Customizing Control Appearance Using Styles | Using the Calendar Web Control


Controlling User Date Selection

You can specify that users can select individual days, individual weeks, or an entire month in the Calendar Web control. Alternatively, you can disable day selection altogether.

To control user date selection

  • Set the Calendar control's SelectionMode property to one of the values defined in the SelectionMode enumeration: Day, DayWeek, or DayWeekMonth. To disable all date selection, set the property to None.
    Note Users can only select a single day, a single week, or the whole month. However, in code you can select any arbitrary set of dates. For details, see Selecting Dates Programmatically.

When selection is enabled, whenever the user selects a day, week, or month, the form is submitted and an event is raised for which you can create a handler. For details, see Responding to Date Selection.

To allow the user to select a date, the calendar is displayed with links. Individual days contain a link with the date number. If you set the calendar to allow users to select a week or the whole month, an extra column with selection links is added to the left of the calender.

Note You can also control date selection for individual days. For example, you might specify that users can click on specific days of the month, such as holidays. For details about customizing days in this way, see Customizing Individual Days.

You can specify the text for the week and month selection links.

To specify week and month selection link text

  • Set the SelectWeekText and SelectMonthText properties to the text you want to use as a navigation link.

    You can also set the font, color, and border style of the column containing the week and month selection links and set styles for days. For details, see Formatting Calendar Elements Using Styles.

See Also

Using the Calendar Web Control


Controlling Month Navigation

By default, the calendar displays the month containing today's date. Users can move from month to month in the Calendar Web control by clicking month navigation links (actually, LinkButton Web controls) in the title bar of the calendar. You can control whether these links are available for the user to click.

You can also navigate programmatically, which is useful if you want to provide an alternate way for users to display a specific month. Finally, you can respond to an event raised when the user navigates.

To enable or disable user month navigation

  • Set the Calendar control's ShowNextPrevMonth property to true or false. If false, the control does not display the LinkButton controls in the title that allow a user to move between months.
    Tip You can change the appearance of the month navigation hyperlinks by setting the NextMonthText, PrevMonthText, NextPrevFormat, and NextPrevStyle properties.

To navigate months programmatically

  • Set the control's VisibleDate property to a date in the month you want to display. The date can be any day within that month, but is typically set to the first day of the month. If the VisibleDate property is empty (it is set to DateTime.Empty), the currently visible month is derived from the value of the TodaysDate property.

    The following example navigates to February, 2000:

    [C#]
Calendar1.VisibleDate = new DateTime(2000, 2, 1);

    Changing the VisibleDate property has no effect on the values of the TodaysDate, SelectedDay, or SelectedDates properties.

If month navigation is enabled, the control raises an event when the user moves to another month. You can handle this event in order to replace or amend the default month navigation. For example, if you are using two Calendar controls in a trip planning page, you can prevent the user from setting the start-date month earlier than the end-date month.

To respond to a month navigation event

  • Create a handler for the control's VisibleMonthChanged event. When this event is raised, the control has already changed the VisibleMonth property by one.

    The VisibleMonthChanged event takes a single argument of type MonthChangedEventArgs. You can use the following properties of this argument to determine or override what the user is doing:

    • PreviousDate The value of the month that was displayed before the user clicked a month navigation button. You can compare the value of this property to that of the control's VisibleMonth property to determine what direction the user is navigating. To cancel the effect of the user's click, set the control's VisibleDate property to this value.
    • NewDate The value of the month that the user has navigated to. The VisibleDate property is updated to this value before the VisibleMonthChanged event is raised.

See Also

Using the Calendar Web Control


Customizing Individual Days

By default, days in the Calendar Web control are simply displayed as numbers. (If day selection is enabled, the numbers appear as links. For details, see Controlling User Date Selection.) However, you can customize the appearance and content of individual days, which allows you to:

  • Programmatically highlight certain days, for example, showing holidays in a different color.
  • Programmatically specify whether an individual day can be selected.
  • Add information into the day display, such as appointment or event information.

When the Calendar control is creating the output to send to the browser, it raises an OnDayRender event you can handle. The control calls your handler for each day as it is preparing the day for display, and you can then programmatically examine which date is being rendered and customize it appropriately.

The OnDayRender event handler takes two arguments, a reference to the control raising the event (the calendar control) and an object of type DayRenderEvent. The DayRenderEvent object provides access to two additional objects:

  • Cell, which you can use to set the appearance of an individual day.
  • Day, which you can use to query information about the day being rendered, control whether the day can be selected, and add content to a day. The Day object supports various properties you can use to learn about the day (for example, IsSelected, IsToday, and so on). It also supports a Controls collection that you can manipulate to add content to the day.

To customize the appearance of an individual day

  1. Create a handler for the Calendar control's OnDayRender event. The event should have the following signature:
    [Visual Basic]
Public Sub Calendar1_DayRender(s As Object, _
    e As DayRenderEvent)
  2. In the handler, set properties of the Cell object available via the DayRenderEvent argument, as in the following example:
    [Visual Basic]
Set e.Cell.BackColor = Colors.Red

The following example shows a simple but complete handler that illustrates how to change the appearance of individual days. The handler causes every other day in the calendar to be rendered in green; alternating days are rendered in the default day color.

[Visual Basic]
public Sub Calendar1_OnDayRender (sender as Object, _
      e as DayRenderEventArgs)
   With e
      If Not .Day.IsOtherMonth And .Day.DayNumberText Mod 2 = 0 Then
         ' For even-numbered days in the current month,
         ' set the background color of the cell to green.
         .Cell.BackColor = System.Drawing.Color.FromString("Green")
      EndIf
   End With
End Sub

To specify that an individual day can be selected

  • In a handler for the Calendar control's OnDayRender event, determine what day is being rendered by getting information from the Day object in the DayRenderEvent argument. Specifically, query the Date property of the Day object. Then set that day's IsSelectable property to true. In the following example, the date July 4, 2000 is made selectable.
    [Visual Basic]
public Sub Calendar1_OnDayRender (sender as Object, _
      e as DayRenderEventArgs)
   myHoliday = new DateTime(2000, 7, 4)
   If e.Day.Date = myHoliday Then
      e.Day.IsSelectable = True
   EndIf
End Sub

To add content to an individual day

  • In a handler for the Calendar control's OnDayRender event, add any valid Web Forms page element (including HTML) to the Controls collection of the Day object from the DayRenderEvent argument.
  • The following example displays holidays. The holidays are created as a two-dimensional array during page load; holiday descriptions are loaded into the elements corresponding to their date. In the OnDayRender event handler, each day is compared against the holiday array. If the corresponding element of the holiday array contains something, a label control is created with the holiday text and added into the Controls collection for that day.
    [Visual Basic]
Dim Holidays(1 to 12, 1 to 31) as String

' Load holidays array during page load
Public Sub Page1_OnLoad(e as EventArgs)
   Holidays(1, 1) = "New Year's Day"
   Holidays(2, 14) = "Valentine's Day"
   Holidays(12, 25) = "Christmas Day"
End Sub

Public Sub Calendar1_OnDayRender(sender as Object, _
      e as DayRenderEventArgs)
   If e.DayCell.IsOtherMonth Then
      e.DayCell.Controls.Clear
   Else
      Dim Hol as String
      dt = new System.DateTime(e.DayCell.Date)
      Hol = Holidays(dt.Month, dt.Day)
      if Len(Hol) > 0 then
         Dim labHoliday as New Label
         labHoliday.Text = "<br>" & Hol
         e.DayCell.Controls.Add labHoliday
      End If
   End If
End Sub

See Also

Using the Calendar Web Control


Responding to Date Selection

If the Calendar Web control's SelectionMode is set to anything other than None, the user can select a day or a range of dates. You can detect and respond to the user's choice.

To respond to a date selection

  • Create a handler for the Calendar control's SelectionChanged event with the following signature:
    [Visual Basic]
Public Sub control_OnSelectionChanged (s As Object, _
      e As EventArgs)
    Note The event is raised only if the date selection is changed by user action in the control. For instance, if the user clicks the same date twice, the second click does not raise an event. The event is also not raised if you set a date range programmatically.

Information about date selection is available in these properties:

  • SelectedDate is a single date. If the user has selected a single date, this property contains that date. If the user has selected multiple dates, this property contains the first date in the range.
  • SelectedDates is a collection containing all the dates selected. The dates in this collection are ordered and unique. Because the Calendar control does not allow the user to select multiple individual dates, the dates in the collection are also sequential.

To determine how many dates are selected

  • Get the value of the Count property of the SelectedDates collection, as shown in the following example.
    [Visual Basic]
Public Sub Calendar1_OnSelectionChanged (s As Object, _
       e As EventArgs)
   Text1.Text = "You selected " & Calendar1.SelectedDates.Count " _
      date(s)."
End Sub

If you've determined that the user has selected multiple dates, you can get the range.

To get the date range of a multi-date selection

  1. Get the count of selected dates using the Count property of the SelectedDates property.
  2. Get the first date in the collection, and then get the last date by extracting the date at the index of the count -1. The following example displays the first and last dates in TextBox controls on the page.
    [Visual Basic]
Public Sub Calendar1_OnSelectionChanged ( s As Object, _
       e As DayRenderEvent)
   If Calendar1.SelectedDates.Count > 1 Then
      Dim FirstDate as DateTime
      Dim LastDate as DateTime
      FirstDate = SelectedDates[0]
      LastDate = SelectedDates[SelectedDates.Count - 1]
      txtFirstDate.Text = FirstDate.toString
      txtLastDate.Text = LastDate.toString
   End If
End Sub

To get the time span of a multi-date selection

  • Create a TimeSpan object and set it to the difference between the last and first dates in the SelectedDates collection, and then get the the timespan object's Days property.
    [Visual Basic]
Public Sub Calendar1_OnSelectedionChanged ( s As Object, _
       e As DayRenderEvent)
      Dim ts as TimeSpan
      ts = Calendar1.SelectedDates[SelectedDates.Count - 1] - _
          Calendar1.SelectedDates[0]
      Text1.Text = "You selected " & ts.Days+1 & " days."
End Sub

See Also

Using the Calendar Web Control


Setting Today's Date Programmatically

By default, the value of "today" in the Calendar Web control is set to match the date of the server on which the Web Forms page runs. However, you might need to adjust the date to accommodate users who are viewing the page from a different time zone.

To set today's date programmatically

  • Set the Calendar control's TodaysDate property to a DateTime value, as in the following example:
    now = new DateTime()
' increment by one day
Calendar1.TodaysDate = now

See Also

Using the Calendar Web Control


Selecting Dates Programmatically

You can set date selections in your own code, either a single date or a range of dates. In contrast to user selection in the control on a page, you can select multiple non-sequential dates in code.

Note Setting a date programmatically does not raise the SelectionChanged event.

To select a single date

  • Set the control's SelectedDate property to an expression of type DateTime.

To select a range of dates

  • Call the Add method of the control's SelectedDates collection. You can add dates in any order, because the collection will order them for you. The collection also enforces uniqueness and will therefore ignore a date you add if the date is already in the collection.

    The following example sets the selection to every Wednesday in the month of February, 2000.

    [Visual Basic]
Public Sub Calendar1_OnSelectedionChanged (s As Object, _
    Calendar1.SelectedDates.Clear
    Calendar1.SelectedDates.Add new System.DateTime(2000, 2, 2)
    Calendar1.SelectedDates.Add new System.DateTime(2000, 2, 9)
    Calendar1.SelectedDates.Add new System.DateTime(2000, 2, 16)
    Calendar1.SelectedDates.Add new System.DateTime(2000, 2, 23)
End Sub

    The following example selects a sequence of seven dates.

    Dim dt As New DateTime
dt = "01/01/2000"
Calendar1.SelectedDates.Add(dt)
For i = 1 to 6
   Calendar1.SelectedDates.Add(dt.AddDays(i))
Next i

To clear a date selection

  • Call the Clear method of the control's SelectedDates collection, as in the following example:
    Calendar1.SelectedDates.Clear

    You can also set the SelectedDay property to DateTime.Empty to clear the selection of a single date.

    [Visual Basic]
Calendar1.SelectedDay = new System.DateTime.Empty

See Also

Using the Calendar Web Control


Validation Controls

An important aspect of creating Web Forms pages for user input is to be able to check that the information users enter is valid. The Web Forms framework provides a set of validation controls that provide an easy-to-use but powerful way to check for errors, and if necessary, display messages to the user.

The following topics tell you more about validation controls and how to use them.

Introduction to Validating User Input in Web Forms

Types of Validation

Client-Side Validation

Error Message Layout

Validating a Required Entry

Validating Against a Specific Value

Validating Against a Data Type

Validating Against Values in a Database

Validating Against Patterns

Validating Against a Range of Values

Validating with a Custom Function

Testing Validity Programmatically

Controlling Validation Error Message Display

Formatting Validation Error Messages

Specifying Layout for In-Place Messages

Disabling Validation at Run Time

Displaying Custom Error Messages

Validating Programmatically

Enhancing or Replacing Client-Side Validation

Allowing Posting with Client-Side Validation Errors

Controlling Client versus Server Validation

See Also

ASP+ Web Forms


Introduction to Validating User Input in Web Forms

You can add input validation to Web Forms by using validation controls. Validation controls provide an easy-to-use mechanism for all common types of standard validation — for example, testing for valid dates or values within a range — plus ways to provide custom-written validation. In addition, validation controls allow you to completely customize how error information is displayed to the user.

Validation controls can be used with any controls that are processed in a Web Form's class file, including both HTML controls and Web controls.

Using Validation Controls

You enable validation of user input by adding validation controls to your form as you would other Web Forms controls. There are controls for different types of validation, such as range checking or pattern matching. A complete list appears in the topic Types of Validation.

Each validation control references an input control elsewhere on the page. When the user's input is being processed (for example, when the form is submitted), the page framework passes the user's entry to the appropriate validation control or controls. The validation controls test the user's input and set a property to indicate whether the entry passed the test. After all validation controls have been called, a property on the page is set; if any of the controls shows that a validation check failed, the entire page is set to invalid.

You can test the state of the page and of individual controls in your own code. For example, you would test the state of the validation controls before updating a data record with information entered by the user. If you detect an invalid state, you bypass the update. Typically, if any validation checks fail, you skip all your own processing and the page is returned to the user. Validation controls that detected errors then produce an error message that appears on the page.

Displaying Error Information

Validation controls are normally not visible in the rendered form. However, if the control detects an error, it produces error message text that you specify. The error message can be displayed in a variety of ways, as listed in the following table.

Display option | Description
In place | Each validation control can individually display an error message or glyph in place (usually next to the control where the error occurred).
Summary | Validation errors can be collected and displayed in one place, for example, at the top of the page. (This strategy is often used in combination with displaying a glyph next to the input fields with errors.) If the user is working in a browser that supports DHTML, the summary can be displayed in a pop-up message box.
Custom | You can create custom error display by capturing the error information and designing your own output.

If you use the in-place or summary display options, you can format the error message text using HTML.

Server-Side and Client-Side Validation

Validation controls perform checking in server code. When the user submits a form to the server, the validation controls are invoked to review the user's input, control by control. If an error has occurred in any of the input controls, the page itself is set to an invalid state so you can test for validity before your code runs.

If the user is working with a browser that supports DHTML, the validation controls can also perform validation using client script. This can substantially improve response time in the page; errors are detected immediately and error messages are displayed as soon as the user leaves the control containing the error. If client-side validation is available, you have greater control over the layout of error messages and can display an error summary in a pop-up message box. For more details, see Client-Side Validation.

The page framework performs validation on the server even if the validation controls have already performed it on the client, so that you can test for validity within your server-based event-handling methods. In addition, it helps prevent users from being able to bypass validation by impersonating another user or a pre-approved transaction.

Validation Object Model

You can interact with validation controls using the object model exposed by individual validation controls and by the page. Each validation control exposes its own IsValid property that you can test to see if a validation test has passed or failed for that control.

At the page level you have access to another IsValid page property that summarizes the IsValid state of all the validation controls on the page. This property makes it easy to do a quick test and see if you can safely proceed with your own processing.

The page also exposes a Validators collection containing a list of all the validation controls on the page. You can loop through this collection to examine the state of individual validation controls.

Note There are a few differences in the object model for client-side validation. For details, see Client-Side Validation.

Custom Validation

You can customize the validation process in a number of ways:

  • You have complete control over the format, text, and location of error messages, and can specify whether they appear individually or in summary form.
  • You can create custom validation using a special control. The control calls your logic but otherwise works the same as other validation controls in setting error state, displaying error messages, and so on. This provides an easy way to create custom validation logic but still participate in the validation framework of the page.
  • If you have specified client-side validation, you can intercept the validation call and substitute or add your own validation logic.

Validating Programmatically

By default, validation controls perform their checks when a form is submitted. However, you can perform validation at any time in your code by calling a validation control's Validate method. Calling this method allows you to evaluate the validity of property settings made programmatically. For example, you might be gathering data from a user using a method other than a Web Form page, and then setting properties of Web forms controls programmatically.

For more details, see Validating Programmatically.

See Also

Validation Controls


Types of Validation

You can implement different kinds of validation by using different validation controls. The following table lists the types of validation controls available and how you can use them.

Type of validation | Control to use | Description
Required entry | RequiredFieldValidator | Ensures that the user does not skip an entry.
Comparison to a value (including database fields) | CompareValidator | Compares a user's entry against a constant value or a property value of another control using a comparison operator (less than, equal, greater than, and so on). To check against a database field, for the comparison value you specify an expression that gets the data to compare against.
Range checking | RangeValidator | Checks that a user's entry is between specified lower and upper boundaries. You can check ranges within pairs of numbers, alphabetic characters, and dates. Boundaries can be expressed as constants or as values derived from another control.
Pattern matching | RegularExpressionValidator | Checks that the entry matches a pattern defined by a regular expression. This type of validation allows you to check for predictable sequences of characters, such as those in social security numbers, e-mail addresses, telephone numbers, postal codes, and so on.
User-defined | CustomValidator | Checks the user's entry using validation logic that you code yourself. This type of validation allows you to check for values derived at run time.

You can attach more than one validation control to an input control. For example, you might specify that a control is both required and that it contain a specific range of values.

See Also

Validation Controls


Client-Side Validation

If the user is working with a browser that supports Dynamic HTML (DHTML), such as Internet Explorer 4.0 or higher, validation controls can perform validation using client script. Because the controls can provide immediate feedback (without a round trip to the server), the user experience with the page is enhanced.

Under most circumstances, you do not have to make any changes to your page or to the validation controls. They detect automatically if the browser supports DHTML and perform their checking accordingly. Client-side validation uses the same error display mechanism as server-side checking.

Note Validation is performed on the server even if it was already performed on the client. This allows you to check validation status in server code and provides security against users bypassing validation.

Differences in Client-Side Validation

There are some additional features of validation available if it is performed on the client:

  • You can write client script to enhance or replace the default validation and error reporting behavior of the validation controls. For details, see Enhancing or Replacing Client-Side Validation.
  • If you are summarizing validation error messages, you can display them in a pop-up message box that appears when the user submits the page. For details, see Controlling Validation Error Message Display.
  • The object model for validation controls is slightly different on the client. See "Client Validation Object Model" below.
  • There are a few minor limitations associated with client-side validation:
  • Regular expressions used with the RegularExpressionValidator control are limited to those supported in ECMAScript.
  • The validation controls create OnChange event handlers for the controls being validated. If you are writing handlers in client script for these events, you cannot bind the OnChange event directly from the input control, because the validation controls will override your binding. Instead, you bind indirectly using the FOR and EVENT attributes of a script block.

Client Validation Object Model

Validation controls present almost the same object mode on the client as on the server. For example, you can test validation by reading the value of a validation control's IsValid property the same way on both the client and the server.

However, there are differences in the validation information exposed at the page level. On the server, the page supports properties; on the client, it contains global variables. The following table compares the information exposed on the page.

Server Page Property | Client Page Variable
IsValid | Page_IsValid
Validators (collection) Contains references to validation controls whose IsValid property has changed since the last round trip to the server. | Page_Validators (array) Contains references to all validation controls on the page, regardless of change in their status.
(no equivalent) | Page_FieldsToValidate (array) A list of references to all the input controls being validated. This array is used internally by the validation controls.
Note All page-related validation information should be considered read only.

Automatic Client-Side Validation

Client-side validation is enabled automatically if you are using Microsoft Internet Explorer 4.0 or later. In some instances, however, you might prefer server-side validation even if client-side validation is possible. For example, server-side validation might be required if the validation requires information or resources that are only available on the server, such as access to a database. For details, see Controlling Client versus Server Validation.

Note Client-side validation requires that your browser support Dynamic HTML and Visual Basic.

Posting with Client-Side Validation Errors

By default, when client-side validation is being performed, the user cannot post the form to the server if there are errors on the page. However, you might find it necessary to allow the user to post even with errors. For example, if the user is working with a multi-page entry form, an error on the first page might be resolved by an entry on a subsequent page. For details, see Allowing Posting with Client-Side Validation Errors.

See Also

Validation Controls


Special-Case Validation Results

Validation ensures that what the user has entered meets criteria that you specify. In most cases, this is a straightforward operation. However, at times the situations under which input is validated introduce special conditions. It is important that you understand what those conditions are, how validation is performed, and what the results of a validation attempt can be.

Validating Required Fields

If validation is being performed exclusively on the server, then a required field will be checked only when the form is submitted. It might therefore appear during data entry as if the user is able to skip the field. Under most circumstances, this isn't a problem, since all other validation is also occurring on the server. However, if you have client script that relies on a value in a required field, the RequiredField validation control won't be able to ensure that there is a value in the field.

If client-side validation is being performed, the user can skip over a required field. However, once a value has been entered into the field, the user cannot clear that value without generating an error.

Comparing Against Blank Fields

Other than the RequiredFieldValidator control, validation controls consider a field valid if it is blank. For example, if you attach a RangeValidator control to a date field, validation passes either if the user enters a date in the specified range or if the field is left blank. If you want to ensure that the user enters a date, you must add a RequiredFieldValidator control to the control along with the range check.

Comparing Against Another Control

When using the CompareValidator or RangeValidator controls, you can specify that validation be performed against the value of another control. For example, you might have two fields for entering dates, a start date and an end date. Using a RangeValidator control, you can ensure that the minimum value for the end date is the value of the start date.

If the validation controls are unable to resolve the value in the other control, they do not raise an error; instead, the validation check passes. This might occur if:

  • The value in the other control is itself missing or invalid.
  • The validation control cannot find the control to compare against, perhaps because a bad ID for the control was specified.

The specific tests and results performed by these controls are:

  • If the validation control's ControlToCompare property is blank, the control sets IsValid to true.
  • If the value derived using the ControlToCompare property cannot be converted to the appropriate data type, IsValid is set to false.
  • Finally, if all of these tests have passed, the control performs the actual comparison and sets IsValid appropriately.

This strategy prevents multiple errors from being reported for the same invalid entry. Validation controls are designed to test and report on only one value and to be able to able to display as precise a message as possible when an error is encountered.

For example, in the example of the end date, imagine that the user entered an invalid start date. The user then enters an end date. When the validation check is performed (usually on the server), the same bad date could potentially raise an error both for the start date field and for the end date. To prevent this from occurring, the end date validation — the validation dependent on another field — does not raise an error.

You should therefore ensure that you provide sufficient validation for each individual field. In the example, it would be up to you to make sure that the user has entered a proper date in the start date field. In many cases, you should include a RequiredFieldValidator control in addition to a validation control that tests the contents of a specific entry.

See Also

Validation Controls


Validation Error Message Layout

When an error message appears on the page, it becomes part of the layout of the page. You need to design the layout of your page to accommodate error text that might appear.

If validation is being performed on the server, validation controls are not even rendered to the page unless an error occurs and therefore take up no space whatsoever. If an error has occurred, the page is recreated during the round trip and the error text is flowed to the page.

If the user is using a DHTML-compatible browser, validation occurs on the client dynamically. In that case, in-place error message text appears immediately and can potentially change the layout of the page.

To control how error message text is displayed with client validation, you can choose two options:

  • Static layout Each validation control takes up space even when no error message text is visible, allowing you to define a fixed layout for the page. Validation controls cannot occupy the same space on the page, so you must give them each separate locations on the page.
  • Dynamic layout Validation controls take up no space unless they are displaying an error message, which allows them to share the same location (a table cell, for example). However, when the error message is displayed, the layout of the page changes, sometimes causing controls to jump around.
Note Static and dynamic layout require that the browser support the visible style attribute. If your Web application will be used with browsers that might not support that attribute, it's advisable instead to lay out the validation controls in a table cell or Panel Web control that is large enough to accommodate the largest possible error message.

See Also

Validation Controls | Specifying Layout for In-Place Error Messages


Validating a Required Entry

You can specify that a user must provide information in a specific control.

Tip Required-entry validation is frequently used in conjunction with other types of validation. You can use as many validation controls for a user-entry field as needed.

To validate a required entry

  1. Add a RequiredFieldValidator control to the page and set the following properties:
    Property | Setting
    ControlToValidate | The ID of the control for which the user must provide a value.
    ErrorMessage, Text, ValidationDisplay | Properties that specify the text and location of the error or errors that will display if the user skips the control. For details, see Controlling Validation Error Message Display.
  2. Add a test in your Web Forms code to check for validity. For details, see Testing Validity Programmatically.

If validation is being performed on the client, the user can leave a required field blank while working in the page, but must fill it in before submitting. However, once a value has been filled into the field, the user cannot clear the field. If the field is cleared, the user sees an error message immediately when leaving the field. In server-side validation, no checking is done until the form is submitted, so the user will not see an error message until after the form is submitted.

The following shows an example in the .aspx file of a Web Form Textbox control with required field validation. A table is used to control layout.

<TABLE>
<TR>
<TD>
   <asp:Textbox id="txtLastName" runat="server"></asp:Textbox>
</TD>
<TD>
   <asp:RequiredFieldValidator id="RequiredFieldValidator1" runat="server"
      ControlToValidate="txtLastName"
      ErrorMessage="Last name is a required field"
      ForeColor="Red">
   </asp:RequiredFieldValidator>
</TD>
</TR>
</TABLE>

See Also

Validation Controls | Introduction to Validating User Input in Web Forms | Controlling Validation Error Message Display | Controlling Client versus Server Validation


Validating Against a Specific Value

You can validate a user's entry against a specific single value using logical operators. For example, you can specify that the user's entry is a date after January 1, 1950 or that it is an integer value greater than or equal to zero. Alternatively, you can specify that the user's entry be compared against a value from another control.

To validate against a specific value

  1. Add a CompareValidator control to the page and set the following properties:
    Property | Setting
    ControlToValidate | The ID of the control to validate.
    ErrorMessage, Text, ValidationDisplay | Properties that specify the text and location of the error or errors that will display if the user skips the control. For details, see Controlling Validation Error Message Display.
  2. Set the value to compare to by setting the following properties:
    • ValueToCompare or ControlToCompare An expression entered as a string. Set ValueToCompare to compare to a constant value. Set ControlToCompare to the ID of another control to compare against. (The CompareValidator control compares the user's entry against whatever property is specified by the other control's ValidationProperty property.) If you set both ValueToCompare and ControlToCompare, ControlToCompare takes precedence.
      Note When validation is done against another control, invalid values in the other control are ignored and the validation passes. For details, see Special-Case Validation Results.
      Note The validation control validates user input using the format specified by the page's locale setting .
    • Type The data type of the two values to be compared. Types are specified using the ValidationDataType enumeration, which allows you to use the type name String, Integer, Double, DateTime, or Currency. The values are converted to this type before the comparison is performed. If the values can't be converted — for example, the user's entry cannot be converted to a date — the validation fails.
    • Operator The comparison to use. Operators are specified using the ValidationCompareOperator enumeration, which allows you to enter the name of the comparison operators, such as Equal, NotEqual, GreaterThan, GreaterThanEqual, and so on.
Note If the user leaves a control blank, the control passes the comparison validation. To force the user to enter a value, add a RequiredField validation control as well. For details, see Validating a Required Entry.
  1. Add a test in your Web Forms code to check for validity. For details, see Testing Validity Programmatically.

The following shows an example in the .aspx file of a Web Form Textbox control with required field validation. A table is used to control layout.

<TABLE>
<TR>
<TD>
   <asp:Textbox id="txtAge" runat="server"></asp:Textbox>
</TD>
<TD>
   <asp:CompareValidator id="CompareFieldValidator1" runat="server"
      ForeColor="Red"
      ControlToValidate="txtAge"
      ValueToCompare=0
      Type="Integer"
      Operator="GreaterThanEqual"
      ErrorMessage="Please enter a whole number zero or greater.">
   </asp: CompareValidator >
</TD>
</TR>
</TABLE>

The following example shows a CompareValidator that compares the user's entry against the value in another control. In a form that allows users to make reservations at a hotel, the validator checks that the user does not enter a departure date earlier than the arrival date.

<TABLE>
<TR>
<TD>
   <asp:Textbox id="txtDepartureDate" runat="server"></asp:Textbox>
</TD>
<TD>
   <asp:CompareValidator id="CompareFieldValidator1" runat="server"
      ForeColor="Red"
      ControlToValidate="txtDepartureDate"
      ControlToCompare="txtArrivalDate"
      Type="DateTime"
      Operator="GreaterThanEqual"
      ErrorMessage="Departure date cannot be earlier than arrival date.">
   </asp: CompareValidator >
</TD>
</TR>
</TABLE>

See Also

Validation Controls | Introduction to Validating User Input in Web Forms | Controlling Validation Error Message Display | Controlling Client versus Server Validation


Validating Against a Data Type

You can validate a user's entry against a specific data type to be sure that what the user has entered is a number, date, and so on.

To validate against a data type

  1. Add a CompareValidator control to the page and set the following properties:
    Property | Setting
    ControlToValidate | The ID of the control for which you are checking the data type.
    ErrorMessage, Text, ValidationDisplay | Properties that specify the text and location of the error or errors that will display if the user skips the control. For details, see Controlling Validation Error Message Display.
  2. Set the data type to compare to by setting the following properties:
    • Type The data type to check against. The type is specified using the ValidationDataType enumeration, which allows you to use the type names String, Integer, Double, DateTime, or Currency.
    • Operator Set this property to DataTypeCheck.
Note If the user leaves a control blank, the control passes the comparison validation. To force the user to enter a value, add a RequiredField validation control as well. For details, see Validating a Required Entry.
  1. Add a test in your Web Forms code to check for validity. For details, see Testing Validity Programmatically.

See Also

  1. Validation Controls | Introduction to Validating User Input in Web Forms | Controlling Validation Error Message Display | Controlling Client versus Server Validation


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Validating Against Values in a Database

[To be supplied.]


Validating Against Patterns

You can check a user's entry matches a predefined pattern, such as a phone number, postal code, email address, and so on. To do so, you use a regular expression.

To validate against a data type

  1. Add a RegularExpressionValidator control to the page and set the following properties:
    Property | Setting
    ControlToValidate | The ID of the control you are validating.
    ErrorMessage, Text, ValidationDisplay | Properties that specify the text and location of the error or errors that will display if the user skips the control. For details, see Controlling Validation Error Message Display.
  2. Set the pattern to compare to by setting the ValidationExpression property to a regular expression. Use the drop-down list in the Properties window to choose a pre-defined expression or to create a new one using a builder.
    Note In client-side validation, you can use only regular expressions supported by JavaScript. For other details about client-side validation, see Client-Side Validation.
  3. Add a test in your Web Forms code to check for validity. For details, see Testing Validity Programmatically.

See Also

  1. Validation Controls | Introduction to Validating User Input in Web Forms |


Validating Against a Range of Values

You can check that a user's entry falls within a specific range of values — for example, between two numbers, between two dates, or between alphabetic characters.

To validate against a range of values

  1. Add a RangeValidator control to the page and set the following properties:
    Property | Setting
    ControlToValidate | The ID of the control you are validating.
    ErrorMessage, Text, ValidationDisplay | Properties that specify the text and location of the error or errors that will display if the user skips the control. For details, see Controlling Validation Error Message Display.
  2. Set the low and high values of the range by setting the MinimumValue or MinimumControl and MaximumValue or MaximumControl properties. These properties take strings representing either a constant value or the ID of a control from which the range value should be derived. (The control-based properties derive their values by extracting the other control's ValidationProperty property.) If you set both MinimumValue and MinimumControl, or both MaximumValue and MaximumControl, the control-based property takes precedence.
    Note When validation is done against another control, invalid values in the other control are ignored and the validation passes. For details, see Special-Case Validation Results.
    Note The validation control assumes that the user has entered data in the format specified by the page's locale setting.
  3. Set the Type to specify the data type of the range settings. The type is specified using the ValidationDataType enumeration, which allows you to use the type name String, Integer, Double, DateTime, or Currency. If the user's entry can't be converted — for example, it cannot be converted to a date — the validation fails.
    Note If the user leaves a control blank, the control passes the range validation. To force the user to enter a value, add a RequiredField validation control as well. For details, see Validating a Required Entry.
  4. Add a test in your Web Forms code to check for validity. For details, see Testing Validity Programmatically.

See Also

  1. Validation Controls | Introduction to Validating User Input in Web Forms |


Validating with a Custom Function

If the existing validation controls do not suit your needs, you can define a custom validation function and call it using special validation control. The control then calls your custom server-side validation logic.

To validate with a custom function

  1. Add a CustomValidator control to the page and set the following properties:
    Property | Setting
    ControlToValidate | The ID of the control you are validating.
    ErrorMessage, Text, ValidationDisplay | Properties that specify the text and location of the error or errors that will display if the user skips the control. For details, see Controlling Validation Error Message Display.
  2. Specify the name of a validation function for the control to call by setting the ServerValidationFunction to the name of a method to call for validation.
  3. Create the validation function, which must have a signature such as the following:
    function Name(validationControl, value)

    The validationControl parameter is a reference to the custom validation control calling this function, and value contains the user input to validate. The function should return true if the value is valid, false otherwise.

    On the client, the function should be written in ECMAScript and placed in a <SCRIPT> block. On the server, you write it in the language that you are using for your Web Form page.

  4. Add a test in your Web Forms code to check for validity. For details, see Testing Validity Programmatically.
    Note You can also create client-side custom validation. To do so, you follow this procedure but specify a method name for the ClientValidationFunction and create a function in client script that duplicates the logic of the server-side method.

The following example illustrates custom client-side validation. An excerpt from the page shows a Textbox control referenced by a CustomValidator validation control. The validation control calls a function called validateLength to make sure that the user has entered at least 8 characters into the Textbox control.

<SCRIPT LANGUAGE="JavaScript">
function validateLength( oSrc, txtValue ){
   var retValue = true; // assume ok
   if(txtValue.length < 8){
      retValue = false;}
   return retValue
}
</SCRIPT>

<asp:Textbox id="text1" runat="server" text="">
</asp:Textbox>

<asp:CustomValidator id="CustomValidator1" runat=server
   ControlToValidate="text1"
   ErrorMessage="You must enter at least 8 characters! " >
   ClientValidationFunction="validateLength(o, v)"
</asp:CustomValidator>

See Also

  1. Validation Controls | Introduction to Validating User Input in Web Forms |


Testing Validity Programmatically

Validation controls test user input, set an error state, and produce error messages. They do not change the flow of page processing — for example, they do not bypass your code if they detect a user input error. Instead, you test the state of the controls in your code before performing application-specific logic. If you detect an error, you prevent your own code from running; the page continues to process and is returned to the user with error messages.

You can test for a general, page-wide state, and you can test the state of individual controls. Typically, you do this in the event handlers that you create for a page.

Note Validation information is not available during a page's initialization or load stage. For details about page states, see Writing a Custom Web Forms Control.

To test for a general error state

  • In your code, test the page's IsValid property. This property rolls up the values of the IsValid properties of all the validation controls on the page (using a logical AND); if any one validation control is set to invalid, the page's property will return false.

    The following example shows the event handler for a button. The code tests for valid user entries before proceeding to another step.

To test for the error state of individual controls

  • Loop through the page's Validators collection, which contains references to all the validation controls whose status has changed since the last round trip. You can then examine the IsValid property of each validation control.

See Also

Validation Controls | Introduction to Validating User Input in Web Forms |


Controlling Validation Error Message Display

You can display error message text:

  • In-line (only) Next to the control (in place where the validation control is located).
  • Summary (only) In a separate summary of all errors. Optionally, the errors can appear in a pop-up message. (The option to use a pop-up message is available only if the user is using a DHTML-compatible browser.)
  • In-line and summarized In that case, the error message can be different in the summary and in place. You can use this option to show a shorter error message in place (with more detail in the summary) or to show an error glyph next to the input field and an error message in the summary.
  • Custom You can create your own error message display. For details, see Displaying Custom Error Messages.

You also have options for specifying the format and layout of error messages. For details, see Formatting Validation Error Messages.

If you want to display summarized or pop-up error messages, you need to add a control to the page.

To add summarized error messages

  1. Add a ValidationSummary control to the page at the location where you want to display the collected error messages.
  2. Set the summary control's DisplayMode property to format the error messages, using one of the following values defined in the ValidationSummaryDisplayMode enumeration:
    • BulletList Each message appears in a bullet list. (This is the default.)
    • List Each messages appears on its own line.
    • SingleParagraph Each message appears as a sentence in a paragraph.
  3. Optionally set the control's HeaderText property to provide a title or header for the error messages.

Choosing inline and summary display involves setting different combinations of properties in the individual validation controls.

To choose in-line and summary error display

  1. If you want to display summary errors, add a ValidationSummary control as described in the procedure above.
  2. Set the ErrorMessage, Text, and ValidatorDisplay properties of the individual validation controls according to the following table:
    Option ValidationSummary control required? Property settings on validation control
    Inline only no ValidatorDisplay = VisibleStaticLayout or
    VisibleDynamicLayout

    ErrorMessage = (error text)
    Summary only (including optional pop-up ) yes ValidatorDisplay = NeverVisible

    ErrorMessage = (error text)
    Inline and summary (including optional pop-up) yes ValidatorDisplay = VisibleStaticLayout or
    VisibleDynamicLayout

    ErrorMessage = (error text for summary)

    Text = (in-line error text)

If the user is working with a DHTML-compatible browser such as Internet Explorer 4.0 or later, summarized error messages can also appear in a pop-up window.

To display summarized error messages in a pop-up window

  • Set the ShowMessageBox property of the ValidationSummary control to true.

    When the user submits the page, errors are displayed both in the summary control and in the pop-up window.

See Also

  1. Validation Controls | Introduction to Validating User Input in Web Forms |


Formatting Validation Error Messages

You can control the format — font, size, and so on — of error message text, or you can substitute a glyph for error text. Validation controls support the same properties as other controls for specifying their look.

To format error messages

  • Set validation control properties such as the following:
    • ForeColor The color of the error message text.
    • BackColor The color behind the text.
    • Font The font face, size, weight, and so on.
    • BorderWidth, BorderColor, and BorderStyle The size and color of a border around the error message.
    • CSSStyle and CSSClass Style settings that are used if the user is working with a browser that supports CSS style sheets.
    Note Some of the settings you make with these properties may not be supported in all browsers.

You can provide formatting within an error message by using HTML elements in the text for the ErrorMessage and Text properties. For example, to apply italics to only one word in the error message, you can surround the word with <I> and </I> tags.

Note To display reserved HTML characters within the error message text, encode them. For example, to display "<", use the character sequence &lt;.

If you are displaying validation errors in a summary, you can specify that a glyph appear in place of the individual validation control.

To display glyphs next to input controls

  1. Set the ErrorMessage property of each validation control to the error text that should appear in the ValidationSummary control.
  2. Set the Text property of each validation control to the glyph to display. The glyph can consist of any valid HTML, including text (such as an asterisk) and an image element.
    Note The glyph is displayed in place of the validation control. It's up to you to lay out the validation control in such a way that the glyph displays properly.

See Also

Validation Controls | Introduction to Validating User Input in Web Forms |


Specifying Layout for In-Place Messages

When an error message appears on the page, it becomes part of the layout of the page. You can specify that the layout be fixed — the error message does not change the page layout when it appears — or dynamic. For details about layout options, see Error Message Layout.

To specify fixed or dynamic layout for client error messages

  • Set the Display property of each validation control to a layout option defined by the ValidatorDisplay enumeration:
    • None (used when you are using only the summary control)
    • Static For a fixed layout.
    • Dynamic For a dynamic layout.
    Note Only browsers that support the visible style attribute can make a distinction between static and dynamic layout.

See Also

Validation Controls | Introduction to Validating User Input in Web Forms |


Disabling Validation at Run Time

At times, you might want to disable validation based on run-time conditions.

To disable validation in server code

  • Set the validation control's Enabled property to false.

    If this property is set to false, the validation control will not be rendered to the page at all, and no validation will take place using that control.

To disable validation in client script

This is not available in the current release.

See Also

Validation Controls | Introduction to Validating User Input in Web Forms |


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Displaying Custom Error Messages

[To be supplied.]


Validating Programmatically

The information that the validation controls use to check user input is available in properties of the control. For example, you set the limits for a range check using the MinimumValue and MaximumValue properties of the RangeValidator control.

Because this information is available in properties, you can change it at run time. A typical use is to set validation values based on information that a user has entered into another control, or to change the error message text based on run-time conditions. You can set any validation control property in code.

If you change validation control properties in code, the control will not [might not?] be able to process user input automatically as part of normal page processing, because validation occurs before your code is called. You therefore must validate dynamically.

To validate programmatically

  1. Set the validation control properties that you want to change.
  2. Call the validation control's Validate method.

    The control will perform its check and if an error is detected, will set its IsValid property to false. When the page is returned to the user, error messages will be displayed as usual.

See Also

Validation Controls | Introduction to Validating User Input in Web Forms |


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Enhancing or Replacing Client-Side Validation

To enhance or replace client-side validation

[To be supplied.]


Allowing Posting with Client-Side Validation Errors

By default, when client-side validation is being performed, the user cannot post the form to the server if there are errors on the page. However, you might find it necessary to allow the user to post even with errors.

To allow posting with client-side validation errors

  • Set the global page variable Page_PostIfValidationError to true.

See Also

Validation Controls | Introduction to Validating User Input in Web Forms | Client-Side Validation


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Controlling Client versus Server Validation

[To be supplied.]


Pagelet Controls

A pagelet is an ASP+ page that is imported as a server control by another ASP+ page (or many other ASP+ pages. Pagelet controls provide an easy way to partition and reuse simple, common UI functionality across a Web application. Pagelets are not precompiled but, because all ASP+ pages are compiled when requested, they are compiled on demand and cached in server memory.

You create a pagelet by authoring an ASP+ page just like a regular page except that the pagelet does not include <html> and <body> elements around the content. Those elements are contained in the ASP+ page where the pagelet is inserted.

Important Pagelets that post events should not contain an HtmlForm control (a <form runat="server"></form> element). Instead, the containing page should include one around the pagelet control tag.

The pagelet filename is subject to the same naming conventions as ASP+ page files. It is recommended that you use the .aspc file extension rather than .aspx so that your pagelets do not get compiled in the same way as a standard Web Form.

Defining a Pagelet

The following simple example shows how a pagelet could be used to encapsulate a navigation bar that remains constant on all pages in an application.

<%@ Page Description=”A Simple Pagelet Control” %>
<table>
   <tr><td>< a href="news.aspx">News</a></td></tr>
   <tr><td>< a href="weather.aspx">Weather</a></td></tr>
   <tr><td>< a href="sports.aspx">Sports</a></td></tr>
</table>

Inserting a Pagelet

To insert a pagelet in another page, use a @ Register directive that specifies:

  • the source of the control
  • a friendly tagprefix:tagname pair that can be used to declare an instance of it on the page.

Then, use a custom server control tag in the desired place on the containing page. For example, if the pagelet shown above was saved as navigation.aspc, a containing page would import the pagelet with the following syntax:

<%@ Page Description="A Sample Page Consuming a Pagelet" %>
<%@ Register TagPrefix="PageletCtrl" TagName="NavBar" Src="navigation.aspc" %>

<html>
   <body>
      < UserCtrl:NavBar id="MyNavBar" runat="server" />
   </body>
</html>

See Also


Using Pagelet Controls

The following example shows how a pagelet can define a data input subform that can be used on several pages or in several places on the same page. This example incorporates the pagelet control twice on an ASP+ order page, once for billing information and once for shipping information.

First, a simple standalone ASP+ name and address input form page will be created that includes several TextBox and Button controls, then the page will be modified to become a pagelet control.

The order subform contains six TextBox controls, a Submit button, a Clear button, and a Label control within the <Form> and </End Form> tags. VBScript in the event procedure btnSubmit_Click executes when the buttons are clicked to verify that data has been entered in all the TextBoxes. Script in the btnClear_Click event clears all the text boxes. The form is directed to run at the server and to post back to itself. If any text boxes are not completed, the name of the missing data item is concatenated to a string that will be used in an error message when the form is submitted. If this page were used on an actual Web site to gather name and address information, code would probably be placed in the btnSubmit_Click event to update a database or take some other action.

<%@ Page Description="Information input page" %>

<html>
   <script language=VB runat=server>
      Sub btnSubmit_Click(ByVal sender As Object, ByVal e As Eventargs)
         Dim sName As String, sCompany As String, sStreet As String
         Dim sCity As String, sState As String, sZip As String
         Dim sMessage As String

         sName =Trim(txtName.Text)
         sCompany=Trim(txtCompany.Text)
         sStreet=Trim(txtStreet.Text)
         sCity=Trim(txtCity.Text)
         sState=Trim(txtState.Text)
         sZip=Trim(txtZip.Text)

         ' If text box is blank, add to error msg string.
         If sName="" Then sMessage = sMessage & "Name, "
         If sCompany="" Then sMessage = sMessage & "Company, "
         If sStreet="" Then sMessage = sMessage & "Street, "
         If sCity="" Then sMessage = sMessage & "City, "
         If sState="" Then sMessage = sMessage & "State, "
         If sZip="" Then sMessage = sMessage & "Zip, "

         ' Trim trailing comma.
         if sMessage<>"" Then
            lblMsg1.Text="Please enter your " & Mid(sMessage, 1, Len(sMessage)-2)
         else
            lblMsg1.Text="Thank you, " & sName & " for your information."
            btnSubmit.Text="ReSubmit"
         end if
      End Sub

      Sub btnClear_Click(ByVal sender As Object, ByVal e As Eventargs)
         lblMsg1.Text=""
         txtName.Text=""
         txtCompany.Text=""
         txtStreet.Text=""
         txtCity.Text=""
         txtState.Text=""
         txtZip.Text=""
         btnSubmit.Text="Submit"
      end sub
   </script>

   <p><h4>Please fill out the form:<h4></p>

   <Form method="Post" action="address.aspx" runat=server>
      <table width="250" bgcolor="#FFFF99">
         <tr>
            <td Align="Right"><b>Name:</b></td>
            <td><Asp:Textbox id="txtName" runat=server/></td>
         </tr>
         <tr>
            <h4><td Align="Right"><b>Company:</b></td></h4>
            <td><Asp:Textbox id="txtCompany" runat=server/></td>
         </tr>
         <tr>
            <td Align="Right"><b>Street:</b></td>
            <td><Asp:Textbox id="txtStreet" runat=server/></td>
         </tr>
         <tr>
            <td Align="Right"><b>City:</b></td>
            <td><Asp:Textbox id="txtCity" runat=server/></td>
         </tr>
         <tr>
            <td Align="Right"><b>State:</b></td>
            <td><Asp:Textbox id="txtState" runat=server/></td>
         </tr>
         <tr>
            <td Align="Right"><b>Zip:</b></td>
            <td><Asp:Textbox id="txtZip" runat=server/></td>
         </tr>
      </table><br>

      <asp:button id="btnSubmit" Text="Submit" OnClick="btnSubmit_Click" runat=server/>
      <asp:button id="btnClear" Text="Clear" OnClick="btnClear_Click" runat=server/>

      <h4><asp:Label id="lblMsg1" runat=server/><h4>
   </form>
</html>

Before the existence of pagelet controls, if you wanted to put two of these data input forms on an ASP+ Web page, you would have to copy all the preceding code and paste it in two places on the containing page, then rename several text boxes to avoid name conflicts. A better solution is to convert the input subform code to a custom pagelet control and use it from any ASP+ page that needs the visual interface and functionality of a name and address input form.

To convert the page to a pagelet control, the <html> and <Form> tags are removed along with the button click events. These items are supplied by the page that uses the new control. After deleting the code and controls that are not needed, the ASP+ page looks like this:

<%@ Page Description="Address input control" %>

<html>
   <table width="250" bgcolor="#FFFF99">
      <tr>
         <td Align="Right"><b>Name:</b></td>
         <td><Asp:Textbox id="txtName" runat=server/></td>
         </tr>
         <tr>
            <h4><td Align="Right"><b>Company:</b></td></h4>
            <td><Asp:Textbox id="txtCompany" runat=server/></td>
         </tr>
         <tr>
            <td Align="Right"><b>Street:</b></td>
            <td><Asp:Textbox id="txtStreet" runat=server/></td>
         </tr>
         <tr>
            <td Align="Right"><b>City:</b></td>
            <td><Asp:Textbox id="txtCity" runat=server/></td>
         </tr>
         <tr>
            <td Align="Right"><b>State:</b></td>
            <td><Asp:Textbox id="txtState" runat=server/></td>
         </tr>
         <tr>
            <td Align="Right"><b>Zip:</b></td>
            <td><Asp:Textbox id="txtZip" runat=server/></td>
         </tr>
   </table>
</html>

If the file is named InputControl.aspc, all that is needed to use this control on another page is this declarative line on the calling page:

<%@ Register TagPrefix="InputControl" TagName="AddressInfo" Src="InputControl.aspc" %>

and this tag where the sub-form should appear:

<InputControl:AddressInfo id="ShipAddress" runat=server />

At this stage, the Input control would appear on the page that incorporates it but it would not be able to actually do anything. More code must be added to the control and to the calling page to accept and verify input and perform some useful work. A means of accessing the values of the text boxes and a means of clearing the text boxes from the calling page must be added.

Here is the pagelet control with the additional code:

<%@ Page Description="Address input declarative control" %>

<html>
   <script language=vb runat=server>
      public property get pName() As String
         pName=txtName.Text
      end property

      public property let pName(sValue As String)
         txtName.Text=sValue
      end property

      public property get pCompany() As String
         pCompany=txtCompany.Text
      end property

      public property let pCompany(sValue As String)
         txtCompany.Text=sValue
      end property

      public property get pStreet() As String
         pStreet=txtStreet.Text
      end property

      public property let pStreet(sValue As String)
         txtStreet.Text=sValue
      end property

      public property get pCity() As String
         pCity=txtCity.Text
      end property

      public property let pCity(sValue As String)
         txtCity.Text=sValue
      end property

      public property get pState() As String
         pState=txtState.Text
      end property

      public property let pState(sValue As String)
         txtState.Text=sValue
      end property

      public property get pZip() As String
         pZip=txtZip.Text
      end property

      public property let pZip(sValue As String)
         txtZip.Text=sValue
      end property

      public property let pHeader(sValue As String)
         lblHeader.Text=sValue
      end property

      public sub ClearFields
         ' Clear all text boxes.
         txtName.Text=""
         txtCompany.Text=""
         txtStreet.Text=""
         txtCity.Text=""
         txtState.Text=""
         txtZip.Text=""
      End Sub
   </script>

   <h4><asp:Label id=lblHeader runat=server/></h4>
   <table width="250" bgcolor="#FFFF99">
      <tr>
         <td Align="Right"><b>Name:</b></td>
         <td><Asp:Textbox id="txtName" runat=server/></td>
      </tr>
      <tr>
         <h4><td Align="Right"><b>Company:</b></td></h4>
         <td><Asp:Textbox id="txtCompany" runat=server/></td>
      </tr>
      <tr>
         <td Align="Right"><b>Street:</b></td>
         <td><Asp:Textbox id="txtStreet" runat=server/></td>
      </tr>
      <tr>
         <td Align="Right"><b>City:</b></td>
         <td><Asp:Textbox id="txtCity" runat=server/></td>
      </tr>
      <tr>
         <td Align="Right"><b>State:</b></td>
         <td><Asp:Textbox id="txtState" runat=server/></td>
      </tr>
      <tr>
         <td Align="Right"><b>Zip:</b></td>
         <td><Asp:Textbox id="txtZip" runat=server/></td>
      </tr>
   </table>
</html>

Property Let and Property Get statements have been added to expose the values of the text boxes as properties of the control. The pHeader property has been added to provide a line of text above the control. Also, a public method named ClearFields has been added to clear the contents of all the text boxes with one external method call.

Here is an ASP+ page that uses two instances of the Input control, one for a billing address and one for a shipping address. The subprocedure Init, is used to set the heading (pHeader property) of each control instance. A different "id" is given to each instance of the control so that they can be addressed separately on the calling page.

<%@ Register TagPrefix="InputControl" TagName="AddressInfo" Src="InputFormControl.aspc"%>

<html>
   <script language=vbscript runat=server>
      Dim sBillName as String, sBillCompany as String, sBillStreet As String
      Dim sBillCity As String, sBillState As String, sBillZip As String
      Dim sShipName as String, sShipCompany as String, sShipStreet As String
      Dim sShipCity As String, sShipState As String, sShipZip As String
      Dim sMessage As String

      Overrides Sub Init()
         BillAddress.pHeader="Please enter your billing information:"
         ShipAddress.pHeader="Please enter your shipping information (if different):"
      End Sub

      Private Sub btnSubmit_Click(ByVal sender as Object, ByVal e As EventArgs)
         sBillName=Trim(BillAddress.pName)
         sBillCompany=Trim(BillAddress.pCompany)
         sBillStreet=Trim(BillAddress.pStreet)
         sBillCity=Trim(BillAddress.pCity)
         sBillState=Trim(BillAddress.pState)
         sBillZip=Trim(BillAddress.pZip)

         sShipName=Trim(ShipAddress.pName)
         sShipCompany=Trim(ShipAddress.pCompany)
         sShipStreet=Trim(ShipAddress.pStreet)
         sShipCity=Trim(ShipAddress.pCity)
         sShipState=Trim(ShipAddress.pState)
         sShipZip=Trim(ShipAddress.pZip)

         If sBillName="" Then sMessage = sMessage & "Billing Name, "
         If sBillCompany="" Then sMessage = sMessage & "Billing Company, "
         If sBillStreet="" Then sMessage = sMessage & "Billing Street, "
         If sBillCity="" Then sMessage = sMessage & "Billing City, "
         If sBillState="" Then sMessage = sMessage & "Billing State, "
         If sBillZip="" Then sMessage = sMessage & "Billing Zip, "

         If sMessage<>"" Then
            lblMessage.Text="Oops! Please enter your " & Mid(sMessage, 1, Len(sMessage)-2)
         Else
            lblMessage.Text="Thank you, " & sBillName & ", for your information."
            btnSubmit.Text="ReSubmit"
            ' Code to update database goes here.
         End if
      end sub

      Private Sub btnClear_Click(ByVal sender as Object, ByVal e As EventArgs)
      ' Call method to clear all fields in BillAddress and ShipAddress controls.
         BillAddress.ClearFields
         ShipAddress.ClearFields
         ' Clear label control.
         lblMessage.Text=""
         btnSubmit.Text="Submit"
      end sub
   </script>

   <body>
      <form method="POST" action="AddressTest.aspx" runat=server>
      <InputControl:AddressInfo id=BillAddress runat=server/><br>
      <InputControl:AddressInfo id=ShipAddress runat=server/><br>
          <asp:button id=btnSubmit text="submit order" OnClick="btnSubmit_Click" runat=server/>
          <asp:button id=btnClear text="Clear All" OnClick="btnClear_Click" runat=server/><br>
          <h4><asp:label id="lblMessage" runat=server/></h4>
      </form>
   </body>
</html>


ASP+ Syntax

An ASP+ file is a text file that contains markup syntax for coding server-side page logic, dynamic output, and literal content. By default, ASP+ files have the extension .aspx or .aspc (for pagelets); however, ASP+ will parse and compile any file that is mapped to the xspisapi.dll under IIS.

There are ten distinct syntax elements in ASP+:

Directives

Directives specify optional settings used by ASP+, such as encoding, transaction semantics, session state requirements, and others.

Code Declaration Blocks

Code declaration blocks define member variables and methods that will be compiled into the generated NGWS class that represents the page.

Code Render Blocks

Code render blocks define inline or inline expressions that execute when the page is rendered.

HTML Server Control Syntax

HTML server syntax enable page developers to insert and programmatically manipulate HTML elements on a page.

Custom Server Control Syntax

Custom server control syntax insert rich server controls on a page (including user-authored controls and the Web Controls that ship with the NGWS runtime).

Databinding Expressions

Databinding expressions declaratively create bindings between server control properties and data sources.

Server-side Object Tags

Server-side object tags declare and instantiate Classic COM and NGWS objects.

Server-side Include Directives

Server-side include directives enable developers to insert the raw contents of a specified file anywhere within an ASP+ Page.

Server-Side Comments

Server-side comments prevent server code or static content from executing or rendering.

Literal Text

Any content of an ASP+ file that does not fall into one of the above categories is treated as literal text. This includes string literals and standard HTML. For example, in the following fragment, there are three instances of literal text: "Name:", "<br>Address:", and "<br>Telephone:".

<form id="myform" runat="server">
   Name:<input type="text" id="name" runat="server" />
   <br>Address:<input type="text" id="address" runat="server" />
   <br>Telephone:< <input type="text" id="phone" runat="server" />
</form>

Each contiguous occurrence of literal text is instantiated by the framework as a literalcontrol object and populated into the page's control tree.

See Also

ASP+ Web Forms


Application File Syntax

An ASP+ application file is a declarative text file that contains markup syntax for coding a server-side application object. An ASP+ application file is named global.asax and resides in the root directory of an ASP+ application. At runtime, global.asax is parsed and compiled into a dynamically generated NGWS class derived from the HttpApplication base class. A global.asax file is composed from the following syntax elements:

Application Directives | Specify optional settings used by the ASP+ processor when handling ASP+ files.
Code Declaration Blocks | Define member variables and methods to be compiled into the generated Page class.
Server-side Object Tags | Declare and create new instances of objects using a declarative, tag-based syntax.
Server-side Include Directives | Insert the raw contents of a specified file anywhere within an ASP+ application file.

See also

ASP+ Page Syntax


Application Directives

Application directives specify optional settings used by the ASP+ page and application compilers when processing ASP+ files. Application directives are located at the top of an application file.

<%@ directive attribute=value [attribute=value … ]%>

Each directive may contain one or more attribute/value pairs specific to that directive. The following directives are supported:

@ Application | Defines application-specific attributes used by the ASP+ application compiler.
@ Import | Explicitly imports a namespace into an application.

See also

ASP+ Application File Syntax


@ Application

Defines application-specific attributes used by the ASP+ application compiler.

<%@ Application attribute=value [attribute=value … ]%>

The following attribute pairs are supported:

Attribute | Supported Values | Description
INHERITS | HTTPPipeline-derived class | Superclass for class to extend.
DESCRIPTION | Any string description | Provides a text description of the application (ignored by the parser and compiler).

Example

<%@ Application Inherits=”My401kApp.Object” Description=”A sample app” %>

The preceding code instructs the ASP+ application compiler to dynamically compile a new application class that extends the My401kApp.Object base class.

See also

ASP+ Application File Syntax | Application Directives


@ Import -- global.asax

Explicitly imports a namespace into an application, making all classes and interfaces of the imported namespace available to the application.

<%@ Import attribute=value [attribute=value … ]%>

The following attribute/value pairs are supported:

Attribute | Supported Values | Description
Namespace | Any valid namespace | Namespace to import.
Assembly | Any valid assembly | Assembly to link into compiled project.

Remarks

In addition to configuring assembly references/namespace imports at the application level, ASP+ also allows developers to specify them at the page level.

Example

The following code uses @ Import directives to import the System.IO and System.Collections namespaces for use throughout an application.

<%@ Import Namespace=”System.IO” %>
<%@ Import Namespace=”System.Collections” %>

See also

ASP+ Application File Syntax | Application Directives


Code Declaration Blocks --global.asax

NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Code Declaration Blocks — global.asax

Define application member variables, event handlers, and methods.

<script runat=”server” language=”language” src=”externalfile”>
   Code goes here….
</script>

Parameters

language
The language used in the current code declaration block. Can be C#, VB, or JScript.
externalfile
The name of a file containing code that is loaded and used in the current code declaration block.

Remarks

Code declaration blocks are defined using <script> tags that contain a runat="server" attribute/value pair. The script tag may optionally utilize a “language” attribute to specify the language of its inner code. If none is specified, ASP+ will default to the language configured for the application (specified in the application’s XML configuration file).

Example

[To be supplied.]

See also

ASP+ Application File Syntax


Server-side Object Tags -- global.asax

NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Server-side Object Tags — global.asax

Declare and create new application and session variables using a declarative, tag-based syntax.

<object id=”id” runat=server class=”Class Name”>
<object id=”id” runat=server progid=”Classic COM ProgID”/>
<object id=”id” runat=server classid=”Classic COM ClassID”/>

Parameters

id
A unique name to use when referencing the object in subsequent code.
Class Name
The class to create a new instance of.
Classic COM ProgID
The Classic COM component to create a new instance of.
Classic COM ClassID
The Classic COM component to create a new instance of.

Attributes

The <object> tag supports the following attributes:

ID | Provides a unique identifier to use when declaring an object on the page.
Runat | Must be set to “server” for the object to execute within ASP+. All non-server values cause the page compiler to assume that the object tag should be transmitted down for a client to handle.
Scope | Declares the scope at which the object is declared. “Pipeline” indicates that the object is available only to the HttpPipeline instance defined by the containing ASP+ application file. “Application” indicates that the object is populated into the HttpApplicationState collection. “Session” indicates that the object is populated into session state. If no scope is specified, scope defaults to “Pipeline”.
Class | Identifies the object type to create a new instance of.
ProgID | Identifies Classic COM component to create a new instance of.
ClassID | Identifies Classic COM component to create a new instance of.

Remarks

When the ASP+ processor encounters a server-side object tag in an ASP+ application file, it generates a get/set property on the class, using the ID attribute of the tag as the property name. The get method is then configured to create an instance of the object on first use.

If the object tag’s scope attribute is set to either “Application” or “Session”, a reference to the object (or at least enough information to lazily create it the first time it is accessed) is added to the StaticObjects collection of the appropriate application or session object. The object is automatically added to the namespace of all .aspx pages within the application.

The classid, progid, and class attributes are mutually exclusive. It is an error to use more than one of these attributes within a single server-side object tag.

Example

[To be supplied.]

See also

ASP+ Application File Syntax


Server-side Include Directives -- global.asax

Insert the raw contents of a specified file anywhere within an ASP+ application file.

<!-- #include pathtype = filename -->

Parameters

pathtype
The type of the filename path. The path type can be one of the following:
File | The file name is a relative path from the directory containing the file with the #include directive. The included file can be in the same directory or in a subdirectory; it cannot be in a directory above the file with the #include directive.
Virtual | The file name is a full virtual path from a virtual directory on your Web site.
filename
The name of the file to be included. filename must contain the file name extension, and you must enclose the file name in quotation marks (").

Remarks

The included file is processed before any dynamic code is executed (much like a C pre-processor).

The #include tag must use HTML/XML comment delimiters to avoid being interpreted as static text.

Example

[To be supplied.]

See Also

ASP+ Application File Syntax


Page Syntax

An ASP+ Page is a declarative text file that contains markup syntax for coding server-side page logic and generating dynamic output (typically HTML or XML). ASP+ page files are composed from the following syntax elements:

Directives | Specify optional settings used by the page-compiler when processing ASP+ files.
Code Declaration Blocks | Define member variables and methods to be compiled into the dynamically-generated class that represents the page.
Code Render Blocks | Allow inline rendering of code within HTML content.
Server-side Comments | Prevent server code (including server controls) and static content from executing or rendering. Used for documentation and testing.
HTML Control Syntax | Allow insertion and programmatic manipulation of standard HTML elements.
Custom Server Control Syntax | Allow insertion and programmatic manipulation of rich server controls (including user-defined controls and controls that ship with the NGWS runtime).
Databinding Expressions | Declaratively create bindings between server control properties and data sources.
Server-side Object Tags | Allow declaration, instantiation and insertion of objects into a page using a declarative, tag-based syntax.
Server-side Includes | Allow insertion of the raw contents of a specified file anywhere within an ASP+ Page.
Literal Text | Any element that does not belong to one of the categories listed above is interpreted as literal text.

See also

ASP+ Application File Syntax


Directives

Directives specify optional settings used by the page compiler when processing ASP+ files. They may be located anywhere within a page file.

<%@ directive attribute=value [attribute=value … ]%>

Each directive may contain one or more attribute/value pairs specific to that directive. The following directives are supported:

@ Page | Defines page-specific attributes used by the ASP+ page parser and compiler.
@ Import | Explicitly imports a namespace into a page.
@ Register | Associates aliases with namespaces and class names for concise notation in Custom Server Control Syntax.
@ Assembly | Declaratively links a assembly against the current page.
@ OutputCache | Declaratively controls the output caching policies of a page.

Remarks

Any directive block (<%@ %>) that does not contain an explicit directive name will be treated as an implicit Page directive.

See Also

ASP+ Page Syntax


@ Page

The @ Page directive defines page-specific attributes used by the ASP+ page parser and compiler.

<%@ Page attribute=value [attribute=value … ]%>
Note Multiple Page directives in a single .aspx file are not allowed. You can only have one Page directive per file. To define multiple attributes, use a space-separated list, with no space around the equals sign, as in TRACE="True".

The following attribute/value pairs are supported. Default values are underlined.

Attribute | Supported Values | Description
BUFFER | True, False | Defines HTTP response buffering semantics.
CODEPAGE | Any valid code page value | Indicates the locale codepage value for the ASP+ page.
CONTENTTYPE | Any valid HTTP content type string | Defines the HTTP content type of the response as a standard MIME type.
CULTURE | Any valid culture string | Indicates the culture setting for the page.
DESCRIPTION | Any string description | Provides a text description of the page.
ENABLESESSIONSTATE | True, False, ReadOnly | Defines session state requirements.
ERRORPAGE | Any valid HTTP URL | Defines target URL for redirection if an un-handled page exception occurs.
INHERITS | Any class derived from the Page Class class. | Code-behind class for page to inherit.
LANGUAGE | VB, C#, JavaScript | Language used when compiling all <% %> and <%= %> blocks within the page.
LCID | Any valid LCID value | Defines the locale identifier for code in the page.
MAINTAINSTATE | True, False | Indicates whether view state should be maintained across page requests.
RESPONSEENCODING | Values from Encoding.GetEncoding | Response encoding of content.
SRC | Source file name | Code-behind class to compile.
TRACE | True, False | Indicates whether tracing is enabled.
TRANSACTION | NotSupported, Supported, Required, RequiresNew | Indicates the transaction semantics.
WARNINGS | True, False | If true, indicates that compiler warnings should be treated as errors; otherwise ignored.

Example

<%@ Page Language=”VB” ContentType=”text/xml”%>

The preceding code instructs the ASP+ page compiler to use Visual Basic as the inline code language and sets the default HTTP MIME ContentType transmitted to the client to be “text/xml”.

See Also

ASP+ Page Syntax | Directives


@ Import

The @ Import directive explicitly imports a namespace into a page, making all classes and interfaces of the imported namespace available to the page. The imported namespace can be either part of the NGWS base class libraries or a user-defined namespace.

<%@ Import namespace="value" %>

Attributes

value
The namespace to import for this page.

Remarks

The @ Import directive cannot have more than one namespace attribute. To import multiple namespaces, use multiple @ Import directives.

The following namespaces are automatically imported into all pages:

  • System
  • System.Collections
  • System.IO
  • System.Web
  • System.Web.UI
  • System.Web.UI.HtmlControls
  • System.Web.UI.WebControls

Example

The following code fragment imports the NGWS base class namespace System.Net and the user-defined namespace Grocery:

<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="Grocery" %>

See Also

ASP+ Page Syntax | Directives


@ Register

The @ Register directive associates aliases with namespaces and class names for concise notation in custom server control cyntax.

<%@ Register Tagprefix="tagprefix" Namespace="namespace" %>
<%@ Register Tagprefix="tagprefix" Tagname="tagname" Src="pathname" %>

Attributes

tagprefix
An alias to associate with a namespace.
tagname
An alias to associate with a class.
namespace
The namespace to associate with tagprefix.
src
The location (relative or absolute) of the pagelet control associated with tagprefix:tagname.

Remarks

In custom server control syntax, a tagname is always used in conjunction with a tagprefix as a colon-separated pair: tagprefix:tagname. A tagprefix can be used alone in a custom server control tag, but a tagname cannot.

Example

The following fragment uses <% Register %> directives to declare tagprefix and tagname aliases for a server control and a user control. The first directive declares the MyTag alias as a tagprefix for all controls residing in the MyCompany:MyNameSpace namespace. The second directive declares Acme:AdRotator as a tagprefix:tagname pair for the user control defined in the file adrotator.aspc. The aliases are then used in custom server control syntax within the form to insert an instance of each server control.

<%@ Register Tagprefix="MyTag" Namespace="MyCompany:MyNameSpace" %>
<%@ Register Tagprefix="Acme" Tagname="AdRotator" Src="AdRotator.aspc" %>
<HTML>
 ...
   <form runat="server">
      <MyTag:MyControl id="Control1" runat="server" /><BR>
      <Acme:AdRotator file="myads.xml" runat="server" />
   </form>
 ...
</HTML>

See Also

ASP+ Page Syntax | Directives


@ Assembly

The @ Assembly directive declaratively links an assembly against the current page, making all of its classes and interfaces available for use on the page. Developers can also use this directive to register assemblies in the configuration file to link assemblies across the entire application.

<%@ Assembly Name="assemblyname" %>

Attributes

assemblyname
The name of the assembly to link against.

Remarks

Assemblies that reside in an application's /bin directory are automatically linked against the page. Such assemblies do not require the @ Assembly directive.

It is not necessary to include the path of an assembly in an @ Assembly directive.

The automatic linking described above is disabled by removing the following line from the <assembly> section of the config.cfg file:

<add assembly="*"/>

As an alternative to using the <%@ Assembly %> directive, you can register such assemblies within the config.cfg file to link assemblies across an entire application.

Example

The following code fragment uses the <%@ Assembly %> directive to link against MyAssembly.dll:

<%@ Assembly Name="myassembly.dll" %>

See Also

ASP+ Page Syntax | Directives


@ OutputCache

The @ OutputCache directive declaratively controls the output caching policies of a page.

<%@ OutputCache Duration="cachetime" Vary="headers" %>

Attributes

Duration
The time (in seconds) that the output cache for the page will be maintained.
Vary
A semicolon-separated list of HTTP headers used to vary the output cache.

Example

This example demonstrates how responses from the output cache can be varied to respond to the appropriate language.

<%@ page language="c#"%>
<%@ import namespace="System"%>
<%@ import namespace="System.Globalization"%>
<%@ import namespace="System.Threading"%>
<%
Random ran;
long ticks;
DateTime date, now;
String lang, accept;

ran = new Random(DateTime.Now.Second);
ticks = ran.Next(0, 65536)
        | ((long)ran.Next(0, 65536) << 16)
        | ((long)ran.Next(0, 65536) << 32)
        | ((long)ran.Next(0, (int)(DateTime.MaxValue.Ticks >> 48)) << 48);
date = new DateTime(ticks);
now = DateTime.Now;

Response.Cache.SetExpires(DateTime.Now.AddSeconds(10));
Response.Cache.SetLastModified(now);
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.Vary.UserLanguage = true;

accept = Request.ServerVariables["HTTP_ACCEPT_LANGUAGE"];

if (accept.IndexOf("de") > - 1) lang = "de";
else if (accept.IndexOf("fr") > - 1) lang = "fr";
else if (accept.IndexOf("es") > - 1) lang = "es";
else if (accept.IndexOf("ja") > - 1) lang = "ja";
else if (accept.IndexOf("zh") > - 1) lang = "zh";
else lang = "en";

Thread.CurrentThread.CurrentCulture = new CultureInfo(lang);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
%>
<html>
<head>
<title>Output Cache Sample</title>
</head>
<body>

<h2>Output Cache Sample</h2>

<p>This sample demonstrates use of the output cache to vary by language.  The output cache is enabled whenever <b>HttpCachePolicy.SetExpires</b> is set to a date in the future, and ii) <b>HttpCachePolicy.SetCacheability</b> is set to <b>HttpCacheability.Public</b>.  Finally, to vary by language, set <b>HttpCachePolicy.Vary.UserLanguage</b> to true.</p>
<hr>
<p>The request Accept-Language: header appears below:<br>
<% Response.Output.WriteLine("<pre>Accept-Language: {0}</pre>", accept); %>
<hr>
<p>A random date is created, and localized below:<br>
<% Response.Output.WriteLine("<pre>{0:F}</pre>", date); %>
<hr>
<p>The value of the response Last-Modified: header appears below:<br>
<% Response.Output.WriteLine("<pre>Last-Modified: {0:r}</pre>", now); %>
<hr>
</body>
</html>

See also

ASP+ Page Syntax | Directives


Code Declaration Blocks

Code declaration blocks define member variables and methods to be compiled into the dynamically generated Page class that represents the ASP+ page.

<script runat=”server” [language=”codelanguage”] >
   Code goes here….
</script>

In addition, an external file containing script can be specified:

< script runat=”server” [language=”codelanguage”] [src=”externalfilename”] />

Parameters

codelanguage
Specifies the language used in this code declaration block.
externalfilename
The name of a script file to load. When the src attribute is used, any other code in the declaration block is ignored.

Remarks

Code declaration blocks are defined using <script> tags that contain a runat attribute defined as “server”. The <script> tag may optionally utilize a language attribute to specify the language of its inner code. If none is specified, ASP+ will default to the language configured for the base page (controlled using the @ Page Directive).

The <script> tag may also optionally specify an external script file by defining a src attribute. When the src attribute is defined, all inner content within a <script runat=server> tag is ignored and the abbreviated form of the closing tag (/>) is used rather than </script>.

Example

The following example demonstrates how you could define event-handling logic for the EnterBtn_Click event.

<html>
  <script language="C#" runat="server">
      void EnterBtn_Click(Object Src, EventArgs E) {
          Message.Text = "Hi " + Name.Text + ", welcome to ASP+!";
      }
  </script>

  <body>
   <form runat="server">
    Enter your name: <asp:textbox id="Name" runat=server/>
                     <asp:button text="Enter" Onclick="EnterBtn_Click" runat="server"/>
        <p>
        <asp:label id="Message" runat=server/>
    </form>
  </body>
</html>

See Also

ASP+ Page Syntax


Code Render Blocks

Code render blocks define inline code or inline expressions that execute when the page is rendered. There are two styles:

Inline code (can be used to define either self-contained code blocks or control flow blocks):

<% inline code %>

Inline expression (short-cut for calling Response.Write):

<%= inline code %>

Remarks

A compilation error occurs if you attempt to include the character sequence %> anywhere inside a code render block. That sequence can only be used to close the code render block. For example, the following fragment will cause an error:

<%@ page language="C#" %>
<%
Response.Write(" %>");
%>

To work around this error, you can build a String containing the offending sequence:

<%@ page language="C#" %>
<%
String s = "%" + ">";
Response.Write(s);
%>

Example

[To be supplied.]

See also

ASP+ Page Syntax


Server-side Comments

Server-side comments prevent server code (including server controls) from executing.

<%-- commented out code or content --%>

Remarks

ASP+ server-side comment blocks have the same uses as traditional language-specific comment blocks, including documentation and testing.

Within <script runat="server"> </script> and <% %> blocks, you can use native language comment syntax.

You cannot use server-side comment blocks within <% %> blocks. A compilation error will result.

The opening and closing comment tags may appear on the same line of code, or can be separated by one or many commented-out lines.

Server-side comment blocks can be nested.

See also

ASP+ Page Syntax


HTML Control Syntax

HTML server control tags insert standard HTML elements as accessible server controls.

<HTMLTag [id=”OptionalName”] [attribute="value" ...] runat="server">
[</HTMLTag>]

Parameters

HTMLTag
The HTML element that this server control tag declares.
OptionalName
A unique identifier to enable programmatic referencing of the control.
attribute
The name(s) of other properties or event bindings to be assigned to the control.
value
The value of each attribute.

Remarks

By default, HTML tags within an ASP+ file are treated as literal text and are programmatically inaccessible to page developers. You can indicate that an HTML tag should be parsed and treated as an accessible server control by adding a runat attribute with a value of “server”. A unique id attribute enables programmatic referencing of the control. Name/value attributes are used to declare property arguments and event bindings on server control instances.

HTML server controls must reside within a containing <form> control with the runat="server" attribute/value pair. The <form> control itself is exempt from this requirement.

ASP+ does not require literal (non-dynamic) HTML content to be well-formed, but it does require that all non-closed HTML tags be properly closed (either with a trailing “/” or an end tag) and cleanly nested (overlapping tags are not allowed).

If an HTML tag that is not supported (such as <body>, <span>, or <div>) is marked with a runat="server" attribute/value pair, ASP+ will use the generic HTMLControl class.

Supported Controls

For a list of supported controls, see Supported HTML Server Controls.

Example

The following simple page shows the use of several HTML server controls:

<html>
   <head>
      <script runat="server" language="VB">
         Sub FruitList_Click(  Source As Object, _
                               E As EventArgs )
            FruitName.InnerHTML= FruitList.Value
            FruitImage.SRC= "images\" & FruitList.Value & ".jpg"
         End Sub
      </script>
   </head>
   <body>
      <form runat="server">
         <font face="Verdana">
         <b>Please Select A Fruit: </b>
         <select id="FruitList" runat="server" size="1">
            <option value="Orange">Orange</option>
            <option value="Apple">Apple</option>
            <option value="Mango">Mango</option>
            <option value="Pear">Pear</option>
            <option value="Banana">Banana</option>
         </select>
         <input type="submit" value="submit" runat="server"
            OnServerClick="FruitList_Click">
         <p>
         <table><tr><td>
            <img id="FruitImage" src="images\blank.gif" runat="server" />
         </td><td>
            <font face="Verdana" size=6>
               <span id="FruitName" runat="server"/>
            </font>
         </td></tr></table></font>
      </form>
   </body>
</html>

See also

ASP+ Page Syntax


Custom Server Control Syntax

Custom server controls enable you to create reusable components that encapsulate common programmatic functionality. You can create your own custom server controls, or use the Web Controls shipped with the NGWS frameworks.

Server Control Declaration Syntax

You can instantiate a control by specifying declarative tags in an .aspx file. These tags must include a runat=server attribute/value pair. If you want to enable programmatic referencing on the control, specify a unique id attribute.

Custom server control tags insert rich server controls (including user-authored controls and all Web Controls.

<tagprefix:tagname id=”OptionalID” runat=server />
OR
<tagprefix:tagname id=”OptionalID” runat=server >  </tagprefix:tagname>

Parameters

tagprefix
An alias for the fully qualified namespace of the control. The alias for Web Controls is ASP. Aliases for user-authored controls are declared with the @ Register Directive.
tagname
The name of the class that encapsulates the control.
OptionalID
A unique identifier to enable programmatic references to the control.

Remarks

Custom server controls are reusable components that encapsulate common programmatic functionality. Page developers use custom server controls by specifying declarative tags that have a runat="server" attribute. The optional id attribute defines a unique identifier for programmatic reference.

Example

The following code fragment inserts a TextBox Web Control:

<ASP:TextBox id="MyTextBox" runat="server" />

Server Control Property Syntax

Custom server control properties are specified using declarative attribute/value pair syntax. The case-insensitive attribute name represents the property name, and the value represents the property value to assign.

<tagprefix:tagname id="OptionalID" propertyname="propertyvalue" runat=server />

Parameters

tagprefix
An alias for the fully qualified namespace of the control. The alias for Web Controls is asp. Aliases for user-authored controls are declared with the @ Register Directive.
tagname
The name of the class that encapsulates the control.
OptionalID
A unique identifier to enable programmatic references to the control.
propertyname
The name of the property.
propertyvalue
The value to assign to propertyname.

Example

The following code fragment inserts a TextBox Web Control and sets its maxlength property to 55.

<ASP:TextBox id="MyTextBox" maxlength="55" runat="server" />

Supported Types

You can declaratively set all of the standard primitive datatypes directly. You can also specify properties represented using an enumeration (where ASP+ will treat the attribute value of the control as the enumeration value name).

Supported Property Data Types
String | Integer | Double
Byte | Char | Boolean
DateTime | TimeSpan | Decimal
Variant | Currency |
Note You can also specifiy properties represented using enumerators. The parser will treat the attibute value of the control as the enumerator value name.

To set properties on member objects, see "Complex Property Syntax" below.

Subproperty Syntax

Custom server controls may have properties that are objects, and those objects may in turn have their own properties. To specify properties for objects that are themselves properties, use a name/value pair where subproperties are identified by a hyphen:

<tagprefix:tagname propertyname-subpropertyname="subpropertyvalue"
   runat=server />

Parameters

tagprefix
An alias for the fully qualified namespace of the control. The alias for Web Controls is asp. Aliases for user-authored controls are declared with the @ Register Directive.
tagname
The name of the class that encapsulates the control.
propertyname
The name of the property that contains properties to be defined.
subpropertyname
The name of the property being defined.
propertyvalue
The value to assign to subpropertyname.

Example

<ASP:Label font-name="Arial" font-size="14" runat="server">
   ...some literal text...
</ASP:Label>

Server Control Event Binding Syntax

Event bindings on server controls are specified using declarative attribute name/value pairs, where the attribute name represents the control’s event name and the value represents the method on the page that should be called when the event is fired.

<tagprefix:tagname EventName=”EventHandlerMethodName” runat="server" />

Parameters

tagprefix
An alias for the fully qualified namespace of the control. The alias for Web Controls is asp. Aliases for user-authored controls are declared with the @ Register Directive.
tagname
The name of the class that encapsulates the control.
EventName
The event name of the control.
EventHandlerMethodName
The name of the method that will handle EventName.

Remarks

At runtime, ASP+ will dynamically configure the appropriate (type-safe) NGWS delegate object between the control event adder method and the target method. The target method can be defined either declaratively within a <script runat=server></script> block or in the page’s superclass.

Example

The following code fragment declares MyButton_Click as an event handler for the button's OnClick event:

<html>
   <script runat=server>
      Sub MyButton_Click (Sender as Object, Evt as EventArgs)
         ‘Do something when someone clicks the button
      End Sub
   </script>
   <body>
      <form action="default.aspx" method="post" runat=server>
         <ASP:Button text="Hello" OnClick="MyButton_Click" runat=server/>
      </form>
   </body>
</html>

Server Control Inline Template Syntax

As a control developer, you can include template properties in the design of custom server controls to separate control behavior from appearance. Control consumers (page developers) can use those template properties to customize the appearance of server controls on their pages.

< tagprefix:tagname [id=”OptionalName”] [attribute=value ...] runat=server/>
   <template name="templatepropertyname">
      inline template values
   </template>
</ tagprefix:tagname >

Parameters

tagprefix
An alias for the fully-qualified namespace of the control. The alias for Web Controls is ASP. Aliases for user-authored controls are declared with the @ Register Directive.
tagname
The name of the class that encapsulates the control.
OptionalName
A unique identifier to enable programmatic references to the control.
attribute
The name(s) of control properties or event bindings to be assigned to the control.
value
The value of each attribute.
templatepropertyname
The name of the server control template property.

The Web Forms page framework interprets this syntax at parse time to dynamically bind an appropriate ITemplate instance to the template property of the parent server control.

Example

The following code fragment shows how a page developer could customize the header and each cell of a Repeater control:

<ASP:Repeater id="myrepeater" runat=server>
   <template name="headertemplate">
      Header content goes here...
   </template>
   <template name="itemtemplate">
      Cell content goes here...
   </template>
</ASP:Repeater>

See also

ASP+ Page Syntax


Databinding Expressions

Databinding expressions declaratively create bindings between server control properties and data sources. Databinding expressions can be written either on the value side of a name/value pair:

<tagprefix:tagname property="<%# databinding expression %> runat="server" />

or inline within literal text:

literal text <%# databinding expression %>

Parameters

tagprefix
An alias for the fully-qualified namespace of the control. The alias for Web Controls is asp. Aliases for user-authored controls are declared with the @ Register Directive.
tagname
The name of the NGWS class that encapsulates the control.
property
The control property for which this databinding is declared.
expression
Any expression that conforms to the requirements under Remarks below.

Remarks

ASP+ supports a hierarchical databinding model that supports associative bindings between server control properties and parent data sources. Any server control property can be databound. Control properties can databind against any public field or property on either the containing page or their immediate naming container.

Using DataBinder.Eval

The Web Forms framework also supplies a static method that evaluates late-bound data binding expressions and optionally formats the result as a string. DataBinder.Eval is convenient in that it eliminates much of the explicit casting the developer must do to coerce values to the desired data type. It is particularly useful when data binding controls within a templated list, since often the both the data row and the data field must be cast.

Consider the following example, where an integer will be displayed as a currency string. With the standard ASP+ data binding syntax, you must first cast the type of the data row in order to retrieve the data field, IntegerValue. Next, this is passed as an argument to the String.Format method:

<%# String.Format("{0:c}", ((DataRowView)Container.DataItem)["IntegerValue"]) %>

This syntax can be complex and difficult to remember. In contrast, DataBinder.Eval is simply a method with three arguments: the naming container for the data item, the data field name, and a format string. In a templated list like DataList, DataGrid or Repeater the naming container is always Container.DataItem. Page is another naming container that can be used with DataBinder.Eval.

<%# DataBinder.Eval(Container.DataItem, "IntegerValue", "{0:c}") %>

The format string argument is optional. If it is omitted, DataBinder.Eval returns a value of type object. For example:

<%# (bool)DataBinder.Eval(Container.DataItem, "BoolValue") %>

Example

<html>
   <script language=”VB” runat=server>
      Sub Page_Load(Sender as Object, E as EventArgs)
                            MyOrderSystem as New OrderSystem
           MyRepeater.DataSource = MyOrderSystem.GetOrdersFromDay(Now)
           MyRepeater.DataBind()
      End Sub
   </script>

   <body>
       <form action=”DatabondPage.aspx” method=”post” runat=server>
         <asp:repeater id=”MyRepeater” runat=server>
             <template name=”itemtemplate”>
            <img src=”<%# Container.DataItem.ImageUrl %>” runat=server>
               <b> Description: </b> <%# Container.DataItem.Description %>
             </template>
          </asp:repeater>
       </form>
   </body>
</html>

See also

ASP+ Page Syntax


Server-side Object Tags

Server-side object tags declare and instantiate Classic COM and NGWS objects using a declarative, tag-based syntax.

<object id=”id” runat=server class=”NGWS Class Name”>
<object id=”id” runat=server progid=”Classic COM ProgID”/>
<object id=”id” runat=server classid=”Classic COM ClassID”/>

Parameters

id
Unique name to use when referencing the object in subsequent code.
NGWS Class Name
Identifies the NGWS class to instantiate.
Classic COM ProgID
Identifies Classic COM component to instantiate.
Classic COM ClassID
Identifies Classic COM component to instantiate.

Attributes

The object tag supports the following attributes:

ID | Unique identifier to use when declaring object on page.
Runat | Must be set to “server” for the object to execute within ASP+. All non-server values cause the page compiler to assume that the object tag should be transmitted to the client.
Class | Identifies NGWS class to instantiate.
ProgID | Identifies Classic COM component to instantiate.
ClassID | Identifies Classic COM component to instantiate.

Remarks

When the page parser/compiler encounters a server-side object tag in an ASP+ file, it generates a get/set property on the page, using the ID attribute of the tag as the property name. The get (accessor) method is then configured to create an instance of the object on first use. The resulting instance is not added as an object within the page’s hierarchical server control tree; it is instead treated as a non-UI variable declaration.

The classid, progid, and class attributes are mutually exclusive. It is an error to use more than one of these attributes within a single server-side object tag.

Example

<html>
   <object id=”MyDatabase” class=”Microsoft.WFC.XDO.OLEDBAdaptor” runat=”server”/>
   <script language=”VB” runat=server>
      Sub Page_Load(Sender as Object, E as EventArgs)
         Dim StockDetails as Recordset
         Set StockDetails = MyDatabase.Execute(“DSN:money”, “select * from stocks”)
      End Sub
   </script>
</html>

See also

ASP+ Page Syntax


Server-side Include Directives

Server-side include directives insert the raw contents of a specified file anywhere within an ASP+ page.

<!-- #include pathtype = filename -->

Parameters

pathtype
Specifies the type of the path to filename. The path type can be one of the following:
File | The file name is a relative path from the directory containing the document with the #include directive. The included file can be in the same directory or in a subdirectory; it cannot be in a directory above the page with the #include directive.
Virtual | The file name is a full virtual path from a virtual directory in your Web site.
filename
Specifies the name of the file to be included. filename must contain the file name extension, and you must enclose the file name in quotation marks (").

Remarks

The included file is processed before any dynamic code is executed (much like a C pre-processor).

The #include tag must be enclosed within HTML/XML comment delimiters to avoid being interpreted as static text.

Example

<html>
   <body>
      <!-- #Include file=”header.inc” -->
      Here is the main body of my file:
      <% For I=0 to 10 %>
         <!-- #Include virtual=”/Includes/Foobar.inc” -->
      <% Next %>
      <!-- #Include virtual=”footer.inc” -->
   </body>
</html>

See also

ASP+ Page Syntax


Naming Services

The Web Forms framework allows page developers to assign a unique ID attribute to each server control on a page for programmatic reference. The ID attribute allows the framework to locate and manipulate any given control on a page. Controls that are not assigned an ID attribute by the developer are automatically assigned one by the page framework.

Server controls that provide built-in repetition capabilities (for example, repeaters and datagrids) expose templates that can contain other server controls. For example, the following fragment shows a repeater control that contains a span in its itemtemplate. In this example, there will be three span controls on the page because there are three data items in the repeater control's data source.

<html>
   <script language=”C#” runat=server>
      private void Page_Load(Object sender, EventArgs e) {
         MyRepeater.Datasource = { “One”, “Two”, “Three” };
         MyRepeater.Databind();
      }
   </script>
   <body>
      <form action=”Repeater.aspx” method=”Post” runat=server>
         <asp:repeater id=”MyRepeater” runat=server>
            <template:itemtemplate>
               <span id=”Message” runat=server>
                  <%# Container.DataItem %>
               </span>
            </template:itemtemplate>
         </asp:repeater>
      </form>
   </body>
</html>

In this case, the developer cannot provide a unique ID for an individual span control, only for all the span controls in common. To extend unique naming services in such scenarios, the Web Forms control framework supports the notion of NamingContainers. NamingContainers are server controls that implement the INamingContainer marker interface. Any NamingContainer on a page logically defines a new ID namespace hierarchy within the control tree. A control’s UniqueID property is guaranteed to be unique among all controls that share its NamingContainer within the tree. The UniqueID property provides read-only access to the fully qualified hierarchical identifier for a control. It consists of the naming container’s ID concatenated before the control’s local ID property.

In the code above, while the local ID property of each of the span controls is Message, the actual fully qualified UniqueID values of the controls are actually: MyRepeater:RepeaterRow0:Message, MyRepeater:RepeaterRow1:Message and MyRepeater:RepeaterRow2:Message.

A control’s UniqueID value is used heavily at runtime during the postback phase of page execution to route client data values and postback event references to appropriate server controls.

You can also utilize a server control’s ID value to obtain a reference to the control instance at runtime. Developers can search the tree for a given ID using the FindControl method exposed on the Control base class. The FindControl method will perform a relative search of the control’s naming container for the given control, and will return a reference to it if found. For example:

<html>
   <script language="C#" runat=server>
      private void Page_Load(Object sender, EventArgs e) {
         MyRepeater.Datasource = { "One", "Two", "Three" };
         MyRepeater.Databind();
      }
      private void ChangeBtn_Click(Object sender, EventArgs e) {
         HtmlGenericControl x = MyRepeater.FindControl("RepeaterRow1:Message");
         if (x != null) {
            x.InnerHtml = "This control was found with FindControl.";
         }
      }
   </script>
   <body>
      <form action="Repeater.aspx" method="Post" runat=server>
         <asp:repeater id="MyRepeater" runat=server>
            <template:itemtemplate>
               <span id="Message" runat=server>
                  <%# Container.DataItem %>
               </span>
            </template:itemtemplate>
         </asp:repeater>
         <input type=submit OnServerClick="ChangeBtn_Click" runat=server>
      </form>
   </body>
</html>

See Also

ASP+ Web Forms


View State Services

The HTTPRuntime infrastructure provides server-side state management services that allow web developers to store state information at either an application or user session level. The ASP+ framework augments the HTTPRuntime state services by allowing page and control developers to persist view state, the state associated with a particular view of a page.

Page and control developers can persist view state by storing and retrieving values to/from the Control.ViewState dictionary, an instance of the ViewStateBag class. The Control.ViewState dictionary automatically maintains a localized "picture" of view state data. Page and control developers can read from the ViewState collection at any time during a page request, but cannot write values into the ViewState dictionary during or after the Render stage of the page lifecycle. For information on the Render stage, see Writing a Custom Web Forms Control.

The following example shows how a control developer can persist view state by storing a public property in the control's ViewState dictionary:

public class Table : Control {
// Expose a public "BGColor" property on the table control.
// Store the value in viewstate to ensure that the value is
// automatically round-tripped back to the server.
   public String BGColor {
      get {
         return ViewState["BGColor"];
      }
      set {
         ViewState["BGColor"] = value;
      }
   }
}

At render time, all server controls on a page that store view state in the ViewState dictionary write that information into a specially named hidden form variable (__VIEWSTATE) on the dynamically generated HTML page:

<form>
   <input type=hidden name="__VIEWSTATE"
      value="<a><s>BgColor:red</s></a>">
   <input type=hidden name="__VIEWSTATEMAC" value=”434343433433”>

   <table bgcolor="red"> </table>
</form>

The __VIEWSTATEMAC hidden form variable supports a Message Authentication Check (MAC) on page postback for security purposes. If the MAC of the incoming view state data is legal, the framework automatically populates the ViewState dictionary with its previously saved state data immediately prior to calling the Init method on each server control.

See Also

ASP+ Web Forms


Trace Functionality

ASP+ aids the debugging and testing process by providing a trace capability that does two things when enabled:

  • it automatically appends a table of performance data to the end of the page.
  • it allows control and page developers to insert custom diagnostic messages throughout their code.

The performance data and the developer-specified messages (if any) are injected into the HTML output stream to the client to help clarify what is occurring as the framework processes a page request.

The trace capability is encapsulated in the TraceContext class, which is exposed as the Page.Trace property of the Page class.

Enabling Tracing

The trace capability must be enabled prior to use. You enable tracing by setting the Trace attribute of the @ Page directive to true (it is false by default):

<%@ Page Language="VB" Trace="true" %>

The Trace attribute sets the value of the TraceContext.IsEnabled boolean.

Inserting and Organizing Trace Messages

There are two methods you can use to insert custom messages: TraceContext.Write and TraceContext.Warn. In their simplest form, each method takes two string arguments:

public void Write( string category, string message )
public void Warn( string category, string message )

The category argument is used to organize messages. For example:

<%@ Page Language="VB" Trace="true" %>
<html>
   <body>
   <%
      Trace.Write( "MyCategory", "This message is mine." )
   Trace.Write( "YourCategory", "This message is yours." )
   %>
</body>
</html>

The difference between Write and Warn is that Warn uses a red font in its output.

Both Write and Warn have overloaded versions that take an additional argument of type Exception. You can use these versions in exception-handling code to display information about the exception.

Sorting Trace Messages

The TraceContext class contains a TraceMode property that defines the way that trace messages are sorted in the page output. TraceMode is of type TraceModeEnum. There are three possible values:

  • SortByTime -- trace messages are output in order of chronological occurrence
  • SortByCategory -- trace messages are sorted alphabetically by the category argument to the Write and Warn methods

See Also

Application-level Tracing | ASP+ Web Forms


Application-level Tracing

In addition to the exposing a generalized trace facility to developers, the ASP+ runtime can also collect helpful request information globally for an application. Collecting this additional data must be explicitly enabled for the application through the configuration system. Unlike custom user-defined Trace statements, these internal trace categories are not exposed in the client browser, but are displayed by the trace viewer application instead. The trace viewer displays trace information for a series of requests to the application, so metrics for each request must be stored in memory while tracing is enabled. This is made possible by inserting an IHttpModule-derived class, TraceModule, that participates in the HTTP Pipeline execution of each request to the application.

Enabling Global Trace through Configuration

Global tracing is enabled through the configuration system at the application level only, by the presence of a <tracing> configuration section with the enabled property set to true. Internal trace information is collected for each request to the application, up to a specific number of requests (the default is 10).

Enabling global tracing has the side effect of enabling page-level trace functionality for the entire application, overriding any settings in page-level directives that explicitly disable Trace. That is, any user-defined Trace.Write or Trace.Warn statements in application code will be executed and stored for viewing in the Trace Viewer. Optionally, the user can define a PageOutput property to force Trace statements to also be rendered in the browser client, as they would if tracing were enabled in a page-level directive. Pages for which tracing is already enabled by a page-level directive are not affected by the absence of this property.

For example:

<configuration>
   <configsections>
      <add name="trace" type="System.Web.Config.TraceConfigHandler">
   </configsections>
   <tracing enabled="false" requestlimit="15" pageoutput="true" />
</configuration>

The following are configuration parameters for tracing.

Property | Description
Enabled | true if tracing is enabled for the application; otherwise false. The default is false.
PageOutput | Indicates whether trace information should be rendered at the end of each page – or only accessible via the trace utility.
RequestLimit | Number of trace requests to store on the server. The default is 10.

See Also

Trace Functionality


Using ASP+ Web Services

The Internet is quickly evolving from websites that just deliver UI pages to browsers to a next generation of programmable Web sites that directly link organizations, applications, services, and devices with one another. These programmable Web sites become more than passively accessed sites - they become reusable, intelligent web services.

In NGWS, a web service is any URL-addressable resource that makes available useful information to HTTP clients. A web service can be as simple as a static data file containing information that is updated periodically. More complex web services support interactive computation ---where the HTTP request includes parameters that the service uses in a calculation--- and provide a response that the requester can understand. For example, a stock quote service could take a stock symbol on the querystring and return a document containing the latest stock details.

In addition to the topics below, this section includes:

Creating an ASP+ Web Service

Exposing and Publishing an ASP+ Web Service

ASP+ Web Services State Management

ASP+ Web Services Security

ASP+ Web Services Transactions

ASP+ Web Services Async Support

Consuming Web Services

ASP+ Web Service and SDL

There is a critical need to describe web services in a standard way so that end-user clients can understand and invoke them programmatically. The public description of a web service is called a “contract.” NGWS uses a new XML grammar, the Service Description Language (SDL), as the standard contract format for describing web services.

SDL provides an extensible format for describing:

  • Transport of Services (endpoint URLs and protocols)
  • Type of Invocation Messages (structure and content)
  • Sequences of Messages (how they are exchanged)

Note that these interactions only represent wire-protocol level details. SDL does not assume anything about the implementation of the target web service (it could be an object based system like ASP+, a database, or a CICS system). As such it should be possible to use it to describe any web service available over the Internet. Note that an ASP+ Web Service implementation dynamically generates SDL contracts automatically when the base URL address is invoked using a standard HTTP-GET request. For more information on invoking ASP+ Web Services with the HTTP-GET request, see Invoking ASP+ Web Services using HTTP-GET.

Web Services and XML

You can use XML to describe the set of available web services that a system defines as well as the type and sequence of messages that should be exchanged between systems. This ability is provided by the Service Description Language (SDL) discussed above. You can also define and embrace canonical XML patterns, like SOAP, for easily marshalling and interchanging XML messages to and from web services.

You can also extend your web service client infrastructure to build-in explicit support for “capturing” or “scraping” content from an HTML page (regardless of whether it is static or dynamic), and programmatically use the resulting data. No action or participation is required from remote site owners, so the technology can be used immediately with popular sites such as Amazon.com, Investor.com, Yahoo.com, to obtain data and information that can be leveraged (copyright restrictions willing) to build rich applications. Note that the programming model to access this type of information on the client is identical to the model where the target data is expressed in XML.


Creating an ASP+ Web Service

Developers create and expose an ASP+ Web Service by authoring and saving text files with the file extension “ASMX” within the virtual path of an ASP+ Web Application. Clients then invoke and consume an ASP+ Web Service by issuing HTTP requests for these URL resources.

ASMX text files reference NGWS runtime classes that encapsulate the functionality of a given web service. These NGWS runtime classes can either live in external, pre-compiled assemblies that the ASMX file references, or can alternatively be defined in-line within the ASMX file itself and will be dynamically compiled and cached on the server on first access.

For example, the directory hierarchy below demonstrates a sample web application that exposes 4 webservices – ZipCodes.asmx, TelephoneNumbers.asmx, OrderSystem.asmx and OrderStatus.asmx.

C:\inetpub\wwwroot\MyApp
      \Bin
           \MyUtility.dll

      \Global.asax

      \Orders
           \OrderSystem.asmx
           \OrderStatus.asmx

      \TelephoneNumbers.asmx
      \ZipCodes.asmx


Syntax for ASP+ Web Service ASMX Files

A ASP+ Web Service ASMX file is a declarative text file with a .asmx extension that contains markup syntax for either referencing or defining an ASP+ Web Service. ASP+ Web Service ASMX files are composed of the following syntax and semantic elements:

ASP+ Web Services Directive
Web Service ASMX files can have directives that specify web service settings (language, class reference, etc). For more information, see ASP+ Web Services ASMX Directive Syntax.
ASP+ Web Services Code Declarations
Contracts can also have code declarations that enable web service classes to be defined and declared in-line within the file (alternatively web service classes can be referenced as compiled classes). This code will be dynamically compiled and cached the first time the web service is invoked from a client. For more information, see ASP+ Web Services Code Declarations Syntax.


ASP+ Web Services ASMX Directive Syntax

ASP+ Web Service ASMX directives specify optional settings used by the ASP+ Web Service compilers when processing files. They are located at the top of an application file, and have the syntax:

<%@ directive {attribute=value}* %>

WebService Directive

The Web Service directive defines a number of web service specific attributes that the ASP+ Compiler uses when parsing/compiling a web service class. These include:

Attribute | Supported Values | Description
CLASS | Any NGWS runtime class | Webservice class to reference
LANGUAGE | VB, JavaScript, C# | Language used when compiling in-line code within the webservice file.

An example web service directive can be found below (it instructs the ASP+ compiler to dynamically compile a new web service class -- using C# as its language):

<%@ WebService Language=”C#” %>
using System.Web.Services;
public class Math : WebService {
     [ WebMethod ]
     public int Add(int num1, int num2) {
          return num1+num2;
     }
     [ WebMethod ]
     public int Subtract(int num1, int num2) {
          return num1-num2;
     }
}

An alternative webservice directive example follows; it instructs ASP+ to reference a pre-compiled web service class called MyName.MyWebService:

<%@ WebService Class=”MyName.MyWebService” %>


ASP+ Web Services Code Declarations Syntax

ASP+ Web Services developers can optionally define new web service classes within a contract file. These classes are defined in-line within the file. Unlike the ASP+ Page and Application files, there is no surrounding <script runat=server> block.

For example:

<%@ WebService Language=”C#” %>
using System.Web.Services;
public class Math : WebService {
     [ WebMethod ]
     public int Add(int num1, int num2) {
          return num1+num2;
     }
     [ WebMethod ]
     public int Subtract(int num1, int num2) {
          return num1-num2;
     }
}

Developers can optionally specify the file’s source language using the “language” attribute on the WebService directive. If no language is specified, the ASP+ Compiler will use the default language specified with the application’s config.web configuration file.


ASP+ Web Services ASMX File Examples

The following ASP+ Web Services ASMX file demonstrates linking to an existing precompiled web service class (“MyWebApplication.MyWebService”). It consists of a single declaration:

<%@ WebService Class=”MyWebApplication.MyWebService” %>

The next example demonstrates an ASMX file that instead defines the web service class inline:

<%@ WebService Language=”C#” %>
using System.Web.Services;
public class Math : WebService {
     [ WebMethod ]
     public int Add(int num1, int num2) {
          return num1+num2;
     }

     [ WebMethod ]
     public int Subtract(int num1, int num2) {
          return num1-num2;
     }
}


ASP+ Web Services Class Definitions

The ASP+ WebServices Framework is implemented within the System.Web.Services namespace. It contains the following public classes/interfaces:

  • System.Web.Services.WebService
  • System.Web.Services.WebMethodAttribute
  • System.Web.Services.WebServiceAttribute


System.Web.Services.WebService

The WebService class defines the base class for all webservices.

namespace System.Web.Service
{
   public class WebService : Component
   {
      // Properties
   public HttpContext         Context       { get; }
   public HttpApplicationState      Application    { get; }
   public SessionState         Session      { get; }
   public HttpServerUtility      Server       { get; }
   public IPrincipal            User         { get; }
   }
}

Properties

Context
Prototype | public HttpContext Context { get; }
Description | HTTPRuntime provided context object that allows developers to gain access to additional pipeline-module exposed objects.
Application
Prototype | public HttpApplicationState Application { get; }
Description | HTTPRuntime provided application intrinsic.
Session
Prototype | public SessionState Session { get; }
Description | HTTPRuntime provided session intrinsic.
Server
Prototype | public HttpServerUtility Server { get; }
Description | ASP Compatible “Server” Intrinsic
User
Prototype | public IPrincipal User { get; }
Description | User security intrinsic


System.Web.Services.WebMethodAttribute

The WebMethodAttribute class defines the metadata customization attribute used by public web methods within a webservice.

namespace System.Web.Service
{
   public class WebMethodAttribute : Attribute
   {
      // Properties
      public String      Description         { get; set; }
      public bool      EnableSessionState      { get; set; }
      public Transaction   Transaction       { get; set; }
   }
       public enum Transaction
   {
              None = 0,
              Required = 1,
              RequiresNew = 2,
              Supported = 3
       }
}

Properties

Description
Prototype | public String Description { get; set; }
Description | Description of the web method (used in HTML Contract generation)
EnableSessionState
Prototype | public bool EnableSessionState { get; set; }
Description | Indicates whether sessionstate is enabled for web method.
Application
Prototype | public Transaction Transaction { get; set; }
Description | Indicates whether a new transaction context should be created for the context of the web method.


System.Web.Services.WebServiceAttribute

The WebServiceAttribute class defines the metadata customization attribute that enable web service developers to custom describe their webservice classes.

namespace System.Web.UI
{
   public class WebServiceAttribute : Attribute
   {
      // Properties
      public String      Description      { get; set; }
   }
}

Properties

Description
Prototype | public String Description { get; set; }
Description | String description of webservice (used in HTML Contract generation).


Exposing and Publishing an ASP+ Web Service

Once created, an ASP+ Web Service is published on a web server using an .ASMX file resource. This URL addressable file is used both to activate/invoke the web service as well as automatically publish its functionality contract to consumers.

This section discusses the following topics:

Exposing Web Methods within a Web Service

Publishing HTML Contracts


Exposing Web Methods within a Web Service

The ASP+ Web Service enables web services developers to expose programmatic functionality to web clients without requiring knowledge of HTTP, XML, or custom marshalling in order to do so.

Developers instead create ASP+ Web Services by simply creating NGWS runtime classes that expose one or more public methods marked with the metadata attribute WebMethod. ASP+ then handles these as “web callable” methods at runtime, and automatically route and marshal appropriate arguments in and out of the methods when it receives requests from a client.

For example, the below class provides a (very) simple stock lookup web service:

<%@ WebService Language=”C#” %>
using System.Web.Services;
public class Investor : WebService {
     public bool ValidateSymbol(string symbol) {
        if ((symbol == “MSFT”) || (symbol == “ONTC”)) {
            return true;
        }
        return false;
     }
     [ WebMethod ]
     public double StockLookup(string symbol) {

           if (ValidateSymbol(symbol) == false) {
               return –1;
           }
           return StockBizObject.LookupLastPrice(symbol);
     }
}

Assuming the above file was saved as Investor.asmx on the http://www.msn.com web site, a user would be able to access the web service as http://www.msn.com/Investor.asmx and would be able to obtain stock information about Microsoft using the StockLookup method accessed via the following url: http://www.msn.com/Investor.asmx/StockLookup?symbol=MSFT.

Important: Note that although the above class exposes two public methods, only one of them, StockLookup, is actually web callable (ValidateSymbol is not marked with the Web Method attribute). ASP+ Web Services has added the extra restriction of requiring public methods to be marked with this metadata to ensure that only those methods that are really intended to be called remotely can actually be invoked (this prevents all of the public methods exposed on the Object base class – like Wait(), HashCode(), ToString(), etc. from being invoked by any random user).


Publishing HTML Contracts

When clients access an ASMX file using a standard HTTP GET request with no method extensions or querystring parameters, ASP+ dynamically reflects over the web service class and generates an HTML page describing the service capabilities, methods exposed, and protocols supported. The HTML page also contains a number of standard browser samples that enable browser clients to invoke and test out the various web methods within the service.

For example, the call to the http://www.msn.com/Investor.asmx example for the previous page would generate an HTML page that looked like:

Note that the HTML page generated can be completely customized by a web service developer (it is just a standard ASP+ Web Form that users can modify) – so it is possible for publishers of webservices to provide their own custom logos, UI, and designs.

Publishing XML Based Service Description Language (SDL) Contracts

In addition to describing web services using HTML UI, ASP+ enables clients to obtain more formal definitions of service capabilities, wire-formats, and marshaling support. These formal contracts are defined using the XML-based Service Description Language (SDL) grammar and are ideal for proxy generation tools capable of using them to generate client code that automatically invokes and marshals data to and/or from the service.

For example, the Investor web service previously discussed would return the following SDL in response to a request for the: http://www.msn.com/Investor.asmx?SDL

<serviceDescription xmlns:s0="http://tempuri.org/main.xsd" xmlns:s1="" name="Investor"
        targetNamespace="" xmlns="urn:schemas-xmlsoap-org:sdl.2000-01-25">

http://yannc1/project1/   <soap xmlns="urn:schemas-xmlsoap-org:soap-sdl-2000-01-25">
http://yannc1/project1/      <service>
http://yannc1/project1/         <addresses>
         <address uri="http://www.msn.com/investor.asmx" />
      </addresses>
http://yannc1/project1/         <requestResponse name="StockLookup" soapMessageName="http://tempuri.org/StockLookup">
         <request ref="s0:StockLookup" />
         <response ref="s0:StockLookupResult" />
      </requestResponse>
   </service>
   </soap>

http://yannc1/project1/   <httpget xmlns="urn:schemas-xmlsoap-org:get-sdl-2000-01-25">
http://yannc1/project1/      <service>
http://yannc1/project1/         <requestResponse name=" StockLookup " href=" http://www.msn.com/investor.asmx /StockLookup">
http://yannc1/project1/            <request>
               <param name="symbol" />
            </request>
http://yannc1/project1/            <response>
               <xmlMime ref="s1:int" />
            </response>
         </requestResponse>
      </service>
   </httpget>

http://yannc1/project1/   <schema targetNamespace=
"http://tempuri.org/main.xsd" xmlns="http://www.w3.org/1999/XMLSchema">
http://yannc1/project1/      <element name="StockLookup">
http://yannc1/project1/         <complexType>
            <element name="symbol" type="string" />
         </complexType>
      </element>
http://yannc1/project1/      <element name="StockLookupResult">
http://yannc1/project1/         <complexType>
            <element name="result" type="int" />
         </complexType>
      </element>
   </schema>

</serviceDescription>

ASP+ includes a utility for generating ASP+ Client Proxies called the WebServicesUtil.exe. This utility can use any SDL description to generate a strongly typed NGWS client proxy class. For example, when executed on the above SDL contract, the utility would create a NGWS proxy class named Investor that exposed a method StockLookup. A consumer of this proxy would then be able to invoke the code in the Investor.asmx web service with the following code:

Investor myStockService = new Investor();
Double stockPrice = myStockService.StockLookup(“MSFT”);

Full more information on the proxy generation utility, see Using the WebServiceUtil.exe Utility.


Invoking Web Services

ASP+ Web Services are invoked and accessed using HTTP requests addressed to the URL location of the service’s .ASMX file (for example: http://www.msn.com/Investor.asmx). Web Service parameters and method arguments can be encapsulated within the HTTP request using one of three different protocol schemes:

  • HTTP-GET
  • HTTP-POST
  • HTTP-SOAP


Invoking ASP+ Web Services using HTTP-GET

ASP+ enables clients to invoke web services using standard HTTP-GET requests (like those typed into a browser address bar), where the method name is identified using the PATHINFO of the request, and the method arguments are provided using URL Encoded querystring values.

URI Syntax Definition:

http://servername/appname/servicename.asmx/methodname?param1=value1&param2=value2

For example, the following URL would invoke the StockLookup method of the Investor web service, passing “MSFT” as the requested stock symbol:

http://www.msn.com/Investor.asmx/StockLookup?symbol=MSFT

Note that querystring values are not matched to method arguments using ordinal positioning, but rather by matching the querystring key to the parameter name (identified from NGWS runtime reflection metadata). This enables the querystring arguments to be positioned in any arbitrary order; they do not have to be fixed by position.

In the current release, ASP+ supports passing any primitive NGWS runtime datatype as a querystring argument (String, Double, Char, Bool, Int, DateTime, etc). ASP+ also supports passing arrays of scalar datatypes. This is represented on the querystring using the standard HTML format for passing multiple parameters (multiple keys with the same name).

For example, the belowASP+ Web Service:

<%@ WebService Language=”C#” %>

using System.Web.Services;

public class ArrayTest : WebService {

     [ WebMethod ]
     public int [] PassArray(string name, int [] numbers) {
   return numbers;
     }
}

Could be invoked with the following URI:

http://www.msn.com/ArrayTest/PassArray?numbers=45&numbers=33&numbers=67&name=MyName

This would result in an array with 3 elements (45, 33 and 67) being passed to the Web Service class along with a string with the value “MyName”.

In the Beta1 timeframe, ASP+ will return the result of any HTTP-GET operation using an XML-encoded payload format. This XML by default contains only a single element tag – whose element name indicates the NGWS runtime datatype of the service’s return argument.

For example, our previous call to the Investor service:

http://www.msn.com/Investor.asmx/StockLookup?symbol=MSFT

Would return the following XML as a response:

<?xml version=”1.0” ?>
<double>74.5</double>

Array values are wrapped within an outer element identifying the array type, and inner elements defining each object type.

For example, our previous call to the ArrayTest service:

http://www.msn.com/ArrayTest/PassArray?numbers=45&numbers=33&numbers=67&name=Scott

Would return the following XML as a response:

<?xml version=”1.0” ?>
<ArrayOfInt>
    <int>45</int>
    <int>33</int>
    <int>67</int>
</ArrayOfInt>

Note that unlike HTTP-SOAP arguments (which are limited to only scalar values and arrays of scalar values), HTTP-GET supports any NGWS runtime ype as a return value (structs, DataSets, arrays of structs, etc). DataSets are a particularly interesting type to return as an argument, as they their tables/rows as structured XML (with optional Schema if specified in the dataset). This makes them ideal for data driven services.


Invoking ASP+ Web Services using HTTP-POST

ASP+ enables clients to invoke web services using standard HTTP-POST requests, where the webmethod name is identified using the PATHINFO of the request (identical to the HTTP-GET model), and the method arguments are provided using name/value pair values UUEncoded within the body of the post (identical format to that used by standard HTML <form method=POST> tags).

Like the HTTP-GET invocation mechanism, requests sent via HTTP-POST in the current release are limited to supporting only standard datatypes and value classes and arrays of standard datatypes and value classes. Datasets and structs are not supported.

Like the HTTP-GET invocation mechanism, requests sent via HTTP-POST supports any NGWS runtime type as a return value.


Invoking ASP+ Web Services using HTTP-SOAP

In addition to supporting HTTP-GET and HTTP-POST, ASP+ Web Services can be invoked using the industry standard SOAP protocol. SOAP defines an XML grammar for identifying the method name and wrapping, with schema, the method parameters. It also defines an XML grammar for returning results.

SOAP provides the richest extensibility mechanism of the three protocols supported by ASP+ in the current release. In addition to supporting all standard datatypes and value classes (like HTTP-GET and HTTP-POST support), SOAP invocations also support scenarios where classes, structs, and datasets can be passed as arguments to methods on a web service. It also supports byref argument parameters. By default it is the protocol used by the classes generated by our client-proxy tool. Note that the payloads transmitted are considerably larger and more complicated that the simple HTTP-GET and HTTP-POST formats.

Class and struct marshalling, both as arguments and as return types, is accomplished by XML serializing all public fields and properties of the target class over the wire. It is important to note that this serialization is an externalized process, meaning that only public fields and properties are marshaled. Objects that utilize private field members will transfer with perhaps inaccurate results if they are not also represented with a corresponding get/set property. For example, the following class correctly transfers over the wire:

public class Order {
    public int      OrderID;
    public Double   SalePrice;
}

This alternate version, which encapsulates all field values with public get/set property accessors, also correctly transfers over the wire:

public class Order {
    private int      m_OrderID;
    private Double   m_SalePrice;
    public int OrderID {
        get {
           return m_OrderID;
        }
        set {
           m_OrderID = value;
        }
    }

    public double SalePrice {
        get {
           return m_SalePrice;
        }
        set {
           m_SalePrice = value;
        }
    }
}

However, the following class will not transfer correctly over the wire. Since its internal Value field is updated via methods, and the field is not publicly accessible via a property accessor:

public class Number {

    private int      m_Value;

    public int GetCurrentValue() {
        return m_Value;
    }

    public void Increment() {
        m_OrderID++;
    }

    public void Decrement() {
        m_OrderID--;
    }
}

ASP+ Web Service developers should explicitly design their classes with marshalling and XML schema specifically in mind. Utilize the transmitted classes more as XML structs than as fully functional business objects.


ASP+ Web Services State Management

ASP+ WebService classes are stateless, meaning that the class instance is instantiated and then torn down on each web request. Class state, the data values of fields and properties, are consequently not maintained across web requests. ASP+ Web Service developers must instead utilize explicit state mechanisms to persist data values between roundtrips on the server.

The following sections discuss two of these mechanisms, ASP+ Application State and ASP+ Session State, and demonstrate how they can be used within an ASP+ Web Service to maintain state on the server.

ASP+ Application State

ASP+ Application State provides a dictionary-like API that enables web developers to store and share values across an entire web application. ASP+ developers can access application state from ASP+ Web Form Pages, ASP+ Application Classes (Global.asax) as well as ASP+ Web Services.

Web Service developers gain access to application state by subclassing their web service class from the System.Web.Service.WebService base class. This base class exposes an Application property that provides access to application state values.

For example, the following web service exposes a single webmethod, UpdateAppCounter, that updates a global HitCounter variable within application state:

<%@ WebService Language=”C#” %>
using System.Web.Services;
public class Counter : WebService {
     [ WebMethod ]
     public void UpdateAppCounter() {

          if (Application[“HitCounter”] == null) {
              Application[“HitCounter”] = 0;
          }
          else {
              Application[“HitCounter”] = ((int) Application[“HitCounter”]) + 1;
          }
     }
}

ASP+ Session State

ASP+ Session State provides a dictionary-like API that enables web developers to store and share values across a web session. Unlike ASP+ Application State, Session State is not shared across all browsers, but is instead scoped only to a specific client instance Each client instance has its own session state.

WebService developers gain access to session state by subclassing their webservice class from the System.Web.Service.WebService base class. This base class exposes a Session property that provides access to session state values.

For example, the below web service exposes a single method, UpdateSessionCounter, that updates a session HitCounter variable within session state:

<%@ WebService Language=”C#” %>

using System.Web.Services;

public class Counter : WebService {

     [ WebMethod ]
     public void UpdateSessionCounter() {

          if (Session[“HitCounter”] == null) {
              Session[“HitCounter”] = 0;
          }
          else {
              Session[“HitCounter”] = ((int) Session[“HitCounter”]) + 1;
          }
     }
}

Session state is available by default to all webservice classes. ASP+ Web Service developers can disable session state on individual methods if they know that session state will not be accessed during that request, and they do not wish to pay the performance penalty required to fetch and load it. Developers disable session state for a method using a custom boolean property, enablesessionstate, on the WebMethod metadata attribute of a method.

For example, the below web service enables session state for the UpdateSessionCounter() method, but disables it for the UpdateAppCounter() equivalent (since it doesn’t use session state):

<%@ WebService Language=”C#” %>

using System.Web.Services;

public class Counter : WebService {

     [ WebMethod ]
     public void UpdateSessionCounter() {

          if (Session[“HitCounter”] == null) {
              Session[“HitCounter”] = 0;
          }
          else {
              Session[“HitCounter”] = ((int) Session[“HitCounter”]) + 1;
          }
     }

     [ WebMethod(EnableSessionState=false) ]
     public void UpdateAppCounter() {

          if (Application[“HitCounter”] == null) {
              Application[“HitCounter”] = 0;
          }
          else {
              Application[“HitCounter”] = ((int) Application[“HitCounter”]) + 1;
          }
     }
}


ASP+ Web Services Security

ASP+ Web Services are secured using the same mechanisms as any other ASP+ resource. Developers and administrators utilize the ASP+ Web Service’s activating .ASMX file to set all authentication and authorization policy.

ASP+ authenticates and authorizes access to all ASP+ Web Service using the Windows authentication system provided by IIS. This enables developers and administrators to enable either Basic, Digest or NTLM protocol level authentication for a particular application, directory or individual web service, and then authorize access to individual resources using NT based file ACLs.

Alternatively, developers and administrators can enable custom ASP+ authentication, either via passport or a custom cookie based scheme. The advantages of these solutions over the Windows authentication approach are that applications are no longer required to maintain a separate NT account for each user. Instead they can either utilize a Internet wide credential system in the case of passport, or role their own credential validation system, such as looking up usernames/passwords in a database, with the custom cookie based scheme.

Important: Note that in this release, the client proxy classes supplied do not support either the passport or custom cookie based authentication schemes. It will be possible to utilize these authentication solutions with a custom client such as if a user was accessing the web service directly from WinInet. However, the automatic SDL based proxy classes are disabled. Users should enable Basic, Digest or NTLM based credential validation.


ASP+ Web Services Transactions

Like ASP+ Web Forms, an ASP+ Web Service enables developers to execute their code within the context of a transaction. This will ensure that all interactions with NGWS-aware resources –-- SQL Servers, MSMQ Servers, Oracle Servers, SNA Servers, etc –-- maintain the ACID (atomic, consistent, isolated, durable) properties required to run robust distributed applications.

ASP+ Web Service developers can run in the context of a transaction using a custom enum property,Transaction, on the WebMethod metadata attribute of a method. This ensures that a new transaction is created whenever the method is activated from the client.

For example, the below web service exposes a single method, DeleteDatabase, that performs a database operation that is scoped within the context of a transaction:

<%@ WebService Language=”C#” %>

using System;
using System.Data;
using System.Data.SQL;
using System.Web.Services;

public class TransactionStuff : WebService {

     [ WebMethod(Transaction=Transaction.Required) ]
     public void DeleteDatabase()  {

           SQLConnection sqlConn = new SQLConnection("MyDB", "sa", "", "northwind");
           SQLDataSetCommand sqlAdapter1 = new SQLDataSetCommand("delete orders", sqlConn);

           SqlConn.Execute();
     }
}

Important: Note that the transaction will only be created if the activating method (ie: the method called from the client) has the transaction metadata. It will not be created if the method on which the ASP+ Web Service is first created is not marked with the appropriate transaction metadata, even if a method that has the transaction metadata is subsequently invoked from it.


ASP+ Web Services Async Support

ASP+ WebServices supports both a synchronous and asynchronous execution model. Synchronous execution is conceptually much simpler to understand; it works just like a normal method call in a class. Asynchronous execution, however, can potentially provide much better server utilization and system throughput when used with code that performs blocking operations, such as reading from the filesystem, executing a query against SQL Server, or other lengthy operations.

Developers write asynchronous code within an ASP+ Web Service by splitting the web- callable methods into two separate routines: one method that executes at the beginning of the request and executes up to an asynchronous operation (that would otherwise block), and the other method that is called once the asynchronous operation completes and normal execution can continue. The two methods a developer writes follow the naming pattern (things in bold+italic are customizable by the webmethod developer):

Begin Method:

   IAsyncResult BeginMethodName(MethodArg arg1-argN, AsyncCallback callback, object state);

End Method:

   ReturnArg EndMethodName(IAsyncResult result);

The resulting webmethod signature exposed to clients would then look like:

    ReturnArg MethodName(MethodArg arg1-argN);

The below example demonstrates a webservice implemented using the async pattern that exposes a single method ReadData that asynchronously reads a file and transmits the bytes back to a client:

<%@ WebService Language=”C#” Class=”MyWebService“ %>

using System.Web.Services;
using System.IO;

public class MyWebService : WebService {

    [ WebMethod ]
    public IAsyncResult BeginReadData(string dataID, AsyncCallback callback, object state) {

        File file = new File(dataID);
        Stream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
        byte[] bytes = new byte[(int)stream.Length];
        ReadDataAsyncState readState = new ReadDataAsyncState(callback, state, bytes);
        AsyncCallback readCallback = null;
        if (callback != null)
            readCallback = new AsyncCallback(ReadDataCallback);
        IAsyncResult result = stream.BeginRead(bytes, 0, bytes.Length, readCallback, readState);
        return new ReadDataAsyncResult(callback, result, readState);
    }

    [WebMethod]
    public string EndReadData(IAsyncResult result) {

        ReadDataAsyncResult readAsyncResult = (ReadDataAsyncResult)result;
        IAsyncResult innerAsyncResult = readAsyncResult.InnerAsyncResult;
        Stream stream = (Stream)innerAsyncResult.AsyncObject;
        int count = stream.EndRead(innerAsyncResult);
        string s = Encoding.ASCII.GetString(readAsyncResult.readState.bytes, 0, count);
        return s;
    }

    private void ReadDataCallback(IAsyncResult result) {

        ReadDataAsyncState state = (ReadDataAsyncState) result.AsyncState;
        ReadDataAsyncResult readAsyncResult = new ReadDataAsyncResult(state.callerCallback,
                                                                                result, state);
        state.callerCallback(readAsyncResult);
    }
}

class ReadDataAsyncState {

    public ReadDataAsyncState(AsyncCallback callerCallback, object callerState, byte[] bytes) {

        this.callerState = callerState;
        this.callerCallback = callerCallback;
        this.bytes = bytes;
    }

    public object callerState;
    public AsyncCallback callerCallback;
    public byte[] bytes;
}

class ReadDataAsyncResult : AsyncResult {

    public ReadDataAsyncState readState;
    public ReadDataAsyncResult( AsyncCallback callback,
                                               IAsyncResult result,
                                               ReadDataAsyncState readState) : base(callback, result) {

        this.readState = readState;
    }
}


Consuming Web Services

An ASP+ Web Service can return information in SDL format from a request in several formats, depending on the request type. The request can be in one of three forms:

HTTP-GET

HTTP-POST

HTTP-SOAP

This section also includes the following topics:

HTML Text Pattern Matching

Invoking Web Services with Client Proxies

Async Support

SDL Text Pattern Matching Syntax

System.Web.Services.Protocols.ClientProtocol


SDL Returned From a HTTP-GET Request

The following sample demonstrates the SDL for a simple web service GetStockDetails method that is invoked using the URL: http://localhost/test/Test.asmx/GetStockDetails?symbol=MSFT with the HTTP-GET Protocol and returns an XML dataset containing the appropriate stock details:

<serviceDescription xmlns:s0="http://tempuri.org/main.xsd" xmlns:s1="" name="Test" targetNamespace=""
                          xmlns="urn:schemas-xmlsoap-org:sdl.2000-01-25">

  <httpget xmlns="urn:schemas-xmlsoap-org:get-sdl-2000-01-25">
    <service>
      <requestResponse name="GetStockDetails" href="http://localhost/test/Test.asmx/GetStockDetails">
        <request>
          <param name="symbol"/>
        </request>
        <response>
          <xmlMime ref="s1:DataSet"/>
        </response>
      </requestResponse>
    </service>
  </httpget>

  <schema targetNamespace="" xmlns="http://www.w3.org/1999/XMLSchema">
    <element name="DataSet">
      <complexType>
        <element ref="schema"/>
        <any/>
      </complexType>
    </element>
  </schema>

</serviceDescription>


SDL Returned From a HTTP-POST Request

The following sample demonstrates the SDL for the sample GetStockDetails web service method that is invoked using the URL: http://localhost/test/Test.asmx/GetStockDetails with the HTTP-POST protocol. Unlike the HTTP-GET sample, all arguments are encoded within the body of the HTTP request (using the standard format used by HTML browsers) and returns an XML dataset containing the appropriate stock details:

<serviceDescription xmlns:s0="http://tempuri.org/main.xsd" xmlns:s1="" name="Test" targetNamespace=""
                        xmlns="urn:schemas-xmlsoap-org:sdl.2000-01-25">

  <httppost xmlns="urn:schemas-xmlsoap-org:post-sdl-2000-01-25">
    <service>
      <requestResponse name="GetStockDetails" href="http://localhost/test/Test.asmx/GetStockDetails">
        <request>
          <form>
            <input name="symbol"/>
          </form>
        </request>
        <response>
          <xmlMime ref="s1:DataSet"/>
        </response>
      </requestResponse>
    </service>
  </httppost>

  <schema targetNamespace="" xmlns="http://www.w3.org/1999/XMLSchema">
    <element name="DataSet">
      <complexType>
        <element ref="schema"/>
        <any/>
      </complexType>
    </element>
  </schema>

</serviceDescription>


SDL Returned From a HTTP-SOAP Request

The below sample demonstrates the SDL for the sample “GetStockDetails” web service method that is invoked using the URL: http://localhost/test/Test.asmx with the HTTP-SOAP protocol. Unlike the HTTP-GET and HTTP-POST sample, all arguments and return types are wrapped with SOAP XML envelope information.

<serviceDescription xmlns:s0="http://tempuri.org/main.xsd" xmlns:s1="" name="Test" targetNamespace=""
                        xmlns="urn:schemas-xmlsoap-org:sdl.2000-01-25">

  <soap xmlns="urn:schemas-xmlsoap-org:soap-sdl-2000-01-25">
    <service>
      <addresses>
        <address uri="http://localhost/test/Test.asmx"/>
      </addresses>
      <requestResponse name="GetStockDetails" soapMessageName="http://tempuri.org/GetStockDetails">
        <request ref="s0:GetStockDetails"/>
        <response ref="s0:GetStockDetailsResult"/>
      </requestResponse>
    </service>
  </soap>

  <schema targetNamespace="http://tempuri.org/main.xsd" xmlns="http://www.w3.org/1999/XMLSchema">
    <element name="GetStockDetails">
      <complexType>
        <element name="symbol" type="string"/>
      </complexType>
    </element>
    <element name="GetStockDetailsResult">
      <complexType>
        <element name="result">
          <complexType>
            <element ref="schema"/>
            <any/>
          </complexType>
        </element>
      </complexType>
    </element>
  </schema>

  <schema targetNamespace="" xmlns="http://www.w3.org/1999/XMLSchema">
    <element name="DataSet">
      <complexType>
        <element ref="schema"/>
        <any/>
      </complexType>
    </element>
  </schema>

</serviceDescription>


HTML Text Pattern Matching

The following sample demonstrates the SDL for the an alternative GetStockDetails web service method that is invoked using the URL: http://stockquote.com/GetStockQuote.asp that returns an HTML page as a result. In addition to invoking the page, the below SDL indicates that the LastPrice and OpenPrice values should be scraped and returned as a result:

<?xml version="1.0"?>
<serviceDescription xmlns:s0="http://tempuri.org/main.xsd" xmlns:s1="" name="Investor" targetNamespace="" xmlns="urn:schemas-xmlsoap-org:sdl.2000-01-25">
  <httpget xmlns="urn:schemas-xmlsoap-org:get-sdl-2000-01-25">
    <service>
      <requestResponse name="GetQuote" href="http://moneycentral.msn.com/scripts/webquote.dll">
        <request>
         <param name='Symbol'/>
        </request>
        <response>
         <text>
          <match name='Last' pattern='Last&lt;/TD&gt;.*?;(.*?)&lt;/B&gt;'/>
          <match name='Change' pattern='Change&lt;/TD&gt;.*?;(.*?)&lt;/B&gt;'/>
         </text>
        </response>
      </requestResponse>
    </service>
  </httpget>
</serviceDescription>


Invoking Web Services with Client Proxies

SDL contracts define the capabilities, schemas and locations of services, but do not themselves enable developers to directly program against them. They do provide the wire-format details necessary for a developer to use a network level API, like the Net Classes, to directly program against them. But there is an easier way to use these services: by creating client proxies.

ASP+ includes a command-line utility, WebServiceUtil.exe, that automatically creates strongly-typed, NGWS client proxy classes from SDL contracts. Each service method is translated into a class method. These proxy classes automatically abstract all of the network and marshalling protocols necessary for the end user and enables programmers to easily invoke and leverage a web service’s functionality much like they would a regular class or business component.


Using the WebServiceUtil.exe Utility

The WebServiceUtil.exe utility takes the path to an SDL contract file and a language (CSharp, VB, or JavaScript) as input arguments. The utility then generates a single source file as output. This file contains a proxy class, defined using the language specified, that exposes methods (both synchronous and asynchronous) for each of the methods exposed by the web service. Each proxy method contains the appropriate network invocation and marshalling code necessary to invoke and receive a response from the remote service.

For example, consider the following SDL file:

<serviceDescription xmlns:s0="http://tempuri.org/main.xsd" xmlns:s1="" name="Investor" targetNamespace=""
                        xmlns="urn:schemas-xmlsoap-org:sdl.2000-01-25">

  <soap xmlns="urn:schemas-xmlsoap-org:soap-sdl-2000-01-25">
    <service>
      <addresses>
        <address uri="http://localhost/test/Test.asmx"/>
      </addresses>
      <requestResponse name="GetStockDetails" soapMessageName="http://tempuri.org/GetStockDetails">
        <request ref="s0:GetStockDetails"/>
        <response ref="s0:GetStockDetailsResult"/>
      </requestResponse>
    </service>
  </soap>

  <schema targetNamespace="http://tempuri.org/main.xsd" xmlns="http://www.w3.org/1999/XMLSchema">
    <element name="GetStockDetails">
      <complexType>
        <element name="symbol" type="string"/>
      </complexType>
    </element>
    <element name="GetStockDetailsResult">
      <complexType>
        <element name="result">
          <complexType>
            <element ref="schema"/>
            <any/>
          </complexType>
        </element>
      </complexType>
    </element>
  </schema>

  <schema targetNamespace="" xmlns="http://www.w3.org/1999/XMLSchema">
    <element name="DataSet">
      <complexType>
        <element ref="schema"/>
        <any/>
      </complexType>
    </element>
  </schema>

</serviceDescription>

The WebServiceUtil.exe utility uses the SDL to generate the following C# proxy class. Note that if multiple protocols are specified in the SDL file, the WebServiceUtil.exe utility will default to using SOAP within the proxy since it currently has the richest marshalling support:

namespace WebProxiesNamespace {
    using System.Xml.Serialization;
    using System.Web.Services.Protocols;
    using System.Web.Services;

    public class Investor : System.Web.Services.Protocols.SoapClientProtocol {

        public Investor() {
            this.Path = "http://localhost/test/Test.asmx";
        }
    [System.Web.Services.Protocols.SoapMethodAttribute("http://tempuri.org/GetStockDetails")]
        public System.Data.DataSet GetStockDetails(string symbol) {
            object[] results = this.Invoke("GetStockDetails", new object[] {symbol});
            return (System.Data.DataSet)(results[0]);
        }

        public System.IAsyncResult BeginGetStockDetails(string symbol, AsyncCallback callback, object asyncState) {
            return this.BeginInvoke("GetStockDetails", new object[] {symbol}, callback, asyncState);
        }

        public System.Data.DataSet EndGetStockDetails(System.IAsyncResult asyncResult) {
            object[] results = this.EndInvoke(asyncResult);
            return (System.Data.DataSet)(results[0]);
        }
    }
}

Once an appropriate SDL Proxy has been generated for a service, a developer can invoke and access the returned result just like they would any other class or business component. For example:

DataSet msftDetails;
DataSet ontcDetails;
Investor investorService = new Investor();
msftDetails = investorService.GetStockDetails(“MSFT”);
ontcDetails = investorService.GetStockDetails(“ONTC”);

Note that the proxy automatically manages the network IO, and appropriately marshals arguments to and from NGWS runtime types as needed. Errors from the web service are surfaced to the calling consumer using standard NGWS runtime exceptions. The namespace created is WebProxiesNamespace. The WebServiceUtil.exe program takes an argument for the name of this namespace. This file can also be edited and the namespace changed. Other classes can be created that inherit from it.


WebServiceUtil.Exe Syntax

The following is the syntax for using the ASP+ Web Services utility, WebService.Util.exe for generating client proxies:

Syntax

WebServiceUtil -install <wwwDirectoryName> <assemblyName>
WebServiceUtil -uninstall <wwwDirectoryName>
WebServiceUtil -client <urlToSDL> <pathToSDL> <language> <namespace> [<outputDirectoryName>] [<pathToXSD>...]
WebServiceUtil -server <urlToSDL> <pathToSDL> <language> <namespace> [<outputDirectoryName>] [<pathToXSD>...]
WebServiceUtil -description <directoryUrl> <assemblyName> [<outputDirectoryName>]
WebServiceUtil -refactor <pathToSDL> [<outputDirectoryName>]

Definitions

Property | Definition
wwwDirectoryName | The name of a sub-directory under wwwroot on http://localhost.
outputDirectoryName | The location to create files.
assemblyName | The name of an assembly containing web services.
pathToSDL | A local path to an service description file
pathToXSD | A local path to an xml schema file (filename must end in .xsd)
directoryUrl | The URL of a directory containing web services
urlToSDL | The URL that the service description file was retrieved from
language | The language to generate the code in. Choose from CSharp, VB, VBCompat, and JS.
namespace | The namespace to generate the code in.

Notes

Option | Description
Install | Creates a virtual directory in IIS, mark it as an IIS application, generate the .ASMX files there and copy the assembly into the \\bin directory.
Uninstall | Deletes the directory.
Client | Creates source code for a web service client class.
Server | Creates source code for a web service server class.
Description | Creates static service description files for web services in an assembly.
Refactor | Creates separate SDL files for each of the protocols found in the SDL file.


Client Proxy Authentication

The client proxies generated by the WebServiceUtil.exe utility will support three authentication schemes in the current release: Basic, Digest and NTLM.

NTLM authentication is handled automatically by the calling proxy, with the credentials of the calling thread being supplied to the remote web service. The proxy consumer setting the UserName and Password properties on the proxy instance handles basic and Digest authentication scenarios. These are then transmitted as appropriate to the remote web service.

If a URI expects the client to be authenticated, but the UserName, Password and Domain properties are not set, then accessing the service will fail and a WebException indicating a 401 auth error will be thrown.

For example, the below sample demonstrates how to utilize the proxy’s authentication services:

DataSet msftDetails;
Investor investorService = new Investor();
investorService.UserName = “MyName”;
investorService.Password = “MyPassword”;
investorSerrvice.Domain = “MyDomain”;
msftDetails = investorService.GetStockDetails(“MSFT”);


Async Support

ASP+ Web Service proxies support both a synchronous and asynchronous execution model. Synchronous execution is conceptually much simpler to understand: it works just like a normal method call in a class. Asynchronous execution, however, can potentially provide much better system utilization and avoids blocking the calling worker thread while waiting for a network response.

Developers call asynchronous methods by splitting their code across two separate routines; one routine invokes and initiates the method (that would otherwise block), and another routine called when the async operation completes in order to obtain the result.

The below example demonstrates calling the previously defined GetStockDetails web service method using the async pattern.

DataSet msftDetails;
Investor investorService = new Investor();
//call the Begin method, and send as null the AsyncCallback, and  StateObject
IAsyncResult result = investorService.BeginGetStockDetails(“MSFT”, null, null);
result.AsyncWaitHandler.WaitOne();
msftDetails = investorService.EndGetStockDetails();

Asynchronous methods created for the client are different than the asynchronous support ASP+ Web Services offers. Client side async, as this example shows, is for the client consumer of the service to call the WebMethod. The method is then called on a thread with a single request and response. There is no callback from the server providing the service and the client.


SDL Text Pattern Matching Syntax

HTML Screen Scrapping instructions are expressed declaratively within an SDL contract using a <text> child tag nested within the <response> tag of a service description. The <text> tag in turn can contain multiple name/value regular expression pairs that identify the specific values (and their programmatic names) that should be pulled out of the response.

<text> Formal Syntax Definition

The <text> tag supports a single attribute, contentType, that enables filtering based on the return content mimetype. The <text> tag can in turn have any number of <regex> tags, each of which represents a separate screen scrapped value, nested within it.

<text contentType='mime type'>
  <match pattern='regex' name='name' maxOccurs='integer or *' group='regex group' />
</text>

The <match> tag in turn supports four attributes:

name | Optional. Variable name of resulting value obtaining from corresponding regular expression
pattern | Regular expression to run in order to obtain value. Any valid PERL 5 regular expression can be used.
group | Optional.
maxOccurs | Optional. Indicates number of values that should be returned from regular expression (default is 1 – the first match found).

Text Pattern Matching Example

The following is a sample SDL file that specifies grabbing the last price as well as the change for a stock quote on Moneycentral’s website.

<?xml version="1.0"?>
<serviceDescription xmlns:s0="http://tempuri.org/main.xsd" xmlns:s1="" name="Investor" targetNamespace="" xmlns="urn:schemas-xmlsoap-org:sdl.2000-01-25">
  <httpget xmlns="urn:schemas-xmlsoap-org:get-sdl-2000-01-25">
    <service>
      <requestResponse name="GetQuote" href="http://moneycentral.msn.com/scripts/webquote.dll">
        <request>
         <param name='Symbol'/>
        </request>
        <response>
         <text>
          <match name='Last' pattern='Last&lt;/TD&gt;.*?;(.*?)&lt;/B&gt;'/>
          <match name='Change' pattern='Change&lt;/TD&gt;.*?;(.*?)&lt;/B&gt;'/>
         </text>
        </response>
      </requestResponse>
    </service>
  </httpget>
</serviceDescription>

This SDL then would create the following proxy class code.

namespace Services {
    using System.Xml.Serialization;
    using System;
    using System.Web.Services.Protocols;

    public class Investor : HttpClientProtocol {
        public Investor() {
            this.Path = "http://moneycentral.msn.com/scripts/webquote.dll";
        }
        [HttpMethod(typeof(TextReturnReader), typeof(UrlParameterWriter))]
        public Matches GetQuote(string symbol) {
            return (Matches)Invoke("GetQuote", this.Path + "", new object[] {symbol});
        }
        public IAsyncResult BeginGetQuote(string symbol, AsyncCallback callback, object asyncState) {
            return BeginInvoke("GetQuote",
                               this.Path + "",
                               new object[] {symbol},
                               callback,
                               asyncState);
        }
        public Matches EndGetQuote(IAsyncResult asyncResult) {
            return (Matches)EndInvoke(asyncResult);
        }
    }

    public class Matches {
        [Match("Last</TD>.*?;(.*?)</B>")]
        public string Last;
        [Match("Change</TD>.*?;(.*?)</B>")]
        public string Change;
    }
}

Which could then be called with the following code:

using Services;
using System;

public class Scrape {
    public static void Main(string[] args) {
        Investor investor = new Investor();
        Matches matches = investor.GetQuote(args[0]);
        Console.WriteLine(matches.Last);
        Console.WriteLine(matches.Change);
    }
}


System.Web.Services.Protocols.ClientProtocol

The ClientProtocol class defines the base class used when implementing all client proxies generated by the WebServiceUtil utility. Note that what protocol-specific subclasses of the ClientProtocol class are used depends on the protocol used to talk to the web service.

Consumers of the proxy can set the public ClientProtocol properties/ methods to customize the proxy’s interaction with the target web service.

namespace System.Web.Service.Protocols
{
   public class ClientProtocol : Component
   {
      // Properties
      public bool         AllowAutoRedirect    { get; set; }
      public bool         EnableCookies       { get; set; }
      public String         Path          { get; set; }
      public String         UserName       { get; set; }
      public String         Password       { get; set; }
      public String         Domain       { get; set; }

   }
}

Properties

AllowAutoRedirect
Prototype | public bool AllowAutoRedirect { get; set; }
Description | Indicates whether proxy should automatically follow redirect requests from target web service (default is true).
EnableCookies
Prototype | public bool EnableCookies { get; set; }
Description | Indicates whether proxy should support and maintain client side HTTP cookies between requests (default is true)
Path
Prototype | public String Path { get; set; }
Description | Indicates path to target webservice. By default it is set to the path specified in the original SDL file.
UserName
Prototype | public String UserName { get; set; }
Description | UserName to use when authenticating against a web service
Password
Prototype | public String Password { get; set; }
Description | Password to use when authenticating against a web service
Domain
Prototype | public String Domain { get; set; }
Description | Domain to use when authenticating against a web service


Browser-hosted ASP+ Applications

ASP+ introduces the Personal Tier. Programming for the Personal Tier allows developers to write powerful ASP+ applications that run in the Microsoft Internet Explorer 5.5 browser process, which is required. Users can install ASP+ applications locally and run them in a secure environment.

For more information, see these topics:

What is the Personal Tier?

Personal Tier Architecture

Personal Tier Application Management

Data Strategies for Personal Tier Applications

Personal Tier Application Security

Personal Tier Applications

Deploying Personal Tier Applications

Personal Tier Administrative UI


What is the Personal Tier?

Personal Tier applications eliminate the server from the standard Web applications paradigm, allowing requests to be processed on the client itself. Developers can use the same powerful ASP+ technologies available for programming on the server to create dynamic content that runs the client. This offers Web application developers the advantages of using a single programming model for both their server-based applications and those that they design to run in the Personal Tier. In addition, developers will frequently be able to share code between these types of applications, making it easy for them to provide seamless navigation from applications that run on the server to Personal Tier applications.

Personal Tier applications can be downloaded and installed without requiring the user to explicitly run an installer. Also supported are one-click application removal and a simple feature that checks for application updates. It also does not cause adverse performance on normal browsing scenarios.

Personal Tier supports browsers on all versions of Windows 2000, Windows NT 4.0, Windows 98, and Windows 95.

Personal Tier application users will have the advantage of running rich ASP+ applications even while their computers are not connected to the Internet. For example, a Web site that sells books could add Personal Tier functionality to their inventory catalog, allowing users to download the catalog onto their local computer to browse and select the books that they wished to purchase. Then when the users wanted to make their order, they would be able to upload their selections to the company's site after simply making sure that they were reconnected to the server.

Some of the benefits of providing ASP+ applications that run on the Internet Explorer process are:

  • The Personal Tier provides a lightweight execution environment that does not require a Web server running on the machine.
  • The process model of the application mirrors the UI. When users close the browser, the application shuts down, too.
  • Users don't need to configure a Web server, nor do they need to concern themselves with storage issues and download requirements inherent in downloading Web server bits onto a client system.

See Also

Browser-hosted ASP+ Applications


Personal Tier Architecture

Personal Tier applications run entirely inside of the Microsoft Internet Explorer process. Because of this, the Personal Tier has a different process model than standard ASP+ applications, and has a separate protocol scheme that allows access to files that are stored on the local computer.

In particular, the Personal Tier serves static as well as dynamic content.

For detailed information, see the following:

Process Model

Protocol Scheme

URI Mapping and Name Resolution

See Also

Browser-hosted ASP+ Applications


Process Model

When requests are made for any Personal Tier application files, done through a custom protocol scheme, urlmon activates an Asynchronous Pluggable Protocol (APP) handler. The APP, which is registered as the sole handler for the Personal Tier protocol scheme, then sends the request so that the URI of the requested file is mapped to the correct local handler. The request is then handled appropriately.

The APP also provides the browser with the appropriate security zone for the given application based on information stored in the Personal Tier local registry.

See Also

Personal Tier Architecture


Protocol Scheme

URIs for all Personal Tier applications begin with myweb:, rather than http:. The Personal Tier APP handler will resolve all URIs that use this custom protocol scheme, whether the URI is to static or dynamic content. For example, an ASP+ application might present the following URIs that are all resolved to local handlers.

myweb://www.microsoft.com/money/default.aspx
myweb://www.microsoft.com/money/images/logo.jpg
myweb://www.microsoft.com/money/accounts.aspx
myweb://www.microsoft.com/money/static/about.htm

The Personal Tier custom protocol scheme for Personal Tier offers a number of benefits. Using the protocol scheme, Personal Tier can intercept requests for local resources and notify the browser that these requests are from a remote location. This helps prevent remote attacks that are looking for a user's private data.

Since Personal Tier starts ASP+ applications and worker processes only when a request originates through the myweb: protocol, there is zero impact on normal Web browsing for users. Browsing will be just as fast, and Personal Tier will not cause the browser to crash.

Remote sites that include Personal Tier and other files can point explicitly to those files stored on the users local computer, thereby allowing load balancing and application portioning between the local computer and the remote site.

See Also

Personal Tier Architecture | URI Mapping and Name Resolution


URI Mapping and Name Resolution

Personal Tier maintains a local mapping of all installed applications and their URI namespaces to their local storage location. This allows the user to refer to remote applications using remote URIs, enables demand-driven installation of Personal Tier applications, and makes these URIs exportable to other installed Personal Tier applications. This mapping also notifies Microsoft Internet Explorer that these applications, though installed locally, originated at a remote location. Therefore, trust settings on the browser should be appropriate for the remote site, not for the local computer.

Once a user has installed a Personal Tier application from a Web site, when they return to the application URI, Personal Tier invokes the locally installed version. Even in this situation, however, the browser knows that it is running a remote application so that the application cannot access local resources.

When resolving names, Personal Tier searches the entire list of local mappings for the longest match. Once found, Personal Tier resolve the match to the application domain of the selected application and then calls the appropriate handler.

The following example demonstrates the Personal Tier URI mapping facility.

A user has two Personal Tier applications installed to their local computer as described in the following table.

Application | Application URI | Local Install Directory
Money | myweb://www.Microsoft.com/money | C:\money
Wingtiptoys | myweb://www.wingtiptoys.com | C:\program files\www.wingtiptoys.com

While applications can have arbitrary directory structures, for this example the directory structure of each application is:

approot
   bin\    # Business logic (various assemblies)
   img\   # images
   data\   # XML stores
   default.aspx
   config.cfg

Mapping works in the same way for either static or dynamic content. Applications can map using absolute or relatives URIs, or use virtual URIs to associate all requests for part of their URI namespace to a handler. The following table demonstrates file URIs and the physical file that Personal Tier maps them to.

URI | Physical Path
myweb://www.Microsoft.com/money/ | C:\money\default.aspx
myweb://www.Microsoft.com/money/img/dollar.jpg | C:\money\img\dollar.jpg
myweb://www.wingtiptoys.com | C:\program files\www.wingtiptoys.com\default.aspx
myweb://www.wingtiptoys.com/order.aspx | C:\programfiles\www.wingtiptoys.com\order.aspx
CAUTION For security reasons, there is a difference in how the Server.MapPath method works on the server and how it works in a locally run Personal Tier application. While the MapPath method returns the file system path for a resource, in Personal Tier it returns a path relative to the application's root directory. If an application tries to call Server.MapPath for a resource located outside of the application’s directory hierarchy, it will throw the SR.Cannot_map_path exception thrown in the server environment.

See Also

Personal Tier Architecture


Personal Tier Application Management

Personal Tier provides administrative support for installing, updating, and removing applications. Users can navigate to the administration page using an icon installed on the Internet Explorer 5.5 toolbar, which is added during the Personal Tier installation process, or by navigating to a start URL, such as myweb://.

For more information see the following topics:

Configuring Personal Tier Applications

Installing Personal Tier Applications

Updating Personal Tier Applications

See Also

Browser-hosted ASP+ Applications


Configuring Personal Tier Applications

Configuring Personal Tier applications is essentially the same as configuring other ASP+ applications, with a few caveats. For example, Personal Tier applications do not support cookieless sessions, and some security implications involved in running Web applications on a local computer.

For information on configuring ASP+ Applications, see ASP+ Configuration Concepts.

See Also

Personal Tier Application Security | Personal Tier Application Management


Installing Personal Tier Applications

In this release, the Personal Tier supports demand-driven installation when a remote URI is requested using the Personal Tier custom protocol scheme.

All installed applications will be maintained in a local MyWeb registry that maps their URI to their installed location. The registry will also maintain relevant installation information including the time of installation and usage information.

The information for applications will be stored in the user's registry in the following location:

HKEY_CURRENT_USER
   SOFTWARE
      Microsoft
         XSP
            MyWeb

For each application, there will be a key named with the origin host and application path.

Upon installation, the registry values in the following table are added for each Personal Tier application.

Value Name | Value Type | Description
AppCreationDate | REG_QWORD | The system time when the application was installed.
AppDirectory | REG_QWORD | The local file directory where the application is located.
AppDomain | REG_SZ | The application domain unique ID.
AppLastAccessDate | REG_SZ | The last time the application was accessed. The default is the AppCreationDate value until the application is accessed after installation.

Installation simply consists of retrieving the CAB file or explicit files specified in the manifest along with any necessary dependencies. CAB files are expanded in a way that preserves their directory hierarchies.

Once the files are retrieved, they are placed into a directory in the file system. An entry is added to the MyWeb registry that maps the application URI to its physical location. So that the name of the physical location cannot be computed easily, a hash algorithm, based on a unique identifier such as a GUID, determines the physical location.

Personal Tier applications will be placed in the file system using the following naming convention. Starting from a base MyWeb directory: first, append the server name, then the application name, and then a uniquely generated identifier. As an example, an application coming from http://www.microsoft.com/bar/baz might reside in the following directory

C:\program files\myweb\www.Microsoft.com\bar\bazbbqbet55b0wrliaqt5tyn145

Personal Tier applications can consist of any ASP+ handler type including Web Forms pages, Web Services files and handlers, and configuration data. The configuration data can activate handlers for the application scope and provide the settings necessary for any given application.

See Also

Personal Tier Application Management


Updating Personal Tier Applications

Personal Tier provides UI that allows a user to check easily for updates on all installed applications. This UI allows a user to check one application at a time for updates.

When Personal Tier checks for an application update, it will first retrieve the manifest file. If the manifest file has a different version, Personal Tier will retrieve and install the various dependencies pending user confirmation.

As with installation, Personal Tier will only download updates from the application’s origin server.

See Also

Personal Tier Application Management | Personal Tier Administrative UI | Personal Tier Manifests


Data Strategies for Personal Tier Applications

Personal Tier applications are stored in and loaded from the file system. This allows applications to access or configuration files and any data that were bundled with the application, rather than making the application retrieve them from the host from where it originated.

The following topics contain information about special considerations you will have to use when developing Personal Tier applications.

Using Isolated Storage with Personal Tier Applications

Using Data Access with Personal Tier Applications

See Also

Browser-hosted ASP+ Applications


Using Isolated Storage with Personal Tier Applications

Personal Tier applications can use the Isolated Storage feature supplied with the NGWS runtime. Isolated storage is key, since it is a local store, but using it does not allow the application to access the file system.

The following example demonstrates how you can store and retrieve data from Isolated Storage for a Personal Tier application.

<%@ Page ClientTarget="downlevel" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.IO.IsolatedStorage" %>
<%@ Import Namespace="System.Runtime.Serialization.Formatters.Binary" %>

<html>
<head>
    <script language="C#" runat="server">

        String _storeFile = "mystore";

        void Page_Load(Object sender, EventArgs e) {

           ArrayList values = RecallLastValues();

           if( null != values ) {

              // field order: principal, interest rate, months
              Principal.Text = values[0].ToString();
              Rate.Text      = values[1].ToString();
              Months.Text    = values[2].ToString();
           }
        }

        ArrayList RecallLastValues() {

           ArrayList values = new ArrayList();

           // read the last values if possible from IsolatedStorage
           // they're stored as a serialized array
           // in order: principal, interest rate, months

           IsolatedStorageFileStream stream;
           stream = new IsolatedStorageFileStream(_storeFile, FileMode.Open);

           if( null != stream ) {
              values = (ArrayList)BinarySerializer.Deserialize(stream);
           }
           else {

              // just add three default values

              values.Add(100000.0);
              values.Add(8.5);
              values.Add(360);
           }

           return values;
        }

        void RememberLastValues(double principal, double interestRate, int months) {

           // write the last values out to IsolatedStorage
           // just serialize them as a three element array

           IsolatedStorageFileStream stream;
           stream = new IsolatedStorageFileStream(_storeFile, FileMode.OpenOrCreate);
           ArrayList values = new ArrayList();
           values.Add(principal);
           values.Add(interestRate);
           values.Add(months);
           BinarySerializer.Serialize(values, stream);
           stream.Close();
        }

        void Calculate_OnClick(Object source, EventArgs e) {

           if( !Page.IsValid ) {

              Msg.Text = "Please enter all required values";
              return;
           }

           double rateAmount = Double.Parse(Rate.Text);
           double principalAmount = Double.Parse(Principal.Text);
           int  monthCount = Int32.Parse(Months.Text);

           double payment = Calculate(principalAmount, rateAmount, monthCount);
           Msg.Text = "Approximate payment is " + Double.Format(payment, "C") + " monthly";

           RememberLastValues(principalAmount, rateAmount, monthCount);
        }

        double Calculate(double principal, double interestRate, int months) {

           // account for interest rates like 9.0, which we'll assume is 9%
           // and 0.9, which we'll also assume is 9%

           if( interestRate >= 1.0 )
              interestRate /= 100.0;

           double rate = (interestRate / 12) + 1.0;
           double top = principal * System.Math.Pow(rate, (double)months);
           double bottom = 0;

           for( int m = months - 1; m > 0; m-- ) {
              bottom += System.Math.Pow(rate, (double)m);
           }

           return top / bottom;
        }
    </script>
</head>

<body>
  <h4>My Web Mortage Calculator</h4>

  <form runat="server">
      <table style="background-color:#ccccff;">
    <tr>
       <td>Principal:</td>
       <td><asp:TextBox id="Principal" runat="server"/></td>
       <td><ASP:RequiredFieldValidator
          ControlToValidate="Principal"
          Display="Static"
          ErrorMessage="*"
          runat="server"/>
       </td>
    </tr>

    <tr>
       <td>Interest Rate:</td>
       <td><asp:TextBox id="Rate" runat="server"/></td>
       <td><ASP:RequiredFieldValidator
          ControlToValidate="Rate"
          Display="Static"
          ErrorMessage="*"
          runat="server"/>
       </td>
    </tr>

    <tr>
       <td>Months</td>
       <td><asp:TextBox id="Months" runat="server"/></td>
       <td><ASP:RequiredFieldValidator
          ControlToValidate="Months"
          Display="Static"
          ErrorMessage="*"
          runat="server"/>
       </td>
    </tr>
    </table>
    <br>
    <asp:Button OnClick="Calculate_OnClick" Text="Calculate" runat="server"/>
        <br>
    <asp:Label id="Msg" runat="server"/>
      </form>
</body>
</html>

See Also

Data Strategies for Personal Tier Applications


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Using Data Access with Personal Tier Applications

[To be supplied.]


Personal Tier Application Security

Downloaded Personal Tier applications are run with the code access permissions granted by the policy on the local computer for the remote domain. When local applications are installed, they are granted permissions based on the URI provided for them at installation time, which can be either local or remote, depending on user preferences.

The Personal Tier host will add policy to the app domain to indicate that any code loaded from the application directory hierarchy is associated with the Personal Tier application's domain of origin. For example, suppose that a Personal Tier application is downloaded and installed from http://www.microsoft.com/myapp. When Personal Tier explicitly loads the code from that application, it provides evidence to the security system that the code is from www.microsoft.com/myapp, so permissions for that domain will apply. In addition, Personal Tier will add a policy so that all code loaded from the application directory, and all subdirectories, are associated with the remote URI www.microsoft.com/myapp. This prevents the application from explicitly loading assemblies from its own directory and bypassing the appropriate domain security.

The Personal Tier host will add additional policy to indicate that network I/O is only allowed to the host of origin, www.microsoft.com in the above example.

Unless the default settings are altered, applications are also completely prohibited from calling native code.

Cookie authentication and authorization are available to Personal Tier applications. Windows authentication, basic authentication, and digest authentication services are not available to Personal Tier applications since ASP+ relies on IIS to provide infrastructure for these authentication services. Passport authentication is also not supported.

File I/O permissions are granted in accordance with policy on the local computer. Unless explicitly changed, these permissions are revoked for both Internet and Intranet applications.

See Also

Browser-hosted ASP+ Applications | ASP+ Web Application Security


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Code Access Issues

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Using Network Security with Personal Tier Applications

[To be supplied.]


Personal Tier Applications

Personal Tier applications can consist of a mixture of static content and managed code. However, if an application is installed using Personal Tier, it can't register or call native code, including classic COM, unless the application has been assigned the appropriate permissions by a user or administrator. Personal Tier requires sessions to run with cookies.

The following topics contain further information.

Using Personal Tier Application Event URLs

Checking the Connection Status of an Application

See Also

Browser-hosted ASP+ Applications


Using Personal Tier Application Event URLs

[To be supplied.]

See Also

Personal Tier Applications | Personal Tier Manifests


Checking the Connection Status of an Application

Personal Tier applications can check their connection status using the Connected property on the System.Web.MyWeb class. This is a boolean property that consults the WinInet InternetGetConnectedStateEx API to determine whether or not the user is working offline. In addition to this property, applications can discover the connection name using the System.Web.MyWeb.ConnectionName property. The following sample shows how this facility might be used from a Personal Tier page.

Example

If MyWeb.Connected and MyWeb.ConnectionName = “DSL Connection” Then
   SynchronizeData
   RenderData
Else
   RenderCachedData
End If

See Also

Personal Tier Applications


Deploying Personal Tier Applications

Once developers have created a Personal Tier application, there are two steps involved in deploying them. The first is to define installation files and support in a myweb.osd manifest file, and the second is to put the files required for the application, or alternate versions of the application, into a CAB file.

For more information, see the following topics.

Personal Tier Manifests

Creating Personal Tier CAB Files

See Also

Browser-hosted ASP+ Applications


Personal Tier Manifests

Personal Tier supports application installation using OSD manifests. For more information about this format, see the document located at http://www.w3.org/TR/NOTE-OSD.html. The default Personal Tier manifest name will be myweb.osd. OSD manifests support an IMPLEMENTATION element that allows a manifest to define varying versions of specific software resources. For example, a developer could define in the manifest that a resource exists in both native Win32 and Personal Tier versions.

A minimal manifest might like something like the following:

<SOFTPKG NAME="com.microsoft.www.money" VERSION="1,0,0,0">
<TITLE>Microsoft Money</TITLE>
<ABSTRACT>Microsoft Money for MyWeb</ABSTRACT>
<LICENSE HREF="http://www.microsoft.com/money/license.html" />

<IMPLEMENTATION>
<CODEBASE
HREF="http://www.microsoft.com./money/money.cab" />
</IMPLEMENTATION>
</SOFTPKG>

There are several other optional elements that can be used to provide information to a potential consumer of the application. For example, OSD has a DISKSPACE element that details how much disk storage is required for the application once it is downloaded from its CAB file.

For security purposes, CODEBASE and DEPENDENCY elements must originate from the same host as the manifest.

Personal Tier supports the following OSD attributes. Elements in italics are extensions to the OSD format supplied by Personal Tier.

Element | Description
SOFTPKG | The top-level OSD element.
IMPLEMENTATION | OSD implementation element.
CODEBASE | The CAB file for the Personal Tier application.
TITLE | The friendly application name.
VERSION | The application version.
ABSTRACT | The description of the application.
LICENSE | The URL where the license for the application can be found.
SIZE | Estimate of how much disk space the application consumes when it is expanded.
AUTHOR | The author of the application.
SOURCE | The source of the application. This can be left blank, or can provide information on the site from which the application was downloaded.
HELPURL | Local or remote URL that provides documentation for the application.
ICONURL | Local URL that provides the application image to the administrative UI.
REMOTEICONURL | Remote URL that provides the application image for the installation process.
REMOTEHELPURL | The remote URL for the application's home page. This is used as the starting point for finding application updates, and the like.
CUSTOMURL | An option element that defines URLs to be shown in the Admin UI. This can be used to display alternate entry application entry point to users. Two attributes are included in this element: HREF and DESCRIPTION.
HOMEPAGE | Default URL of the local application. This can be helpful to the user if an application has been installed from a page other than the home page.


Creating Personal Tier CAB Files

Creating a CAB file can be done with a utility such as CABARC. CABARC can be downloaded as part of the Microsoft® Cabinet SDK. Reference instructions for the utility can be found on the MSDN site.

The following command, executed in the application root of the Personal Tier application that you're preparing for distribution creates a CAB file consisting of all the ASP+ pages, HTML files, images, style sheets, and business logic under the current hierarchy.

 cabarc -p -r n myweb.cab *.aspx *.htm* *.jpg *.gif *.css *.dll *.web

See Also

Deploying Personal Tier Applications


Personal Tier Administrative UI

When a user installs a Personal Tier application, a button is added to the Internet Explorer toolbar. When that button is clicked, it navigates to myweb://Home, which will invoke the administrative UI. User navigation to myweb:// or myweb://Home will also run the UI.

The administrative UI allows users to easily install, remove, and update Personal Tier applications on their computer with the click of a button.

See Also

Browser-hosted ASP+ Applications


ASP+ State Management

ASP+ provides easy-to-use Application and Session state facilities that are familiar to ASP developers and are readily compatible with all other NGWS frameworks APIs.

The following topics contain detailed information about state management and issues that developers need to consider when using these facilities.

Session State

Application State


Session State

HTTP is a stateless protocol – meaning that it provides no way for a server programmer to automatically recognize that a sequence of requests are all from the same client, or to even be able to determine whether a single browser instance is still actively viewing a page or site. As a result, building web applications that need to maintain some cross-request state information (shopping carts, data scrolling, etc) can be extremely challenging without additional infrastructure help.

ASP+ ships with built-in Session State functionality that provides developers with an easy, robust and scalable way to: 1) automatically identify and classify requests coming from a single browser client into a logical application “session” on the server; 2) store session-scoped data on the server for use across multiple browser requests; 3) raise appropriate session lifetime management events (OnSessionStart, OnSessionEnd, and so on) that can be handled in application code, 4) automatically release session data/information if the browser does not re-visit an application after a specified timeout period.

ASP+ provides the following support for sessions:

  • An easy to use session state facility that is familiar to ASP developers and consistent with other NGWS frameworks APIs.
  • A reliable session state facility that can survive IIS crashes and worker process restarts. Ensure that session data is not lost when these failures/problems occur.
  • A scalable session state facility that can be used in both web farm (multi-machine) and web garden (multi-process) scenarios. Enable administrators to allocate more processors/ machines to a web application, and its scalability improve (a radical concept).
  • A session state facility that works with browsers that do not support the use of HTTP cookies for privacy or legal reasons (note: you can actually go to jail in Germany for using them).
  • An ASP equivalent or better throughput for core Session State scenarios (50/50 read/write when putting stuff into shopping carts, modifying last page visited, credit card details, and so on.)

See Also

ASP+ State Management


Application State

ASP+ defines an application as the sum of all files, pages, handlers, modules and code that can be invoked through the scope of a given virtual directory and its subdirectories. For example, a “401k” application might be published via the “/401k” virtual directory on a web server machine.

Just as traditional standalone applications allow developers to share global information throughout an application, so too do ASP+ applications. Developers accomplish this via the HTTPApplicationState class – which exposes a key-value dictionary of objects that developers can use to store both NGWS object instances and scalar values across multiple web requests.

A single instance of an HTTPApplicationState class is created the first time a client requests any URL resource from within a particular ASP+ Application virtual directory namespace (note: a separate, single, instance is created for each ASP+ Application on the computer). A reference to this instance is then exposed via the “Application” property on the HTTPContext object provided to all IHTTPModules and IHTTPHandlers during a given web request.

ASP+ provides the following Application State support:

  • An easy to use state facility that is compatible with ASP, works with all NGWS languages, and is consistent with other NGWS frameworks APIs.
  • An application state dictionary that is available to all request handlers invoked within an application. Unlike IIS/ASP—which restricted access to application state only to pages—all IHTTPHandler and IHTTPModule instances can store and retrieve global variables within it.
  • A simple and intuitive synchronization mechanism that enables developers to easily coordinate concurrent access to global variables stored in application-state.
  • Application state values that are only accessible from code running within the context of the originating application. Other applications running on the system cannot get access or modify the values.

See Also

ASP+ State Management


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

HTTP Runtime Support

[To be supplied.]


ASP+ Caching Features

Caching is an extremely important technique for building high-performance, scalable Web server applications. The idea is simple: some items that are expensive to construct can be built once and used for a period of time before they are considered invalid. These items are stored in memory where they can be efficiently retrieved without incurring the cost of being reconstructed.

Straight out of the box, ASP+ offers a number of caching mechanisms that enhance Web application performance. For example, all ASP+ pages are compiled into an instance of the Page class and cached on the server; they are updated in the cache only when the page changes or the caching period expires. Additionally, configuration settings are read the first time an application is started and cached.

ASP+ also exposes caching services that allow developers to modify cache settings as needed. For more information, see the following topics:

ASP+ Output Cache

Cache APIs


ASP+ Output Cache

The ASP+ output cache is a module that provides a rich and flexible caching mechanism for caching dynamic response data.

The output cache provides increased throughput for dynamic content with little effort on the part of the content developer. Developers can use it to cache requests that would not be cacheable otherwise. The output cache can also handle cached fragments and substitution blocks (useful for ad rotators, for example). The output cache is enabled by default and will cache anything with either a valid expiration or validation policy and public cache visibility.

The output cache can also be used to cache private content. You can do this by having a handler mark the content as public before it passes through the output cache module. The content can then be cached, and you can write a module that returns the content to private before it is sent in the response.

ASP+ provides an API that exposes the cache in a type-safe manner. Much of the complexity of HTTP 1.1 caching is hidden without restricting the options available to the programmer using the API.

The following topics describe the output cache in greater detail.

Expiration Policies

Preserving Restrictions

See Also

ASP+ Caching Features | | @ OutputCache Directive


Expiration Policies

There are two types of expiration policies: relative and absolute expirations. Relative expirations occur when a set period of time has passed after a response, while absolute expirations occur at a specified time.

If you want to set a relative expiration, use the Cache-Control: max-age directive, which contains the number of seconds for which the response should be cached. The HTTP protocol allows you to set an absolute expiration using the HTTP Expires: header to an RFC 822 compatible date. However, if you set the header directly and later use the cache object model, the output cache recognizes that you have made modifications. It will set the cache header itself as the response is sent out. Therefore, it is highly recommended that you use the ASP+ object model and @ Output Cache directive to set these values.

Many Web servers add another twist on top of relative expirations: they allow administrators to always expire items relative to their request time. For example, if a site administrator decided that all images were valid for at least 24 hours, they could enable a policy which would cause the server to emit either Expires: or Cache-Control: maxage indicating that the resource expires 24 hours from the time the response is sent.

The ASP+ output cache enables these scenarios in the following ways.

  • Sliding Expiration: the output cache updates the expiration time when the response is sent so that the resource always expires at the end of the specified period after the response is sent. This analogous to the Web server scenario where the resource expires at some determined time relative to when the response is sent.
  • Absolute Expiration: the expiration time is set using a date after which the resource is stale, and this is always respected and never updated by the cache. This can be set (depending on the method used) as an absolute time or as a period from the current time.
    Note All forms of expiration end up being set in the same way: as an absolute date with an Expires: header or with a Cache-Control: maxage directive. The difference is that sliding expiration is renewed whenever the response is sent. Absolute and relative expiration differ only in the APIs used to set them.

See Also

ASP+ Output Cache | Preserving Restrictions | System.Web Namespace | HttpCachePolicy Class | OutputCacheItemInformation Class | HttpCacheability Class


Preserving Restrictions

Many HTTP cache related directives have a hierarchy of restrictiveness. This means that for a given mechanism (say cacheability), a set of options exists that ranges from permissive options (cache this response anywhere) to restrictive options (only cache this response on the client, not on a shared cache or never cache this response at all). The ASP+ output cache will always try to preserve the most restrictive setting for a given mechanism.

For example, if a pagelet control sets Cache-Control: private, restricting the item’s cacheability only to clients, another pagelet associated with the first will not be able to make the setting more permissive (Cache-Control: public). Attempts to use the cache API to later change this option in this manner will fail. If a subsequent pagelet enables a more restrictive setting, the cache will honor that.

Note All methods that preserve a hierarchy of restrictiveness are so noted in the documentation for that method. This will also be noted by the Restrict modifier on the method detail description.

See Also

ASP+ Output Cache | Preserving Restrictions | System.Web Namespace | HttpCachePolicy Class |


Cache APIs

The ASP+ cache APIs provide scalable caching services to Web applications. While basic cache settings are simple to use, these APIs also provide advanced semantics if you need to optimize your applications for performance and scalability.

You can set cached items to expire at an absolute time or at a time relative to the time they were last accessed. The cache can also track dependencies on external files or directories and other cache keys. For example, if your application uses data from an XML file source, that data could be cached. If the XML file changes, the cached data would be invalidated and your application could reconstruct it. The new data would then be cached anew.

The cache also provides a mechanism for discarding items if the server is low on memory. You can design your applications to prioritize data when items are added to the cache.

The cache has no information about the contents of the items it contains. It merely holds a reference to those objects and provides means to track their dependencies and set expiration policies.

See Also

ASP+ Caching Features | ASP+ Output Cache | Optimizing ASP+ Applications | System.Web.Cache Namespace | Cache Class | CacheDependency Class


Configuring ASP+

A central requirement of any web application server is a rich and flexible configuration system – one that enables developers to easily associate settings with an installable application without having to embed values into code, and enables administrators to easily adjust or customize these values post-deployment.

The ASP+ configuration system addresses both of these requirements. It does this through a hierarchical configuration infrastructure that enables extensible configuration data to be defined and used throughout ASP+ applications. At the same time, ASP+ provides a rich set of initial configuration settings.

For complete information on configuring your ASP+ Web applications, see ASP+ Configuration Concepts.

See Also

Creating ASP+ Web Applications


ASP+ Optimization

In a wide area network such as the Internet, Web application performance is of premium importance. ASP+ has been designed to offer improved performance over other Web development platforms. Web developers can improve application performance simply by upgrading to ASP+.

For more information about how to monitor and optimize the performance of your ASP+ applications, see Optimizing ASP+ Applications.

See Also

Creating ASP+ Web Applications


Securing ASP+ Web Applications

ASP+, working in conjunction with Internet Information Services (IIS) 5.0, can authenticate user credentials (such as names and passwords) using any of five different authentication methods: Basic, Digest, Windows, Passport, and Cookie authentication. Access to site information can be controlled in either of two ways: by comparing authenticated credentials (or representations of them) to NTFS file system (NTFS) permissions, or by comparing them to an XML file containing authorized users, authorized roles (groups), or authorized HTTP verbs.

For complete information on securing you ASP+ applications, see ASP+ Web Application Security.

See Also

Creating ASP+ Web Applications


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Creating Win Forms Applications

[To be supplied.]


Introduction to Win Forms

Win Forms and the NGWS Frameworks

Win Forms is the new platform for Microsoft Windows application development, based on the NGWS frameworks. The NGWS frameworks provide a clear, object-oriented, extensible set of classes that enable you to develop rich Windows applications. Additionally, Win Forms can act as the local user interface in a multi-tier distributed solution.

What is a Form?

A form is a bit of screen real estate, usually rectangular, that you can use to present information to the user and to accept input from the user. Forms can be standard windows, multiple document interface (MDI) windows, dialog boxes, or display surfaces for graphical routines. The easiest way to define the user interface for a form is to place controls on its surface. Forms are objects that expose properties which define their appearance, methods which define their behavior, and events which define their interaction with the user. By setting the properties of the form and writing code to respond to its events, you customize the object to meet the requirements of your application.

As with all objects in the NGWS frameworks, forms are instances of classes. The Win Form you create with the designer is a class, and when you display an instance of the form at run time, this class is the template used to create the form. The framework also allows you to inherit from existing forms to add functionality or modify existing behavior. When you add a form to your project, you can choose whether it inherits from the Form class provided by the framework, or from a form you've previously created.

Within a Win Forms project, the form is the primary vehicle for user interaction. By combining different sets of controls and writing code, you can elicit information from the user and respond to it, work with existing stores of data, and query and write back to the file system and Registry on the user’s local computer.

See Also

Anatomy of the Visual Basic Code behind a Win Form | Displaying Win Forms in the Designer | Win Forms as the Presentation Tier of a 3-Tier Application | Dialog Boxes | Controls and Win Forms in the Technology Preview


Win Forms as the Presentation Tier of a Three-Tier Application

A Win Forms program can act as a standalone executable or as the client aspect of a two-tier system. Additionally, Win Forms can act as a user-interface layer within a three-tier application. Using HTTP calls to a server that is connected to a database, Win Forms are capable of acting as the front-end to a very powerful, data-enabled architecture that leverages the rich user interface of a client application with the advanced server processing enabled by business logic.

For more information, see Creating a 3-tier Data Application

See Also

Introduction to Win Forms | Win Forms Creation


Win Forms Creation

The first step in designing a form is to set its properties. Then you are ready to add controls on the form and set the properties of its controls.

For information about | See
Setting the borders of a Win Form | Changing the Borders of a Win Form
Making your Win Form see-through | Creating Transparent Win Forms
Showing a dialog box modally versus modelessly | Displaying Modal and Modeless Forms
Ensuring your Win Form stays on top of all other forms at run time | Keeping a Win Form on Top
Setting the dimensions of a Win Form | Resizing Win Forms
Setting the screen coordinates of a Win Form | Setting the Location of Win Forms
Using a Win Form as the front-end of a multi-tier application | Win Forms as the Presentation Tier of a 3-Tier Application
Different user-interface layouts | Win Forms Layout Options

See Also

Introduction to Win Forms | Treating Forms as Objects


Win Forms Layout Options

If you've been using Windows®-based applications for a while, you've probably noticed that not all user interfaces look or behave the same. There are three main styles:

  • the single-document interface (SDI)
  • the multiple-document interface (MDI)
  • the Explorer-style interface

An example of the SDI interface is the WordPad application included with Microsoft Windows. In WordPad, only a single document can be open; you must close one document in order to open another.

WordPad, a single-document interface (SDI) application

Microsoft Excel is an example of an MDI interface; it allows you to display multiple documents at the same time, with each document displayed in its own window. You can recognize an MDI application by the inclusion of a Window menu containing commands for switching between windows or documents.

Microsoft Excel, a multiple-document interface (MDI) application

In determining which interface style is best, you need to look at the purpose of the application. An application for processing insurance claims might lend itself to the MDI style — a clerk is likely to be working on more than one claim at a time or might need to compare two claims. On the other hand, a calendar application would be best suited to the SDI style — it's not likely that you would need more than one calendar open at a time; in the rare event that you did, you could open a second instance of the SDI application.

The SDI style is the more common layout option for Windows applications. There are a number of considerations and techniques unique to creating MDI applications, which are addressed in Multiple Document Interface (MDI) Applications.

In addition to the two most common interface styles, SDI and MDI, a third interface style is becoming more popular: the Explorer-style interface. The Explorer-style interface is a single window containing two panes or regions, usually consisting of a tree or hierarchical view on the left and a display area on the right, as in the Microsoft Windows Explorer. This type of interface lends itself to navigating or browsing large numbers of documents, pictures, or files.

The Windows Explorer, an explorer-style interface

See Also

Introduction to Win Forms | Win Forms Creation | Creating a Windows Explorer-Style Application


Displaying Modal and Modeless Forms

Forms and dialog boxes are either modal or modeless. A modal form or dialog box must be closed (hidden or unloaded) before you can continue working with the rest of the application. For example, a dialog box is modal if it requires you to click OK or Cancel before you can switch to another form or dialog box. For more information about working with dialog boxes, see How to Handle User Input to Dialog Boxes.

Dialog boxes that display important messages should always be modal — that is, the user should always be required to close the dialog box or respond to its message before proceeding. Often, MessageBox is used to show information in this fashion, as it requires the user to close it before proceeding.

Modeless forms and dialog boxes let you shift the focus between the form or dialog box and another form without having to close the initial form or dialog box. The user can continue to work elsewhere in any application while the form or dialog box is displayed.

Modeless forms and dialog boxes are harder to program, because users can access them in an unpredictable order. You have to keep the state of the application consistent no matter what the user does. Often, tool windows are shown in a modeless fashion. A Find and Replace dialog box, is an example of a modeless dialog box. Use modeless forms and dialog boxes to display frequently used commands or information.

To display a form as a modal dialog box

  • Call the ShowDialog method.

    The following example shows how to display an About dialog box modally.

    [Visual Basic]
Dim f As frmAbout = New frmAbout
' Display frmAbout as a modal dialog.
f.ShowDialog
[C#]
' Display frmAbout as a modal dialog
Form frmAbout = new Form();
FrmAbout.ShowDialog();

    The ShowDialog method has an optional argument, owner, that can be used to specify a parent-child relationship for a form. For example, when code in your main form shows a dialog box, you can pass Me (in Visual Basic) or this (in C#) as the owner of the dialog box to establish your main form as the owner, as the following code shows.

    [Visual Basic]
Private Sub mnuAbout_Click(…args…)
   Dim f As frmAbout = New frmAbout
   f.ShowDialog Me
End Sub
[C#]
    Note If a form is displayed as modal, the code following the ShowDialog method is not executed until the dialog box is closed. However, when a form is shown as modeless, the code following the Show method is executed immediately after the form is displayed.

To display a form as a modeless dialog box

  • Call the Show method.

    The following example shows how to display an About dialog box in modeless format.

    [Visual Basic]
Dim f As frmAbout = New frmAbout
' Display frmAbout as a modeless dialog.
f.Show
[C#]
' Display frmAbout as a modaless dialog
Form frmAbout = new Form();
FrmAbout.Show();

See Also

Introduction to Win Forms | Win Forms Creation | Retrieving Dialog Box Information Using Multiple Properties | Retrieving Dialog Box Information Using Objects | Creating Transparent Win Forms | Resizing Win Forms | Setting the Location of Win Forms | Dialog Boxes | Treating Forms as Objects


Keeping a Win Form on Top

With the Windows 2000 operating system, a top-most form always stays in front of all windows in a given application. In Windows 98, a top-most form stays in front of all windows in all applications. For example, you may want to keep a floating tool window in front of your application's main window. The TopMost property controls whether a form is a top-most form. Note that a top-most form will float above other, non–top-most forms even when it is not active.

To make a form the top-most form in a Win Forms application programmatically

  • In a procedure, set the the TopMost property to True.
    [Visual Basic]
Public sub MakeOnTop()
   myTopForm.TopMost = True
End Sub
[C#]

See Also

Introduction to Win Forms | Win Forms Creation | Displaying Modal and Modeless Forms | Creating Transparent Win Forms | Resizing Win Forms | Setting the Location of Win Forms | Dialog Boxes


Creating Transparent Win Forms

With Windows 2000 you can control the opacity of windows that are displayed. To make a form more transparent, adjust its Opacity property.

To control the opacity of Win Forms

  • Set the Opacity property of the form to a value between 0.0 (complete transparency) and 1.0 (complete opacity).

See Also

Introduction to Win Forms | Win Forms Creation | Displaying Modal and Modeless Forms | Resizing Win Forms | Setting the Location of Win Forms | Dialog Boxes


Resizing Win Forms

Forms can be resized at run time. For example, if a form displays a bitmap, you may want to resize the form whenever the user selects a new bitmap.

To resize a form programmatically

  • Define the size of a form at run time by setting the Size property of the form. The following example shows the form size set to 100 by 100 pixels.
    [Visual Basic]
Form1.Size = New System.Drawing.Size(100, 100)
[C#]

To change form width and height programmatically

  • Once the Size object is defined, change either its Width or Height property. In the example below, the width of the form is set to 300 pixels from the left edge of the form, while the height stays constant.
    [Visual Basic]
Form1.Width = 300
[C#]

    – or –

  • Change Width or Height by setting the Size property. As the following code shows, however, this approach is more cumbersome than simply setting Width or Height properties.
    [Visual Basic]
Form1.Size = New Size(300, Form1.Size.Height)
[C#]

To change form size by increments

  • To increment the size of the form, set the Width and Height properties. In the example below, the width of the form is set to 200 pixels wider than the current setting.
    [Visual Basic]
Form1.Width += 200
[C#]

    Caution Always use the Height or Width property to change the size of a form, unless you're setting both at the same time. The following code will not change the form size. The Size property returns a Size structure containing a copy of the form's height and width, and the X member of this structure is incremented by 100 — however, the copied and incremented structure is then discarded.

    [Visual Basic]
Dim f As frmAbout = New frmAbout
f.Size.X += 100
[C#]

    Use the syntax displayed in the bullet point above to increment the form's width.

See Also

Introduction to Win Forms | Win Forms Creation | Resizing Controls on Win Forms | Displaying Modal and Modeless Forms |Creating Transparent Win Forms | Setting the Location of Win Forms | Dialog Boxes


Setting the Location of Win Forms

You can specify where a form is to be displayed on the computer screen by setting the Location property. This specifies the position, in pixels, of the top-left corner of the form. Also, you need to set the StartPosition property to indicate the boundaries of the display area.

Note Keep in mind that screen size and resolution often vary depending on the user's system. Additionally, systems that have multiple monitors attached may have trouble recognizing the boundaries of the display area. These two situations will often cause a form's location to change unpredictably, despite the Location property setting.
For this reason, the default setting for the StartPosition property for a Windows Application is "WindowsDefaultLocation", which tells the operating system to compute the best location for the form at startup, based on the current hardware. Another alternative is to set the StartPosition property to Center and then change the location of the form in code. See "To position forms programmatically" below for more information.

Note Keep in mind that screen size and resolution may differ on your user's system. A form's location as a user sees it might thus differ from what you see when you set the Location property.

To position forms programmatically

  • Define the location of a form at run time by setting the Location property of the form to a Point.
    [Visual Basic]
Form1.Location = New Point (100, 100)
[C#]

    – or –

  • Change the X or Y coordinate of the form's location using the X or Left property. The following example adjusts the form's X coordinate to the 300 pixel point.
    [Visual Basic]
Form1.X = 300
'OR
Form1.Left = 300
[C#]

To change form position by increments

  • Increment the X coordinate of the form using the X or Left property. The following example adjusts the form's X coordinate by 200 pixels.
    [Visual Basic]
Form1.X += 200
'OR
Form1.Left += 200

[C#]

    Tip Use the Location property to set a Win Form's X and Y positions simultaneously. To set them individually, use the form's Left (X) or Top (Y) property. Do not try to implicitly set the X and Y coordinates of the Point structure that represents the form's location, because this contains a copy of the form's coordinates. Below, the X member of this structure is incremented by 100 — however, the copied and incremented structure is then discarded.

    [Visual Basic]
Dim f As Form1 = New Form1
f.Location.X += 100
[C#]
    Use the syntax displayed in the bullet point above to increment the form's X coordinate.

In lieu of using the Location property, the DesktopLocation property can be used to set the location of your form. This property sets the location of your form relative to the taskbar and is useful if the taskbar has been docked to the top or left of the user's monitor. Docking the taskbar in this fashion obscures the desktop coordinates (0,0). A form with the desktop location of (0, 0) always appears in the upper left corner of the primary monitor, but not under the taskbar.

To set the Desktop Location property programmatically

  • Set the DesktopLocation property as you would any other property. The following example establishes a new location for an Accounts form.
    [Visual Basic]
Dim frmAccounts as Form = new frmAccounts
Set FrmAccounts.DesktopLocation = new Point (100,100)
[C#]

See Also

Introduction to Win Forms | Win Forms Creation | Positioning Controls on Win Forms | Anchoring Controls on Win Forms | Docking Controls on Win Forms | Displaying Modal and Modeless Forms |Creating Transparent Win Forms | Resizing Win Forms | Dialog Boxes


Changing the Borders of a Win Form

There are several border styles to choose from when determining the look of your Win Forms. By changing the FormBorderStyle property, you can control the resizing behavior of the form. In addition, setting the FormBorderStyle affects how the caption bar is displayed as well as what buttons might appear on it.

Setting | Description
None | None (no border or border-related elements). Used for startup forms.
Fixed 3D | Used when 3D border effect is desired. Not resizable. Can include control-menu box, title bar, Maximize and Minimize buttons on the title bar. Creates a raised border relative to the body of the form.
Fixed Dialog | Used for dialog boxes. Not resizable. Can include control-menu box, title bar, Maximize and Minimize buttons on the title bar. Creates a recessed border relative to the body of the form.
Fixed Single | Not resizable. Can include control-menu box, title bar, Maximize button, and Minimize button. Resizable only using Maximize and Minimize buttons. Creates a single line border.
Fixed Tool Window | Used for tool windows. Displays a non-sizable window with a Close button and title bar text in a reduced font size. The form does not appear in the Windows taskbar.
Sizable | (Default) Often used as main window. Resizable. Can include control-menu box, title bar, Maximize button, and Minimize button. Can be resized using control-menu box, Maximize button, and Minimize button on the title bar, or by using the mouse pointer at any edge.
Sizable Tool Window | Used for tool windows. Displays a sizable window with a Close button and title bar text in a reduced font size. The form does not appear in the Windows taskbar.
Note All border styles above, except the None setting, feature the Close box on the right-hand side of the title bar.

The border style of a form is set using the FormBorderStyle enumeration. Setting or changing the border style of your form in code is merely a matter of setting the BorderStyle property to one of the values of this enum.

An example of a form that has a definitive border style is a dialog box, which uses the Fixed Dialog border style.

To set the border style of Win Forms programmatically

  • Set the BorderStyle property to the desired style. The sample code below sets the border style of form DlgBx1 to FixedDialog.
    [Visual Basic]
DlgBx1.Borderstyle = FormBorderStyle.FixedDialog
[C#]
DlgBx1.Borderstyle = FormBorderStyle.FixedDialog;

    For more information on dialog boxes, see Creating Dialog Boxes.

See Also

Introduction to Win Forms | Win Forms Creation | Displaying Modal and Modeless Forms |Creating Transparent Win Forms | Resizing Win Forms | Setting the Location of Win Forms | Dialog Boxes


Control Manipulation on Win Forms

As you design and modify the user interface of your solutions, it will be necessary to add, align, and position controls. Controls are objects that are contained within form objects. Each type of control has its own set of properties, methods, and events that make it suitable for a particular purpose. You can manipulate and add controls dynamically at run time.

For information about | See
Ways to add controls to Win Forms | Putting Controls on Win Forms
Things you can do with controls on forms | Arranging Controls on Win Forms
Keyboard shortcuts, text labels on controls, and modifier keys | Working with Individual Controls
Controls that work with Win Forms, and basic things you can accomplish with each control | Controls You can Use on Win Forms
All available controls, organized categorically | Controls by Category

See Also

Controls and Win Forms in the Technology Preview | Control Technologies and Where to Use Them


Putting Controls on Win Forms

For information about | See
Attaching controls to your form | Adding Controls to Win Forms
Inserting controls onto your form dynamically | Adding Controls at Run Time to Win Forms


Working With Individual Controls

Controls added to Win Forms have properties and methods that are used to further specialize the user experience. Customizing your user interface to suit the needs of the user is extremely important for well-designed Windows applications.

For information about | See
Creating predefined access keys | Creating Keyboard Shortcuts for Controls
Accepting keyboard input | Determining Which Modifier Key Was Pressed
Assigning a text label to a control | Setting the Text Displayed by a Control


Adding Controls to Win Forms

Most forms are designed by adding controls to the surface of the form to define a user interface. A control is a component on a form used to display information or accept user input.

You can add controls dynamically to a form at run time. In the example below, a Textbox control will be added to the form when a Button control is clicked.

To add a control to a form programmatically

  1. In your code editor, open the form that you want to add controls to.
  2. Create a Private member variable for the Button within your class:
    [Visual Basic]
Private Button1 As Button
[C#]
private Button Button1;
  3. In the method that handles the Click event within your form's class, insert code similar to the following to add a reference to your control variable, set the control's Size and Location, and add the control:
    [Visual Basic]
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
   Dim MyText as New Textbox
   MyText.Size = New Point(25,125)
   MyText.Location = New Point(25,25)
   Me.Controls.Add MyText
End Sub
[C#]
private void Button1_Click(object sender, EventArgs e) {
    Textbox MyText = new Textbox();
    MyText.Size = new Size(25,125);
    MyText.Location = new Point(25,25);
    this.Controls.Add (MyText);
}
    Note You can also add code to initialize other properties of the control.

See Also

Control Manipulation on Win Forms | Anchoring Controls on Win Forms | Docking Controls on Win Forms | Layering Objects on Win Forms | Positioning Controls on Win Forms | Resizing Controls on Win Forms | Setting the Tab Order on Win Forms | Setting the Text Displayed by a Control | Controls You can Use on Win Forms


Arranging Controls on Win Forms

For information about | See
Setting controls to resize dynamically at run time | Anchoring Controls on Win Forms
Establishing a Button as the control to cancel the form | Designating a Button As the Cancel Button
Establishing a Button as the decision entered if a user presses ENTER | Designating a Button As the Default Button
Making controls "stick" to the side(s) of a form | Docking Controls on Win Forms
Establishing a set of RadioButton controls as related to one another | Grouping RadioButton Controls to Function As a Set
Establishing which controls are on top relative to the z-axis (z-order) | Layering Objects on Win Forms
Setting the coordinates of the controls on a form | Positioning Controls on Win Forms
Setting the size of controls on a form | Resizing Controls on Win Forms
Regulating the order in which controls will have focus when the user presses TAB | Setting the Tab Order on Win Forms


Layering Objects on Win Forms

When creating a complex user interface, or working with a multiple document interface (MDI) form, it is often desirable to layer both controls and child forms. To move and keep track of controls and windows within the context of a group, you manipulate their z-order. Z-order is the visual layering of controls on a form along the form's z-axis (depth). The window at the top of the z-order overlaps all other windows. All other windows overlap the window at the bottom of the z-order.

To layer controls programmatically

  • Use the BringToFront and SendToBack methods to manipulate the z-order of the controls. For example, if a Textbox control, txtFirstName, is underneath another control and you wish to have it on top, use the following code:
    [Visual Basic]
txtFirstName.BringtoFront
[C#]
txtFirstName.BringToFront();

It should also be noted that Win Forms supports control containment. Control containment involves placing a number of controls within a containing control, such as a number of RadioButton controls within a GroupBox control. You can then layer the controls within the containing control. Moving the group box moves the controls as well, as they are contained inside it.

See Also

Control Manipulation on Win Forms | Controls by Category | Anchoring Controls on Win Forms | Docking Controls on Win Forms | Positioning Controls on Win Forms | Resizing Controls on Win Forms | Setting the Tab Order on Win Forms | Setting the Text Displayed by a Control | Working with Individual Controls | Controls You can Use on Win Forms | Control Technologies and Where to Use Them | Win Form Controls by Function


Positioning Controls on Win Forms

You can use the Location property or the Left and Top properties to position a control on a form at run time.

To position a control programmatically

  1. Set the Location property of the control to a Point.
    [Visual Basic]
Button1.Location = New Point (100, 100)
[C#]
Button1.Location = new Point (100,100);
  2. Change the X or Y coordinate of the control's location using the Left and Top properties.
    [Visual Basic]
Button1.Left = 300
Button1.Top = 300
[C#]
Button1.Left = 300;
Button1.Top = 300;

To increment a control's location programmatically

  • Set the Left property to increment the X coordinate of the control and the Top property to increment the Y coordinate of the control.
    [Visual Basic]
Button1.Left += 200
Button1.Top += 200
[C#]
Button1.Left += 200;
Button1.Top += 200;

    Tip Use the Location property to set a control's X and Y position simultaneously. To set a position individually, use the control's Left (X) or Top (Y) property. Do not try to implicitly set the X and Y coordinates of the Point structure that represents the button's location, because this structure contains a copy of the button's coordinates. Below, the X member of this structure is incremented by 100 — however, the copied and incremented structure is then discarded.

    [Visual Basic]
Dim b As Button = New Button
B.Location.X += 100
[C#]
Button b = new Button();
b.Location.X += 100;
    Use the syntax displayed in the bullet point above to increment the button's X coordinate.

See Also

Control Manipulation on Win Forms | Controls by Category | Adding Controls to Win Forms | Anchoring Controls on Win Forms | Docking Controls on Win Forms | Layering Objects on Win Forms | Resizing Controls on Win Forms | Setting the Tab Order on Win Forms | Working with Individual Controls | Controls You can Use on Win Forms | Win Form Controls by Function


Resizing Controls on Win Forms

You can resize controls at run time by specifying the Size and Location in a Rectangle object in code and assigning the Rectangle to the Bounds property of the control.

To size a control programmatically

  1. Open your form in your code editor.
  2. Type code to create a private method with a Rectangle object and a control. For example:
    [Visual Basic]
Private Sub MakeSizedButton()
   Dim rect1 as Rectangle
   Dim button1 as Button

   Set rect1 = new Rectangle
'Use the SetBounds method to set the X,Y,Width, and Height of the Rectangle.
   rect1.SetBounds(75, 23, 100, 50)
[C#]
private void MakeSizedButton() {
    Rectangle rect1 = new Rectangle (50, 100, 75, 23);
  3. Add code to this private method to set the Bounds property of the Button control equal to the dimensions of the Rectangle object. For example:
    [Visual Basic]
   Set button1.Bounds = rect1
End Sub
[C#]
    button1.Bounds = rect1;
}
    Note Alternatively, you can also use the Size and Location properties to independently specify the size and location of the control respectively. For more information about the Location property, see Positioning Controls on Win Forms.
  4. If you do not have a constructor for the form, create one. Add the following code after the InitForm method is called in this constructor:
    [Visual Basic]
MakeSizedButton
[C#]
MakeSizedButton();

See Also

Control Manipulation on Win Forms | Controls by Category | Adding Controls to Win Forms | Anchoring Controls on Win Forms | Docking Controls on Win Forms | Layering Objects on Win Forms | Setting the Tab Order on Win Forms | Working with Individual Controls | Controls You can Use on Win Forms | Control Technologies and Where to Use Them | Win Form Controls by Function


Anchoring Controls on Win Forms

If you are designing a form that the user can resize at run time, the controls on your form should resize and reposition properly. To resize controls dynamically with the form, you can use the Anchor property of Win Forms controls. The Anchor property defines an anchor position for the control. When a control is anchored to a form and the form is resized, the control maintains the distance between the control and the anchor positions. For example, if you have a Textbox control that is anchored to the left, right, and bottom edges of the form, as the form is resized, the Textbox control resizes horizontally so that it matches the same distance from the right and left sides of the form. In addition, the control positions itself vertically so that its location is always the same distance from the bottom edge of the form. If a control is not anchored and the form is resized, the position of the control relative to the edges of the form is changed.

To anchor a control on a form at run time

  1. Open the form that contains the controls you want to anchor in your code editor.
  2. In a method in your form, set the Anchor property for the control that you want to anchor. For example, to anchor a TextBox control to the left and right edges of the form add code the following code:
    [Visual Basic]
Private Sub AnchorMyControl()
   'Set the TextBox control' to anchor to left and right.
   txtMain.Anchor = AnchorStyle.LeftRight
End Sub
[C#]
private void AnchorMyControl()
{
   ' Set the TextBox control' to anchor to left and right.
   txtMain.Anchor = AnchorStyle.LeftRight;
}

When your form is displayed at run time, the control resizes to remain positioned at the same distance from the left and right edges of the form. The distance from the anchored edge always remains the same as the distance defined when the control is created.

Note Certain controls, such as the ComboBox control, have a limit to their height. Anchoring the control to the bottom of its form or container cannot force the control to exceed its height limit.

See Also

Control Manipulation on Win Forms | Controls by Category | Adding Controls to Win Forms Docking Controls on Win Forms | Layering Objects on Win Forms Setting the Tab Order on Win Forms | Working with Individual Controls | Controls You can Use on Win Forms | Control Technologies and Where to Use Them | Win Form Controls by Function


Docking Controls on Win Forms

You can dock controls to the edges of your form or have them fill the control's container (either a form or a container control). For example, Windows Explorer docks its TreeView control to the left side of the window and its ListView control to the right side of the window. Use the Dock property for all visible Win Forms controls to define the docking mode.

To dock a control on a form at run time

  1. Open the form that contains the control you want to dock in your code editor.
  2. In a method in your form, set the Dock property for the control that you want to dock. For example, to dock a TreeView control to the left edge of the form add code the following code:
    [Visual Basic]
Private Sub DockMyControl()
   'Set the TreeView control' to anchor to left.
   trvMain.Dock = DockStyle.Left
End Sub
[C#]
private void DockMyControl()
{
   ' Set the TreeView control' to anchor to left.
   trvMain.Dock = DockStyle.Left;
}

    When your form is displayed at run time, the control resizes to the size of the borders the control is docked to.

See Also

Control Manipulation on Win Forms | Controls by Category | Resizing Controls on Win Forms | Setting the Tab Order on Win Forms | Working with Individual Controls | Controls You can Use on Win Forms | Control Technologies and Where to Use Them | Win Form Controls by Function


Setting the Tab Order on Win Forms

The tab order is the order in which a user moves focus from one control to another by pressing the TAB key. Each form has its own tab order. By default, the tab order is the same as the order in which you created the controls. Tab-order numbering begins with zero.

To set the tab order of a control

  • Set TabIndex property to a number representing its order in the tab order for the form. The TabIndex property of a control determines where it is positioned in the tab order. By default, the first control added to a form has a TabIndex value of 0, the second has a TabIndex of 1, and so on.

Additionally, by default, a GroupBox control has its own TabIndex value, which is a whole number. A GroupBox control itself cannot have focus at run time. Thus, each control within a GroupBox has its own decimal TabIndex value.

Finally, any control of the many on your form can be skipped in the tab order. Usually, pressing TAB successively at run time selects each control in the tab order. By setting the TabStop property, a control can be passed over in the tab order of the form.

To remove a control from the tab order

  • Set the control's TabStop property to false.

    A control whose TabStop property has been set to false still maintains its position in the tab order, even though the control is skipped when you cycle through the controls with the TAB key.

    Note A radio button group has a single tab stop at run time. The selected button (that is, the button with its Checked property set to true) has its TabStop property automatically set to true, while the other buttons have their TabStop property set to false. For more information about grouping RadioButton controls, see Grouping RadioButton Controls to Function As a Set.

See Also

Control Manipulation on Win Forms | Controls by Category | Anchoring Controls on Win Forms | Docking Controls on Win Forms | Layering Objects on Win Forms | Positioning Controls on Win Forms | Resizing Controls on Win Forms | Controls You can Use on Win Forms | Control Technologies and Where to Use Them | Win Form Controls by Function


Event Handling

An event-handling method is a method in your code that determines the actions to be performed when an event occurs, such as the user clicking a button or a message queue receiving a message.

For information about | See
The event model and the role of delegates | Introduction to Events
How to handle events | Introduction to Event-handling methods
Responding to system or user events dynamically | Creating Event-handling methods at Run Time for Win Forms

See Also

Controls and Win Forms in the Technology Preview


Introduction to Events

An event is an action to which you can respond, or “handle”, in code. Events can be generated by a user action, such as clicking the mouse or pressing a key; by program code; or by the system.

Event-driven applications execute code in response to an event. Each form and control exposes a predefined set of events that you can program against. If one of these events occurs and there is code in the associated event-handling method, that code is invoked.

The types of events raised by an object vary, but many types are common to most controls. For example, most objects will handle a Click event — if a user clicks a form, code in the form's Click event-handling method is executed.

Note Many events occur in conjunction with other events. For example, in the course of the DoubleClick event occurring, the MouseDown, MouseUp, and Click events occur.

The Role of Delegates

The NGWS frameworks event model uses delegates to bind events to the methods used to handle them. The delegate allows other classes to register for event notification by specifying a handler method. When the event occurs, the delegate calls the bound method.

Delegates can be bound to a single method or to multiple methods, referred to as multicasting. When creating a delegate for an event, you typically create a multicast event. A rare exception might be an event that results in a specific procedure (such as displaying a dialog box) that would not logically repeat multiple times per event.

A multicast delegate maintains an invocation list of the methods it is bound to. The multicast delegate supports a Combine method to add a method to the invocation list and a remove method to remove it.

When an event is recorded by the application, the control raises the event by invoking the delegate for that event. The delegate in turn calls the bound method. In the most common case (a multicast delegate) the delegate calls each bound method in the invocation list in turn, which provides a one-to-many notification. This strategy means that the control does not need to maintain a list of target objects for event notification — the delegate handles all registration and notification.

Delegates also allow multiple events to be bound to the same method, allowing a many-to-one notification. For example, a button-click event and a menu-command–click event can both invoke the same delegate, which then calls a single method to handle these two separate events the same way.

The binding mechanism used with delegates is dynamic — a delegate can be bound at run time to any method whose signature matches that of the event-handling method. This feature allows you to set up or change the bound method depending on a condition and to dynamically attach an event-handling method to a control.

See Also

Event Handling | Introduction to Event-handling methods


Introduction to Event-Handling Methods

An event-handling method is a method that is bound to an event. When the event is raised, the code within the event-handling method is executed. Each event-handling method provides two parameters that allow you to handle the event properly. The following example shows an event-handling method for a Button control's Click event:

The first parameter, sender, provides a reference to the object that raised the event. The second parameter, e in the example above, passes an object specific to the event that is being handled. By referencing the object's properties (and sometimes, its methods), you can obtain information such as the location of the mouse for mouse events or data being transferred in drag-and-drop events.

Typically each event produces an event-handling method with a different event object type for the second parameter. Some event-handling methods, such as those for the MouseDown and MouseUp events, have the same object type for their second parameter. For these types of events, you can use the same event-handling method to handle both events.

[Visual Basic]
Public Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)

End Sub
[C#]
private void Button1_Click(object sender, EventArgs e) {

}

You can also use the same event-handling method to handle the same event for different controls. For example, if you have a group of RadioButton controls on a form you could create a single event-handling method for the Click event and have each control's Click event bound to the single event-handling method. For more information, see Connecting Multiple Events to a Single Event-handling method.

See Also

Event Handling | Introduction to Events | Creating Event-Handling Methods at Run Time for Win Forms


Creating Event-Handling Methods at Run Time for Win Forms

You can bind an event to an event-handling method at run time. This allows you to connect event-handling methods based on conditions in code at run time.

To create an event-handling method at run time

  1. Open the form that you want to add an event-handling method to in your code editor.
  2. Add a method to your form with the method signature for the event that you want to handle.

    For example, if you were handling the Click event of a Button control, you would create a method such as the following:

    [Visual Basic]
Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs)
   'Add event-handling method code here
End Sub
[C#]
private void Button1_Click(object sender, EventArgs e) {
//Add event-handling method code here
}
  3. Add code to the event-handling method as appropriate to your application.
  4. Determine which form or control you want to create an event-handling method for.
  5. In a method within your form's class, add code that calls the appropriate AddOn method for the event that you want to handle. For example, if you are handling the Click event of a Button control, you would add code similar to the following:
    [Visual Basic]
Button1.AddOnClick New System.EventHandler(AddressOf Me.Button1_Click)
[C#]
button1.AddOnClick(new EventHandler(Button1_Click));

    The AddOnClick method establishes a click event-handling method for the button.

See Also

Event Handling | Introduction to Event-Handling Methods


Dialog Boxes

Dialog boxes are used to interact with the user and retrieve information. In simple terms, a dialog box is a form with its BorderStyle property set to FixedDialog. You can construct your own custom dialog boxes the Form class. Add controls such as Label, Textbox, and Button to customize dialog boxes to your specific needs. The NGWS frameworks also include predefined dialog boxes (such as File Open, and message boxes), which you can adapt for your own applications.

For information about | See
Basic dialog box construction | Creating Dialog Boxes
Using predefined dialog box controls | Dialog Box Controls
Showing dialog boxes | Displaying Dialog Boxes for Win Forms
Showing message boxes | Displaying Message Boxes
Showing a dialog box modally versus modelessly | Displaying Modal and Modeless Forms
The property that stores the information created when a dialog box is closed | How to Handle User Input to Dialog Boxes
Working with the property that stores the information created when a dialog box is closed | Closing Dialog Boxes and Retaining User Input
Working with the resultant information generated by a dialog box being closed | Retrieving the Result for Dialog Boxes
Using properties to return individual elements of dialog box data | Retrieving Dialog Box Information Selectively Using Multiple Properties
Using an object to expose a group of related data from a dialog box | Retrieving Dialog Box Information Collectively Using Objects
Accessing information from a dialog box's parent form | Retrieving Information from the Parent Form of a Dialog Box
Incorporating the tab control into a dialog box | Tab Control

See Also

Controls and Win Forms in the Technology Preview


Creating Dialog Boxes

Dialog boxes are displayed modally to prevent users from performing tasks outside of the dialog box. For more information on modal and modeless dialog boxes, see Displaying Modal and Modeless Forms

To create a dialog box at run time

  1. Create a class that inherits from Form to your project
  2. Set the BorderStyle property to FixedDialog.
  3. Customize the appearance of the form as needed.
  4. Set the ControlBox, MinimizeBox, and MaximizeBox properties to False.

    Dialog boxes do not usually include menu bars, window scroll bars, Minimize and Maximize buttons, status bars, or sizable borders.

  5. Customize event procedures using your code editor. For more information, see Closing Dialog Boxes and Retaining User Input.

Additionally, the NGWS frameworks have a number of pre-formatted dialog boxes that you can implement for basic tasks within your application that involve user input (opening files, printing files, saving files). For more information, see Dialog Box Controls.

See Also

Dialog Boxes | Displaying Dialog Boxes for Win Forms | Closing Dialog Boxes and Retaining User Input | Retrieving the Result for Dialog Boxes | Retrieving Dialog Box Information Selectively Using Multiple Properties | Retrieving Dialog Box Information Collectively Using Objects | Retrieving Information from the Parent Form of a Dialog Box


Displaying Dialog Boxes for Win Forms

You display a dialog box in the same way you display any other form in an application. The startup form loads automatically when the application is run. To make a second form or dialog box appear in the application, write code to load and display it. Similarly, to make the form or dialog box disappear, write code to unload or hide it.

To display a dialog box

  1. In your code editor, navigate to the event-handling method with which you want to open the dialog box. This can happen when a menu command is selected, when a button is clicked, or when any other event occurs.
  2. In the event-handling method, add code to open the dialog box. In this example, a Click event for a Button is used to show the dialog box:
    [Visual Basic]
Private Sub Button1_Click()
   Dim dlg1 as new DialogBox
   dlg1.ShowDialog
End Sub
[C#]
private void Button1_Click(object sender, EventArgs e) {
   MyDialogBox dlg1 = new MyDialogBox();
   dlg1.ShowDialog();
}

    For more information on the ways to show forms and dialog boxes, see Displaying Modal and Modeless Forms.

See Also

Dialog Boxes | Creating Dialog Boxes | Closing Dialog Boxes and Retaining User Input | Retrieving the Result for Dialog Boxes | Retrieving Dialog Box Information Selectively Using Multiple Properties | Retrieving Dialog Box Information Collectively Using Objects | Retrieving Information from the Parent Form of a Dialog Box


How to Handle User Input to Dialog Boxes

Dialog boxes are typically used to prompt the user for data that is then used by an application. The form displaying the dialog box processes that information. When you display a dialog box, it is important to know how it is closed, or its "result." For example, if the user clicks a Cancel button, the data entered by the user is disposed of rather than retained. You can use the Button.DialogResult or Form.DialogResult property to find out how a dialog box is closed.

From the form that displays the dialog box (known as the dialog box's parent form), you can use the value of the DialogResult property to determine if an OK button or a Cancel button is clicked. Based on the DialogResult returned, you decide whether you need to retrieve the dialog box's information. You can manually set the value of a dialog box's DialogResult property when you do not use Button controls on your dialog box but still want to return a DialogResult.

The DialogResult property can also handle the Click event and close the dialog box by default. For example, setting the DialogResult property for a Button to OK will close the dialog box when the user clicks the button. This feature allows you to avoid adding code to the control's Click event handler to close the dialog box. If you need to perform additional tasks in the Click event handler, you can write code to handle the event and the DialogResult property will nonetheless close the dialog box for you.

See Also

Dialog Boxes | Creating Dialog Boxes | Closing Dialog Boxes and Retaining User Input | Retrieving the Result for Dialog Boxes


Closing Dialog Boxes and Retaining User Input

The way a dialog box is closed, or its “result,” can be set at either design time or run time: At design time, you can set the DialogResult property for all of the Button controls on a dialog box. Setting the DialogResult property at run time allows you to dynamically handle user responses.

You can set the dialog result for user-performed actions other than clicking a Button control. If your dialog box does not contain buttons to close the dialog box, you can manually set the result of the dialog box at run time.

To set the DialogResult property for a control or form at run time

  1. In your coed editor, open the event-handling method or method that you want to set the DialogResult property for.
  2. Type code similar to the following:
    [Visual Basic]
Public Sub InformationProcessed()
   'This code will set the DialogResult for a form.
   Me.DialogResult = DialogResult.Yes
   'OR
   'This code will set the DialogResult for a button.
   Button1.DialogResult = DialogResult.No
End Sub
[C#]
private void InformationProcessed() {
   // This code will set the DialogResult for a form.
   DialogResult = DialogResult.Yes;
   // OR
   // This code will set the DialogResult for a button.
   Button1.DialogResult = DialogResult.No;
}

Although setting the DialogResult property will cause your dialog box to close automatically, you can still handle the control's Click event and the dialog box will close once the event-handling method’s code is finished. While handling the Click event, you may want to halt the closure of the dialog box.

To stop the DialogResult property from closing the dialog box

  • In the event-handling method, type code similar to the following:
    [Visual Basic]
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
   Me.DialogResult = DialogResult.NONE
End Sub
[C#]
private void Button1_Click(System.Object sender, System.EventArgs e) {
   this.DialogResult = DialogResult.None;
}

See Also

Dialog Boxes | How to Handle User Input to Dialog Boxes | Creating Dialog Boxes | Retrieving the Result for Dialog Boxes | Retrieving Dialog Box Information Selectively Using Multiple Properties | Retrieving Dialog Box Information Collectively Using Objects | Retrieving Information from the Parent Form of a Dialog Box


Retrieving the Result for Dialog Boxes

Once a dialog box is closed, the form that displayed the dialog box can retrieve the result of that dialog box by referencing its DialogResult property or the return value of a call to the ShowDialog method. The form that displayed the dialog box then responds according to the value returned.

To retrieve the DialogResult value

  • Add code similar to the following to the method that displayed the dialog box.

    Typically, this code is entered below the code that creates and displays the dialog box:

    [Visual Basic]
Public Sub DisplayDialog()
   'Create and display an instance of the dialog box
   Dim dlg as MyDialogBox = new MyDialogBox

   'Show the dialog and determine the state of the
   'DialogResult property for the form.
   If dlg.ShowDialog = DialogResult.OK Then
      'Do Something Here to Handle Data From Dialog Box
   End If
End Sub
[C#]
private void DisplayDialog() {
   // Create and display an instance of the dialog box
   MyDialogBox dlg = new MyDialogBox();

   // Show the dialog and determine the state of the
   // DialogResult property for the form.
   if (dlg.ShowDialog() == DialogResult.OK ) {
      // Do Something Here to Handle Data From Dialog Box
   }
}

See Also

Dialog Boxes | How to Handle User Input to Dialog Boxes | Creating Dialog Boxes | Displaying Dialog Boxes for Win Forms | Closing Dialog Boxes and Retaining User Input | Retrieving Dialog Box Information Selectively Using Multiple Properties | Retrieving Dialog Box Information Collectively Using Objects | Retrieving Information from the Parent Form of a Dialog Box


Retrieving Dialog Box Information Selectively Using Multiple Properties

A common way to process the information returned from a dialog box is to provide a set of properties that return individual elements of dialog box data. This way, you can selectively extract data from the dialog box.

If you have dialog box data that is related, consider exposing some information through objects. For details, see Retrieving Dialog Box Information Collectively Using Objects.

To expose dialog box information through properties

  1. Display the class for your dialog box in your code editor.
  2. Create as many properties as needed to expose the necessary information for your dialog box. Provide a return value that is appropriate for the type of data you are providing.
  3. Add code to the Get portion of the property definition. If you want to prevent a user from changing the dialog box information outside of the dialog box, remove the Set portion of the property definition.

    The following code demonstrates how to expose the value of a combo box through a property defined in a dialog box:

    [Visual Basic]
Public Property Get StateSelected () as String
   StateSelected = cmbState.Text
End Sub
[C#]
public string StateSelected {
   get{ return comboBox1.Text; }
}

Once you have exposed properties for all of the data you want to provide, you can retrieve the data from the application that uses the dialog box.

To retrieve data from the properties of a dialog box

  • In the form that is displaying the dialog box, open the event- handling method or method that you used to display the dialog box and determine its DialogResult. Add code to collect the properties of the dialog box's form as demonstrated in the following example:
    [Visual Basic]
Public Sub ShowMyDialog()
   'Create and display an instance of the dialog box
   Dim Dlg as MyDialogBox = new MyDialogBox
   Dlg.ShowDialog

   'Determine the state of the DialogResult property for the form.
   If Dlg.DialogResult = DialogResult.OK Then
      'Display the state that was selected in the dialog box's combo       'box in a MessageBox.
      MessageBox.show Dlg.StateSelected
   End If
End Sub
[C#]
private void ShowMyDialog() {
   // Create and display an instance of the dialog box
   DialogBox dlg = new DialogBox();
   dlg.ShowDialog();
   // Determine the state of the DialogResult property for the form.
   if (dlg.DialogResult == DialogResult.OK) {
      // Display the state that was selected in the dialog box's combo       // box in a MessageBox.
      MessageBox.Show (dlg.StateSelected);
   }
}

See Also

Dialog Boxes | How to Handle User Input to Dialog Boxes | Creating Dialog Boxes | Displaying Dialog Boxes for Win Forms | Closing Dialog Boxes and Retaining User Input | Retrieving the Result for Dialog Boxes | Retrieving Dialog Box Information Collectively Using Objects | Retrieving Information from the Parent Form of a Dialog Box


Retrieving Dialog Box Information Collectively Using Objects

Most dialog boxes have properties that expose information provided within the dialog box. You might need to expose some or all of the information, depending on the amount of it the dialog box needs to convey to its parent form.

You can expose a group of related data from a dialog box by creating a class object that stores all of a dialog box's information. With this object created and defined, you can return all of the dialog box's data through a single property.

Before you can retrieve dialog box information, you need to create the class object that will disclose the dialog box information and expose it using a property in your dialog box class.

To provide dialog box data using an object

  1. Add a Class file to your project.
  2. Add properties to the class.
    Note The properties allow you to set and retrieve values that are appropriate for the data returned.
  3. In the dialog box form, create the property that you will use to expose the data. Use the class you created as the data type for the property.
  4. In the property, add code to create an instance of the class you will use to provide the dialog box's data. Add code to set the class instance properties with values from your dialog box, and assign the object to the property.

    The following code demonstrates an example of how to define code for a dialog box's property that exposes geographical coordinate information:

    [Visual Basic]
Public Property Get MapCoordinates() as MapCoordinates
'Create an instance of your object. In this case, create a map coodinates
' object that stores map latitude and longitude coordinates.
   Dim mc as MapCoordinates = new MapCoordinates
   mc.Latitude = Me.Latitude
   mc.Longitude = Me.Longitude
   MapCoordinates = mc
End Sub
[C#]
public MapCoordinates MapCoordinates {
   get {
      // Create an instance of your object. In this case, create a map coodinates
      // object that stores map latitude and longitude coordinates.
      MapCoordinates mc = new MapCoordinates();
      mc.Latitude = this.Latitude;
      mc.Longitude = this.Longitude;
      return mc;
   }
}

You can retrieve the dialog box data through the parent form after you have provided a property and an object to expose through that property.

To retrieve dialog box data using an object

  1. In the method that displays the dialog box, add code to create an empty instance of the object you created to expose the dialog box data.
  2. Add code to assign the value of the property that contains the dialog box information to your empty instance of the dialog box data object. Add this code after the code that determines the result of the dialog box.
  3. Use the data provided by the object in your application.

    The following code demonstrates accessing the data from the MapCoordinates object:

    [Visual Basic]
Public Sub DisplayMapDialogBox()
   'Create and display an instance of the dialog box
   Dim dlg as MapDialog = new MapDialog
   Dlg.ShowDialog
   'Determine the state of the DialogResult property for the form.
   If dlg.DialogResult = DialogResult.OK Then
      'Create an instance of the MapCoordinates object to store the
      'dialog data.
      Dim mc as MapCoordinates
      mc = dlg.MapCoordinates
      'Display the values returned from the dialog box's return
      'object.
      MessageBox.show mc.Latitude
      MessageBox.show mc.Longitude
   End If
End Sub
[C#]
public void DisplayMapDialogBox() {
   // Create and display an instance of the dialog box
   MapDialog dlg = new MapDialog();
   dlg.ShowDialog();
   // Determine the state of the DialogResult property for the form.
   if (dlg.DialogResult == DialogResult.OK) {
      // Create an instance of the MapCoordinates object to store the
      // dialog data.
      MapCoordinates mc = dlg.MapCoordinates;
      // Display the values returned from the dialog box's return
      // object.
      MessageBox.Show (mc.Latitude);
      MessageBox.show (mc.Longitude);
   }
}

See Also

Dialog Boxes | How to Handle User Input to Dialog Boxes | Creating Dialog Boxes | Displaying Dialog Boxes for Win Forms | Closing Dialog Boxes and Retaining User Input | Retrieving the Result for Dialog Boxes | Retrieving Dialog Box Information Selectively Using Multiple Properties | Retrieving Dialog Box Information Collectively Using Objects | Retrieving Information from the Parent Form of a Dialog Box


Retrieving Information from the Parent Form of a Dialog Box

Depending on what you need to do with your dialog box, you might want to have access to information provided by the dialog box's parent form. This information might be required for initialization of the dialog box, or involve specific details about the application state of the parent form.

To access public data from a parent form

  1. In your code editor, open the dialog box class.
  2. At the appropriate location within your code, use the ParentForm property of the dialog box to access the public members of the parent form.

    The following code demonstrates using the ParentForm property to access a method (in this example, GetMyParentFormData) on the parent form:

    [Visual Basic]
Public Sub GetParentData()
   Dim x as String
   x = Me.ParentForm.GetMyParentFormData
End Sub
[C#]
public void GetParentData(){
   string x = ((Form1)this.ParentForm).GetMyParentFormData();
}

See Also

Dialog Boxes | ParentForm Property | Creating Dialog Boxes | Displaying Dialog Boxes for Win Forms | Closing Dialog Boxes and Retaining User Input | Retrieving the Result for Dialog Boxes | Retrieving Dialog Box Information Selectively Using Multiple Properties | Retrieving Dialog Box Information Collectively Using Objects


Displaying Message Boxes

A message box is a predefined dialog box that displays application-related information to the user. Message boxes are also used to request information from the user.

To display information to the user in a message box

  1. Open your code editor for your class and navigate to where you would like to add the code for the message box.
  2. Add code using the MessageBox.Show method.

    The following code demonstrates how to call the Show method of the MessageBox class to display information to the user. The call to the Show method uses the optional Style parameter to specify the type of icon to display in the message box that best fits the type of message box being displayed:

    [Visual Basic]
Public Sub PerformCalculations()
   'Code is entered here that performs a calculation
   'Display a message box informing the user that the calculations
   ' are complete
   MessageBox.Show "The calculations are complete", "My Application" _
      , MessageBox.IconInformation
End Sub
[C#]
public void PerformCalculations() {
   // Code is entered here that performs a calculation
   // Display a message box informing the user that the calculations
   // are complete
   MessageBox.Show ("The calculations are complete", "My Application",
      MessageBox.IconInformation);
}

Message boxes can also receive input. The Show method of the MessageBox class returns a value that can be used to determine a choice made by the user. You can store this value in an Integer variable type or compare the value returned when you display the message box using an If statement. The Style parameter of the Show method can be set to display the proper buttons to ask a user for information.

To display a message box to request information

  1. Open your code editor for your class and navigate to where you would like to add the code for the message box.
  2. Add code that uses the Show method of the MessageBox class to display a message box.

    The following code demonstrates how to call the MessageBox method to retrieve information from the user and then determine the value that was selected:

    [Visual Basic]
Public Sub ExitApplication()
   'Display a message box asking users if they want to exit the
   'application.
   If MessageBox.Show ("Do you want to exit?", "My Application", _
         MessageBox.YesNo + MessageBox.IconQuestion) _
         = DialogResult.Yes Then
      Application.Exit
   End If
End Sub
[C#]
public void ExitApplication(){
   // Display a message box asking users if they want to exit the
   // application.
   if (MessageBox.Show ("Do you want to exit?", "My Application",
         MessageBox.YesNo + MessageBox.IconQuestion)
         == DialogResult.Yes) {
      Application.Exit();
   }
}

See Also

Dialog Boxes | MessageBox Class | DialogResult Class | Creating Dialog Boxes | Displaying Dialog Boxes for Win Forms


Button Control

The Win Forms Button control allows the user to click on it to perform actions. When the button is clicked, it not only carries out the appropriate action, it also looks as if it’s being pushed in and released. Whenever the user clicks a button, the Click event-handling method is invoked. You place code in the Click event-handling method to perform any action you choose.

The text displayed on the button is contained in the Text property. If your text exceeds the width of the button, it will wrap to the next line. However, it will be clipped if the control cannot accommodate its overall height. For more information, see Setting the Text Displayed by a Control. An access-key character in button text is designated with an ampersand (&) in front of the character. See Creating Access Keys for Controls for more information. The appearance of the text is controlled by the Font property and the TextAlign property.

The Button control can also display images using the Image and ImageList properties. For more information, see Setting the Image Displayed by a Control.

See Also

Button Class | Responding to Button Clicks | Selecting a Button Control Programmatically


Responding to Button Clicks

The most basic use of a Win Forms Button control is to run some code when the button is clicked. The code can be as simple or as complex as needed.

To respond to a button click

  • In the Button control's Click event write the code to run. Example:
    [Visual Basic]
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
   MessageBox.Show "Button1 was clicked"
End Sub
[C#]

Clicking a Button control also generates a number of other events, such as the MouseEnter, MouseDown, and MouseUp events. If you intend to attach event procedures for these related events, be sure that their actions don't conflict.

If the user attempts to double-click the Button control, each click will be processed separately; that is, the control does not support the double-click event.

See Also

Button Class | Button Control | Selecting a Button Control Programmatically


Selecting a Button Control Programmatically

A Win Forms button can be selected programmatically in the following ways:

  • Use a mouse to click the button.
  • Invoke the button’s Click event in code.
  • Move the focus to the button by pressing the TAB key, and then choose the button by pressing the SPACEBAR or ENTER.
  • Press the access key shortcut (ALT + the underlined letter) for the button. For more information on access keys, see Creating Access Keys for Controls.
  • If the button is the “accept” button of the form, pressing ENTER chooses the button, even if another control has the focus. For more information, see Designating a Button As the Accept Button.
  • If the button is the “cancel” button of the form, then pressing ESC chooses the button, even if another control has the focus. For more information, see Designating a Button As the Cancel Button.

See Also

Button Class | Button Control | Responding to Button Clicks


Designating a Button As the Accept Button

On any Win Form you can designate a Button control to be the accept button (also known as the default button). Whenever the user presses the ENTER key, the default button is clicked regardless of which other control on the form has the focus. (The exception to this is when the control with focus is another button — in that case, the button with the focus will be clicked.)

You can programmatically set a button as the default button.

To designate the accept button programmatically

  • Set the form’s AcceptButton property to the appropriate Button control. Example:
    [Visual Basic]
Private Sub SetDefault(myDefaultBtn As Button)
  Set Me.AcceptButton = myDefaultBtn
End Sub
[C#]

See Also

Button Control | Selecting a Button Control Programmatically | Responding to Button Clicks | Accept Member | Designating a Button As the Cancel Button | AcceptButton Property


Designating a Button As the Cancel Button

On any Win Form you can designate a Button control to be the cancel button. A cancel button is clicked whenever the user presses the ESC key, regardless of which other control on the form has the focus. Such a button is usually programmed to allow the user to quickly exit an operation without committing to any action.

You can programmatically set a button as the cancel button.

To designate the cancel button programmatically

  • Set the form’s CancelButton property to the appropriate Button control. Example:
    [Visual Basic]
Private Sub SetCancelButton(myCancelBtn As Button)
  Set Me.CancelButton = myCancelBtn
End Sub
[C#]
[MC++]

See Also

Button Control | Selecting a Button Control Programmatically | Responding to Button Clicks | CancelButton Property | Designating a Button As the Accept Button


Label Control

Win Forms Label controls are used to display text and cannot be edited by the user. They are used to identify objects on a form – provide a description of what a certain control will do if clicked, for example – or at run time, they can display information in response to an event or process in your application. For example, you can use labels to add descriptive captions to text boxes, list boxes, combo boxes and so on. You can also write code that changes the text displayed by a label in response to events at run time. For example, if your application takes a few minutes to process a change, you can display a processing-status message in a label.

Because the Label control cannot receive the focus, it can also be used to create access keys for other controls.

The caption displayed in the label is contained in the Text property. The Alignment property allows you to set the alignment of the text within the label control to either Left Justify (0, the default), Center (1), or Right Justify (2). For more information, see Setting the Text Displayed by a Control.

See Also

Label Class | Sizing a Label Control to Fit Its Contents | Using Labels to Create Access Keys


Sizing a Label Control to Fit Its Contents

For Win Forms Label controls, single-line captions can be specified. But what if you want to have a longer caption, or a caption that will change at run time? The AutoSize property helps you size the controls to fit larger or smaller captions.

To make a label control resize dynamically to fit its contents

  • Set its Autosize property to true.

If AutoSize is set to false, the words specified in the Text property will wrap to the next line if possible, but the control will not grow.

See Also

Label Class | Using Labels to Create Access Keys | Label Control


Creating Access Keys with Label Controls

Win Forms Label controls can be used to define keyboard shortcuts for other controls. When you define an access key in a label control, the user can press the ALT key plus the character you designate to move the focus to the control that follows it in the tab order. Because labels can't receive focus, focus automatically moves to the next control in the tab order. Use this technique to assign access keys to text boxes, picture boxes, combo boxes, list boxes, and data grids.

To assign an access key to a control with a label

  1. Add the controls in any order to your class and set the TabIndex property of the label to one less than the control you want to assign an access key to.
  2. Set the label’s UseMnemonic property to true.
  3. Use an ampersand in the label's Text property to assign the access key for the label. For more information, see Creating Keyboard Shortcuts for Controls.
    Note You may want to display ampersands in a label control, rather than use them to create access keys. This may occur if you bind a label control to a field in a recordset where the data includes ampersands. To display ampersands in a label control, set the UseMnemonic property to false.

See Also

Label Class | Sizing a Label Control to Fit Its Contents | Label Control


TextBox Control

Win Forms text boxes are used to get input from the user or to display text. The TextBox control is generally used for editable text, although it can also be made read-only. Text boxes can display multiple lines, wrap text to the size of the control, and add basic formatting such as quotation marks and placeholder characters for passwords.

The text displayed by the control is contained in the Text property. By default, you can enter up to 2048 characters in a text box. If you set the MultiLine property to True, you can enter up to 32K of text. The Text property can be set at run time in code, or by user input at run time. The current contents of a text box can be retrieved at run time by reading the Text property.

The code below sets text in the control at run time. The InitializeMyControl procedure will not execute automatically: it must be called.

Private Sub InititalizeMyControl()
   ' Put some text into the control first.
   TextBox1.Text = "This is a TextBox control."
End Sub

See Also

Controlling the Insertion Point in a TextBox Control | Creating a Password Text Box with the TextBox Control | Creating a Read-Only Text Box | Putting Quotation Marks in a String Programmatically | Selecting Text Programmatically in the TextBox Control | Viewing Multiple Lines in the TextBox Control


Viewing Multiple Lines in the TextBox Control

By default, the Win Forms TextBox control displays a single line of text and does not display scroll bars. If the text is longer than the available space, only part of the text is visible.

To view multiple lines in the TextBox control

  1. Set the MultiLine property to True. If WordWrap is True (the default), then the text in the control will appear as one or more paragraphs; otherwise it will appear as a list, in which some lines may be clipped at the edge of the control.
  2. Set the ScrollBars property to an appropriate value.
    • Use the default value, None, if the text will be a paragraph that almost always fits the control. The user can use the cursor to move around inside the control if the text is too long to display all at once.
    • If you want to display a list of lines, some of which may be longer than the width of the TextBox control, choose Horizontal. If the list may be longer than the height of the control, choose Both.
    • If you want to display one or more paragraphs of text, choose Vertical.
  3. Set the WordWrap property to an appropriate value.
    • If you chose Horizontal scroll bars or Both, in order to display a list of lines, set WordWrap to false. Text in the control will not automatically be wrapped, so it will scroll to the right until a line break is reached.
    • If you chose Vertical scroll bars or None, to display one or more paragraphs, set WordWrap to true (the default). When WordWrap is set to True, the horizontal scrollbar will not appear.

See Also

Controlling the Insertion Point in a TextBox Control | Creating a Password Text Box with the TextBox Control | Creating a Read-Only Text Box | Putting Quotation Marks in a String Programmatically | Selecting Text Programmatically in the TextBox Control | TextBox Control | Viewing Multiple Lines in the TextBox Control


Controlling the Insertion Point in a TextBox Control

When a Win Forms TextBox control first receives the focus, the default insertion within the text box is to the left of any existing text. The user can move it with the keyboard or the mouse. If the text box loses and then regains the focus, the insertion point will be wherever the user last placed it.

In some cases, this behavior can be disconcerting to the user. In a word processing application, the user might expect new characters to appear after any existing text. In a data entry application, the user might expect new characters to replace any existing entry. The SelectionStart and SelectionLength properties allow you to modify the behavior to suit your purpose.

To control the insertion point in a TextBox control

  1. Set the SelectionStart property to an appropriate value. Zero places the insertion point immediately to the left of the first character.
  2. (Optional) Set the SelectionLength property to the length of the text you want to select.

The code below always returns the insertion point to 0. The TextBox1_GotFocus event handler must be bound to the control; see Creating Event Handlers at Run Time for Win Forms for more information.

[Visual Basic]
Private Sub TextBox1_GotFocus(ByVal sender As Object, ByVal e As EventArgs)
   TextBox1.SelectionStart = 0
   TextBox1.SelectionLength = 0
End Sub
[C#]

See Also

Creating a Password Text Box with the TextBox Control | Creating a Read-Only Text Box | Putting Quotation Marks in a String Programmatically | Selecting Text Programmatically in the TextBox Control | Viewing Multiple Lines in the TextBox Control | TextBox Control


Creating a Password Text Box with the TextBox Control

A password box is a Win Forms text box that displays placeholder characters while a user types a string.

To create a password text box

  1. Set the PasswordChar property of the TextBox control to a specific character.

    The PasswordChar property specifies the character displayed in the text box. For example, if you want asterisks displayed in the password box, specify * for the PasswordChar property. Then, regardless of what character a user types in the text box, an asterisk is displayed.

  2. Set the MaxLength property. The property determines how many characters can be typed in the text box. If the maximum length is exceeded, the system emits a beep and the text box does not accept any more characters.

The code below initializes a text box that will accept a string up to 14 characters long and display asterisks in place of the string. The InitializeMyControl procedure will not execute automatically: it must be called.

[Visual Basic]
Private Sub InitializeMyControl()
   With TextBox1
      .Text = ""   ' Set to no text.
      .PasswordChar = "*"   ' asterisk
      .MaxLength = 14   ' After 14, the control beeps.
   End With
End Sub

[C#]

See Also

Controlling the Insertion Point in a TextBox Control | Creating a Read-Only Text Box | Putting Quotation Marks in a String Programmatically | Selecting Text Programmatically in the TextBox Control | Viewing Multiple Lines in the TextBox Control| TextBox Control


Creating a Read-Only Text Box

You can transform an editable Win Forms text box into a read-only control. For example, if the text box is bound to a primary key field in a database, you may want to prevent users from editing the field.

To create a read-only text box

  • Set the TextBox control's ReadOnly property to True. With the property set to True, users can still scroll and highlight text in a text box without allowing changes. A Copy command is functional in a text box, but Cut and Paste commands are not.
    Note The ReadOnly property only affects user interaction at run time. You can still change text box contents programmatically at run time by changing the Text property of the text box.

See Also

Controlling the Insertion Point in a TextBox Control | Creating a Password Text Box with the TextBox Control | Putting Quotation Marks in a String Programmatically | Selecting Text Programmatically in the TextBox Control | Viewing Multiple Lines in the TextBox Control| TextBox Control


Putting Quotation Marks in a String Programmatically

Sometimes quotation marks (" ") appear in a string of text. For example:

She said, "You deserve a treat!"

To place quotation marks in a string in your code

  • Insert an additional set of quotation marks for each set to display in a string. Two quotation marks in a row are interpreted as an embedded quotation mark. For example, to create the preceding string, use the following code:
    [Visual Basic]
Private Sub InsertQuote()
   TextBox1.Text = "She said, ""You deserve a treat!"" "
End Sub
[C#]

    To achieve the same effect, you can use the ASCII character (34) for a quotation mark:

    [Visual Basic]
Private Sub InsertAscii()
   TextBox1.Text = "She said, " & Chr(34) & "You deserve a treat!" & Chr(34)
End Sub
[C#]

    You can also define a constant for the character, and use it where needed:

    [Visual Basic]
Const quote = """"

Private Sub QuoteByConstant()
   TextBox1.Text = "She said, " & quote & "You deserve a treat!" & quote
End Sub

[C#]

See Also

Controlling the Insertion Point in a TextBox Control | Creating a Password Text Box with the TextBox Control | Creating a Read-Only Text Box | Selecting Text Programmatically in the TextBox Control | Viewing Multiple Lines in the TextBox Control| TextBox Control


CheckBox Control

The Win Forms CheckBox control indicates whether a particular condition is on or off. It is commonly used to present a Yes/No or True/False selection to the user. You can use check box controls in groups to display multiple choices from which the user can select one or more.

The check box control is similar to the radio button control in that each is used to indicate a selection that is made by the user. They differ in that only one radio button in a group can be selected at a time. With the check box control, however, any number of check boxes may be selected.

A check box may be connected to elements in a database using simple data binding. Multiple check boxes may be grouped using the GroupBox control. This is useful for visual appearance.

The CheckBox control has two important properties, Checked and CheckState. The Checked property returns either True or False. The CheckState property returns either CheckState.Checked or CheckState.Unchecked; or, if the ThreeState property is set to True, CheckState may also return CheckState.Indeterminate.

See Also

Setting Options with CheckBox Controls | Responding to CheckBox Clicks


Setting Options with CheckBox Controls

A Win Forms check box is used to give users True/False or Yes/No options. The control displays a check mark when it is selected.

To set options with CheckBox controls

  • Examine the value of the Checked property to determine its state, and use that value to set an option.
    [Visual Basic]
Private Sub SetOptions()
   ' Create a new object and set its properties according
   ' to the Checked value of two CheckBox controls.
   Dim MyObj As New myObject
   ' Presuming the object has properties named "Property1" and
   ' "Property2."
   MyObj.Property1 = CheckBox1.Checked
   MyObj.Property2 = CheckBox2.Checked
End Sub
[C#]
[MC++]

See Also

CheckBox Control | Responding to Checkbox Clicks


Responding to CheckBox Clicks

Whenever a user clicks a Win Forms CheckBox control, the Click event occurs. You can program your application to perform some action depending upon the state of the check box.

To respond to CheckBox clicks

  • In the Click event-handling method, use the Checked property to determine the control's state, and perform any necessary action.
    [Visual Basic]
Private Sub Check1_Click()
   ' The CheckBox control's Text property changes each time the
   ' control is clicked, indicating a checked or unchecked state.
   If CheckBox1.Checked = True Then
      CheckBox1.Text = "Checked"
   Else
      CheckBox1.Text = "Unchecked"
   End If
End Sub
[C#]
[MC++]
Note If the user attempts to double-click the check box control, each click will be processed separately; that is, the check box control does not support the double-click event.
Note When the AutoCheck property is True (the default), the CheckBox is automatically checked or unchecked when it is clicked. Otherwise, you must manually set the Checked property when the Click event occurs.

You can also use the CheckBox control to determine a course of action.

To determine a course of action

  • In a Select Case statement, query the value of the CheckState property to determine a course of action. When the ThreeState property is set to True, the CheckState property may return three possible values, which represent the box being checked, the box being unchecked, and a third indeterminate state in which the box may be checked but displayed in gray.
    [Visual Basic]
Private Sub CheckBox1_Click(ByVal sender As System.Object, ByVal e As SystemEventArgs)
  Select Case CheckBox1.CheckState
  Case CheckState.Checked
    ' Code for checked state.
  Case CheckState.Unchecked
    ' Code for unchecked state.
  Case CheckState.Indeterminate
    ' Code for indeterminate state.
  End Select
End Sub
[C#]
[MC++]
Note When the ThreeState property is set to True, the Checked property returns True for both CheckState.Checked and CheckState.Indeterminate.

See Also

CheckBox Control | Setting Options with CheckBox Controls


GroupBox Control

Win Forms GroupBox controls are used to provide an identifiable grouping for other controls. Typically, you use group boxes to subdivide a form by function. For example, you may have an order form that specifies mailing options such as which overnight carrier to use. Grouping all options in a group box gives the user a logical visual cue; and at design time all the controls can be moved easily — move the single GroupBox control, and all its contained controls move too.

The GroupBox control is similar to the Panel control; however, only the GroupBox control displays a caption, and only the Panel control may have scroll bars.

The group box’s caption is defined by the Text property. For more information, see Setting the Text Displayed by a Control.

See Also

Grouping Controls with the GroupBox Control | Panel Control | GroupBox Class


Grouping Controls with the GroupBox Control

Win Forms GroupBox controls are used to group other controls. There are three reasons to group controls. One is visual grouping of related form elements for a clear user interface; another is programmatic grouping, for example of radio buttons; the last is for moving the controls as a unit at design time.

To create a group of controls at run time

  1. Add a GroupBox control on a form.
  2. Add other controls to the GroupBox using the Add method of the GroupBox control.
  3. Set the Text property of the GroupBox to an appropriate caption.

See Also

GroupBox Control | GroupBox Class


RadioButton Control

Win Forms RadioButton controls present a set of two or more mutually exclusive choices to the user. While radio buttons and check boxes may appear to function similarly, there is an important difference: when a user selects a radio button, the other radio buttons in the same group cannot be selected as well. In contrast, any number of check boxes can be selected. Defining a radio button group tells the user, “Here is a set of choices from which you can choose one and only one.”

When a RadioButton control is clicked, its Checked property is set to true and the Click event-handling method is called. If the AutoCheck property is set to true (the default), when the radio button is clicked all others in the group are automatically deselected. This property is usually only set to false when validation code is used to make sure the radio button selected is an allowable option. The text displayed within the control is set with the Text property, which can contain access key shortcuts. For more information, see Creating Keyboard Shortcuts for Controls and Setting the Text Displayed by a Control.

The RadioButton control can appear like a toggle button if the Appearance property is set to Appearance.Button. Radio buttons can also display images using the Image and ImageList properties. For more information, see Setting the Image Displayed by a Control.

See Also

Panel Control | GroupBox Control | CheckBox Control | Creating Keyboard Shortcuts for Controls | Setting the Text Displayed by a Control | Grouping RadioButton Controls to Function As a Set


Grouping RadioButton Controls to Function As a Set

Win Forms RadioButton controls are designed to give users a choice among two or more settings, of which only one can be assigned to a procedure or object. For example, a group of RadioButton controls may display a choice of package carriers for an order, but only one of the carriers will be used. Therefore only one RadioButton at a time can be selected, even if it's a part of a functional group.

You group radio buttons by drawing them inside a container such as a Panel control, a GroupBox control, or a form. All radio buttons that are added directly to a form become one group. To add additional groups, you need to place them inside panels or group boxes. For more information on panels or group boxes, see Panel Control and GroupBox Control.

To group RadioButton controls to function as a set

  1. Add a GroupBox or Panel control to the form.
  2. Use the Add method of the GroupBox or Panel control to add RadioButton control to the GroupBox or Panel control.
  3. Position the RadioButton controls within the GroupBox or Panel as needed.

See Also

RadioButton Control | Panel Control| GroupBox Control | CheckBox Control


PictureBox Control

The Win Forms PictureBox control is used to display graphics.

The picture that is displayed is determined by the Image property, which can be set at run time or at design time. The SizeMode property controls how the image and control fit with each other.

See Also

Loading a Picture at Design Time | Modifying the Size or Placement of a Picture at Run Time | Setting Pictures at Run Time


Setting Pictures at Run Time

You can set the image displayed by a Win Forms PictureBox control at run time.

To set a picture at run time

  • Set the Image property using the LoadImage method of the Image class, as shown below:
    [Visual Basic]
Private Sub LoadNewPict()
   ' Change the path to a valid bitmap if needed:
   Dim path As String = "C:\Windows\WINLOGO.GIF"
   Set PictureBox1.Image = Image.LoadImage(path)
End Sub
[C#]

Icon (.ico) files contain separate images at different sizes and color depths to support a range of display devices. The LoadImage method's settings allow you to select images with specific color depths and sizes from an .ico file. In cases where an exact match to the requested settings isn't available, LoadImage loads the image with the closest match available.

To clear a graphic

  • Set the Image property without specifying a file name. For example:
    [Visual Basic]
Set Image1.Image = Nothing
[C#]

See Also

PictureBox Control | Loading a Picture at Design Time | Modifying the Size or Placement of a Picture at Run Time


Modifying the Size or Placement of a Picture at Run Time

If you use the Win Forms PictureBox control on a form, you can set the SizeMode property on it to:

  • Adjust the size of the control to fit the picture it displays
  • Center the picture within the control
  • Stretch any picture it displays to fit the control

Stretching a picture (especially one in bitmap format) can produce a loss in image quality. Metafiles, which are lists of graphics instructions for drawing images at run time, are better suited for stretching than bitmaps are.

To set the SizeMode property at run time

  • Set PictureBox.SizeMode to AutoSize, CenterImage, or StretchImage.
    [Visual Basic]
Private Sub StretchPic()
   ' Change the path to a valid bitmap if needed:
   Dim path As String = "C:\Windows\Waves.bmp"
   ' Stretch the picture to fit the control.
   PictureBox1.SizeMode = PictureBoxSizeMode.StretchImage
   ' Load the picture into the control.
   Set PictureBox1.Image = Image.LoadImage(path)
End Sub
[C#]

See Also

Loading a Picture at Design Time | PictureBox Control | Setting Pictures at Run Time


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Creating Service Applications

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Accessing Data

[To be supplied.]


Accessing Data with ASP+

Accessing databases from ASP+ applications is an often-used technique for displaying data to Web site visitors. ASP+ makes it easier than ever to access databases for this purpose and provides support for managing the data in the database.

For performance reasons, it is highly recommended that only SQL databases be used for Web server applications. Therefore, accessing SQL databases is the primary focus of this section.

The general procedure for accessing databases from ASP+ is as follows:

  1. Connect to the database using the SQLDataSetCommand.
  2. Fill a new DataSet using SQLDataSetCommand.FillDataSet.
  3. Set up a new DataView for the desired table.
  4. Bind a server control such as DataGrid to the DataView.

Of course, to implement such a procedure, there are many details that must be accomplished as well. The subsections that follow present examples showing these details. The discussion in these sections parallels the samples contained in the ASP+ Quick Start program shipped with the NGWS frameworks and runtime.


Binding SQL Data to a DataGrid Object Using a Select * Query

The NGWS frameworks and runtime has a DataGrid class that makes the display of large databases an easier task. This section describes how to access a SQL database and bind that data to a DataGrid object. It uses a "select *" SQL query to get all rows and columns of data from the database. Sections that follow this one will then show how to manage that data.

To access a SQL database

  1. Import the proper namespaces into your page. This provides your code with access to the necessary classes.
    <%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SQL" %>
  2. Within the <script language="C#" runat="server"> tag, implement a Page_Load function that contains a connection to the database, creates and fills a new DataSet with the database information, and then binds the DataGrid object to the DataSet. This is shown in the following example code.
    <script language="C#" runat="server">
    public DataView Source;
    protected void Page_Load(Object Src, EventArgs E )
    {
    1. Set up a connection to the "pubs" database located on the local computer.
              SQLConnection myConnection = new
           SQLConnection("server=localhost;uid=sa;pwd=;database=pubs");
    2. Connect to the SQL database using a "select *" query to get all the data from the "Authors" table.
              SQLDataSetCommand myCommand = new SQLDataSetCommand("select *
                                      from Authors", myConnection);
    3. Create and fill a Dataset.
              DataSet ds = new DataSet();
        myCommand.FillDataSet(ds, "Authors");
    4. Set up a DataView, and bind MyDataGrid to it.
              Source = new DataView(ds.Tables[0]);
        MyDataGrid.DataSource=Source ;
        MyDataGrid.DataBind();
    }
</script>
  3. In the <body> of the page, display the DataGrid information.
    <body>
    1. Show a page title.
        <h3><font face="Verdana">Simple Select to a DataGrid
                           Control</font></h3>
    2. Display the data.
        <ASP:DataGrid id="MyDataGrid" runat="server"
    Width="700"
    BackColor="#ccccff"
    BorderColor="black"
    ShowFooter="false"
    CellPadding=3
    CellSpacing="0"
    Font-Name="Verdana"
    Font-Size="8pt"
    HeaderStyle-BackColor="#aaaadd"
    MaintainState="false"
  />
</body>

To see this example run, go to the ASP+ Quick Start and run sample DataGrid1.aspx.


Inserting Data Into a SQL Database

Once connected and bound to a database, you might want to add data rows to that database. This section shows how to add new rows of data to a database using a custom form for the input.

To add new rows of data to a database

  1. Import the necessary namespaces.
    <%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SQL" %>
<%@ Import Namespace="System.Text"%>

    Within the <script language="C#" runat="server"> tag, this example implements three functions: Page_Load, AddAuthor_Click, and BindGrid. These functions are described in more detail in the steps that follow.

    <script language="C#" runat="server">
    SQLConnection myConnection;
  2. Implement a Page_Load function that sets up the connection information for a connection to the "pubs" database. It also checks to see if this page is a PostBack, and, if not, calls a custom BindGrid function. For a description of BindGrid, see step 4.
        protected void Page_Load(Object Src, EventArgs E )
    {
        myConnection = new SQLConnection
                     ("server=localhost;uid=sa;pwd=;database=pubs");
        if (!IsPostBack)
            BindGrid();
    }
  3. Implement an AddAuthor_Click function. This function does some data validation on the input form, and then builds a string containing all the fields of the input form. Next, it adds this string to the database and tests (using the try command) to see if the data was added. Finally, it rebinds the DataGrid to show the new data.
    public void AddAuthor_Click(Object sender, EventArgs E)
    {
    1. Check that four of the input values are not empty. If any of them are empty show a message to the user and rebind the DataGrid.
              if (au_id.Value == "" || au_fname.Value == "" ||
                      au_lname.Value == "" || phone.Value == "")
        {
          Message.InnerHtml = "ERROR: Null values not allowed for
                               Author ID, Name or Phone";
          Message.Style["color"] = "red";
          BindGrid();
          return;
        }
    2. Build a comma-delimited string of all the input form field values. Creating new parameters for the SQLCommand object and initializing them to the input form field values does this. The SQLCommand is also initialized with the new string and the connection information.
              String insertCmd = "insert into Authors values
                   (@Id, @LName, @FName, @Phone, @Address, @City,
                    @State, @Zip, @Contract)";
        SQLCommand myCommand = new SQLCommand(insertCmd,
                                                    myConnection);
        myCommand.Parameters.Add(new SQLParameter("@Id",
                                        SQLDataType.VarChar, 11));
        myCommand.Parameters["@Id"].Value = au_id.Value;
        myCommand.Parameters.Add(new SQLParameter("@LName",
                                        SQLDataType.VarChar, 40));
        myCommand.Parameters["@LName"].Value = au_lname.Value;
        myCommand.Parameters.Add(new SQLParameter("@FName",
                                        SQLDataType.VarChar, 20));
        myCommand.Parameters["@FName"].Value = au_fname.Value;
        myCommand.Parameters.Add(new SQLParameter("@Phone",
                                           SQLDataType.Char, 12));
        myCommand.Parameters["@Phone"].Value = phone.Value;
        myCommand.Parameters.Add(new SQLParameter("@Address",
                                        SQLDataType.VarChar, 40));
        myCommand.Parameters["@Address"].Value = address.Value;
        myCommand.Parameters.Add(new SQLParameter("@City",
                                        SQLDataType.VarChar, 20));
        myCommand.Parameters["@City"].Value = city.Value;
        myCommand.Parameters.Add(new SQLParameter("@State",
                                            SQLDataType.Char, 2));
        myCommand.Parameters["@State"].Value = state.Value;
        myCommand.Parameters.Add(new SQLParameter("@Zip",
                                            SQLDataType.Char, 5));
        myCommand.Parameters["@Zip"].Value = zip.Value;
        myCommand.Parameters.Add(new SQLParameter("@Contract",
                                          SQLDataType.VarChar,1));
        myCommand.Parameters["@Contract"].Value = contract.Value;
    3. Connect to the database and insert the string (row) that was just built.
              SQLConnection myConnection = new SQLConnection
                 ("server=localhost;uid=sa;pwd=;database=pubs");
        SQLCommand myCommand = new SQLCommand(insertCmd.ToString(),
                               myConnection);
    4. Connect to the database. This updates the database with the new row (string).
              myCommand.ActiveConnection.Open();
    5. Test to see if the new row was added and present the appropriate message box to the user.
              try
        {
            int rowsAffected = 0;
            myCommand.Execute(ref rowsAffected);
            Message.InnerHtml = "<b>Record Added</b><br>" +
                                insertCmd.ToString();
        }
        catch (SQLException e)
        {
            if (e.Number == 2627)
                Message.InnerHtml = "ERROR: A record already exists
                                    with the same primary key";
            else
                Message.InnerHtml = "ERROR: Could not add record,
                                    please ensure the fields are
                                    correctly filled out";
            Message.Style["color"] = "red";
        }
    6. Close the active connection to the database.
              myCommand.ActiveConnection.Close();
    7. Rebind to the DataGrid that contains the new row.
              BindGrid();
    }
  4. Implement a BindGrid function. This function connects to the database and implements the standard "select *" SQL query to get all of the data in the "Authors" table of the database.
    public void BindGrid()
    {
    1. Set up the database connection to the "pubs" database located on the local computer.
              SQLConnection myConnection = new SQLConnection
                  ("server=localhost;uid=sa;pwd=;database=pubs");
    2. Connect to the database with a "select *" query on the "Authors" table.
              SQLDataSetCommand myCommand = new SQLDataSetCommand("select
                                     * from Authors", myConnection);
    3. Create and fill a new DataSet.
              DataSet ds = new DataSet();
        myCommand.FillDataSet(ds, "Authors");
    4. Bind MyDataGrid to the "Authors" table in the DataSet.
              MyDataGrid.DataSource=ds.Tables["Authors"].DefaultView;
        MyDataGrid.DataBind();
    }
</script>
  5. In the <body> section of the page, display the DataGrid, and create and display the input form.
    <body style="font: 10pt verdana">
    1. Open a form.
        <form runat="server">
    <h3><font face="Verdana">Inserting a Row of Data</font></h3>
    2. Create a table.
          <table width="95%">
      <tr>
        <td valign="top">
    3. Put the DataGrid in the first column of the table.
                <ASP:DataGrid id="MyDataGrid" runat="server"
            Width="700"
            BackColor="#ccccff"
            BorderColor="black"
            ShowFooter="false"
            CellPadding=3
            CellSpacing="0"
            Font-Name="Verdana"
            Font-Size="8pt"
            HeaderStyle-BackColor="#aaaadd"
            MaintainState="false"
          />
    4. Create a second table column.
              </td>
        <td valign="top">
    5. Put the input form (a second table) in the second column.
                <table style="font: 8pt verdana">
            <tr>
              <td colspan="2" bgcolor="#aaaadd" style="font:10pt
                                  verdana">Add a New Author:</td>
            </tr>
            <tr>
              <td nowrap>Author ID: </td>
              <td><input type="text" id="au_id" value="000-00-0000"
                                          runat="server"></td>
            </tr>
            <tr>
              <td nowrap>Last Name: </td>
              <td><input type="text" id="au_lname" value="Doe"
                                                runat="server"></td>
            </tr>
            <tr nowrap>
              <td>First Name: </td>
              <td><input type="text" id="au_fname" value="John"
                                                runat="server"></td>
            </tr>
            <tr>
              <td>Phone: </td>
              <td><input type="text" id="phone" value="808 555-5555"
                                                runat="server"></td>
            </tr>
            <tr>
              <td>Address: </td>
              <td><input type="text" id="address" value="One
                                 Microsoft Way" runat="server"></td>
            </tr>
            <tr>
              <td>City: </td>
              <td><input type="text" id="city" value="Redmond"
                                                runat="server"></td>
            </tr>
            <tr>
              <td>State: </td>
              <td>
                <select id="state" runat="server">
                  <option>CA</option>
                  <option>IN</option>
                  <option>KS</option>
                  <option>MD</option>
                  <option>MI</option>
                  <option>OR</option>
                  <option>TN</option>
                  <option>UT</option>
                </select>
              </td>
            </tr>
            <tr>
              <td nowrap>Zip Code: </td>
              <td><input type="text" id="zip" value="98005"
                                                runat="server"></td>
            </tr>
            <tr>
              <td>Contract: </td>
              <td>
                <select id="contract" runat="server">
                  <option value="0">False</option>
                  <option value="1">True</option>
                </select>
              </td>
            </tr>
            <tr>
              <td></td>
              <td style="padding-top:15">
                <input type="submit" OnServerClick="AddAuthor_Click"
                                  value="Add Author" runat="server">
              </td>
            </tr>
            <tr>
              <td colspan="2" style="padding-top:15" align="center">
                <span id="Message" MaintainState="false"
                          style="font: arial 11pt;" runat="server"/>
              </td>
            </tr>
          </table>

        </td>
      </tr>
    </table>

  </form>

</body>

To see this example run, go to the ASP+ Quick Start and run sample DataGrid4.aspx.


Updating Data in a SQL Database

Often it will be desirable to update data fields in an existing database. This section shows how to do this.

In this example, when the page is presented, there is an additional column on the left side of the DataGrid display. It has an "edit" link that, when clicked, causes all the fields in that row to become text boxes showing the existing data. These fields can now be edited to contain the new information. When ready, the user can click the "update" link and the data will be modified in the database.

To update existing data

  1. Import the necessary namespaces.
    <%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SQL" %>
<%@ Import Namespace="System.Text"%>

    Within the <script language="C#" runat="server"> tag, this example implements five functions: Page_Load, MyDataGridEdit, MyDataGridCancel, MyDataGridUpdate, and BindGrid. These functions are described in more detail in the steps that follow.

    <script language="C#" runat="server">
SQLConnection myConnection;
  2. Implement a Page_Load function that sets up the connection information for a connection to the "pubs" database. It also checks to see if this page is a PostBack, and if not, calls a custom BindGrid function. For a description of BindGrid, see step 4 of Inserting Data Into a SQL Database.
  3. Implement the MyDataGrid_Edit function. This function sets up an index to the row in the DataGrid that is clicked, and then calls BindGrid.
    public void MyDataGrid_Edit(Object sender, DataGridCommandEventArgs E)
    {
        MyDataGrid.EditItemIndex = (int)E.Item.ItemIndex;
        BindGrid();
    }
  4. Implement the MyDataGrid_Cancel function. This function resets the index to the row's previous settings, and then calls BindGrid.
    public void MyDataGrid_Cancel(Object sender, DataGridCommandEventArgs E)
    {
        MyDataGrid.EditItemIndex = -1;
        BindGrid();
    }
  5. Implement the MyDataGrid_Update function. When the "update" link is clicked, this function builds a comma-delimited string of the SQL UPDATE command and fieldname/value pairs (fieldname=value,), connects to the database, updates the row's field information in the database, and then rebinds the DataGrid to show the updated information.
    public void MyDataGrid_Update(Object sender,
                                  DataGridCommandEventArgs E)
    {
    1. Set up a SQLCommand using parameters and the connection information that was implemented in step 2.
              String updateCmd = "UPDATE Authors SET au_id = @Id,
                           au_lname = @LName, au_fname = @FName,
                           phone = @Phone, address = @Address,
                           city = @City, state = @State, zip = @Zip,
                           contract = @Contract where au_id = @Id";
        SQLCommand myCommand = new SQLCommand(updateCmd, myConnection);
        myCommand.Parameters.Add(new SQLParameter("@Id",
                                             SQLDataType.VarChar, 11));
        myCommand.Parameters.Add(new SQLParameter("@LName",
                                             SQLDataType.VarChar, 40));
        myCommand.Parameters.Add(new SQLParameter("@FName",
                                             SQLDataType.VarChar, 20));
        myCommand.Parameters.Add(new SQLParameter("@Phone",
                                                SQLDataType.Char, 12));
        myCommand.Parameters.Add(new SQLParameter("@Address",
                                             SQLDataType.VarChar, 40));
        myCommand.Parameters.Add(new SQLParameter("@City",
                                             SQLDataType.VarChar, 20));
        myCommand.Parameters.Add(new SQLParameter("@State",
                                                 SQLDataType.Char, 2));
        myCommand.Parameters.Add(new SQLParameter("@Zip",
                                                 SQLDataType.Char, 5));
        myCommand.Parameters.Add(new SQLParameter("@Contract",
                                               SQLDataType.VarChar,1));
        String[] cols = {"@Id","@LName","@FName","@Phone","@Address",
                        "@City","@State","@Zip","@Contract"};
    2. Initialize the SQLCommand "@ID" parameter to the row ID of the row that must be clicked.
              myCommand.Parameters["@Id"].Value =
                            MyDataGrid.DataKeys[(int)E.Item.ItemIndex];
    3. Create an array of column names.
              String[] cols = {"@Id","@LName","@FName","@Phone","@Address",
                        "@City","@State","@Zip","@Contract"};
    4. Skipping the first, second, and last columns, iterate through the columns, checking for empty values. If an empty value is found, give the user a message box. Also, initialize the SQLCommand parameter values.
              int numCols = E.Item.Cells.Count;
        for (int i=2; i<numCols-1; i++)
        {
            String colvalue =
                           ((TextBox)E.Item.Cells[i].Controls[0]).Text;
            if (i<6 && colvalue == "")
            {
                Message.InnerHtml = "ERROR: Null values not allowed for
                                    Author ID, Name or Phone";
                Message.Style["color"] = "red";
                return;
            }
            myCommand.Parameters[cols[i-1]].Value = colvalue;
        }
    5. Append the last field, converting true/false values to 0/1.
              if (String.Compare(((TextBox)E.Item.Cells
                       [numCols-1].Controls[0]).Text, "true", true)==0)
            myCommand.Parameters["@Contract"].Value =  "1";
        else
            myCommand.Parameters["@Contract"].Value =  "0";
    6. Connect to the database and update the information.
              myCommand.ActiveConnection.Open();
    7. Test to see if the data was updated and present the appropriate message to the user.
              try
        {
            myCommand.Execute(ref rowsAffected);
            Message.InnerHtml = "<b>Record Updated</b><br>" +
                                                  updateCmd.ToString();
            MyDataGrid.EditItemIndex = -1;
        }
        catch (SQLException e)
        {
            if (e.Number == 2627)
                Message.InnerHtml = "ERROR: A record already exists
                                            with the same primary key";
            else
                Message.InnerHtml = "ERROR: Could not update record,
                                     please ensure the fields are
                                     correctly filled out";
            Message.Style["color"] = "red";
        }
    8. Close the connection.
              myCommand.ActiveConnection.Close();
    9. Rebind the DataGrid to show the updated information.
              BindGrid();
    }
  6. Implement a BindGrid function. This function connects to the database and implements the standard "select *" SQL query to get all of the data in "Authors" table of the database. For the details on BindGrid, see step 4 of Inserting Data Into a SQL Database.
  7. In the <body> of the page, display the data.
    <body style="font: 10pt verdana">
  <form runat="server">
    <h3><font face="Verdana">Updating a Row of Data</font></h3>
    <span id="Message" MaintainState="false" style="font: arial 11pt;"
                                                    runat="server"/><p>
    <ASP:DataGrid id="MyDataGrid" runat="server"
      Width="800"
      BackColor="#ccccff"
      BorderColor="black"
      ShowFooter="false"
      CellPadding=3
      CellSpacing="0"
      Font-Name="Verdana"
      Font-Size="8pt"
      HeaderStyle-BackColor="#aaaadd"
      OnEditCommand="MyDataGrid_Edit"
      OnCancelCommand="MyDataGrid_Cancel"
      OnUpdateCommand="MyDataGrid_Update"
      DataKeyField="au_id"
    >
      <property name="Columns">
        <asp:EditCommandColumn EditText="Edit" CancelText="Cancel"
                           UpdateText="Update" ItemStyle-Wrap="false"/>
      </property>
    </ASP:DataGrid>
  </form>
</body>

To see this example run, go to the ASP+ Quick Start and run sample DataGrid6.aspx.


Deleting Data in a SQL Database

In this example, when the page is presented, there is an additional column at the left of the table that has a link for each row. Clicking this link deletes that row from the database.

To delete rows from a database

  1. Import the necessary namespaces.
    <%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SQL" %>

    Within the <script language="C#" runat="server"> tag, this example implements three functions: Page_Load, MyDataGrid_Delete, and BindGrid. These functions are described in more detail in the steps that follow.

    <script language="C#" runat="server">
SQLConnection myConnection;
  2. Implement a Page_Load function that sets up the connection information for a connection to the "pubs" database. It also checks to see if this page is a PostBack, and, if not, calls a custom BindGrid function. For a description of BindGrid, see step 2 of Inserting Data Into a SQL Database.
  3. Implement the MyDataGrid_Delete function. This function sets up a SQL Delete statement, connects to the database, deletes the indicated row, and then rebinds the DataGrid to display the updated database.
    public void MyDataGrid_Delete(Object sender,
                                            DataGridCommandEventArgs E)
    {
    1. Set up the SQLCommand information.
              String deleteCmd = "DELETE from Authors where au_id = @Id";
        SQLCommand myCommand = new SQLCommand(deleteCmd, myConnection);
        myCommand.Parameters.Add(new SQLParameter("@Id",
                                             SQLDataType.VarChar, 11));
    2. Initialize the SQLCommand "@ID" parameter to the row ID that was clicked.
              myCommand.Parameters["@Id"].Value =
                            MyDataGrid.DataKeys[(int)E.Item.ItemIndex];
    3. Connect to the database and delete the specified row.
              myCommand.ActiveConnection.Open();
    4. Test to see if the delete was accomplished and present the appropriate message to the user.
              try
        {
            int rowsAffected = 0;
            myCommand.Execute(ref rowsAffected);
            Message.InnerHtml = "<b>Record Deleted</b><br>" +
                                                     deleteCmd;
        }
        catch (SQLException)
        {
            Message.InnerHtml = "ERROR: Could not delete record";
            Message.Style["color"] = "red";
        }
    5. Close the connection.
              myCommand.ActiveConnection.Close();
    6. Rebind the DataGrid to show the updated information.
              BindGrid();
    }
  4. Implement a BindGrid function. This function connects to the database and implements the standard "select *" SQL query to get all of the data in the "Authors" table of the database. For the details on BindGrid, see step 4 of Inserting Data Into a SQL Database.
  5. In the <body> of the page, display the data.
    <body style="font: 10pt verdana">
  <form runat="server">
    <h3><font face="Verdana">Deleting a Row of Data</font></h3>
    <span id="Message" MaintainState="false" style="font: arial 11pt;" runat="server"/><p>
    <ASP:DataGrid id="MyDataGrid" runat="server"
      Width="800"
      BackColor="#ccccff"
      BorderColor="black"
      ShowFooter="false"
      CellPadding=3
      CellSpacing="0"
      Font-Name="Verdana"
      Font-Size="8pt"
      HeaderStyle-BackColor="#aaaadd"
      DataKeyField="au_id"
      OnDeleteCommand="MyDataGrid_Delete"
    >
      <property name="Columns">
         <asp:ButtonColumn Text="Delete Author" Command="Delete"/>
      </property>
    </ASP:DataGrid>
  </form>
</body>

To see this example run, go to the ASP+ Quick Start and run sample DataGrid10.aspx.


Sorting Data in a SQL Database

This example accesses a database, binds a DataGrid to it with the column names that are hot. When the column names are clicked, the table will re-sort itself according to the order in that column.

To create a table that can be re-sorted

  1. Import the necessary namespaces.
    <%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SQL" %>

    Within the <script language="C#" runat="server"> tag, this example implements three functions: Page_Load, MyDataGrid_Sort, and BindGrid. These functions are described in more detail in the steps that follow.

    <script language="C#" runat="server">
SQLConnection myConnection;
  2. Implement a Page_Load function that sets up the connection information for a connection to the "pubs" database. It also checks to see if this page is a PostBack, and if not, calls a custom BindGrid function. For a description of BindGrid, see step 2 of Inserting Data Into a SQL Database.
  3. Implement the MyDataGrid_Sort function. This function takes the DataGridSortCommandEventArgs E, which is passed in to it from the OnSortCommand attribute in the DataGrid control, and passes it with a call to BindGrid.
    protected void MyDataGrid_Sort(Object Src,
                                        DataGridSortCommandEventArgs E)
    {
        BindGrid(E.SortField);
    }
  4. Implement the BindGrid function. This function is implemented slightly differently from the previous examples. The sortfield parameter (the sort column) is passed to it from the MyDataGrid_Sort function. It uses this information to sort the table according to the specified column.
    public void BindGrid(String sortfield)
    {
    1. Set up the SQLDataSetCommand to get the "Authors" table from the "pubs" database (myConnection was set up in step 2).
              SQLDataSetCommand myCommand = new SQLDataSetCommand
                               ("select * from Authors", myConnection);
    2. Set up and fill a new DataSet.
              DataSet ds = new DataSet();
        myCommand.FillDataSet(ds, "Authors");
    3. Set up a DataView on the new DataSet, and sort the data using the sortfield.
              DataView Source = ds.Tables["Authors"].DefaultView;
        Source.Sort = sortfield;
    4. Bind the DataGrid to the sorted DataView.
              MyDataGrid.DataSource=Source;
        MyDataGrid.DataBind();
    }

To see this example run, go to the ASP+ Quick Start and run sample DataGrid11.aspx.


What Is ADO+

ADO+ is an evolutionary improvement to Microsoft ActiveX Data Objects (ADO). It is a standards-based programming model for creating distributed, data-sharing applications.

The centerpiece of any software solution using ADO+ is the data set. A data set is an in-memory copy of database data. A data set contains any number of data tables, each of which typically corresponds to a database table or view. A data set constitutes a "disconnected" view of the database data. That is, it can exist in memory without an active connection to the database (or databases) containing the corresponding tables or views.

At run time, Components can exchange data sets. That is, one component can transmit a data set to another component. For example, a CustomerOrder component can contain a data set with details about an order and the customer who made it. The component can include business logic enforcing rules about order processing. That component can transmit the data set to a user-interface component (a WinForm or Web Form, for example) for display to a user.

To accommodate the exchange of data sets among components, ADO+ uses an XML-based persistence and transmission format. That is, to transmit a data set from one component to another, an ADO+ solution expresses the in-memory data (the data set) as an XML file, and then sends the XML file to the other component.

You manipulate the contents of a data set through the DataSet object. The DataSet object lets you manipulate the contents of the data set with methods that are consistent with the relational model. For example, the DataSet object has a collection of DataTable objects, and each DataTable object has columns, rows, and relationships to other DataTable objects. When a component transmits a data set to another component, the receiving component materializes the received data set as a DataSet object.

Besides transmitting data sets among components, an ADO solution must coordinate activities between the data set and the corresponding database tables. For example, to materialize data set with pricing information from a PriceList database table, a solution must retrieve the information from the database and put it in the appropriate DataTable object within the dataset. Similarly, if the component allows users to change prices, the solution must transmit the updated prices to the database.

To accommodate the exchange of data between data sets and the underlying data sources, an ADO+ solution uses an object called a data set command. Microsoft provides two data set command objects:

  • The SQLDataSetCommand object

    This object mediates communication between a table within a data set and a table or view in a SQL Server database. SQL Server 7.0 and later are currently supported.

  • The ADODataSetCommand object

    This object mediates communication between a table within a data set and a table or view in any data source that has an OLE DB provider.

The following illustration shows the major components of an ADO+ solution.


How ADO+ Promotes Data Sharing

ADO+ helps you share your data in several ways.

  • It works closely with DBMS technology

    A Database Management System (DBMS), such as Microsoft SQL Server 2000, mediates data sharing in several ways. First, it manages the concurrent access to data, ensuring that users who are manipulating the same data simultaneously do not interfere with each other. Furthermore, the DBMS conceals from programmers many of the details of the physical storage, allowing users and programmers to concentrate on what the data means, rather than how it is arranged on disk. Most DBMSs provide this storage-agnostic view of the data through the SQL language.

    ADO+ can conceal from you some of the complexity of SQL, while delivering to you the features of DBMS technology.

  • It works closely with OLE DB

    OLE DB provides access to data across the organization. It defines a collection of COM interfaces that encapsulate various DBMS features and services. OLE DB providers exist for most commercial relational DBMS products, and for many non-relational stores.

    Through the ADODataSetCommand object, ADO+ can work with any OLE DB provider, so you can use ADO+ to manipulate data in virtually any relational DBMS and data in many non-relational storage formats.

  • It accommodates software architectures that promote data sharing

    Distributed architectures promote data sharing in several ways. For example, they can support multiple clients operating simultaneously, thereby delivering database data to multiple users. Furthermore, distributed architectures minimizing the number of active connections to the database can greatly increase the number of served users by preventing the database from becoming an application bottleneck.

    ADO+ provides an easy way to establish "disconnected" access to the database, thereby eliminating the database as an application bottleneck.

  • It uses industry standards

    Industry standards such as XML – or for that matter, SQL and OLE DB – promote data sharing by letting programmers use the same methods and languages to access data in disparate products. That is, standards conceal from programmers the idiosyncrasies of particular products or product versions.

    For example, as a standard language for manipulating database data, SQL lets programmers manipulate data in virtually any relational DBMS. OLE DB goes further, allowing programmers to manipulate data from a wide variety of storage formats, including non-relational formats such as that used by Microsoft Excel.

    Similarly, XML allows programmers to exchange data with other programs in an agreed-upon but highly flexible format.

    In solutions employing disconnected access to database data, ADO+ uses XML as a persistence and transmission format for disconnected in-memory representations of database data.


Benefits of ADO+

ADO+ offers several advantages over previous versions of ADO and over other data access components. These benefits fall into the following categories:

Interoperability

ADO+ solutions can take advantage of the flexibility and broad acceptance of XML. Because XML is the format for transmitting data sets among components, any component that can read the XML format can process components. In the extreme case, the receiving component need not be an ADO+ component at all. That is, the transmitting component can simply save the data set as an XML file, transmit the component to its destination. The destination component might be a Visual Studio project, a Visual Studio solution, or any software artifact implemented in with any tool whatsoever. What's important is that the receiving component is able to accept the XML file formatted as a data set. As an industry standard, XML was designed with exactly this kind of interoperability in mind.

Maintainability

In the lifetime of a deployed system, modest changes are possible, but substantial, architectural changes are rarely attempted because they are so difficult. That is unfortunate, because in a natural course of events, such substantial changes can become necessary.

For example, as a deployed application becomes popular within its user community, the increased performance load might require architectural changes. As the performance load on a deployed application server grows, system resources can become scare and response time or throughput can suffer. Faced with this problem, software architects can choose to divide the server's business-logic processing and user-interface processing onto separate tiers on separate machines. In effect, the application server layer is replaced with two layers, alleviating the shortage of system resources.

If the original solution is implemented in ADO+ using data sets, this transformation is made easier. Remember, when you replace a single tier with two tiers, you arrange for those two tiers to trade information. Because they tiers can transmit data through transmit XML-formatted data sets, the communication is relatively easy.

Programmability

Programmers can manipulate objects of the ADO+ model through strongly-typed programming. Strongly typed programming is programming in which the types of things that are important to users are recognized by the programming environment or programming language itself.

To appreciate the benefits of strongly typed programming, you must appreciate the difference between types of things that are of interest to programmers (design-time users) and types of things that are of interest to end users (run-time users).

For example, consider the following line of code, using conventional (not strongly typed) programming:

IF TotalCost > Table("Customer").Column("AvailableCredit")

The code contains some words that are about types of things the users consider interesting. The words "Customer" and "Available Credit" are such words. But the code also contains the words "Table" and "Column." These words refer to things that your end users do not care about. Remember that strictly speaking, your users do not directly care about tables, columns, classes, and other artifacts of the software you deliver to them. The users care about their information. Tables, columns, variable names, class names and other artifacts of software implementation are, from the user's perspective, immaterial.

In a nutshell, strongly typed programming is a style of programming in which the user words figure prominently. For example, consider the following line of code using strongly typed programming:

IF TotalCost > Customer.AvailableCredit

This line of code is equivalent to the earlier line using conventional programming. In the strongly typed code, the user vocabulary is much more evident. The prominence of the user vocabulary makes the code easier to read -- a business analyst with little or no programming experience can grasp the meaning of the condition being tested without having to filter out the programmer vocabulary present in the conventional line of code.

Besides being easier to read, the strongly typed code is easier to write. Specifically automatic statement completion can be sensitive to the user vocabulary. For example, in the following imcomplete statement:

IF TotalCost > Customer.

the text "Available Credit" appears in a dropdown list of choices for completing the statement.

Performance

For disconnected applications, ADO+ data sets offer performance advantages over ADO disconnected record sets. When using COM marshalling to transmit a disconnected data set among tiers, a significant processing cost can result from converting the values in the record set to data types recognized by COM. In ADO+, such data type conversion is not necessary.

Interoperability

When transmitting an ADO disconnected record set from one component to another, the receiving component must accommodate COM marshalling. That is, it must be a COM component. This restriction does not apply when transmitting an ADO+ data set from one component to another. Because the transmission of data sets occurs through XML files, and because XML is a simple text-based standard with broad industry acceptance, the receiving component has no architectural restrictions whatsoever. Thus, virtually any two components can share data sets, provided both components use the same XML schema for the format of the transmitted XML-based data set.


Scalability

Because the Web can vastly increase the demands on your data, scalability has become an especially important factor for characterizing the quality of your applications. If your application uses a Web-based interface, there is a virtually limitless supply of potential users. Although an application might serve a dozen users well, it might not serve hundreds equally well.

Remember, the greatest impediment to scalability is the contention for limited resources. If an application consumes resources such as database locks, database connections, the application will not serve high numbers of users well, because as the number of users grows, the user demand for such resources will eventually exceed their supply. Thus, an application that accommodates dozens of users well might server hundreds of users very poorly.

ADO+ accommodates scalability by encouraging programmers to conserve the limited resources for which users contend. Because any ADO+ application employs disconnected access to database data, it does not retain database locks or active database connections for long durations. Thus, the contention for limited database resources is limited.


ADO+ and previous versions of ADO

ADO+ is an evolutionary improvement of ADO. One way to quickly understand the features of ADO+ is to compare them to particular features of ADO.


In-memory representations of database data

In ADO, the in-memory representation of database data is the RecordSet. In ADO+, it is the DataSet. There are important differences between them.

  • Number of tables

    An ADO RecordSet looks like a single table. If a RecordSet is to contain data from multiple database tables, it must use a JOIN query, which assembles the data from the various database tables into a single result table.

    In contrast, an ADO+ DataSet is a collection of one or more tables. The tables within a data set are called data tables; specifically, they are DataTable objects. If a data set is to contain data from multiple database tables, it will typically contain multiple DataTable objects. That is, each DataTable object typically corresponds to a single database table or view.

    Typically a data set also contains relationships. A relationship within a data set is analogous to a foreign-key relationship in a database: it associates rows of one data table with rows of another. For example, if a data set contains a table of investors, and a second containing each investor's stock purchases, it would also contain a relationship. The relationship would connect each row of the investor table with the attendant rows of the purchase table. In ADO+, the relation is represented by a DataRelation. See Adding DataRelation

  • Data visitation

    In ADO+, the methods you use to read, modify, or otherwise visit a data item differ from the programming methods you use in ADO.

    In ADO, you scan sequentially through the rows of the record set.

    In ADO+, you employ a navigation paradigm, moving from a row of one data table to the corresponding row or rows of another data table by following the relationship. For example, starting from the data table row of the Investor table describing "Lucretia Shekkar", you can navigate to the set of rows of the Purchase table describing her purchases.


Disconnected access to database data

In ADO+, the data set provides disconnected access to database data. In ADO, the record set can provide disconnected access, but is typically used to provide connected access.

There is one significant difference between disconnected processing in ADO and ADO+. In ADO, you communicate with the database by making calls to an OLE DB provider. But in ADO+, you communicate with the database through a DataSetCommand object, which makes calls to an OLE DB provider (or, in some cases, directly to the APIs provided by the DBMS). The important difference is this: you can modify the code of a DataSetCommand object. Thus, you can control how the changes to the data set are transmitted to the database. This means you can optimize for performance, perform data validation checks, or add any other extra processing.


Sharing data between tiers or components

Transmitting an ADO+ data set between tiers or components is easier than transmitting an ADO disconnected record set among them.

To transmit an ADO disconnected record set from one component to another, you use COM marshalling. To transmit an ADO data set, you simply transmit an XML file.

The transmission of XML files offers the following advantages over COM marshalling:

  • Richer data types

    COM marshalling provides a limited set of data types – those data types defined by the COM standard. Because the transmission of data sets is based on an XML format, there is no restriction on data types. Thus, the components sharing the data set can use whatever rich set of data types they would ordinarily use.

  • Performance

    Transmitting a large ADO record set or a large ADO+ data set can consume network resources; as the amount of data grows, the stress placed on the network also rises. Both ADO and ADO+ let you minimize which data is transmitted.

    But ADO+ offers another performance advantage, in that ADO+ does not require data type conversions. By contrast, ADO, which requires COM marshalling to transmit records sets among components, does require that ADO data types be converted to COM data types.

  • Penetrating firewalls

    A firewall can interfere with two components trying to transmit disconnected ADO record sets. Remember, firewalls are typically configured to allow HTML text to pass, but to prevent system-level requests (such as COM marshalling) from passing.

    Because components exchange ADO+ data sets using XML, firewalls can allow data sets to pass. Remember, XML is a standard based on HTML.


ADO+ References

NGWS frameworks is an object-oriented system. When using specific parts of the frameworks, you need to include references to the namespace in code, and when building your application, to the Dynamic Link Library (DLL) that contains the namespace.

When using ADO+ from either Visual Basic 7.0 or C#, you must reference the System.Data namespace, as well as either System.Data.ADO or System.Data.SQL, depending on which Managed Provider you choose to use. For example, to access data using the SQL Managed Provider, in Visual Basic you would use:

Imports System.Data
Imports System.Data.SQL

In C#:

using System.Data;
using System.Data.SQL;


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Using ADO+

© 2000 Microsoft Corporation. All rights reserved.


Working our way through ADO+

As we mentioned earlier, there are two basic parts to ADO+, the DataSet and the Managed Providers. We'll start our trip through ADO+ by with a brief overview of the DataSet.

The DataSet is a simply in-memory database that provides a consistent programming model no matter where the data comes from. The DataSet consists of a collection of tables, columns, rows, constraints and relationships. The object model for the DataSet is shown below:


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Working with a DataTable

© 2000 Microsoft Corporation. All rights reserved.


What is a DataTable?

A DataTable represents one table of in-memory data. It contains a collection of columns (the ColumnsCollection) that represents the table's schema. A DataTable also contains a collection of rows (the RowsCollection), representing the data held by the table. It remembers original state along with current state, tracking what kinds of changes have occurred.

To use the DataTable you must include System.Data.


Creating a DataTable

The DataTable has two constructors:

    public DataTable()
    public DataTable(string tableName)

The following examples construct an instance of a DataTable:

[VB]
Dim workTable as DataTable
workTable = New DataTable("Customer")
workTable.CaseSensitive = false
workTable.MinimumCapacity = 100
[C#]
DataTable workTable = new DataTable("Customer");
workTable.CaseSensitive = false;
workTable.MinimumCapacity = 100;

The first thing that we want to do in creating a DataTable instance is to set its name. The easiest way to do this is through the constructor. However, you could also simply set the name through the table object's TableName property.

DataTable worktable = new DataTable();
worktable.TableName = "Customers";

Additionally, we want to indicate whether the schema that adds to the table should be case sensitive or not. If you set the case sensitive property to true, this will affect string comparisons in sorting, searching, and filtering.

Setting the Minimum Capacity property enables the system to create an initial set of rows that will help to optimize performance.


Adding Columns to a DataTable

The DataTable contains a collection of DataColumn objects. This collection of columns defines the structure of the table. To add a new column to this collection, use the collection's Add method.

In the example below, we add three columns to a DataTable using the ColumnsCollection class's Add method; the method specifies the ColumnName and DataType properties.

[VB]
 Dim workTable as DataTable
 Dim workColumn as DataColumn

 WorkTable = new DataTable("Customers")

 workColumn = workTable.Columns.Add("CustID",
System.Type.GetType("System.Int32") )

 workColumn.AllowNull = false
 workColumn.Unique = true

 workColumn = workTable.Columns.Add("CustomerNameLast",
System.Type.GetType("System.String") )
 workColumn = workTable.Columns.Add("CustomerNameFirst",
System.Type.GetType("System.String") )

 workColumn = workTable.Columns.Add("Purchases",
System.Type.GetType("System.Double") )
[C#]
DataColumn workColumn = null;

 DataTable workTable = new DataTable("Customers");

 workColumn = workTable.Columns.Add("CustID",
System.Type.GetType("System.Int32") );

 workColumn.AllowNull = false;
 workColumn.Unique = true;

 workColumn = workTable.Columns.Add("CustomerNameLast",
System.Type.GetType("System.String") );
 workColumn = workTable.Columns.Add("CustomerNameFirst",
System.Type.GetType("System.String") );
 workColumn = workTable.Columns.Add("Purchases",
System.Type.GetType("System.Double") );

The first task is to declare a variable of type DataColumn named "workColumn." Using C#, the variable is initialized to null.

DataColumn workColumn = null;

Next, we declare an instance of the table to which we're going to add the new columns. We use the constructor to give the table the name "Customers" by which we will reference it later.

DataTable worktable = new DataTable("Customers");

Now we start the job of actually adding the new columns to the table. We'll actually add three columns: CustID (type: Int32), CustomerNameLast (type: String), and CustomerNameFirst (type: String).

workColumn = workTable.Columns.Add("CustID",
System.Type.GetType("System.Int32") );

When the data type is assigned to the column, we're use a NGWS frameworks data type - not an OLE DB or database specific data type. To set the type, use the System function – System.Type.GetType - that returns a type name for the actual data type that we want.

System.Type.GetType("System.Int32")

The GetType method returns a null if the type specified could not be loaded.

The ColumnsCollection on the DataTable provides has two overloads of the Add method:

   Public DataColumn Add(String columnname, Type type)
   Public DataColumn Add(String columnname)


Expression Columns

ADO+ also allows you to create and define expression columns. Expressions in ADO+ are used for:

  • Filtering
  • Computing
  • Aggregating column information

To create an expression column, set the DataType property to a type appropriate to the values the expression will return; then set the Expression property to a valid expression:

[VB]
Dim dc As DataColumn = New DataColumn
dc.DataType = System.Type.GetType("System.Currency")
dc.Expression = "total * .086"

[C#]
DataColumn dc = New DataColumn;
dc.DataType = System.Type.GetType("System.Currency");
dc.Expression = "total * .086";

You can also create an expression column using an Add method. For example, the code below adds a column that calculates the rebate due to a customer based on 10% of their purchases. The expression multiplies the column named "Purchases" column by 10%.

[VB]
Dim workColumn As DataColumn
workColumn = workTable.Columns.Add("Rebates", _
System.Type.GetType("System.Double"), "Purchases * .1" )
[C#]
System.Data.DataColumn workColumn;
workColumn = workTable.Columns.Add("Rebates", System.Type.GetType("System.Double"), "Purchases * .1");

When the table is populated, this column's value will be 10% of the value in the Purchases column.

DataSet expressions can reference other expression columns. However, an error will be thrown if a "circular reference" is established.

For rules on writing expressions, see the help topic for the DataColumn class's Expression property.

AutoIncrement Columns

Another feature of the DataColumn is its ability to be an auto-incrementing column. An auto-incrementing column automatically increments the value held in the column when new rows are added. To create an auto-incrementing column, set the column's AutoIncrement property to true. Once set, the column with start from the value defined in the column's AutoIncrementSeed property. With each row added, the value of the AutoIncrement column will raise by the value held in the column's AutoIncrementStep property

For example, to create a column that starts incrementing from 200 in steps of 3:

[VB]
workColumn = workTable.Columns.Add _
   ("CustID", System.Type.GetType("System.Int32")
workColumn.AutoIncrement = True
workColumn.AutoIncrementSeed = 200
workColumn.AutoIncrementStep = 3
[C#]
workColumn = workTable.Columns.Add("CustID",
System.Type.GetType("System.Int32") );
workColumn.AutoIncrement = true;
workColumn.AutoIncrementSeed = 200;
workColumn.AutoIncrementStep = 3;

We can also set the column to be read-only by setting its ReadOnly property to true.


Building a Primary Key for a Table

With the basic structure of the Customers table, we can clean up the table's definition. The first task is to define a primary key. In a database, it's a general rule that every table has a column, or group of columns, that uniquely identify each row in the table. This column (or columns) is called the primary key. For our Customers table, the "CustID" column will be the primary key.

To define a primary key, we'll set some additional properties on the "CustID" column to make it work like a primary key in a database. The first property "AllowNull" specifies whether a null value is allowed. As in a true database primary key, we set the property to false.

 workColumn.AllowNull = false;

Setting the Unique property specifies whether the values contained within the column must be unique. In other words, no two values in the column can be identical. Again, since this column will act like our primary key we'll set this value to true.

 workColumn.Unique = true;

Now that the column is configured like a primary key, it can be assigned to the DataTable object's PrimaryKey property. The primary key is a column, or set of columns, containing values that are unique for each row in the table. For the Customers table, the primary key will be the CustID column:

[VB]
Dim myColArray As DataColumn()
myColArray = workTable.Columns("CustID")
workTable.PrimaryKey = myColArray
[C#]
workTable.PrimaryKey = new DataColumn[] {worktable["CustID"]};

If we were going to define additional columns as part of the primary key, it would look link this:

[VB]
myColArray(0) = workTable.Columns("Col1")
myColArray(1) = workTable.Columns("Col2")
workTable.PrimaryKey = myColArray
[C#]
workTable.PrimaryKey = new DataColumn[] {worktable["Col1"], worktable["Col2"]};


Adding data to the Table

Now that there is a table with columns and keys, some data can be added. The following code adds ten rows to the newly created Customers table.

[VB]
  Dim iCounter as integer
  Dim workRow as DataRow

  For i = 0 to 9
   workRow = worktable.NewRow()
   workRow("CustID") = iCounter
   workRow("CustomerNameLast") = "CustName" & i.ToString()
   worktable.Rows.Add(workRow)
  Next
[C#]
 DataRow workRow = null;
 for (int i = 0; i <= 9; i++)
 {
  workRow = workTable.NewRow();
  workRow[0] = i;
  workRow[1] = "CustName" + i.ToString();
  workTable.Rows.Add(workRow);
 }

In the code above, we declare a new variable as type DataRow. A new DataRow object is returned when the NewRow method is invoked. The DataTable builds the DataRow object based on the structure of the table (as defined by the ColumnsCollection).

[VB]
Dim workRow As DataRow
workRow = workTable.NewRow()
[C#]
Data.DataRow workRow;
workRow = workTable.NewRow();

We then work with the newly built row either through an index or the column's name. Both of the following lines of code are valid:

[VB]
workRow("CustID") = "CustName" & i
workRow(1) = "CustName" & i
[C#]
workRow["CustID"] = "CustName" + i.ToString();
workRow[1] = "CustName" + i.ToString();

Once a row is populated with values, it is added into the table's RowsCollection using the Add method.

[VB]
workTable.Rows.Add(workRow)
[C#]
workTable.Rows.Add(workRow);

In addition to adding rows this way, we can also add a new row my passing in an array of values (typed as object) using the Add method.

[VB]
workTable.Rows.Add(new Object() {1, "CustName1"})
[C#]
workTable.Rows.Add(new Object[] {1, "CustName1"});

This option simply creates a new row inside the table and sets its column values to the values in the object array. Note that the values in the array are matched sequentially to the columns in the table.


Row States

Each DataRow has a RowState property that can be examined to determine its state.

In the example above, the newly created row's RowState is set to Detached; once it is added to the RowsCollection using the Add method, however, the RowState property becomes New.

The table below identifies the possible states that a row can have.

RowState | Description
Unchanged | No changes have been made since the last call to AcceptChanges.
New | The row has been added to the table, but AcceptChanges has not been called.
Modified | Some element of the row has been changed.
Deleted | The row has been deleted from the table using the Delete method.
Detached | Either the row has been deleted, but AcceptChanges has not been called; or the row has been created, but not added to the table.


Removing Versus Deleting a DataRow from a Table

As you work with data in a table there will be times when you want to remove one or more rows from the table. Note, however, that "removing" a row is not the same as "deleting" a row as Deleted. "Removing" a row means physically removing the row from the DataSet. This distinction will be important later in the discussion of persisting the values in the table to a data store.

To remove a row from the table call the Remove method of the table's RowsCollection (accessed through the DataTable object's Rows property) with either the index of the row or the actual instance of the row.

[VB]
workTable.Rows.Remove(3)
[C#]
workTable.Rows.Remove(3);

or

[VB]
workTable.Rows.Remove(workTable.Rows(3))
[C#]
workTable.Rows.Remove(workTable.Rows[3]);

After calling this method, the specified row will be removed from the rows collection.

In contrast, you can call the Delete method on a DataRow to change its state to Deleted:

[VB]
workTable.Rows(3).Delete
[C#]
workTable.Rows[3].Delete

Marking a row as deleted signals that when the DataTable object's AcceptChanges method is invoked, the row will be removed from the table. Thus the RowState for the row is Deleted. In contrast, if RejectChanges is invoked, this row's RowState will revert to what it was before being marked as deleted.

The difference between the Remove and Delete methods is when the actual removal takes place.

Note If a row's RowState is New, meaning its just been added to the table, and it is then marked as Deleted, it will be removed from the table.


Working with the Table's data

Now that we have a table with columns, a primary key and data, we can display its content. In this example we'll filter the dataset so that we're only working with the current version of the row.

[VB]
Dim CurrRows() As DataRow = workTable.Select(Nothing, Nothing, _
   System.Data.DataViewRowState.CurrentRows)
Dim list As String = System.String.Empty
Console.WriteLine("")
Console.WriteLine("Writing current rows for " & workTable.Tablename)
Dim RowNo As Integer
Dim ColNo As Integer

For RowNo = 0 to CurrRows.Count - 1
   For ColNo = 0 To workTable.Columns.Count – 1
      list = ""
      list &= workTable.Columns(colNo).ColumnName & " = " & _
         CurrRows(RowNo)(ColNo).ToString
      Console.WriteLine(list)
   Next
Next

If CurrRows.Count < 1 Then Console.WriteLine("No CurrentRows Found")

[C#]
 DataRow[] CurrRows = workTable.Select(null,
null, System.Data.DataViewRowState.CurrentRows );

 string list = System.String.Empty;

 Console.WriteLine("");
 Console.WriteLine("Writing Current rows for " + workTable.TableName );
 for (int RowNo = 0; RowNo <= CurrRows.Count -1; RowNo++)
 {
  for (int ColNo = 0; ColNo <= workTable.Columns.Count - 1; ColNo++)
  {
list = System.String.Empty;
list += workTable.Columns[ColNo].ColumnName + " = " +
CurrRows[RowNo][ColNo].ToString();
Console.WriteLine(list);
  }
 }
 if (CurrRows.Count < 1 )
 {
  Console.WriteLine("No Current Rows Found");
 }

In the code above, we introduce the DataTable class's Select method. The method enables you to retrieve a set of rows that match specified criteria. The method returns an array of DataRow objects.

In the example the method retrieves all of the "Current" rows. What is a "Current" row? The DataTable maintains three versions of each row: Original, Current and Proposed. An original row is the version of the row as it was first added to the table.

The current version of the row holds any changes that have been made to the row. If you make a change to a value in a row, the new value is the current value while the value prior to the change is the original value.

The proposed version exists under special circumstances for a short period of time. The proposed version represents the state moving from original to current. A row moves into the proposed state when the system or the user calls BeginEdit on the row, then edits the row.

[VB]
workRow.BeginEdit
' Make some changes to the row.
If ValidateColValue(proposedValue)
   workRow.EndEdit
Else
   workRow.CancelEdit
End If
[C#]
workRow.BeginEdit
// make some changes to the row
if (ValidateColValue(proposedValue))
   workRow.EndEdit;
else
   workRow.CancelEdit;

It is during the "proposed" row state that you can apply validation logic to individual columns by responding to the DataTable object's ColumnChange event. The table passes a reference to the changing column and the proposed value in the ColumnChange event. The user can modify the proposed value or throw an exception to cancel the edit. The row moves out of the proposed state when EndEdit is called. You can also cancel any edits while the row is in a proposed state by calling CancelEdit.


Adding and Reading Row Error Information

As end users edit values in the table, they may make mistakes. In these cases, we may not want to stop the user while he or she is in the process of editing the data, but rather simply add the error information to the row for display later. The DataRow supports this ability by providing a RowError property on each row.

The two examples below show how to add/read error information associated with a DataRow.

Example 1:

[VB]
Public Sub AddErrorToRow(ByVal workTable As DataTable, ByVal Row As Integer, _
      ByVal ErrorString As String)
   workTable.Rows(Row).RowError = ErrorString
End Sub

[C#]
public void AddErrorToRow(DataTable workTable, int Row, string ErrorString)
{
 workTable.Rows[Row].RowError = ErrorString;
}

Example 2:

Public Sub WriteTableErrors(ByVal workTable As DataTable)
   If workTable.HasErrors Then
      Dim ErrorRows() As DataRow = workTable.GetErrors()
      Console.WriteLine("")
      Console.WriteLine("=================")
      Console.WriteLine("DataTable " & workTable.TableName & " has " & _
         ErrorRows.Count.ToString & " Errors!")
      Dim i As Integer
      For i = 0 to Errors.Count – 1
         Console.WriteLine("Row Error for row " & _
         ErrorRows(i)("CustID").ToString &  "Error Msg =" & _
         ErrorRows(i).RowError)
       Next
   Else
   Console.WriteLine("")
   Console.WriteLine("=================")
      Console.WriteLine("DataTable " + workTable.TableName + " Has no errors")
   End If
End Sub

[C#]
public void WriteTableErrors(DataTable workTable)
{
 if ( workTable.HasErrors )
 {
  DataRow[] ErrorsRows = workTable.GetErrors();
  Console.WriteLine("");
  Console.WriteLine("=================");
  Console.WriteLine("DataTable " + workTable.TableName + " has " +
ErrorsRows.Count.ToString() + " Errors!");

  for (int i = 0; i <= ErrorsRows.Count -1; i++)
  {
Console.WriteLine("Row Error for row " +
ErrorsRows[i]["CustID"].ToString() + " Error Msg=" +
ErrorsRows[i].RowError);
  }
 }
 else
 {
Console.WriteLine("");
Console.WriteLine("=================");
Console.WriteLine("DataTable " + workTable.TableName + " Has no errors");
 }
}

The first example takes a row number (Row) and adds an error message (ErrorString). The second example prints the RowError for each row of the workTable that has an error associated with it.


Accepting or Rejecting changes to DataTable rows

Once you sure of the changes you've made, you'll want to commit the changes and move the current row values to be the original values. At this point we need to understand that everything we've talked about is in reference to the data held in-memory in the DataSet. We'll discuss how accepting or rejecting changes in the dataset effect updating data in the data store in the section on managed providers.

The code below shows two ways of committing (accepting) the row changes.

Example 1:

[VB]
workTable.AcceptChanges()

[C#]
workTable.AcceptChanges();

Example 2:

[VB]
Public Function AcceptChanges(ByVal workTable As DataTable) As Integer
   Dim acceptedRows As Integer = 0
   Dim i As Integer
   For i = 0 To workTable.Rows.Count – 1
      If Not workTable.Rows(i).HasErrors Then
         acceptedRows += 1
         workTable.Rows(i).AcceptChanges()
      Else
         workTable.Rows(i).RejectChanges()
      End If
   End If
   AcceptChanges = acceptedRows
End Function

public int AcceptChanges(DataTable workTable)
{
 int acceptedRows = 0;

 for (int i = 0; i <= workTable.Rows.Count -1 ; i++)
 {
 if (!workTable.Rows[i].HasErrors )
 {
       acceptedRows++;
       workTable.Rows[i].AcceptChanges();
 }
 else
  {
    workTable.Rows[i].RejectChanges();
  }
 }
 return acceptedRows;
}

The first example accepts all changes that have been made to the table. The second example loops through the changes and accepts each on a row-by-row basis. To determine if AcceptChanges should be called, the code examines the HasErrors property.

[VB]
If Not workTable.Rows(i).HasErrors) Then

[C#]
if (!workTable.Rows[i].HasErrors )

The DataRow allows you to enter an error string and associate it with a row within the table. If any row in the table has an error string associated with it, the HasErrors property will return true. In the second example, we iterate through the rows and accept changes for each that doesn't have any errors.

Once the changes on a row are accepted, all of the original values for the row are replaced with the current values. The row is no longer consisted to have been modified.


Working with DataTable Events

The DataTable provides a series of events that can be processed by the user's code. These events include:

  • ColumnChange
  • RowChanged
  • RowChanging
  • RowDeleting
  • RowDeleted

Adding the event handler to the event.

[VB]
workTable.AddOnColumnChanging _
(New System.Data.DataColumnChangeEventHandler(AddressOf me.MyColChanging))
workTable.AddOnRowChanging _
(New System.Data.DataRowChangeEventHandler(AddressOf me.MyRowChanging))
workTable.AddOnRowChanged _
(New System.Data.DataRowChangeEventHandler(AddressOf me.MyRowChanged))

Private Sub MyColChanging _
(ByVal sender As Object, ByVal e As DataColumnChangeEventArgs)

End Sub

Private Sub MyRowChanging _
(ByVal sender As Object, ByVal e As DataRowChangeEventArgs)

'Add this to capture the row changing event
 Console.WriteLine("Adding row " + e.Row[0].ToString())

End Sub

Private Sub MyRowChanged _
(ByVal sender As Object, ByVal e As DataRowChangeEventArgs)
   If e.Action = Delete Then
      Console.WriteLine("deleting row " & e.Row(0).ToString)
   End If
End Sub
[C#]
workTable.AddOnColumnChanging(new DataColumnChangeEventHandler(MyColChanging));
workTable.AddOnRowChanging(new DataRowChangeEventHandler(MyRowChanging));
workTable.AddOnRowChanged(new DataRowChangeEventHandler(MyRowChanged));

Public void MyColChanging(object sender, DataColumnChangeEventArgs e)
{
}

public void MyRowChanging(object sender, DataRowChangeEventArgs e) {
 //Add this to capture the row changing event
 Console.WriteLine("Adding row " + e.Row[0].ToString());
}

public void MyRowChanged(object sender, DataRowChangeEventArgs e){
  if (e.Action == Delete)
  {
   Console.WriteLine("deleting row " + e.Row[0].ToString());
  }
}

In the example above, we've created three methods: MyColChanging, MyRowChanging and MyRowChanged. Each of these methods will occur when a column or row changes.


Filtering and sorting rows in the Table

The Select method enables users to retrieve rows based on three elements: a filter expression, sort order and by DataViewRowState.

In the example below, we pass in null values to parts of the statement we want to ignore. In this case the select method will bring back all the current rows.

[VB]
Dim CurrRows() As DataRow = workTable.Select(Nothing, Nothing, _
   System.Data.DataViewRowState.CurrentRows)

[C#]
DataRow[] CurrRows = workTable.Select(null,
null, System.Data.DataViewRowState.CurrentRows );

We could ask for all rows with a last name of "Smith" ordered by the column named "CustomerNameFirst."

[VB]
Dim myNames() As DataRow = workTable.Select("CustomerLastName = 'Smith'"), _
"CustomerNameFirst", System.Data.DataViewRowState.CurrentRows)

[C#]
DataRow[] myNames = worktable.Select("CustomerNameLast = 'Smith'", "CustomerNameFirst", System.Data.DataViewRowState.CurrentRows);


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Working with Datasets

© 2000 Microsoft Corporation. All rights reserved.


What is a DataSet

A DataSet object represents an in-memory cache of data. It contains a collection of tables and knowledge of relationships between those tables. Each table contains a collection of columns. These objects represent the schema of the DataSet. Each table can then have multiple rows, representing the data held by the DataSet. These rows remember their original state along with their current state, so that the DataSet tracks what kinds of changes have occurred. Additionally, the DataSet provides persistence and de-persistance through XML.

The DataSet can be considered the centerpiece of ADO+, as it represents a complete set of data including multiple, related tables, and constraints. The DataSet thus provides a rich object model to work with when passing data between various components of an enterprise solution. For example, in a typical solution, a client application requests data through a URL. When the request arrives at an appropriate middle tier component, a DataSet is created using an adapter. The DataSet is then converted into an XML document for transport back to the requester. On the client application, the data is displayed using a combination of controls, such as the DataGrid. The user can manipulate the data—adding, deleting, and editing—to his or her satisfaction. When ready, the DataSet is again converted into an XML document for transmission back to the server component. The component converts the XML document back into a DataSet, and uses an adapter to attempt to merge the changed data back into the DBMS. If any collisions occur, the server component can reconcile them using built-in business rules. The reconciled DataSet is then returned back to the client. The DataSet can then be merged back into the existing DataSet, and the user can resume working with the DataSet that reflects the latest version.

There are two general ways to employ a DataSet. You can programmatically create a DataSet and add DataTable objects to it, then create DataRelation objects to link each table. Use the programmatic method when you need to create a DataSet at run time to capture data generated by an application.

Alternatively, you can use an existing DBMS, such as SQLServer, to fill tables in the DataSet. In this method, use one SQLDataSetAdapter per table to fill the DataTable object with data. You must, however, create the DataRelation objects between each table.

To use the DataSet you must include System.Data:

using System.Data


General steps for using a DataSet With Existing Data

These are the general steps to use a DataSet with existing data:

  1. Build and fill each table in a DataSet with data from a DBMS using a SQLDataSetCommand or ADODataSetCommand. See Creating a Simple DataSetCommand
  2. Change the data in individual DataTable objects by adding, updating, or deleting DataRow objects. See ...
  3. Invoke the GetChanges method to create a second DataSet that features only the changes to the data. The method requires one argument, the DataRowState of the changes you are interested in. The example below uses the method to return a DataSet with only the modified rows:
    [VB]
Dim changedDataSet As DataSet
changedDataSet = ds.GetChanges(DataRowState.Modified)
[C#]
System.Data.DataSet changedDataSet;
changedDataSet = ds.GetChanges(DataRowState.Modified);
  4. Check for errors in the second DataSet by examining its HasErrors property, which informs you if any table in the set has errors. See section…
  5. If errors are present, check the HasErrors property of each DataTable . If the table has errors, invoke the DataTable class's GetErrors method to return an array of DataRow objects with errors.
  6. On each DataRow examine the RowError property for specific data.
  7. Reconcile the errors, if possible.
  8. Invoke the Merge method to merge the changes from the second DataSet into the first.
    [VB]
ds.Merge(changedDataSet)
[C#]
ds.Merge(changedDataSet);
  9. Call the Update method of the SQLDataSetCommand (or ADODataSetCommand), passing the merged DataSet as an argument.
    [VB]
workDSCMD.Update(ds)
[C#]
workDSCMD.Update(ds);
  10. Invoke the AcceptChanges on the DataSet. Alternatively, invoke RejectChanges to cancel the changes.
    ds.AcceptChanges


Creating a DataSet Programmatically

The dataset provides two basic constructors:

public DataSet()
public DataSet(string DataSetName)

The first constructs an empty DataSet; the second constructs the DataSet with the specified name.

The following example constructs an instance of a DataSet:

[VB]
Dim workDataSet as DataSet
Set workDataSet As New DataSet() ' Empty DataSet.
' Or
Set workDataSet As New DataSet("CustomerOrders")
[C#]
DataSet workDataSet = new DataSet(); // Empty DataSet.
// Or
DataSet workDataSet = new DataSet("CustomerOrders");


Adding a DataTable to a DataSet

ADO+ enables you to create tables externally and then add them to an existing dataset. To create a DataTable, see section 3.2.2

Note For this release, you should add a newly created table to the dataset prior to populating it with data. In the current version, if the table does not have an associated dataset, we create and assign one in invisibly.

The following example constructs a DataSet (ds), adds a new DataTable named "Orders," and adds three columns (OrderID, OrderQuantity, and CustID) to the DataTable. Finally, the code sets the OrderID column as the primary key column.

[VB]
ds.Tables.Add(New DataTable("Orders"))
ds.Tables("Orders").Columns.Add("OrderID", System.Type.GetType("System.Int32"))
ds.Tables("Orders").Columns.Add("OrderQuantity", _
             System.Type.GetType("System.Int32"))
ds.Tables("Orders").Columns.Add("CustID", _
             System.Type.GetType("System.Int32"))
ds.Tables("Orders").PrimaryKey = New DataColumn(ds.Tables.Columns("Orders"), _
             ds.Tables.Columns("OrderID"))
' Or

Dim workTable As DataTable = New DataTable("Orders")
workTable.Columns.Add("OrderID", System.Type.GetType("System.Int32"))
workTable.Columns.Add("OrderQuantity", System.Type.GetType("System.Int32"))
workTable.Columns.Add("CustID", System.Type.GetType("System.Int32"))
ds.Tables("Orders").PrimaryKey = _
            New   DataColumn(ds.Tables("Orders").Columns("OrderID"))
ds.Tables.Add(workTable)

[C#]
ds.Tables.Add(new DataTable("Orders"));

ds.Tables["Orders"].Columns.Add("OrderID",
System.Type.GetType("System.Int32"));
ds.Tables["Orders"].Columns.Add("OrderQuantity",
System.Type.GetType("System.Int32"));
ds.Tables["Orders"].Columns.Add("CustID", System.Type.GetType("System.Int32"));

ds.Tables["Orders"].PrimaryKey =
new DataColumn[]{ds.Tables["Orders"].Columns["OrderID"]};

// Or

DataTable workTable = new DataTable("Orders");
workTable.Columns.Add("OrderID",
System.Type.GetType("System.Int32"));
workTable.Columns.Add("OrderQuantity",
System.Type.GetType("System.Int32"));
workTable.Columns.Add("CustID", System.Type.GetType("System.Int32"));
ds.Tables["Orders"].PrimaryKey =
new DataColumn[]{ds.Tables["Orders"].Columns["OrderID"]};
ds.Tables.Add(workTable);


Adding a Relationship Between Two Tables

Because the DataSet can contain multiple DataTable objects, there must also be a way to relate each table to another. This is needed for navigating through the tables, and for returning related values.

The essential arguments of a DataRelation are the two columns that serve as the primary key and the foreign key columns in the relation, and the name of the DataRelation. The name can then be used for navigating or when retrieving values.

Note Relations can also be built with more than one column per table. You can use an array of DataColumn objects for both the primary and the foreign key arguments.

The example below presumes two DataTable objects exist in the DataSet. Both tables have a column named "CustID" which serves as the link between the two tables. The example adds a single DataRelation to the DataSet object's Relations collection. The first argument ("CustOrders") specifies the name of the relation. The second and third arguments are the DataColumn objects that link the two tables.

[VB]
Dim ds As DataSet = New DataSet("CustomerOrders")
' With ds do the following:
'  - add a customer table with CustID and CustName as columns.
'  - set Customers.CustID as the PrimaryKey column.
'  - add an Orders table with three columns: OrderID, CustID, and Quantity
'  - set Orders.OrderID as the PrimaryKey column.
' Add a DataRelation to the Relations collection specifying its name, and the
' appropriate DataColumn objects as arguments.
ds.Relations.Add("CustOrders", ds.Tables("Customers").Columns("CustID"), _
         ds.Tables("Orders").Columns("CustID"))

' Or
Dim dr As DataRelation = _
New DataRelation("CustOrders", ds.Tables("Customers").Columns("CustID"), _
         ds.Tables("Orders").Columns("CustID"))
ds.Relations.Add(dr)

[C#]
   DataSet ds = new DataSet("CustomerOrders");

   // With ds do the following:
   // - add a customer table with CustID and CustName as columns
   // - set Customers.CustID as Primary Key
   // - add an Orders table with three columns: OrderID, CustID, and Quantity
   // - set Orders.OrderID as primary key
  // Add a DataRelation to the Relations collection specifying its name,
  // and the appropriate DataColumn objects as arguments.

ds.Relations.Add("CustOrders",ds.Tables["Customers"].Columns["CustID"],
     ds.Tables["Orders"].Columns["CustID"]);
// Or
DataRelation dr;
dr = new DataRelation("CustOrders",ds.Tables["Customers"].Columns["CustID"],
                        ds.Tables["Orders"].Columns["CustID"]);
ds.Relations.Add(dr)


Navigate a Relationship from one table to another

A primary function of the DataRelation is to allow navigation from one table to another within the DataSet. In practice, this allows you to retrieve all of the related DataRow objects in one table when given a single DataRow from a related table. For example, if given a DataRow from the Customers table, you can retrieve all of the orders for a particular customer from the Orders table.

The code example below returns an array of DataRow objects (orders) from one table using the DataRelation and the single DataRow from another (customers).

[VB]
Dim x() As DataRow = ds.Tables("Customers").ChildRelations("CustOrders"). _
            GetChildRows(ds.Tables("Customers").Rows(0))
Console.WriteLine("")
Console.WriteLine("Total Child records for CustOrders Relationship = " & _
               x.Count.ToString)
Console.WriteLine("Total Tables in DataSet = " & ds.Tables.Count.ToString)
[C#]
DataRow[] x = ds.Tables["Customers"].ChildRelations["CustOrders"]
    .GetChildRows(ds.Tables["Customers"].Rows[0]);

Console.WriteLine("");
Console.WriteLine("Total Child records for CustOrders Relationship = " +
x.Count.ToString());

Console.WriteLine("Total Tables in DataSet = " + ds.Tables.Count.ToString());


Adding Constraints to a DataSet

In a relational database, it's imperative that the integrity of the data be maintained. One way of maintaining integrity in ADO+ is by adding constraints to tables. A constraint is an automatic rule that determines the course of action when the value of a row is somehow altered. There are two kinds of constraints found in ADO+: the ForeignKeyConstraint, and the UniqueConstraint.

ForeignKeyConstraint

When a value in a row is deleted or updated, and the value is also used in one or more related tables, a ForeignKeyConstraint determines what happens in the second table that is linked to the same column through a DataRelation. Possible actions include:

  • Cascade –deleting or updating the related rows.
  • SetNull—setting the values in the related rows to null.
  • SetDefault—setting the values in the related rows to default values.
  • None—no action—the values in the related rows are unaffected.
  • Default—the default action for the property—the default action is to cascade the action.

To use the ForeignKeyConstraint, create the ForeignKeyConstraint, then set the DeleteRule and UpdateRule properties to one of the appropriate values, as shown in the example below:

[VB]

Dim fk As ForeignKeyConstraint = _
New ForeignKeyConstraint(ds.Tables("Customers").Columns("CustID"), _
ds.Tables("Orders").Columns("CustID"))
fk.DeleteRule = Cascade
fk.UpdateRule = SetDefault
' Add the constraint to the Customers table.
ds.Tables(0).Constraints("Customers").Constraints.Add(fk)

[C#]

ForeignKeyConstraint fk = new ForeignKeyConstraint(ds.Tables
["Customers"].Columns["CustID"], ds.Tables["Orders"].Columns["CustID"]);
fk.DeleteRule = Cascade;
fk.UpdateRule = SetDefault;
ds.Tables["Customers"].Constraints.Add(fk);

UniqueConstraint

When it's imperative that all the values in a column must be unique, you can add a UniqueConstraint to the DataTable. The UniqueConstraint can be assigned either a single column, or an array of columns (of the same DataTable), and ensure that all values in the column are unique.

[VB]

Dim uc As UniqueConstraint = _
New UniqueConstraint(ds.Tables("Customers").Columns.("CustID"))
' Add the consstraint to the Customers table.
ds.Tables("Customers").Constraints.Add(uc)

[C#]

Data.UniqueConstraint uc = new
UniqueConstraint(ds.Tables("Customers").Columns.("CustID");
// Add the consstraint to the Customers table.
ds.Tables["Customers"].Constraints.Add(uc);


Working with DataSet Events

The DataSet provides a series of event that can be processed by the user's code. These event include:

  • PropertyChange
  • MergeFailed
  • RemoveTable
  • RemoveRelation
  • Adding the event handler to the event.

[VB]

ds.AddOnPropertyChange(new System.ComponentModel.PropertyChangeEventHandler _
(AddressOf me.DataPropertyChange))
ds.AddOnMergeFailed(new System.Data.MergeFailedEventHandler _
(AddressOf me.DataSetMergeFailed))

Private Sub DataSetPropertyChange _
   (ByVal sender As Object, ByVal e As System.PropertyChangeEventArgs)
   MessageBox.Show("DataSet property change")
End Sub

Private Sub DataSetMegeFailed _
   (ByVal sender As Object, ByVal e As System.Data.MergeFaileedEventArgs)
   MessageBox.Show("Merge failed!")
End Sub

[C#]

ds.AddOnPropertyChange
(new System.ComponentModel.PropertyChangeEventHandler(DataSetPropertyChange));
ds.AddOnMergeFailed
(new System.Data.MergeFailedEventHandler(DataSetMergeFailed));

private void DataSetPropertyChange
   (object sender, System.PropertyChangeEventArgs e){
   MessageBox.Show("DataSet property change")}

private void DataSetMergeFailed
   (object sender, System.Data.MergeFailedEventArgs e){
   MessageBox.Show("Merge failed!")}


Working with a Typed DataSet

ADO provides late bound access to values within its recordset through weakly typed variables. ADO+ provides the ability to access the data held in the DataSet through a "Strongly-Typed" metaphor. Meaning, you can access tables and columns that are part of the dataset through user-friendly names and strongly type variables. So, rather then doing :

A typed DataSet is a class that derives from DataSet. As such, it inherits all of the methods and properties of a DataSet. Additionally, however, a typed DataSet provides "strongly-typed" methods. In practice, this means you can access tables and columns by name, instead of using collection based methods. See also Section 1.2.3, "Programmability," in the introduction above.

For example, in ADO, you could use the following Visual Basic code to find a specific record in a recordset, and change the value of a field:

AdoRecordSet.Find("Customerid = 'ALFKI'")
AdoRecordSet.Fields("FirstName").Value = "Bob"

In a typed DataSet, the same result would be obtained with this code:

CustomerDataSet.Customers("ALFKI").CustomerName = "Bob"

Aside the from the improved readability of the code, a typed DataSet also allows the compiler to automatically complete lines as you type.

Additionally, the "strongly-typed" DataSet provides access to row value as the correct strongly typed value. In case of ADO, you worked through variants when assigning and retrieving data. If the value that you assigned was of the wrong type, ADO would give you a run-time error. With ADO+, if the value is integer and you attempt to assign a string, you will get a compile-time error.

Given an XML schema that complies with the XSD standard, you can generate a "typed" dataset using the XSD.exe tool provided with this SDK. The syntax for generating a dataset using this tool is:

xsd.exe /d /l:C# {XSDSchemaFileName.xsd}

The /d directive tell the tool to generate a dataset and the /l: tells the tool what language to use. For the language you can use either "C#" or "VB."

The example below uses a typed dataset call "customerDataSet" to load a list of customer from the Northwind database. Once the data is loaded using the FillDataSet method, we loop through each customer in the "customers" table using the typed customerRow object. You will notice that we have direct access to the "customerid" field as opposed to accessing it through the fields collection.

[C#]

customerDataSet myCustDS = new customerDataSet();
SQLDataSetCommand myDSCommand = new SQLDataSetCommand("SELECT * FROM
Customers", "server=localhost;uid=sa;pwd=;database=northwind");

myDSCommand.FillDataSet(myCustDS, "Customers");

foreach(customersRow myCustomer in myCustDS.customers)
{
Console.WriteLine(myCustomer.CustomerID.ToString());
}

[VB]

Dim myCustDS as DataSet = new dataset

Dim myDSCommand as SQLDataSetCommand
myDSCommand = new SQLDataSetCommand("Select * from customers",
"server=localhost;uid=sa;pwd=;database=northwind")

myDSCommand.FillDataSet(myCustDS, "Customers")

Dim myCustomer as DataRow

For Each myCustomer in myCustDS.Tables(0).Rows
Console.WriteLine(myCustomer(0).ToString())
Next


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Working with Managed Providers

© 2000 Microsoft Corporation. All rights reserved.


The Command, Connection and DataReader

The Command, Connection and DataReader represent the core elements of the ADO+ model. The Connection understands how to connect to a specific data store. Microsoft provides two Connection objects with NGWS frameworks: SQLConnection and the ADOConnection. The SQLConnection understands how to connect to Microsoft SQL Server Databases.


Managed Connections

The ADOConnection allows you to establish a connection through an OLE DB provider.

To use the managed providers that come with NGWS frameworks, you need to include the following namespaces:

  • SQL Managed Provider – System.Data.SQL
  • ADO Managed Provider – System.Data.ADO

The following code demonstrates creating and opening a connection to a SQL Server Database:

SQLConnection

[VB]

Dim connectionString As String = _ "server=localhost;uid=sa;pwd=;database=northwind"
Dim myConnection As SQLConnection = New SQLConnection(connectionString)
myConnection.Open()

[C#]

String connectionString = "server=localhost;uid=sa;pwd=;database=northwind";
SQLConnection myConnection = new SQLConnection(connectionString);
MyConnection.Open();

ADOConnection

[VB]

Dim connectionString As String = "Provider= SQLOLEDB.1; Data " & _
"Source=localhost; uid=sa; pwd=; Initial Catalog=northwind;"
Dim myConnection As ADOConnection = New ADOConnection(connectionString)
myConnection.Open()

[C#

String connectionString = "Provider= SQLOLEDB.1; Data Source=localhost;
uid=sa; pwd=; Initial Catalog=northwind;";
ADOConnection myConnection = new ADOConnection(connectionString);
MyConnection.Open();

The ADO and SQL Connection objects provide many of the properties that you are accustomed to with ADO.

Connection String Format – ADOConnection

For the ADO Managed Provider, the connection string format is identical to the connection string format used in ADO.

Connection String Format – SQLConnection

The SQL Managed Provider supports a connection string format that is a similar to the ADO connection string format. The following table summarizes the connection-string keywords supported by the SQLConnection object:

Name | Synonyms | Default | Description
App | "Application Name" | no default | Name of the application
AttachDBFilename | "extended properties", "Initial File Name" | no default | name of the primary file (include the full path name) of an attachable database. You must also specify the database name with the 'database' keyword.
Timeout | "Connection Timeout" | 15 | Length of time, in seconds, spent waiting to connect to the server before abandoning the attempt.
Database | "Initial Catalog" | no default | Database name (initial catalog)
Isolation Level <none> ReadCommitted The transaction isolation level for the connection. Can be one of the following strings:

ReadCommitted

ReadUncommitted

RepeatableRead

Serializable
Language | "Current Language" | no default | SQL Server Language record name
Network "Network Library", "Net" 'dbmssocn' Net-library used to establish a connection to a SQL Server in the organization. Valid values are:

dbmssocn

dbnmpntw

dbmsrpcn

dbmsvinn

dbmsadsn

dbmsspxn

Note that the corresponding network library dll must be installed on the system.
Password | "Pwd" | no default | SQL Server login password
Server | "Data Source", "Address", "Addr", "Network Address" | no default | Name or network address of a SQL Server in the organization
Trusted_Connection | "Integrated Security" | 'no' | Recognized values are 'yes' or 'no'. The value 'sspi' is also supported and equivalent to 'yes'.
Uid | "User id" | no default | SQL Server login record name
Wsid | "Workstation Id" | Current computer name. | Workstation identifier
PersistSecurityInfo "Persist Security Info" 'no' When equal to 'no', security-sensitive information such as the password is not returned as part of the connection string or from the corresponding property.

Note that this property cannot be changed while the connection is opened. An invalid operation exception will be thrown if this is attempted.

The value of this property may be changed on a closed connection, but the previously set password cannot be retrieved if this property had been set to 'no'. However, the password may be set and then retrieved, until the connection is reopened.


Managed Commands

Now that we can establish a connection, we need to be able to execute statements against the database. The simplest and most direct route for this is though the ADO and SQL Command objects.

ADOCommand

[VB]

Dim SQlStmt As String = "SELECT * FROM Customers"
Dim myCommand As ADOCommand = New ADOCommand(SQLStmt, myConnection)

[C#]

String SQLStmt = " SELECT * FROM Customers";
ADOCommand myCommand = new ADOCommand(SQLStmt, myConnection);

SQLCommand

[VB]

Dim SQlStmt As String = "SELECT * FROM Customers"
Dim myCommand As SQLCommand = New SQLCommand(SQLStmt, myConnection)

[C#]

String SQLStmt = " SELECT * FROM Customers";
SQLCommand myCommand = new SQLCommand(SQLStmt, myConnection);

With a command and connection, we can execute the query against the database. But first, one more object must be created before calling the command Execute method with the object as a parameter.

ADO

[VB]

Dim myReader As ADODataReader = Nothing
myCommand.Execute(myReader)

[C#]

ADODataReader myReader = null;
myCommand.Execute(out myReader);

SQL

[VB]

Dim myReader As SQLDataReader = Nothing
myCommand.Execute(myReader)

[C#]

SQLDataReader myReader = null;
myCommand.Execute(out myReader);

The example declares a variable that will hold the instantiated datareader that is returned as a parameter of the command's execute method.


The DataReader

When a large amount of data is being retrieved, the issue of holding memory open becomes a factor. For example, reading 10000 rows out of a database causes a DataTable to allocate and maintain memory for those 10000 rows for the lifetime of the table. If 1000 users do this against the same machine at the same time, memory usage becomes critical.

Also, if you are unsure of the results of a given query, and you need code to iterate over the stream, a DataTable is not ideal, as again, the entire steam would be loaded.

To resolve this, the DataReader is a read-only, forward-only steam returned from the database. Only one record at a time is ever in memory.

The DataReader is a forward-only, read-only accessor on top of a stream returned from the data store. You walk through the returned datareader in a very simple format:

[VB]

While myReader.Read
   ' Do something with the current row
End While

[C#]

While (myReader.Read())
{
   //do something with the current row
}

The DataReader also provides a series of Get methods that allow accessing the field values as its native time. Examples of this are: GetDataTime, GetDouble, GetGuid, GetInt32, GetStream.


Using stored procedures with a Command

When you are expecting to return only a single row, using a stored procedure with output parameters is probably the quickest way. When using SQL Server, or other procedure based databases, parameters can be used to retrieve a singleton row. Using parameters works just as in ADO; you can pass the string in the command, or use the parameters collection.

When using parameters with the SQLCommand, the names of the parameters added to the command's parameters collection must match the names of the parameter markers in the stored procedure. The SQL managed provider treats these as "named" parameters and will look for the matching marker.

[VB]

Dim myConnection As SQLConnection = New SQLConnection _
   ("server=delphi;uid=sa;pwd=;database=northwind")
Dim myCommand As SQLCOmmand = New SQLCommand _
   ("GetCustomerListbyState 'WA'", myConnection)
myCommand.CommandType = CommandType.StoredProcedure
Try
   myConnection.Open()
   myCommand.Execute(myReader)
   While myReader.Read
      Console.WriteLine(myReader("CompanyName").ToString))
   End While
   Catch e As Exception
      Console.WriteLine(e.ToString)
   Finally
      myReader.Close()
      myConnection.Close()
End Try

[C#]

SQLConnection myConnection = new
SQLConnection("server=delphi;uid=sa;pwd=;database=northwind");
SQLCommand myCommand = new SQLCommand("GetCustomerListbyState 'WA'", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;

try
{
 myConnection.Open();
 myCommand.Execute(out myReader);

 while (myReader.Read())
 {
      Console.WriteLine(myReader["CompanyName"].ToString());
 }
}
catch(Exception e)
{
 Console.WriteLine(e.ToString());
}
finally
{
 myReader.Close();
 myConnection.Close();
}

In the following code, we build a command that executes a stored procedure call named "GetComanyName." This stored procedure is declared in SQL Server as:

CREATE PROCEDURE GetCompanyName
@CustomerID nvarchar(5),
@CompanyName nvarchar(40) output
as
Select @CompanyName = CompanyName from Customers where CustomerID = @CustomerID

To execute this stored procedure, we need to do three things: (1) Create the Command, (2) set the command type to Stored procedure, and (3) create and set the parameters.

SQL

[VB]

Dim myConnection As SQLConnection = new _
SQLConnection("server=delphi;uid=sa;pwd=;database=northwind")
Dim myCommand As SQLCommand = New SQLCommand("GetCompanyName", myConnection)
myCommand.CommandType = CommandType.StoredProcedure
Dim workPara As SQLParameter = Nothing
workParam = myCommand.Parameters.Add
(New SQLParameter("@CustomerID", SQLDataType.NChar, 5))
workPara.Direction = ParameterDirection.Input
workParam.Value = "ALFKI"
workParam.myCommand.Parameters.Add
(New SQLParameter("@CompanyName", SQLDataType.NChar, 40))
workParam.Direction = ParameterDirection.Output
Try
   myConnection.Open()
   myConnection.Execute()
   Console.WriteLine("CompanyName = " & _
   myCommand.Parameters("@CompanyName").Value
Catch e As Exception
   Console.WriteLine(e.ToString)
Finally
   myConnection.Close()
End Try

[C#]

SQLConnection myConnection = new
SQLConnection("server=delphi;uid=sa;pwd=;database=northwind");

SQLCommand myCommand = new SQLCommand("GetCompanyName", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;

SQLParameter workParam = null;

workParam = myCommand.Parameters.Add(new SQLParameter("@CustomerID",
SQLDataType.NChar, 5));
workParam.Direction = ParameterDirection.Input;
workParam.Value = "ALFKI";

workParam = myCommand.Parameters.Add(new SQLParameter("@CompanyName",
SQLDataType.NChar, 40));
workParam.Direction = ParameterDirection.Output;

try
{
 myConnection.Open();
 myCommand.Execute();
 Console.WriteLine("CompayName= " +
myCommand.Parameters["@CompanyName"].Value);
}
catch(Exception e)
{
 Console.WriteLine(e.ToString());
}
finally
{
 myConnection.Close();
}

ADO

[VB]

Dim myCommand As ADOCommand = New ADOCommand("GetCompanyName", myConnection)
myCommand.CommandType = CommandType.StoredProcedure
Dim workPara As ADOParameter = Nothing
workParam = myCommand.Parameters.Add("@CustomerID", SQLDataType.NChar, 5)
workPara.Direction = ParameterDirection.Input
workParam.Value = "ALFKI"
workParam.myCommand.Parameters.Add("@CompanyName", SQLDataType.NChar, 40)
workParam.Direction = ParameterDirection.Output
Try
   myConnection.Open()
   myConnection.Execute()
   Console.WriteLine("CompanyName = " & _
   myCommand.Parameters("@CompanyName").Value
Catch e As Exception
   Console.WriteLine(e.ToString)
Finally
   myConnection.Close()
End Try

[C#]

ADOCommand myCommand = new ADOCommand("GetCompanyName", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;

ADOParameter workParam = null;

workParam = myCommand.Parameters.Add("@CustomerID", ADODBType.Char, 5);
workParam.Direction = ParameterDirection.Input;
workParam.Value = "ALFKI"

workParam = myCommand.Parameters.Add("@CompanyName", ADODBType.Char, 40);
workParam.Direction = ParameterDirection.Output;

try
{
 myConnection.Open();
 myCommand.Execute();
 Console.WriteLine("CompayName= " +
myCommand.Parameters["@CompanyName"].Value);
}
catch(Exception e)
{
 Console.WriteLine(e.ToString());
}
finally
{
 myConnection.Close();
}

We create the command as we've done before, pointing it to our connection. We set the commandtext property of the command through the class's constructor to the name of the stored procedure.

ADOCommand myCommand = new ADOCommand("GetCompanyName", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;

The other options would be to simply create a command and assign the connection and commandtext through their property accessors

ADOCommand myCommand = new ADOCommand();
myCommand.ActiveConnection = myConnection;
myCommand.CommandText = "GetCompanyName";
myCommand.CommandType = CommandType.StoredProcedure;

Next, we create two parameter objects and add them to the command's parameters collection.

SQL

SQLParameter workParam = null;

workParam = myCommand.Parameters.Add(new SQLParameter("@CustomerID",
SQLDataType.NChar, 5));
workParam.Direction = ParameterDirection.Input;
workParam.Value = "ALFKI";

workParam = myCommand.Parameters.Add(new SQLParameter("@CompanyName",
SQLDataType.NChar, 40));
workParam.Direction = ParameterDirection.Output;

ADO

workParam = myCommand.Parameters.Add("@CustomerID", ADODBType.Char, 5);
workParam.Direction = ParameterDirection.Input;
workParam.Value = "ALFKI";

workParam = myCommand.Parameters.Add("@CompanyName", ADODBType.Char, 40);
workParam.Direction = ParameterDirection.Output;

We do three things here: build and add the parameters, set the parameter direction and set the value for the input parameter.

You'll note that there is a difference in doing this between the SQLCommand and the ADOCommand. This simply because the models are not exactly alike at this moment.

Next, we open the connection and execute the command. Once finished, we simply retrieve the output parameter and write it to the console.

[VB]

myConnection.Open()
myCommand.Execute()
Console.WriteLine("CompanyName = " & _
myCommand.Parameters("@CompanyName").Value)

[C#]

myConnection.Open();
myCommand.Execute();
Console.WriteLine("CompanyName= " + myCommand.Parameters["@CompanyName"].Value);

The last important thing to note is the finally block. The finally block of the try-catch statement is always guaranteed to execute no matter what the exception.

[VB]

Finally
   myConnection.Close

[C#]

finally
{
 myConnection.Close();
}

With connection and datareaders, you should always use a finally block to ensure that if anything fails, they will still be closed.

Note that in this example we used an overload of the command's Execute method that doesn't return a DataReader. If you used return a datareader, and have output parameters, they won't be populated until after the datareader is closed.


What is a DataSetCommand?

In the past, data processing has been primarily connection based. Now, in an effort to make multi-tiered apps more efficient, data processing is turning to a message based approach that revolves around chunks of information. At the center of this approach for ADO+ is the DataSetCommand, which provides a bridge to retrieve and save data between a DataSet object and its source data store. It accomplishes this by invoking the appropriate SQL commands against the data store.

Both the SQLDataSetCommand and the ADODataSetCommand classes feature four "command" methods. These commands provide the Create, Read, Update and Delete functions for a specific DataTable in the DataSet. Both the SQLDataSetCommand and the ADODataSetCommand inherit from the DataSetCommand. Thus, both data set commands provides a mapping layer between the data store and the dataset.


Creating a Simple DataSetCommand

The basic function of a data set command is to pull data out of a data store and push it into a DataTable within the DataSet. To accomplish this, the data set command requires two pieces of information: the connection and the select command to execute.

In the following example we build a ADODataSetCommand by providing the select command text and an OLEDB Connection string:

[VB]

Dim workDSCMD As ADODataSetCommand
WorkDSCMD = New ADODataSetCommand _
("Select * from Customers", _
"Provider=SQLOLEDB.1;Initial Catalog=Northwind;" & _
"Data Source=MyServer;User ID=sa;")

[C#]

ADODataSetCommand workDSCMD = new ADODataSetCommand("Select * from
Customers", "Provider=SQLOLEDB.1;Initial Catalog=Northwind;
Data Source=MyServer;User ID=sa;");

The above code (1) creates a new ADOConnection object based on the connection string, (2) creates a new ADOCommand object based on the select statement and the newly created connection, and (3) assigns the new ADOCommand object to the Select command of workDSCMD.

Another way of doing this would be to create instances of each of the objects and then construct the data set command through it's properties.

[VB]

Dim workDSCMD As ADODataSetCommand
Dim workCMD As ADOCommand
Dim workConn As ADOConnection

workConn = New ADOConnection _
("Provider=SQLOLEDB.1;Initial Catalog=Northwind;" & _
"Data Source=MyServer;User ID=sa;")

workCMD = New ADOCommand("SELECT * FROM Customers", workConn)
workDSCMD = new ADODataSetCommand()
workDSCMD.SelectCommand = workCMD

[C#]

ADODataSetCommand workDSCMD;
ADOCommand workCMD;
ADOConnection workConn;

workConn = New ADOConnection("Provider=SQLOLEDB.1;Initial Catalog=Northwind;
      Data Source=MyServer;User ID=sa;");
workCMD = New ADOCommand("SELECT * FROM Customers", workConn);
workDSCMD = New ADODataSetCommand()
workDSCMD.SelectCommand = workCMD

We now have a DataSetCommand that can be used to fill a DataTable contained by a DataSet, but here follows a discussion detailing how to fill the table, and what actually happens when you do.

How you do accomplish this is easy: both the ADO and SQL DataSetCommand objects feature the FillDataSet method which takes two parameters, a DataSet instance and a source table name. The DataSet instance represents the DataSet to fill, and the source table name identifies the table inside of the DataSet.

In the following example, a new instance of a DataSet is created, and passed as an argument when invoking the FillDataSet method.

[VB]

Dim workDS As DataSet = New DataSet("myDataSet")
Dim workDSCMD As ADODataSetCommand = New ADODataSetCommand _
   (SELECT * FROM Customers", _
      "Provider=SQLOLEDB.1;Initial Catalog=Northwind;" & _
      "Data Source=MyServer;User ID=sa;")
workDSCMD.FillDataSet(workDS, "Customers")

DataSet workDS = new DataSet("mydataset");
ADODataSetCommand workDSCMD = new ADODataSetCommand("SELECT * FROM
Customers", "Provider=SQLOLEDB.1;Initial Catalog=Northwind;
Data Source=MyServer;User ID=sa;");

workDSCMD.FillDataSet(workDS, "Customers");

After invoking the FillDataSet method, the result is a populated table named "Customers" in the DataSet. The question is, how did the Customers table get into the DataSet since we didn't actually create it the prior to executing the FillDataSet method.

With both the SQL and ADO DataSetCommand objects, if you don't change the default settings, any schema (table/column/primary key definitions) from the data store that don't exist inside of the DataSet will be created automatically. So, in the example above, none of the schema from the Customers table existed previously.

Therefore, if you build the Customers table prior to executing the FillDataSet method, the DataSetCommand will simply fill the existing table.

Because there is no physical link between the DataSet and the DataSetCommand, the DataSetCommand can be used to fill any number of DataSet instances. Simply create the DataSet instance and pass it as an argument in the FillDataSet method.


Updating the Database with a DataSetCommand and the DataSet

It's mentioned in the previous section that the ADO and SQL DataSetCommands contains four Command objects. The first is the Select command, already covered. Now we'll cover the Insert, Update and Delete commands.

After making changes in the DataSet, you'll want to push those changes back to the database. You do this by calling the Update method of the DataSetCommand. The Update method, like the FillDataSet method takes an instance of a DataSet and the source table name. The DataSet instance is the DataSet that contains the changes that you've made, while the source table name identifies the DataTable from which to pull the changes.

When the FillDataSet method is called, it analyzes the changes that have been made and executes the appropriate command. The DataSet takes the changes in the order they are presented. So, if you want to have all of the updates done prior to deletes, you will need to filter them before sending them down to the Update method.

[VB]

Dim workDS As DataSet = New DataSet("myDataSet")
Dim workDSCMD As ADODataSetCommand = New ADODataSetCommand _
("SELECT * FROM Customers", _
"Provider=SQLOLEDB.1;Initial Catalog=Northwind;" & _
"Data Source=MyServer;User ID=sa;")
workDSCMD.FillDataSet(workDS, "Customers")
' Make some changes to the customer data in the DataSet.
workDSCMD.Update(workDS, "Customers")

[C#]

DataSet workDS = new DataSet("mydataset");
ADODataSetCommand workDSCMD = new ADODataSetCommand("SELECT * FROM
Customers", "Provider=SQLOLEDB.1;Initial Catalog=Northwind;
Data Source=MyServer;User ID=sa;");
workDSCMD.FillDataSet(workDS, "Customers");
// Make some changes to the customer data in the DataSet.
workDSCMD.Update(workDS, "Customers");

This is a good place to recall how "removing" differs from "deleting" (see Removing Versus Deleting Data from a Table). If you actually "remove" the row from the collection, the DataSetCommand won't see that row when you update the DataSet. Thus it won't execute any command for that row. However, if the row is marked as "deleted," it will the Delete command will be invoked.


Setting up Table and Column Mappings

In the examples in the previous sections, we used the source table parameter to identify the table to fill or to pull changes from. Additionally, when we allowed the DataSetCommand to create the table on the fly, we accepted the schema defined in the database.

We can change these default behaviors by creating a tablemapping and/or columnmappings. The following example shows creating a tablemapping using "Table" as the source table name and "MyAuthors" as the DataTable name.

[VB]

workDSCMD.TableMappings.Add("Table", "MyAuthors")

[C#]

workDSCMD.TableMappings.Add("Table", "MyAuthors");

What are tablemappings and what are they used for? A tablemapping provides a master mapping between the data returned from the table in the database, and the DataTable inside the DataSet.

In the above example, we've created a virtual link between the table in the database (specified in the select and given a default name of "Table") and the table in the DataSet called "MyAuthors."

In the earlier samples, when we passed in the source table name, we created a tablemapping on the fly. Its as if we actually used the source table for both parameters of the TableMappings.Add method. Only the tablemapping that we create is never added to the tablemappings collection.

So what's the advantage of creating this tablemapping? The big advantage is in the ability to change the names of the columns you work with from their names in the database. In the examples below, we map the columns from the Authors table of the Pubs database to a set of more user-friendly names in the DataSet. Since we've created the mapping, the DataSet knows how to match these back together when we update the table.

[VB]

With workDSCMD.TableMappings.Item(0).ColumnMappings
   .Add("au_id", "AuthorID")
   .Add("au_lname", "lastname")
   .Add("au_fname", "firstname")
   .Add("phone", "phone")
   .Add("address", "address")
   .Add("city", "city")
   .Add("state", "state")
   .Add("zip", "zip")
   .Add("contract", "contract")
End With

[C#]

workDSCMD.TableMappings[0].ColumnMappings.Add( "au_id", "AuthorID");
workDSCMD.TableMappings[0].ColumnMappings.Add( "au_lname", "lastname");
workDSCMD.TableMappings[0].ColumnMappings.Add( "au_fname", "firstname");
workDSCMD.TableMappings[0].ColumnMappings.Add( "phone", "phone");
workDSCMD.TableMappings[0].ColumnMappings.Add( "address", "address");
workDSCMD.TableMappings[0].ColumnMappings.Add( "city", "city");
workDSCMD.TableMappings[0].ColumnMappings.Add( "state", "state");
workDSCMD.TableMappings[0].ColumnMappings.Add( "zip", "zip");
workDSCMD.TableMappings[0].ColumnMappings.Add( "contract", "contract");

For the ADO and SQL DataSetCommands, When the Update or FillDataSet method is called without a source table, the DataSetCommand looks for a mapping with a source table named "Table." It then takes the DataSet table name from the mapping and uses it to either find the table in the DataSet object's table collection, or add a new table to the collection using that value for its name.

[VB]

Dim myDS As DataSet = New DataSet()
Dim myConnection As ADOConnection = New ADOConnection _
("Provider=SQLOLEDB;Data Source=Delphi;Initial Catalog=pubs;user id=sa")
Dim workDSCMD As ADODataSetCommand = New ADODataSetCommand _
("SELECT * FROM Authors", myConnection)
workDSCMD.TableMappings.Add("Table", "Authors")
With workDSCMD.TableMappings(0).ColumnMappings
   .Add("au_id", "AuthorID")
   .Add("au_lname", "lastname")
   .Add("au_fname", "firstname")
   .Add("phone", "phone")
   .Add("address", "address")
   .Add("city", "city")
   .Add("state", "state")
   .Add("zip", "zip")
   .Add("contract", "contract")
End With
workDSCMD.FillDataSet(myDS)

[C#]

DataSet myDS = new DataSet();
ADOConnection myConnection = new ADOConnection("Provider=SQLOLEDB;Data Source=Delphi;Initial Catalog=pubs;user id=sa");
ADODataSetCommand workDSCMD = new ADODataSetCommand("SELECT * FROM Authors", myConnection);
   workDSCMD.TableMappings.Add("Table", "Authors");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "au_id", "AuthorID");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "au_lname", "lastname");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "au_fname", "firstname");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "phone", "phone");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "address", "address");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "city", "city");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "state", "state");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "zip", "zip");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "contract", "contract");
workDSCMD.FillDataSet(myDS);

In example above, we create a tablemapping and make it the default by naming it "Table." When we call the Update or FillDataSet commands without a source table name, the DataSetCommand will use this mapping.

In more advanced situations, you may decided that you want the same DataSetCommand to support loading different tables with different mappings. We can do this simply by adding additional tablemappings.

In the example below, we create a tablemapping with a source table name of "bob" and a DataSet table name of "King."

[VB]

workDSCMD.TableMappings.Add("bob", "King")
With workDSCMD.TableMappings(0).ColumnMappings
   .Add("au_id", "AuthorID")
   .Add("au_lname", "lastname")
   .Add("au_fname", "firstname")
   .Add("phone", "phone")
   .Add("address", "address")
   .Add("city", "city")
   .Add("state", "state")
   .Add("zip", "zip")
   .Add("contract", "contract")
End With
workDSCMD.FillDataSet(myDS, "bob")

[C#]

   workDSCMD.TableMappings.Add("bob", "King");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "au_id", "AuthorID");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "au_lname", "lastname");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "au_fname", "firstname");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "phone", "phone");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "address", "address");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "city", "city");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "state", "state");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "zip", "zip");
   workDSCMD.TableMappings[0].ColumnMappings.Add( "contract", "contract");
   workDSCMD.FillDataSet(myDS, "bob");

Call the FillDataSet method, passing in an instance of a DataSet and a reference to the source table. The DataSetCommand looks in the tablemappings collection to see if a mapping with that source table name exists. If so, it uses that mapping to build and fill the table. But if the source table name doesn't exist in the collection, it will build the TableMappings on the fly.

In the example above we created a mapping that will cause the records brought back from the Select command to be mapped into a table called "King."


Mapping/Changing a Column's mapping at runtime

Even though the TableMappings collection provides a strong mechanism for mapping tables and columns in the database to table and columns in the DataSet, there may be times that you want to dynamically change this mapping at runtime. To support this behavior there is the SchemaMappingEvent. At the point where the DataSetCommand builds its internal structure defining what the result of the mappings are, we provide access to the those internal mapping structures.

As an example imagine a stored procedure that returns a column without a name by performing the following select:

   SELECT @@IDENTITY

This would normally cause an error while trying to load the result into the DataSet, since the DataSet requires all columns to have a name. Since it doesn't have a name, how do I map it to a column within a DataTable?

The examples below works against the SQLSchemaMappingEvent to change the incoming column name from an empty value to "someuniquevalue".

In this case, the event is actually working against the SQLDataSetCommand. If this were being generated by the ADODataSetCommand, you would use the ADOSchemaMappingEvent declaration.

[VB]

Private Sub SchemaMappingEventHolder(ByVal sender As Object, ByVal e As _
SQLSchemaMappingEvent)
   Dim column As DataColumn = e.SchemaTable.Columns("DBCOLUMN_NAME")
   Dim row As DataRow
   For Each row in e.SchemaTable.Rows.All
      If row.RowState = DataRowState.Deleted Then
         row.RejectChanges()
      End If
      If row.IsNull(column) Or 0 = row(column).ToString Then
         column = e.SchemaTable.Columns("DataColumn")
         row(column) = New DataColumn("SomeUniqueName")
       End If
    Next
End Sub

[C#]

private void SchemaMappingEventHandler(object sender, SQLSchemaMappingEvent e)
{
 DataColumn column = e.SchemaTable.Columns["DBCOLUMN_NAME"];

 foreach(DataRow row in e.SchemaTable.Rows.All)
 {
  if(DataRowState.Deleted == row.State)
  {
     row.RejectChanges();
     }
 if (row.IsNull(column) || 0 == row[column].ToString())
 {
// unnamed column
column = e.SchemaTable.Columns["DataColumn"];
row[column] = new DataColumn("SomeUniqueName");
     }
 }
}


Using Parameters with DataSetCommands

So how do I setup a DataSetCommand to use a parameterized query or stored procedure for the update method? Basically you do the exact same thing you would do to get these types of statements to work with a Command Object (see section 4.2.5 Using stored procedures with a Command). The only additional thing that you need to consider is the rowversion that you want to use in each parameter.

In the three strings below, we establish the parameterized queries to do the insert, update and delete actions for an ADODataSetCommand (that why we have ? for parameter markers).

string insertSQL = "INSERT INTO [Customers]([CustomerID], [CompanyName],
[ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax]) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";

string updateSQL = "UPDATE [Customers] SET [CustomerID] = ?, [CompanyName] = ?,
[ContactName] = ?, [ContactTitle] = ?, [Address] = ?, [City] = ?,
[Region] = ?, [PostalCode] = ?, [Country] = ?, [Phone] = ?, [Fax] = ?
WHERE [CustomerID] = ? ";
string deleteSQL = "DELETE FROM [Customers] WHERE [CustomerID] = ? ";

When I build the parameters collection we need to set six items on each of the parameters. The first three we can do through the either the Parameters.Add method or the Parameter constructor. In the line below we set the parameter's name "CustomerID" in the parameters collection; the data type and the size.

workParam = workCommand.Parameters.Add("CustomerID", ADODBType.WChar, 5);

For intrinsic type like integer we don't need to include the size or simply set to the default size

workParam = workCommand.Parameters.Add("FldItemID", ADODBType.Integer, 4);

The item is to set the direction of the parameter.

workParam.Direction = ParameterDirection.Input;

The Parameter Direction enum support the following values:

    public enum ParameterDirection {
        Input       = 1,
        Output      = 2,
        InputOutput = 3,
        ReturnValue = 6
    }

The last two items that we need to set are SourceColumn and SourceVersion. SourceColumn tells the DataSetCommand which column in the table is going to provide its value. To set this, you assign the name of the column as its held in the DataSet's table.

workParam.SourceColumn = "CustomerID";

The SourceVersion tells the DataSetCommand which version of the it should actually pull the value from. The options for this are:

    public enum DataRowVersion {
        Default     = 0,
        Original =  1,
        Current  =  2,
        Proposed =  3,
    }

An example of how these values fit in, is the update statement defined above. You'll notice that the "CustomerID" column is used in two place. In the "update" part of the statement and the where clause. In using this statement to change the value a primary from "ALFKI" to "AAAAA" we have to be able to say: "Update the row whose primary key is currently "ALFKI" to have a primary key of AAAA." So in this case we set all of the parameters in the upate part to use the DataRowVersion.Current. But we set the where clause parameters to use DataRowVersion.Original.

ADOParameter workParam = null;

workParam = workCommand.Parameters.Add("CustomerID", ADODBType.WChar, 5);
workParam.Direction = ParameterDirection.Input;
workParam.SourceColumn = "CustomerID";
workParam.SourceVersion = DataRowVersion.Current;

workParam = workCommand.Parameters.Add("CompanyName", ADODBType.VarWChar, 40);
workParam.Direction = ParameterDirection.Input;
workParam.SourceColumn = "CompanyName";
workParam.SourceVersion = DataRowVersion.Current;

.. all the other parameters then

workParam.SourceVersion = DataRowVersion.Current;
workParam = workCommand.Parameters.Add("oldCustomerID", ADODBType.WChar, 5);
workParam.Direction = ParameterDirection.Input;
workParam.SourceColumn = "CustomerID";
workParam.SourceVersion = DataRowVersion.Original;


Input / OutPut Paramaters and Return Values

With stored procedures, you can have return values in addition to input and output parameters. ADO+ treats a return value from an stored procedure just like an output value. The following stored procedure takes a customerID as an input parameter, returns the companyname as an output parameter, and has a return value.

CREATE PROCEDURE GetCompanyName
@CustomerID nvarchar(5),
@CompanyName nvarchar(40) output
as
Select @CompanyName = CompanyName from Customers where CustomerID = @CustomerIDGO

Return 99

The example bellows shows the code to call the GetCompanyName stored procedure.

[VB]

Imports System
Imports System.Data
Imports System.Data.ADO

Namespace ConsoleApplication1
Module Module1
Shared Sub Main()
   Console.WriteLine("Requires a CustomerID to be passed in as a argument")
   Console.WriteLine("example: OutParamsWithACommand ALFKI")

   ' Create a new Connection and DataSetCommand
   Dim myConnection As ADOConnection = New ADOConnection _
   ("provider=SQLOLEDB;database=northwind;data source=delphi;user id=sa;")
   Dim myCommand As ADOCommand = New ADOCommand("GetCompanyName", myConnection)
   myCommand.CommandType = CommandType.StoredProcedure;

   Dim workParam As ADOParamater = Nothing

   workParam = myCommand.Parameters.Add("RETURN VALUE", ADODBType.Integer)
   workParam.Direction = ParameterDirection.ReturnValue

   workParam = myCommand.Parameters.Add("@CustomerID", ADODBType.Char, 5)
   workParam.Direction = ParameterDirection.Input
   workParam.Value = args(0)

   workParam = myCommand.Parameters.Add("@CompanyName", ADODBType.Char, 40)
      workParam.Direction = ParameterDirection.Output
   Try
      myConnection.Open()
   myCommand.Execute()
      Console.WriteLine("CompanyName= " & _
          myCommand.Parameters("RETURN_VALUE").Value)
      Console.WriteLine("CompanyName= " & _
         myCommand.Parameters("@CompanyName").Value)
   Catch e As Exception
      Console.WriteLine(e.ToString)
   Finally
      myConnection.Close

End Sub
End Module
End Namespace

[C#]

using System;
using System.Data;
using System.Data.ADO;

class ConsoleApplication
{
 public static void Main(String[] args)
 {

   if (args.Length < 1 )
   {
     Console.WriteLine("Requires a CustomerID to be passed in as a argument");
     Console.WriteLine("example: OutParamsWithACommand ALFKI");
     return 0;
   }

   // Create a new Connection and DataSetCommand
    ADOConnection myConnection = new
 ADOConnection("provider=SQLOLEDB;database=northwind;data
source=delphi;user id=sa;");

   ADOCommand myCommand = new ADOCommand("GetCompanyName", myConnection);
   myCommand.CommandType = CommandType.StoredProcedure;

   ADOParameter workParam = null;

   workParam = myCommand.Parameters.Add("RETURN_VALUE", ADODBType.Integer);
   workParam.Direction = ParameterDirection.ReturnValue;

   workParam = myCommand.Parameters.Add("@CustomerID", ADODBType.Char, 5);
   workParam.Direction = ParameterDirection.Input;
   workParam.Value = args[0];

   workParam = myCommand.Parameters.Add("@CompanyName", ADODBType.Char, 40);
   workParam.Direction = ParameterDirection.Output;

   try
   {
    myConnection.Open();
 myCommand.Execute();
  Console.WriteLine("CompanyName= " +
    myCommand.Parameters["RETURN_VALUE"].Value);
    Console.WriteLine("CompanyName= " +
myCommand.Parameters["@CompanyName"].Value);
   }
   catch(Exception e)
   {
    Console.WriteLine(e.ToString());
   }
   finally
   {
  myConnection.Close();
   }
 }
}

The only requirement to get the return value from a stored procedure is to mark the direction of the first parameter (in the parameters collection) as output.


Optimistic Concurrency

There are two basic way to do update to data within a database: Optimistic and Pessimistic Concurrency.

Pessimistic concurrency is a system of locks that prevents users from modifying data in a way that affects other users. In a pessimistic model, when a user performs an action that causes a lock to be applied, other users can't perform actions that would conflict with the lock until the lock owner releases it. This model is mostly used in environments where there is high contention for data, where the cost of protecting data with locks is less than the cost of rolling back transactions if concurrency conflicts occur.

Thus in a pessimistic model, when a user reads a record with the intention of changing it, they establish a lock. Until that user has finished that update and released that lock, no one else can change that record.

With Optimistic concurrency, users do not lock data when read it. When a user wants to update a record, it is the application's responsibility to check if another user has changed the record since it was read. Optimistic concurrency is generally used in environments with a low contention for data.

In an optimistic model, there are traditionally three ways of identifying changes made to the record. The first is to simply compare all of the column values that were read from the database to the current values.

For example:

1:00 PM. User1 reads a row from the database with the following values:

CustID LastNameFirstName

101SmithBob

Column Name | Original Value | Current Value | Value in DB
CustID | 101 | 101 | 101
LastName | Smith | Smith | Smith
FirstName | Bob | Bob | Bob

1:01 PM. User2 reads the same row

1:03 PM. User2 changes the first name from "Bob" to "Robert" and updates the database.

Column Name | Original Value | Current Value | Value in DB
CustID | 101 | 101 | 101
LastName | Smith | Smith | Smith
FirstName | Bob | Robert | Bob

Since the values in the database at the time of update match the original values that user2 has, the update succeeds.

1:05 PM. User1 changes Bob's first name to "James" and tries to update the row.

Column Name | Original Value | Current Value | Value in DB
CustID | 101 | 101 | 101
LastName | Smith | Smith | Smith
FirstName | Bob | James | Robert

Since the original value for user1 is "Bob," and the value in since user2's update is "Robert," user1's update fails.

The second and third ways of performing optimistic concurrency are actually very similar: either use a date-time stamp column, or a row version column. Whenever a user successfully updates a row, the date-time stamp (or row version) column is set to a new, unique value.

Thus, wherever a user attempts to update a row, the application compares the date-time stamp (or row version) value between the original and the current version in the database. If the values are identical, the row hasn't changed since it was read, and the update can proceed (resulting in a new value for the date-time or row version column).

To perform this task with SQL syntax is simple –

Update Table1 set x = newx1, y = newy where datetimestamp = originaldatetime

Or

Update Table1 set x = newx1, y = newy where rowversion = originalrowversion

The other option is more performance oriented: setup a stored procedure to handle updates.

To keep the datetimestamp/version accurate, set up a trigger on the table to update their values anytime a change to a row occurs.

So how do we do it? VS7 and the DataSetCommand Configuration Wizard (DCW) provides automatic support for this if you use it to create stored procedures. The example below represents an update that the DCW generated for the authors table of the pubs database. You will notice that the parameter list of this stored procedure actually contains two sets of parameters. The second set (@param1 - @param9) is simply a copy of the first 9 parameters.

CREATE PROCEDURE AuthorUpdate
(
   @au_id id,
   @au_lname varchar(40),
   @au_fname varchar(20),
   @phone char(12),
   @address varchar(40),
   @city varchar(20),
   @state char(2),
   @zip char(5),
   @contract bit,
   @Param1 id,
   @Param2 varchar(40) /* Optimistic Concurrency Check */,
   @Param3 varchar(20) /* Optimistic Concurrency Check */,
   @Param4 varchar(40) /* Optimistic Concurrency Check */,
   @Param5 varchar(20) /* Optimistic Concurrency Check */,
   @Param6 bit /* Optimistic Concurrency Check */,
   @Param7 char(12) /* Optimistic Concurrency Check */,
   @Param8 char(2) /* Optimistic Concurrency Check */,
   @Param9 char(5) /* Optimistic Concurrency Check */
)
AS
   SET NOCOUNT OFF;
   UPDATE authors SET au_id = @au_id, au_lname = @au_lname, au_fname = @au_fname, phone = @phone, address = @address, city = @city, state = @state, zip = @zip, contract = @contract WHERE (au_id = @Param1) AND (address = @Param2) AND (au_fname = @Param3) AND (au_lname = @Param4) AND (city = @Param5) AND (contract = @Param6) AND (phone = @Param7) AND (state = @Param8) AND (zip = @Param9);

The second set of parameters holds the original values of the fields as they were loaded from the data store. This is used to perform the comparison in the "WHERE" clause of the update statement.

When we loaded the DataSet, the Authors record looked like:

   Au_id = "999-99-9999"
   Au_Lname = "Smith"
   Au_Fname = "Jane"
   Phone = "111 111-1111"
   Address = "123 Main"
   City = "Redmond"
   State = "WA"
   Zip = "98052"
   Contract = 1

Our update changed the phone number to "222 111-1111." However, prior to our update, but after we loaded it, another user else changed Jane's last name from "Smith" to "Doe." When we execute our update, no records will be affected because the au_lname column no longer matches the original value.

Thus in our form code or business object we can check the number of rows affected to see if our transaction succeeded or failed because no matching record was found.


Doing transactions

Database transactions are used to control data commitment to databases. For example, in standard account procedures, it is necessary to debit one account and credit another at the same time. Since computers fault on occasion (power outages, network outages, etc.) there is a potential for one record to be updated/added, but not the other. To avoid these situations we use transactions. Transactions in ADO+ are, just as in ADO, handled at the database level: your database must support transactions.

There are three basic commands for transactions: begin, commit and rollback. "Begin" marks the beginning of a transaction. All procedures attempted between "Begin" and the next command (either "Rollback" or "Commit") are considered part of the transaction. Consider the following pseudo T-SQL command:

begin transaction

insert into account (account,amount,debitcredit) values (100,100,'d')
insert into account (account,amount,debitcredit) values (300,100,'c')

if (@@ERROR > 0)
rollback

else
commit

In ADO+ (as in ADO), you can control transactions through the Connection object. In fact, when you use the ADOConnection, it uses the same underlying OLEDB transaction model. Therefore, if you were able to do transactions against your database with ADO, you can do them with ADO+, too. The sample code below uses the SQLConnection and SQLCommand to update a Customer record, and add an order. If either action fails, the records are rolled back.

**Important Note***
The DataSet object also has a commit model (AcceptChanges,RejectChanges) but it doesn't not affect the database. This commit model is for the cache of data in the DataSet alone.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Accessing XML and Relational Data

[Information in this document is accurate as of 5/26/2000]


Overview

The new XML and ADO+ frameworks provide a programming model, and the classes necessary to access data represented as either relational data, or XML data.

Relational data is represented as sets of tables, consisting of rows of columns. Each column has a name, data type, and other static self-descriptive information and attributes; and each row consists of repeating sets of columns. The intersection of a row and a column, sometimes called a field, contains a value of that column's data type. Tables may be associated with each other through constraints and relationships on their columns, enabling complex data representations.

XML data is represented textually by the data values themselves, delimited by tags that dynamically identify structure inherent to the data, and self-descriptive information such as the name and attributes of the data.

Relational data efficiently represents rigidly repeating sets of values, such as information in enterprise file and database systems. XML data efficiently represents information with possibly varying structure, such as data in e-mail systems and the Web.

The XML and ADO+ frameworks provide a common means of working with data regardless of its source. Information input as relational data can be accessed as if the source were XML data, and information input as XML data can be accessed as if the source were relational data. The programming model is straight-forward, and the object model delivers high performance.

The classes that comprise the XML and ADO+ frameworks enable three broad scenarios:

  • Accessing relational data
  • Manipulating XML documents
  • Working with XML documents that have relational content

The next sections will provide details about the programming model for each of these scenarios.


Accessing Relational Data

A detailed conceptual discussion about accessing relational data is provided elsewhere in this documentation. (See Introduction to Distributed Applications and Data Integration)

Working examples showing different aspects of accessing relational data, including source code, are provided elsewhere in this documentation. (See NGWS SDK Quickstart Tutorial, How Do I…?, ADO+ Data)


Accessing XML Data

The following section offers a more detailed discussion of the concepts and classes used to access XML data.

Working examples showing different aspects of accessing XML data, including source code, are provided elsewhere in this documentation. (See NGWS SDK Quickstart Tutorial, How Do I…?, XML Data)


Accessing XML Data with an XmlDocument

The XML and ADO+ frameworks object model provides two implementations of an XML document store:

XmlDocument is a base class that stores unstructured XML as nodes.

XmlDataDocument is a derived class that uses an associated DataSet to optimize storage of structured XML and support relational access to the mapped XML. Because it is a derived class, the XmlDataDocument can be used anywhere the XmlDocument is used.

In either case, XML data is read with an XmlTextReader object into the XML document (the XmlDocument base class, or the derived XmlDataDocument which optimizes storage and access to structured XML).

Applications accessing the data in an XML Document with XML semantics (such as XPath and XslTransform) use either the methods, properties, and events of an XmlNavigator (the DocumentNavigator in the case of the XmlDocument or the DataDocumentNavigator in the case of the XmlDataDocument). Alternatively, the application may access the data using the the XmlDocument (or XmlDataDocument) itself.

An XmlTextWriter object is used to output data in XML format. This object offers precise control over how the data is output, and convenience features to simplify the process.


Reading XML

The following classes may be used to read XML data.

XmlUrlResolver

The XmlUrlResolver class (System.NewXml.XmlUrlResolver) resolves external XML resources needed to correctly parse input, which are named by a URI. It is used by the XmlTextReader to load DTDs, entities, and XDR, XSD, or namespace schemas. It is also used by XslTransform to process "<xsl:include>" elements.

XmlReader and XmlTextReader

The abstract XmlReader class (System.NewXml.XmlReader) defines methods to input XML documents conforming to the W3C XML 1.0 and Namespaces specifications.

The XmlTextReader class (System.NewXml.XmlTextReader) extends XmlReader. This class obtains XML data in a forward-only manner from a specified stream object, TextReader (System.IO.TextReader) object, or URL that identifies a Web site or local file.

An XmlUrlResolver is used, if one is available, when external DTDs or schemas are required for validation.

XmlTextReader has methods and properties to:

  • Read entire XML content, in those situations where the content is available from beginning to end, such as in the case of a file of XML text
  • Read XML content as it becomes available (for example, a stream opened on the live feed from a stock ticker)
  • Read individual components of the XML text based on the component's data type. For example, there are strongly-typed methods that read 16-, 32-, and 64-bit integers; characters, Booleans, dates, currency, and so on
  • Read node attributes and properties
  • Move through the input text or stream
  • Handle tags, namespaces, and entities
  • Perform schema and DTD validation

XmlNode

The XmlNode class (System.NewXml.XmlNode) represents a node as defined in the W3C Document Object Model (DOM). DOM level 1, level 2, and Microsoft enhancements are supported. XmlNode encapsulates the structural relationships of nodes.

The XmlNode class deals with concepts such as parent, child, and sibling nodes; and structural operations such as inserting, replacing, and removing nodes.

XmlDocument and XmlDataDocument

The XmlDocument class (System.NewXml.XmlDocument) extends XmlNode, and represents a document as defined in the W3C Document Object Model (DOM). DOM level 1, level 2, and Microsoft enhancements are supported. The document exists as a "tree" of nodes.

The XmlDataDocument class (System.NewXml.XmlDataDocument) extends XmlDocument. The XmlDataDocument can be associated with a DataSet in order to optimize storage of and access to structured data within an XML document. This structured data is exposed through the DataSet and may be accessed, bound, and updated through the DataSet without compromising the full fidelity of the source XML. Since the XmlDataDocument derives from the XmlDocument, it can be used anywhere that an XmlDocument can be used. When the user works with data through the XmlDataDocument (as opposed to the DataSet), the user deals with the document as a whole: the nodes of the entire document are present and available for processing and the user can reference several nodes simultaneously.

Methods of this class can create standard XML features such as document type, CDATA, comment, entity reference, processing instruction, and text elements. Microsoft enhancements provide methods to load the document from a stream, file, or XmlReader derived class; or save the document to a stream or file.

Note that an XmlDataDocument class object has a property that returns a DataSet object, and an existing DataSet object can be used in the construction of a new XmlDataDocument. These two objects provide relational and XML representations of the same data. Together, they enable the user to easily go back and forth between XML and relational data.

As part of XSD schema support, the DataSet class supports mappings, which enable the user to explicitly associate some feature of data in the XML representation (for example, an attribute in a tag) to a feature in the relational representation (for example, a field), and vice versa.

XmlDataDocument adds methods and properties to:

  • Construct a new XmlDataDocument from an existing DataSet
  • Return a DataSet from the XmlDataDocument to expose a relational view over the structured content of the XML document
  • Return a DataRow object (System.Data.DataRow) when given an XmlElement object (System.NewXml.XmlElement), and vice versa
  • Enable and disable event notification

XslTransform

The XslTransform class (System.NewXml.XslTransform) implements the W3C XSL Transformations (XSLT) specification. XslTransform can load an XSLT stylesheet using an XmlReader or XmlNavigator object, transform the input data, then write the results using an XmlWriter or stream object. Objects of the XslTransform class are free-threaded and stateless so they can be shared.

XmlNavigator, DocumentNavigator, and DataDocumentNavigator

The abstract XmlNavigator class (System.NewXml.XmlNavigator) defines an efficient means of examining and editing XML data. The XmlNavigator class also supports XPath expressions, and may be used as an input argument to XslTransform class objects. Data in an XML document may represent many nodes; however, the XmlNavigator uses a cursor-model that only presents one node, the "current node", at any given time.

The XmlNavigator class is an abstract base class. You use the following derived classes to access XmlDocument and XmlDataDocument objects:

  • The DocumentNavigator class (System.NewXml.DocumentNavigator) is derived from XmlNavigator. The DocumentNavigator accesses the data in an XmlDocument.
  • The DataDocumentNavigator class (System.NewXml.DataDocumentNavigator) is derived from XmlNavigator. The DataDocumentNavigator accesses the data in an XmlDataDocument. The DataDocumentNavigator class knows what a mapped DataSet is; and if given a specific row (that is, a DataRow object), will move to the corresponding XML element.

Both the DocumentNavigator and DataDocumentNavigator have the following methods and properties to:

  • Delete the current node of the underlying XmlDocument or XmlDataDocument. This feature is not supported for XmlDataDocument in this release
  • Move, insert, and copy nodes and sub-nodes from other node "trees" to the current node (the "current node" remains in effect for a move or copy operation). This feature is not supported for XmlDataDocument in this release
  • Create clones, each of which can navigate independently over the XmlNavigator.
  • Set and retrieve node properties and attributes.
  • Select a subset of nodes using an XPath expression
  • Evaluate XPath expressions
  • Check whether the current node matches a specified XPath expression
  • Move the "current node" position to a specific node (such as first, last, next, child, parent, and so on); a node identified by its attribute; a node within an subset specified by an XPath expression; or a node corresponding to a row
  • Remember one or more locations of the "current node" (on a stack) and move back to them later


Writing XML

The following classes may be used to write XML data.

XmlWriter and XmlTextWriter

The abstract XmlWriter class (System.NewXml.XmlWriter) defines methods to output XML documents conforming to the W3C XML 1.0 and Namespaces specifications. These methods merge text specified by the user with standard text appropriate for headers and element types, as well as determining encoding (such as UTF-8), and so on.

The XmlTextWriter class (System.NewXml.XmlTextWriter) extends XmlWriter. The XmlTextWriter class defines methods that write XML data in a forward-only manner to streams, files, and TextWriter objects.

XmlTextWriter has methods and properties to:

  • Write a syntactically valid XML declaration and version number, document type, specific start and end tags with namespace decoration, comments, attributes, CDATA, processing instructions, entities and entity references, strings of text, raw markup text (useful when re-routing segments of XML), and so on
  • Encode text in base-64, and hexadecimal in appropriate escape characters
  • Manage the output, including means to report output state, write multiple documents to one output stream, and flush or close the output
  • Report the current namespace prefix, or language or space scope
  • Write valid names, qualified names, and name tokens
  • Write values or attributes with specific data types such as Boolean; date and time; decimal, single and double precision floating point, and 16-, 32-, or 64-bit integer numbers
  • Copy the entire input to the output
  • Properties specifying the state of, or whether to, format, encode, indent, or convert data types to tags


Accessing XML that Contains Relational Data

The XML and ADO+ frameworks provide rich support for a common scenario – working with XML data that has some relational content. The programming model enables the following approaches:

  • The user reads fully structured XML data into a DataSet, or less structured data into an XmlDataDocument. Alternatively, the user may load the DataSet with relational data from a data store, such as a database or Web site, or obtain data dynamically by generating it programmatically or querying a consumer for it through a user interface.
  • Given an XmlDataDocument, the user may obtain a structured relational view over a portion of the XML by applying mappings and retrieving a DataSet that corresponds to the mapped data.
  • Given a DataSet, the user can create a new instance of an XmlDataDocument associated with that DataSet.
  • Data loaded into the XmlDataDocument retains its full XML fidelity, including element ordering, whitespace, and unmapped data that doesn't match the DataSet's structure.
  • Data exposed through the DataSet's relational view can be bound to a control, or edited directly, without compromising the fidelity of the source XML loaded into the XmlDataDocument.

Figure 1 summarizes the programming model. Notice data can be read using one representation (for example, relational), then written using another representation (for example, XML). Therefore, either kind of data can be accessed within the same environment.

Figure 1

DataSet and XmlDataDocument

In Figure 1, data is read into and written from either an XmlDataDocument or a DataSet. The DataSet object, and associated supporting objects, provides a relational representation of data. This representation consists of tables/views, rows/columns, and relations/constraints (both unique and foreign).

The principal way of populating the DataSet is with commands, such as SQL query commands (executed with the assistance of managed provider objects). However, the DataSet object also supports reading XML directly.

The XmlDataDocument class derived from the XmlDocument class provides an XML representation of data. Data is represented as nodes, and components of nodes, in a W3C-compliant XML document. Data may be viewed as individual nodes, sets of nodes, or an entire document. Enhancements are provided for ease-of-use and high performance.

The principal way of populating the XmlDataDocument is by reading XML directly. Employing this object is the recommended way of invoking enhancements such as XML DTD and schema validation.


Securing Your Application

The following topics are provided:

  • Key Concepts in Security
  • Code Access Security
  • Role-Based Security
  • Security Policy Management
  • Security Tools


Key Concepts in Security

The NGWS runtime offers code access security and role-based security to help address security concerns about mobile code and to provide support that enables components to make decisions about what users are allowed to do. Both of these security mechanisms were designed to have a simple, consistent model so that developers familiar with code access security could easily use role-based security, and vice-versa; Also, both code access security and role-based security are implemented using a common infrastructure supplied by the NGWS runtime.

Because they use the same model and infrastructure, code access security and role-based security have in common several underlying concepts. You should make sure that you are familiar with the following concepts before you read the documentation for NGWS code access security and role-based security:

  • Permissions
  • Verification of Type Safety
  • Security Policy
  • Principal
  • Authentication
  • Authorization.


Permissions

The NGWS runtime allows code to perform only those operations that the code has "permission" to perform. To implement its mechanism for enforcing restrictions on managed code, the runtime uses objects called permissions. The primary uses of permissions are as follows: code can request the permissions it needs to access resources or perform operations; the runtime can grant permissions to code, based on characteristics of the code's identity, on what permissions were requested, and on how much the code is trusted; and code can demand that its callers have specific permissions. For more information about how the runtime decides which permissions to grant, see Security Policy and Permission Grants.

There are three kinds of permissions, each of which has a specific purpose:

  • Code access permissions, which represent the right of code to access a protected resource or perform a protected operation.
    Permission Class Name | Right represented
    EnvironmentPermission | Read or write environment variables.
    FileDialogPermission | Access files that have been selected by the user in an Open dialog box.
    FileIOPermission | Read, append, or write files or directories.
    IsolatedStoragePermission | Access temporary, publisher-specific files on a client's disk.
    ReflectionPermission | Discover information about a type at run time.
    RegistryPermission | Read, write, create, or delete registry keys and values.
    SecurityPermission | Execute, assert permissions, call into unmanaged code, skip verification, as well as other rights.
    UIPermission | Access user interface functionality.
  • Identity permissions, which indicate that code has credentials that support a particular kind of identity.
    Class Name | Identity represented
    PublisherIdentityPermission | The software publisher's digital signature.
    SiteIdentityPermission | The web site from which the code originated.
    StrongNameIdentityPermission | The strong (shared) name of the assembly (for more information, see What is a shared name?).
    ZoneIdentityPermission | The zone from which the code originated.
    URLIdentityPermission | The URL from which the code originated (including the protocol prefix—http, https, ftp, etc.).
  • Role-based security permissions, which provide a mechanism for discovering whether a user (or the agent acting on the user's behalf) has a particular identity or is a member of a specified role. PrincipalPermission is the only role-based security permission.

The runtime provides built-in permission classes in the System.Security.Permissions namespace and also supplies support for designing and implementing custom permission classes.


Code Access Permissions

Code access permissions are permission objects that are used to protect resources and operations from unauthorized use. They are a fundamental part of the runtime's mechanism for enforcing security restrictions on managed code.

Each code access permission represents either:

  • the right to access a protected resource, such as files or environment variables, or
  • the right to perform a protected operation, such as accessing unmanaged code.

All code access permissions can be requested and demanded by code, and the runtime decides which code access permissions, if any, to grant the code.

Each code access permission is derived from the CodeAccessPermission class, which means that all code access permissions have methods in common with each other, such as Demand, Assert, Deny, PermitOnly, IsSubsetOf, Intersect, and Union.


Identity Permissions

Identity permissions represent characteristics by which an assembly can be identified. Each identity permission corresponds to a particular kind of descriptive information about code, such as the strong (shared) name of the assembly or the web site from which the code originated. Identity permissions cannot be requested; however, the runtime grants identity permissions to assemblies based on the information it obtains about the assembly. This information, called evidence, is provided by the loader or a trusted host and can include such information as the digital signature of the assembly or the web site it originates from.

Identity permissions help protect code from unauthorized access. Identity permissions are special in that they can be used to control access to code, based on whether the calling code is recognized as having a particular identity. Because the identity permissions have a set of functionality in common with code access permissions, they are derived from the same base class that the code access permissions are derived from, CodeAccessPermission.

Identity permissions cannot be requested because identity is an inherent trait of the code itself – either the code has credentials that support a particular identity or it doesn't. The runtime grants identity permissions when the assembly is loaded, based on information the runtime discovers about the code. However, code can demand that its callers have a particular identity permission.


Role-Based Security Permissions

PrincipalPermission is a role-based security permission that can be used to determine whether a user has a specified identity or is a member of a specified role. For more information about roles and using the PrincipalPermission, see Role-based Security Checks.


Verification of Type Safety

For the runtime to successfully enforce security restrictions on managed code, it must be able to reliably determine all the callers in the call stack so that it can examine their permissions. If the runtime did not have this ability, managed code would be vulnerable to luring attacks, where a less trusted caller could use more highly trusted code to perform malicious actions. The runtime can reliably locate all the callers in the call stack when managed code is type-safe, which means that the code accesses types only in well-defined, allowable ways.

To ensure that the requirement for type-safety is met, IL code and metadata are examined as part of the JIT compilation process to see whether the code can be determined to be type-safe. For more detailed information about verification of type-safety, see JIT Compilation.


Security Policy

Security policy is the configurable set of rules that the NGWS runtime follows when it decides what it will allow code to do. Administrators set security policy, and the runtime enforces it. The runtime ensures that code can access only the resources and call only the code that security policy allows it to access.

Whenever an attempt is made to load an assembly, the runtime uses security policy to determine which permissions to grant to the assembly. By examining information called evidence that describes the assembly, the runtime examines security policy to decide how much the code is trusted and, therefore, what permissions to grant to that assembly. Evidence includes, but is not limited to, the code's publisher, its site, and its zone. Security policy also determines which permissions to grant to application domains.

For more detailed information about how the runtime uses security policy to determine which permissions to grant, see Security Policy Management.


Principal

A principal is a user or an agent that is acting on the user's behalf. NGWS runtime role-based security has support for three kinds of principals: unauthenticated, Windows, and custom principals.

  • Generic principals represent unauthenticated users and the roles available to them.
  • Windows principals represent Windows users and the roles (or the Windows NT/Windows 2000 groups) they are in. A Windows principal can impersonate another user, which means the principal can access a resource on a user's behalf while presenting the identity that belongs to that user.
  • Custom principals can be defined by an application in whatever way that is needed for that particular application. They can extend the basic notion of the identity and the roles that the principal is in. However, the application must provide an authentication module and types that implement the principal.


Authentication

Authentication is the process of discovering and verifying the identity of a principal by examining the user's credentials and validating those credentials against some authority. The information obtained during authentication is directly usable by your code. That is, once the identity of the principal is discovered, you can use NGWS runtime role-based security to determine whether to allow that principal to access your code. If authentication does not take place, the user is said to be anonymous.

There are many authentication mechanisms in use today, any of which can be used with NGWS runtime role-based security. Some of the most commonly used mechanisms are Basic, Digest, Passport, operating system (such as NTLM or Kerberos), or application-defined mechanisms.


Authorization

Authorization is the process of discovering what a principal is allowed to do. Authorization generally takes place after authentication, and it uses information about the principal's identity to determine what resources the principal should have access to. NGWS runtime role-based security can be used to implement authorization.


Code Access Security

Today's highly connected computer systems are frequently exposed to mobile code, which can come from any source and be embedded and used in a multitude of environments. Mobile, executable code can be attached to e-mail, it can be contained in documents, and it can be downloaded over the Internet. Unfortunately, many computer users have experienced firsthand the damaging effects of malicious mobile code, such as viruses or worms, which can damage or destroy data and cost time and money.

Typically, users and administrators try to protect code and data from malicious mobile code by increasing the frequency of data backups, using and updating anti-virus software, and putting firewalls in place. Yet, despite these precautions, malicious code still sometimes manages to successfully damage, destroy, or copy private data.

Other security mechanisms are being used, such as security systems that give rights to users based on their login credentials (usually a password) and restrict what resources (often directories and files) the user is allowed to access. However, several issues are not addressed by this approach: users obtain code from many sources, some of which might be unreliable; code can contain bugs or vulnerabilities that enable it to be exploited by malicious code; and code sometimes does things that the user doesn't know it will do. So, unfortunately, computer systems can be damaged and private data can be leaked when cautious and trustworthy users run malicious or buggy software. When using operating system security mechanisms, essentially every piece of code must be trusted completely if it is allowed to run, except perhaps for script on a web page. Therefore, there is still a need for a widely applicable security mechanism that allows code originating on one computer system to be allowed to execute safely on another system, even when there is no trust relationship between the systems.

To help protect computer systems from malicious mobile code and to provide a way to allow mobile code to run safely, the NGWS runtime provides a security mechanism called code access security. Code access security assigns allows code to be trusted to varying degrees, depending on where the code comes from and on other aspects of the code's identity. Because code access security also enforces these varying levels of trust on code, the amount of code that must be fully trusted in order to run is minimized. Using code access security can reduce the likelihood that your code can be misused by malicious code, and it can reduce your liability because you can specify the set of operations your code should be allowed to perform as well as the operations your code should never be allowed to perform.


Introduction to Code Access Security

Code access security is a mechanism that controls the access code has to protected resources and operations. In NGWS, code access security performs the following functions:

  • Defines permissions and named permission sets that represent the right to access various system resources.
  • Enables administrators to configure security policy, which associates sets of permissions with groups of code.
  • Allows code to request the permissions it requires in order to run, as well as the permissions that would be useful to have and the permissions the code must never have.
  • Grants permissions to code when an assembly is loaded, based on the permissions requested by the code and on the operations permitted by security policy.
  • Enables code to demand that its callers have specific permissions.
  • Enforces restrictions on code at runtime by comparing each caller's granted permissions to the permissions that callers are required to have.

Code access security can prevent less trusted code from using highly trusted code to perform actions it is not authorized to perform. In addition, code access security can help minimize the damage that can result from security vulnerabilities in your code.

One typical use for code access security is the scenario where a web application downloads a control to the client so that the user can input data. Security policy might, for example, associate the pre-defined Internet set of permissions with all code that is downloaded from the Internet. Therefore, the control is granted no more permissions than the ones associated with the Internet permission set. If security policy specifies that code is given special authority (more than Internet) when it is digitally signed by a trusted source, then the code will be granted the permissions that security policy applies to all code from that trusted source. The control is built using a class library, so that whenever the library accesses a protected resource or calls unmanaged code, it does a security demand causing the permissions of its callers to be checked for the appropriate permission grants. These security checks prevent the control from being able to do things on the client's computers that it isn't authorized to do.


Using Code Access Security

The only thing that application and component developers must do to enable their code to benefit from code access security is to use a compiler that generates verifiably type-safe code. However, if you want to ensure that your component or application takes advantage of all the protective mechanisms that code access security offers, you should read the following topics and follow the suggestions they contain:

  • Requesting permissions
  • Using secure class libraries
  • Using managed wrapper classes.

If you design class libraries, you need to be aware that because class libraries often access protected resources and unmanaged code, programming errors in your class library can expose security vulnerabilities. Therefore, you need to understand the details of how code access security works and be aware of your special responsibilities with respect to securing your class library. The following topics are relevant for class library developers:

  • Making Security Demands
  • Overriding Security Checks
  • Creating Custom Permissions


Writing Verifiably Type-safe Code

Type-safe code is code that accesses types only in well-defined, allowable ways. For example, given a valid object reference, type-safe code can access memory at fixed offsets corresponding to actual field members. However, if the code accesses memory at arbitrary offsets outside the range of memory that belongs to that object's publicly exposed fields, it is not type-safe.

JIT compilation performs a process called verification that examines code and attempts to determine whether the code is type-safe. Code that is proven during verification to be type-safe is called verifiably type-safe code. Code can be type-safe and yet not be verifiably type-safe, due to the limitations of the verification process or of the compiler. Not all languages are type-safe, and some language compilers, such as Visual C++, cannot generate type-safe managed code. To find out whether the language compiler you use will allow you to generate verifiably type-safe code, consult the compiler's documentation. If you use a language compiler that generates verifiably type-safe IL only when you avoid certain language constructs, you might decide to use an NGWS SDK utility called PEVerify to determine whether your IL code and metadata is verifiably type-safe.

If code is not verifiably type-safe, it can still execute if security policy allows the code to bypass verification; however, security cannot be reliably enforced if the code violates rules of type-safety.


Requesting Permissions

Requesting permissions is the way you let the runtime know what your code needs to be allowed to do. You request permissions for your code by specifying the needed permissions when the assembly is created. The requested permissions are stored in the assembly manifest.

To request permissions, you must be aware of the resources and protected operations your code is using, and you need to know which permissions protect those resources and operations. You should keep track of the resources used by your components as well as the resources accessed by any class library methods that are called by your components. For a list of the code access permissions that the runtime supports, see the Permissions topic.

You can request the following three kinds of permissions for your code:

required permissions | Permissions your code must have in order to run.
optional permissions | Permissions that your code could use, but will be able to run effectively without.
refused permissions | Permissions that you want to ensure will never be granted to your code, even if security policy would have allowed them to be granted.

Based on the permissions that your code requests, the runtime uses security policy to determine at load time which permissions to grant your code. If your code does not request permissions, it will be granted whatever permissions security policy allows, which might be different every time your code runs (or it might be no permissions at all). If required permissions are specified, the code will be granted each required permission that is allowed by security policy. The code will be allowed to run only if it is granted all the permissions it requires.

However, your code does not have to request permissions. If the code doesn’t access protected resources or perform protected operations, then it is not necessary to request any permissions. For example, permissions might not be necessary if the code simply computes a result without using any resources, based on inputs passed to it. If the code accesses resources but doesn't request the necessary permissions, the code will still be allowed to execute as long as security policy happens to allow sufficient permissions to be granted.

But there are important reasons why you might decide to request permissions for your code:

  • Requesting permissions increases the likelihood that your code will run properly if it is allowed to execute. If you don't identify the minimum set of permissions your code requires to be able to run, your code would need to gracefully handle any and all situations where not being granted some permission might prevent it from executing properly.
  • Requesting permissions helps ensure that your code is granted only the permissions it needs. If your code is not granted "extra" permissions, your code cannot damage the resources protected by those "extra" permissions, even if your code is exploited by malicious code or has bugs that could be leveraged to damage resources.
  • Requesting a minimal set of permissions can increase the probability that your code will be allowed to run. You should request only those permissions that your code needs, and no more.


Using Secure Class Libraries

A secure library is a class library in which security demands are used to ensure that the library's callers have permission to access the resources that the library exposes. For example, a secure class library might have a method for creating files and that method would demand that its callers have permissions to create files. The NGWS Frameworks comprises secure class libraries.

If you use a secure library to access resources, you don’t have to worry that your code might expose security weaknesses by accessing those resources. If your code requests and is granted the permissions that are required by the class library, your code will be allowed to access the library and the resource will be protected from unauthorized access; if your code doesn’t have the appropriate permissions, it will not be allowed to access the class library, and malicious code will not be able to use your code to indirectly access the resource.

Code access security doesn't eliminate the possibility of human error in writing code; however, if applications use secure class libraries to access protected resources, security risk for application code is decreased because class libraries are closely scrutinized for potential security problems.


Using Managed Wrapper Classes

Most applications and components (except secure libraries) should not directly call unmanaged code. There are several reasons for this. If code calls unmanaged code directly, it will not be allowed to run in many circumstances since code must be granted a high level of trust in order to call native code. If policy is modified to allow such an application to run, it can significantly weaken the security of the system, leaving the application free to perform almost any operation.

In addition, if code has permission to access unmanaged code, it can probably perform almost any operation by calling into an unmanaged API. For instance, if code has permission to call unmanaged code, then it doesn’t need FileIOPermission to access a file -- it can just call an unmanaged (Win32) file API directly without going through the managed file API that requires FileIOPermission. If managed code has permission to call into unmanaged code and it does call directly into unmanaged code, the security system will not be able to reliably enforce security restrictions, since the runtime cannot enforce security restrictions on unmanaged code.

If you want your application to perform an operation that requires accessing unmanaged code, then it should do so through a trusted, secure managed class that wraps the required functionality (if such a class exists). Do not create a wrapper class yourself if one already exists in a secure class library. The wrapper class, which must be granted a high degree of trust to be allowed to make the call into unmanaged code, is responsible for demanding that its callers have whatever permissions are appropriate. If you use the wrapper class, your code just needs to request and be granted the permissions that the wrapper class demands.


Making Security Demands

If you want to ensure that only callers that have a specified permission can call your code, you can programmatically or declaratively demand that callers of your code have specific a permission or permission set. Demands cause the runtime to perform security checks to enforce restrictions on calling code. During a security check, the runtime examines the permissions of each caller in the call stack, checking to see whether the permission that is being demanded has been granted to each caller. If a caller is found that does not have the demanded permission, the security check fails and a SecurityException is thrown. You can cause a security check to take place every time a particular method is called or before a particular block of code is executed. Or, if you want the security check to occur when any member of a particular class is called, you can place the demand so that it applies to every member of the class. The remainder of this topic explains how you make security demands, when you should do so, and why you might choose one type of security demand over another.

If you are writing code that directly accesses a protected resource and if this access is exposed to the caller, you must make a security demand to ensure that all callers in the call stack have permission to access that resource. Your demands can be declarative demands or imperative demands. However, no matter what type of demand you make, there are some general guidelines to follow.

If you want to ensure that the caller originated from a particular site or zone, you can demand that callers have a particular identity permission. However, it is recommended that you do this only when you are giving additional access based on matching an identity, not when you are denying access based on matching identity. The reason for this is that it is relatively simple to modify or mask code's identity, so denying access based on identity alone is not a reliable way of protecting your code and the resources it accesses from unauthorized access.

When you want to ensure that an object cannot be created except by callers who have a specific permission, you should place the demand on the constructor for that object. For example, suppose you have a class called myFileStream, which derives from the frameworks FileStream class, and you want to ensure that only authorized callers can create instances of myFileStream. You would place a declarative or imperative demand on the constructor(s) of the myFileStream class.

You can also put demands in code that sets or gets a property. In general, you put demands for less restrictive permissions on the get accessor (the Property Get in Visual Basic) than on the set accessor (the Property Let or Set in Visual Basic).

Note: Role-based security checks have slightly different semantics than code access security checks do. For more information, see the Role-based Security topic.


Declarative Demands

A declarative demand is a technique for invoking a security check without adding any statements to your code. You make a declarative demand by placing information at the class or member level, indicating the permissions you want your callers to have. If you place a declarative security check at the class level, the security check applies to each member of the class. However, if you place a declarative security check at the member level, it applies to only that member and it overrides the permission specified at the class level, if there is one.

For example, suppose you specify at the class level that PermissionA is required, and for that class's Method1 you indicate that PermissionB is required. When Method 1 is called, a security check will look for only PermissionB, but other methods of the class still require PermissionA.

You might choose declarative demands if you are using a tool that makes them easier to apply than imperative checks. Some demands, such as link-demand and inheritance demands, can only be applied declaratively.

Link-demands and inheritance demands, which protect your assemblies from unauthorized callers, are sometimes useful if your application consists of more than one assembly. The security checks that result from link-demands and inheritance demands occur at JIT compilation time instead of execution time. Link-demands, which can be placed either at the method or class level, ensure that only code that has been granted the demanded permission can bind (through calls or references) to your code. Care must be taken when using link-demands to secure your code because only the immediate caller is checked instead of all the callers in the call stack. Also, it's important to understand that exceptions thrown when link-demands fail cannot be caught by user code. Inheritance demands can be placed at the class level to ensure that only code with the specified permission can inherit from your class. Inheritance demands placed on methods require code to have the specified permission in order to overload the method.


Imperative Demands

With imperative demands, you cause a security check to occur by creating a permission object that represents the permission you want your callers to have and then calling the Demand method on that permission object.

When you create the permission object, you must initialize the state data of the permission object so that it represents the particular form of that permission that you need. For example, when creating a FileIOPermission object, you can pass parameters to the FileIOPermission constructor, indicating what type of access you want the object to represent (i.e., read, append, or write) and what files you want the object to represent access to. Or, you can use the default constructor to create a FileIOPermission object that represents unrestricted access to all files. You can then use the unrestricted FileIOPermission object to make a security demand to ensure all callers have been granted unrestricted access to all files, or you can call methods of the permission class to initialize the FileIOPermission object to represent only certain types of access to files that you specify.

The imperative demand that you place in your code effectively protects all of the remaining code in the block where the Demand method is called. The security check is performed when the Demand is executed; if the security check fails, a SecurityException is thrown and the rest of the code in that method or member is never executed unless the SecurityException is caught and dealt with.

You might use an imperative demand instead of a declarative one when you only know at run time some information that you need to specify the permission. For example, if you want to ensure that callers have permission to read a certain file, but you don't know the name of the file that will be read until run time, an imperative demand is called for. You might also choose to use imperative checks instead of declarative checks when you need to determine at runtime whether a condition holds, and based on the result of the test, make a security demand (or not).


Overriding Security Checks

Normally, a security check examines every caller in the call stack to ensure that each caller has been granted the specified permission. However, you can override the outcome of security checks by calling Assert, Deny, or PermitOnly on the permission object. Depending on which of these methods you call, you can cause the security check to succeed or fail, even though the permissions of all callers on the stack might not have been checked.

Application developers will not need to invoke these methods, and component and class library developers will rarely need to use them; however, they are appropriate in some situations, which are described in the Assert, Deny, and PermitOnly topics.


Assert

Assert is a method that can be called on code access permission classes, which allows you to ensure that your code (and downstream callers) are allowed to do something that your code has permission to do, even if your code's callers don't have permission to do that thing. A security assertion changes the normal process that the runtime performs during a security check. When you assert a permission, it tells the security system not to check the callers of your code for the asserted permission; instead, when the assertion is encountered during a stack walk, the security check automatically succeeds. Assertions should be used only with great care because they can open security holes and undermine the runtime's mechanism for enforcing security restrictions. To perform assertions, your code needs to be granted both the permission you are asserting and the SecurityPermission that represents the right to do assertions; although you could assert a permission your code hasn't been granted, the assertion would be pointless because the security check would fail before the assertion would cause it to succeed.

Assertions are often used in class libraries, which are usually more highly trusted than the applications that call the library. If you are designing a class library and your class accesses a protected resource, you should, in most cases, make a security demand to require that the callers of your class have the appropriate permission. Then, if your class performs an operation that you know most of your callers will not have permission for, and if you are willing to take the responsibility for letting these callers call your code, you can assert the permission by calling the Assert method on a permission object that represents the operation you are performing. Using Assert in this way lets callers call your code that normally could not call it. Therefore, if you assert a permission, you should be sure to perform appropriate security checks before you do so to prevent your component from being misused.

For example, suppose your highly trusted library class has a method that deletes files; it accesses the file by calling an unmanaged Win32 function. A caller invokes your code's Delete method, passing in the name of the file to be deleted, "c:\test.txt". Within the Delete method, your code creates a FileIOPermission object representing write access to "c:\test.txt" (write access is required to delete a file). Then your code invokes an imperative security check by calling the FileIOPermission object's Demand method. If one of the callers in the call stack doesn't have this permission, a SecurityException is thrown. If no exception is thrown, you know that all callers have the right to access c:\test.txt. Because you believe that most of your callers will not have permission to access unmanaged code, your code then creates a SecurityPermission object that represents the right to call unmanaged code and calls the object's Assert method. Finally, it calls the unmanaged Win32 function to delete "c:\text.txt" and returns control to the caller.

Assertions can also be used in other situations, such as those in which your code accesses a resource in a way that is completely hidden from callers. For example, suppose your code does some calculations, and in the process of doing so, it looks up and uses data that resides in a file accessible to your application. In this case, you decide that it is not reasonable or necessary to require that callers of your code have permission to access that particular file, so you assert permission for reading that file.

Warning: You must be sure that you do not do assertions in situations where your code can be used by other code to access a resource that is protected by the permission you are asserting. For example, in code that writes to a file whose name is specified by the caller as a parameter, you would not assert the FileIOPermission for writing to files because your code would be open to misuse by a third party.


Deny

Calling Deny prevents access to the resource specified by the denied permission. If your code calls Deny and a downstream caller subsequently invokes a security check for the permission that you denied, the security check will fail, even if all callers have permission to access that resource.

For example, suppose callers A, B, and C have been granted a permission called P1. Then caller A creates an instance of the P1 class, and then calls P1's Deny method to ensure that A cannot be used to access the resource protected by P1. Next, A calls B, which calls C. Caller C directly accesses the resource that P1 protects, so it invokes a security check for P1 by calling P1's Demand method. The runtime checks the permissions of method B, and because B has been granted P1, it goes on to examine method A. But because method A denies P1, the security check invoked by method C fails at that point, and a SecurityException is thrown. It doesn't matter whether method A and its callers (if there are any) have been granted P1 – the security check still fails.

You can use calls to Deny in your code to protect yourself from liability because denials make it impossible for your code to be used to access the denied resource. However, the call to Deny does not block future security assertions by downstream callers.


PermitOnly

Calling PermitOnly has essentially the same effect as calling Deny, but it is a different way of specifying the conditions under which the security check should fail. Instead of saying that a specified resource cannot be accessed (as Deny does), PermitOnly says that only the resource(s) you specify can be accessed. Therefore, calling PermitOnly on permission X is the same as calling Deny on all permissions except permission X. If you call PermitOnly, your code can be used to access only the resources protected by the permissions that you specify when you call PermitOnly.

You use PermitOnly instead of Deny when it is more convenient to describe what resources can be accessed instead of what resources can't be accessed.


Creating Custom Permissions

The runtime supplies a set of code access permission classes that are designed to protect a set of specific resources and operations. These permission classes are described briefly in the Permissions topic and in detail in the reference documentation for each class. For most environments, the built-in permissions are adequate. However, if you are defining a class library that accesses a resource that is not covered by the built-in permission classes but needs to be protected from unauthorized code, you should consider creating a custom permission class. If you want to be able to make declarative demands for your custom permission, you must also define an Attribute class for the permission. Providing these classes and making demands for the permission from within your class library will enable the runtime to prevent unauthorized code from accessing that resource.

To create your own code access permission class, you should derive a class from the System.Security.CodeAccessPermission class, which contains methods and properties that implement IPermission functionality, among others. The abstract methods, such as Copy, need to be implemented appropriately for the permission you are creating. To create an attribute class, you derive a class from the System.SecurityAttribute class.

Custom permissions require some special considerations with respect to packaging and security policy. For example, when you are deciding which assembly to place your custom permission class in, you must remember that you cannot place the custom permission in an assembly that requests that custom permission. Also, the default security policy does not "know" about the existence of your custom permission. Therefore, you should create a file for a named permission set that includes the custom permission and then use the caspol utility to add this file to the security policy on the computer where the code is to run. Then, you should associate the new permission set with the appropriate code groups so that security policy can grant the custom permission to the code that should have it.

You need to be aware that the named permission set called "Everything" contains all the built-in code access permissions that the runtime provides, but it does not include any custom permissions.


Role-Based Security

Typically, a server responds to incoming requests from remote systems, using handlers to process requests appropriately. Before application code is actually called, a request might be processed by one or more handlers performing tasks such as routing, logging, and authentication. After authentication verifies the user's identity, the server attempts to determine whether the user making the external request is authorized to access the application. To perform authorization, the server must have access to information about the authenticated user. This information needs to be associated with the incoming request so that the information can be examined by the thread currently processing the request.

NGWS runtime role-based security provides support for authorization by making information about the principal available to the current thread. NGWS applications can make authorization decisions based on either the principal's identity or role membership or both. A role is a named set of users that have the same privileges with respect to security (such as a teller or a manager), and a principal can be a member of one or more roles. Therefore, applications can use role membership to determine whether a principal is authorized to perform a requested action.

Roles are often used in financial or business applications as a means of policy enforcement. For example, an application might impose limits on the size of the transaction being processed, depending on whether the user making the request is a member of a specified role. Clerks can process transactions that are less than a specified threshold, supervisors have a higher limit on the size of the transactions they can process, and vice-presidents have an even a higher limit (or no limit at all). Another situation where role-based security might be used is when an application requires multiple approvals to complete an action, such as might be the case in a purchasing system in which any employee can generate a purchase request, but only a purchasing agent can convert that request into a purchase order that can be sent to a supplier.

In the past, applications that used roles and principal information for authorization handled authorization using proprietary mechanisms. Microsoft introduced infrastructure to support role-based security with MTS and, more recently, provided role-based security in COM+ 1.0 services. The NGWS runtime provides role-based security support that is flexible and extensible enough to meet the needs of a wide spectrum of applications. Role-based security is particularly well-suited for use in ASP+ web applications, which are processed primarily on the server. However, NGWS runtime role-based security can be used on either the client or the server.

If you have used COM+ 1.0, you might already be familiar with the way role-based security works in that environment. However, because NGWS runtime role-based security is similar to (but more generic than) COM+ 1.0 security, it might be helpful to compare these two security models. COM+ 1.0 security relies on Windows NT accounts and tokens and requires Windows NT trust relationships. Roles are managed on a per-application basis, and the application code must be registered with the registration database. For registered applications, COM+ 1.0 tracks the security information associated with the code and maintains the mapping between user accounts and roles. With NGWS runtime role-based security, principals are not necessarily associated with Windows NT accounts and tokens –generic and custom principals are supported as well as Windows principals. There is no underlying assumption of trust relationships between computer systems, but applications can require these relationships if appropriate. Application-specific roles can be defined, and the runtime provides support for allowing administrators to map operating system accounts to roles as well as support for maintaining the mapping. There is no registration requirement for NGWS applications except in some interoperability scenarios. The NGWS runtime attaches a principal object to the call context, which is always available to the current thread. The principal object contains a reference to an identity object as well as the roles to which the identity belongs.

COM+ 1.0 does not need to be present on the computer for NGWS runtime role-based security to work. However, if COM+ 1.0 is installed, NGWS runtime interoperability features allow applications that use COM+ 1.0 security to interact with NGWS applications. For more information, see Interoperation with COM+ 1.0 Security.

To provide ease of use and consistency with code access security, NGWS runtime role-based security uses PrincipalPermission objects to enable the runtime to perform authorization in a way that is similar to code access security checks. However, you can access a principal's identity information directly and perform role and identity checks in your code when you need to.


Principal and Identity Objects

Managed code can discover the identity or the role of a principal through a Principal object, which contains a reference to an Identity object. The runtime attaches a Principal object to the call context when a new thread is created and flows the call context properties between threads as they are sequentially assigned to process a thread. Support for built-in, generic, and Windows principals are provided, as well as a framework for implementing custom Principal and Identity objects. In the discussion that follows, Principal object means an instance of a class that implements IPrincipal, and Identity object means an instance of a class that implements IPrincipal.

The following classes and interfaces are discussed briefly (for detailed reference information about a specific class or interface, click a link in the list):

  • IPrincipal
  • GenericPrincipal
  • WindowsPrincipal
  • IIdentity
  • GenericIdentity
  • WindowsIdentity

The IPrincipal interface defines a property for accessing an associated Identity object as well as a method for determining whether the user identified by the Identity object is a member of a given role. All Principal classes implement the IPrincipal interface as well as any additional properties and methods that are necessary. For example, the runtime provides the WindowsPrincipal class, which implements additional functionality for mapping Windows NT or Windows 2000 group membership to roles. All Identity classes implement the IIdentity interface, which defines properties for accessing a name and an authentication type that indicates the authentication mechanism used to generate the identity information (such as Kerberos V5 or NTLM). There is no required relationship between an Identity object and the Windows NT process token under which a thread is currently executing. However, if the Identity object is a WindowsIdentity object, then the identity is assumed to represent a Windows NT token.

A Principal object is bound to a call context (CallContext) object within an application domain (AppDomain). A default call context is always created with each new AppDomain, so there is always a call context available to accept the Principal object. When a new thread is created, a CallContext object is created for the new thread. The Principal object reference is automatically copied from the creating thread to the new thread's CallContext. If it cannot be determined which Principal object belongs to the creator of the thread, the runtime follows the default policy for Principal and Identity object creation.

A configurable application domain-specific policy defines the rules for deciding which Principal object to associate with a thread of execution that is transitioning into a new application domain. If appropriate, the application domain security policy can be modified to specify that when the host is a highly trusted one (such as ASP+), the host provides the Principal and Identity objects to the runtime. In circumstances where security policy permits it, the runtime can create Principal and Identity objects that reflect the operating system token associated with the current thread of execution. But by default, the runtime uses Principal and Identity objects that represent unauthenticated users. The runtime does not create these default Principal and Identity objects until the code attempts to access them. Depending on policy settings, the runtime can also create special Principal and Identity objects that support interoperation with COM+ 1.0 security.

Principal objects are not transmitted between machines or between processes. Instead, the appropriate user credentials, as defined by the called application, are transmitted so that the correct Principal and Identity objects can be constructed for the caller's context. In some cases, the remoting infrastructure automatically determines the correct credentials to transmit. In others, a user interface requests credentials, such as name and password, certificate, etc. When transmitting a Principal object across application domains, but within the same process, the remoting infrastructure copies a reference to the Principal object associated with the caller's context to the callee's context.

You can neither modify nor destroy Principal and Identity objects after they have been constructed: garbage collection will destroy them when they are no longer referenced.


Role-based Security Checks

Managed code can use security checks to determine whether a particular Principal object is a member of a known role, has a known identity, or represents a known identity acting in a role. To cause the security check to occur, a security demand for an appropriately constructed PrincipalPermission object must be made, either declaratively or imperatively. During the security check, the runtime examines the principal to see if its identity and role match those represented by the PrincipalPermission that is being demanded. If the PrincipalPermission has not been granted, a SecurityException is thrown. (Only the permissions of the principal are examined, not the permissions of all callers in the call stack as is the case with code access permission security checks.)

For an imperative demand, the code creates a PrincipalPermission object that represents the desired identity and/or role and then calls the Demand method of the PrincipalPermission object to determine whether the current Principal object represents the specified identity and/or role.

For example, the following code demands that the principal have the identity "fred" and be a member of the "Teller" role:

String id = “fred”;
String role = “Teller”;
PrincipalPermission principalPerm = new PrincipalPermission(id, role);
principalPerm.Demand();

When the security check is being performed, both the specified identity and role must match for the check to succeed. However, when you create the PrincipalPermission object, you can pass a NULL identity string to indicate that the identity of the principal can be anything. Similarly, passing a NULL role string indicates that the principal can be a member of any role (or no roles at all).

You might use declarative demands instead of imperative demands if the compiler you are using provides a tool that makes applying declarative demands simpler than imperative ones. Declarative demands for PrincipalPermission work the same way that declarative demands for code access permissions work: demands can be placed at the class level as well as on individual methods, properties, or events. If a declarative demand is placed at both the class and member level, the declarative demand on the member overrides (or replaces) the demand at the class level.


Using PrincipalPermission Objects

In most cases, you use PrincipalPermission objects in the same way that you use code access security permission objects. However, some aspects of permission functionality are more useful than others. For example, you can call the Intersect method to intersect two PrincipalPermission objects, but intersection might not be very useful because identity and role are intersected independently to return a PrincipalPermission object that represents the identity and role common to the intersected objects. Therefore, in many cases, the returned object contains an empty string for the identity, the role, or both, and the object matches only an unauthenticated user (identity = "") or a user that does not belong to any roles (role = "").

Subset operations determine whether one PrincipalPermission object is a strict subset of another PrincipalPermission object. Effectively, this means that IsSubsetOf returns true only if either the identities and roles exactly match or if a role or identity is null. Therefore, IsSubsetOf has limited usefulness for this permission.

On the other hand, performing a Union operation on two PrincipalPermission objects can be useful when you want to compactly represent a set of conditions that you want to test, such as when you want to check for the presence of either one identity or another identity. For example, the following security check will succeed if the Principal object represents fred or sally in the role of Administrator.

String id1 = “fred”;
String role1 = “Administrator”;
PrincipalPermission myPrincipalPerm1 = new PrincipalPermission(id1, role1);
String id2 = “sally”;
String role2 = “Administrator”;
PrincipalPermission myPrincipalPerm2 = new PrincipalPermission(id2, role2);
(myPrincipalPerm1.Union(myPrincipalPerm2)).Demand();


Directly Accessing a Principal Object

Although using imperative and declarative demands to invoke role-based security checks is the primary mechanism for checking and enforcing identity and role membership, there might be cases where you want to access the Principal object and its associated Identity object directly so that you can do authorization tasks without creating permission objects. In these cases, you can use the static CurrentPrincipal property on the Thread class to get access to the Principal object and call its methods.

Note: The CurrentPrincipal property is set using the following call:

CallContext.SetData(principalName, principalObject).

After you get the principal object, you can use conditional statements to control access to your code based on the principal name as follows:

p = Thread.CurrentPrincipal;
If (p.Identity.Name == "fred")
// Permit access to some code

You can also programmatically check role membership by calling the IsInRole method on the current Principal object.

p = Thread.CurrentPrincipal;
If (p.IsInRole("Administrator"))
// Permit access to some code

Accessing the Principal object directly might be a good choice when you need to obtain the Principal object so that you can call Principal.Impersonate. Or, you might use this technique when you want to access behaviors that are specific to an application-defined Principal object. However, in most cases, you would use the PrincipalPermission class to control access to your code based on identity or role membership.


Impersonation and Reverting

If an Identity object is a WindowsIdentity object, then the Identity object might represent a Windows NT token capable of impersonation. Given a WindowsIdentity object and sufficient Windows NT privilege, a thread can use the WindowsIdentity object to impersonate the referenced account. This simply requires calling the WindowsIdentity.Impersonate method, which returns the WindowsImpersonationContext for the impersonation. Reverting to self can be done by calling the Undo method on the WindowsImpersonationContext.

Note that neither calling Impersonate nor Undo changes the Principal object associated with the current call context. Instead, impersonation and reverting changes the token associated with the current operating system process..


Replacing a Principal Object

For applications that provide managed code with authentication services, it is necessary for those applications to be able to replace the Principal object for a given thread. It is important that the ability to replace Principal objects be protected by the security system because the Principal object carries information that affects what the principal is allowed to do. Therefore, applications that require the ability to replace Principal objects must request and be granted the System.Security.Permissions.SecurityPermission for principal object control. (Note that this permission is not required for performing role-based security checks or creating Principal objects.)

The current Principal object can be replaced by successfully performing the following tasks:

  • Create the replacement Principal object and associated Identity object, usually after performing authentication.
  • Create a new System.Security.Permissions.SecurityPermission object, passing in the ControlPrincipal enumeration value to the constructor.
  • Call the Assert method on the SecurityPermission you just created. In general, this assertion is necessary because other code in the call stack is unlikely to have been granted this permission. Note: the code performing the assertion must be granted the SecurityPermission that is being asserted as well as the SecurityPermission that represents the right to perform assertions.
  • Attach the new Principal object to the call context, as follows:
    Thread.CurrentPrincipal = principalObject;

Automatic memory management (garbage collection) can take place whenever all references to an object have been released; therefore, if an application replaces an active Principal object, it needs to release any references to the old Principal object so that the memory associated with that object can be released.


Setting Policy for Creating Principal and Identity Objects

Trusted code creating an application domain can set the application domain policy that controls construction of the default Principal and Identity objects. This application domain-specific policy applies to all execution threads in that application domain. An unmanaged, trusted host inherently has the ability to set this policy; but managed code creating an application domain (AppDomain) must have the System.Security.Permissions.SecurityPermission(PrincipalObjectManagement) permission to set this policy.

You call the static SetPrincipalPolicy method on the AppDomain object to set the policy, passing in a PrincipalPolicy enumeration value that indicates what the new policy should be. Supported values are NoPrincipal, UnauthenticatedPrincipal, and WindowsPrincipal.


Interoperation with COM+ 1.0 Security

You can extend an existing COM+ 1.0 application with new NGWS managed components. The COM+ 1.0 security context is still managed by COM+ 1.0, and the COM+ 1.0 administrative user interface is used to configure the application. Basically, from a COM+ 1.0 application, NGWS objects look like COM+ 1.0 objects.

To make NGWS objects visible to COM+ 1.0 security services, you must mark the class with the ComEmulate custom attribute. This mechanism ensures that all calls into that class are made using COM-compatible mechanisms. You must also run tools (TlbExp.exe) supplied by the NGWS SDK to generate type libraries for the public interfaces and register the objects so that COM+ 1.0 can locate them. In this case, COM+ 1.0 administrative facilities must be used to configure roles and other role-based security behavior.

There are some limitations to COM+ 1.0 security interoperability. For example, you cannot use COM+ 1.0 security from a "pure" NGWS application. In addition, COM+ 1.0 security properties are not propagated across process or machine boundaries or to newly created execution threads within managed code. COM+ 1.0 security services can only be used by managed code on Windows 2000 systems.

For more information, see COM+ 1.0 Interop.


Cryptographic Services

The System.Security.Cryptography namespace contains a rich set of cryptographic functionality including both symmetric and asymmetric cryptography, hashes, and random number generation.

Cryptographic Streaming Objects

Most cryptographic operations perform either a computation or a transformation on a large amount of data, so the majority of cryptographic operations are performed as stream-based operations. This model works well for hashes, symmetric encryption, and message-based operations.

These stream-based objects all support a single standard interface for dealing with the data transfer portion of the object. Since all of the objects are built on a standard interface, you can chain together multiple objects (such as a hash object followed by an encryption object), and you can perform multiple operations on the data without needing any intermediate storage for it. The streaming model also allows you to build objects from smaller objects. For example, a combined bulk encryption and hash algorithm can be viewed as a single stream object even though this object might be built by internally creating a set of objects.

In addition to the interface published by each object for supporting the streaming, some objects need to be able to query specific values of the object on their immediate left. For example, a PKCS#1 padding object needs to know the block size of the encryption object on its left in order to get the padding correct.

Asymmetric operations

Asymmetric operations differ from symmetric operations in that they often occur on a fixed buffer size rather than the variable length buffer associated with symmetric operations. Streaming is a factor only when the same operation is used on more than one data input (such as when multiple hashes are signed with the same RSA key). Therefore, asymmetric operations do not use the same streaming model as the symmetric operations use.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Generating Signatures

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Verifying Signatures

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Encrypting and Decrypting Data

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Generating Keys for Encryption and Decryption

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Encrypting Data

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Decrypting Data

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Ensure Data Integrity with Hash Codes

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Managing and Storing Keys

[To be supplied.]


Security Policy Management

The following topics are provided:

  • Introduction to Security Policy
  • Security Policy Model
  • Permission Grants


Introduction to Security Policy

Security policy is the configurable set of rules that the NGWS runtime follows when deciding which permissions to grant to code. The runtime determines what access to resources code should have by examining identifiable characteristics of the code, such as the web site or zone from which the code originates. Security policy defines several zones and associates each of them by default with a named set of permissions. If no other identifying characteristics of the code are known, the runtime uses the policy for the zone from which the code originates. The default security policy is suitable for many situations; however, administrators can modify or customize security policy to tailor it to the specific needs of their organizations.

Administrators can configure security policy so that individual sites and publishers are allowed to have more or fewer permissions than default policy allows. For example, an administrator might specify that all code downloaded from the website of XYZ Corporation, a trusted business partner, is allowed to have the set of all permissions. The same administrator might specify that all other code from the Internet be given a more restricted set of permissions, such as limited access to isolated storage and use of safe user interface functionality.

Based on policy, the runtime grants permissions to both assemblies and application domains. During execution, the runtime ensures that code accesses only the resources that it has been granted permission to access.

For more detailed information about permissions and how they are used, see Permissions. To learn how the runtime enforces security restrictions on managed code, see Code Access Security.


Security Policy Model

Security policy consists of the following policy levels: machine, user, and application domain policy. Machine policy is always present by default, but the other two levels of policy do not exist unless they have been specified.

The following table describes the three policy types:

Policy type | Specified by | Applies to
Machine policy | Administrator | All managed code on the computer.
User policy | Administrator or user | Code in all of the processes associated with the current operating system user when the runtime starts up.
Application domain policy | Application domain host code | All managed code in the host’s application domain.

Each policy level has its own hierarchy of code groups that provides the framework for establishing and configuring security policy. Every code group by definition is associated with a named permission set, which specifies the permissions that the runtime will allow code in that code group to have. The evidence provided by a trusted host or by the loader enables the runtime to determine which code groups the code is a member of, and therefore which permissions the policy allows the code to have.

The set of permissions that is granted to an assembly is determined when the assembly containing that code is loaded. At load time, the runtime examines evidence describing the assembly's identity and uses that information to discover which permissions policy allows the assembly to have. The requirements of all existing policies (machine, user, and application domain) are taken into account, along with the requested permissions of the assembly, when the runtime grants permissions to assemblies. Application domains are also granted permissions by the runtime, but only the machine policy and, optionally, the user policy are used to determine the granted permissions for application domains. For more details about how the runtime decides which permissions to grant, see Permission Grants.


Application Domain Hosts

Each NGWS application runs in an application domain under the control of a host that sets up the domain and loads assemblies into it. The host has access to information about the code, such as the zone from which the code originates or the digital signatures of assemblies in the application domain. A trusted host is a host that has permission to provide the runtime with this kind of information, called evidence.

The following entities can be application domain hosts:

  • Shell host – launches applications (EXEs) from the shell.
  • Browser host (Internet Explorer) – runs code within the context of a web site.
  • Server host (ASP+) – runs code that handles requests submitted to a server.
  • Custom-designed hosts – creates domains and/or loads assemblies into domains, including dynamic assemblies.

After creating a new application domain, a host can specify the policy it wants to be applied to code within the application domain. By specifying application domain policy, which is essentially a hierarchy of code groups, a host can reduce the set of permissions that would have been allowed by machine policy; however, the application domain policy cannot expand the set of permissions allowed by machine policy. Policy can be set only once for an application domain. Once application domain policy is set, further attempts to set application domain policy will fail. After application domain policy is set, all subsequently loaded assemblies will be granted permissions under the new policy (machine policy and user policy and application domain policy). Previously loaded assemblies will get grants under the pre-existing policy (i.e., machine and user policy only). Assemblies that were loaded into the application domain before the application domain policy is in place will not have their permission grants reevaluated under the newly set application domain policy.

If the host is trusted, it can provide information (evidence) to the runtime about assemblies that are loaded into the application domain. Typically, after the application domain is created, the host loads the first (main) assembly into the application domain and calls into that assembly to begin execution. When code in the first assembly references code in another assembly, the loader resolves the reference, loads the appropriate assembly into the application domain, and supplies the evidence about the assembly to the runtime.

The System.AppDomain class provides the application domain functionality used by hosts.


Evidence

Evidence is the set of information that serves as input to the runtime's mechanism for making decisions based on security policy. Evidence indicates to the runtime that code has a particular characteristic. Common forms of evidence include digital signatures and the location from which code originates, but evidence can also be custom designed to represent other information that is meaningful to the application.

Trusted application domain hosts can present evidence about an application domain that enables the runtime to decide what permissions to grant the application domain. This information enables the runtime to evaluate machine and user policy and return the set of permissions to grant to the application domain. If the host does not have permission to provide evidence, the application domain will get the permissions that have been granted to the host.

The runtime gets evidence about assemblies from trusted application domain hosts or directly from the loader. Some evidence, such as where the code originates from, needs to come from the application domain host because only the host knows this information. Other evidence, such as an assembly's digital signature, is inherent in the code itself, so it can come from the loader or a trusted host. Typically, when code is loaded, each assembly's digital signature is validated by the runtime. If the digital signature is valid, the host passes the signature information as evidence to the runtime's policy mechanism. In addition, an assembly or a host can provide custom evidence as a resource that is part of the assembly. Administrators and developers can define these new types of evidence and extend security policy to recognize and use it.

The following types of evidence can be presented by the host:

Evidence | Description
Publisher | Software publisher signature, i.e., the AuthentiCode™ signer of the code.
Site | Site of origin, such as www.microsoft.com.
Strong Name | Cryptographically strong name of assembly.
URL | URL of origin.
Zone | Zone of origin, such as Internet Zone.
Custom | Application or system-defined.

The runtime's policy mechanism combines the evidence from the host and the assembly and then uses the evidence to determine which code groups the code is a member of. Ultimately, code group membership together with requested permissions determine which permissions the code is granted.


Code Groups

A code group is a logical grouping of code in which a specified condition must be met for code to be a member of the group. Any code that meets the membership condition is included in the group. Each code group is also associated with a named permission set, and code groups can have attributes, which affect how the code group is used to define policy. Administrators configure security policy by managing code groups.

The runtime uses evidence that describes the code to determine whether a group's membership condition has been met. For example, if the membership condition of the code group is "Code that is from the www.microsoft.com web site", the runtime examines the evidence to determine whether the code does, in fact, originate from www.microsoft.com. The runtime decides which permissions can be granted to code by determining which code groups the code belongs to and then adding together the permission sets associated with the groups of which code is a member.

Each type of policy (machine, user, and application domain) is represented by a hierarchy of code groups. The root of each hierarchy is the group containing all code. The "all code" group has child nodes, and those child nodes have children, and so on. If code is a member of the parent code group then the code might be a member of one or more of that group's child code groups. If code is not a member of the parent code group, it cannot be a member of any of the code groups that are descended from that parent.

Code groups can be defined to have the following kinds of membership conditions:

  • Software publisher, based on public key of a valid Authenticode™ signature
  • Zone of origin of the code
  • Strong name, based on the cryptographically signed namespace
  • Web site of origin of the code, e.g. www.microsoft.com or *.microsoft.com
  • URL of origin of the code, including final wildcard e.g. http://site/app/*


Named Permission Sets

A named permission set is a set of permissions that administrators can associate with code groups. A named permission set consists of at least one permission and a name and description for the permission set. By associating named permission sets with code groups, administrators can establish or modify the policy that determines which permissions the runtime allows code in those groups to have. More than one code group can be associated with the same named permission set. The runtime provides several built-in named permission sets. Modifying an existing named permission set affects the permissions that the runtime can grant to all code that is a member of a code group associated with that permission set.

The built-in named permission sets and their descriptions are as follows:

  • Nothing: no permissions (cannot run).
  • Execution: only permission to run, but no permissions that allow use of protected resources.
  • Internet: the default policy permission set suitable for content from unknown origin.
  • LocalIntranet: the default policy permission set for within an enterprise.
  • Everything: all standard (i.e., built-in) permissions except permission to skip verification.
  • FullTrust: full access to all resources protected by permissions that can be unrestricted, which includes all of the built-in code access permissions.

Custom named permission sets can be defined by administrators as long as the names do not conflict with the names of built-in named permission sets. Named permission sets cannot contain identity permissions.


Attributes

Code groups can have the following attributes, which affect how allowed permission sets are determined:

Exclusive: If code is a member of a code group that is marked Exclusive, the allowed permission set for the policy level will be the permission set associated with that code group. When all policy levels are taken into account, the code will never be allowed more permissions than those associated with the Exclusive code group. Within a given policy level, code can be a member of no more than one code group that has the Exclusive attribute.

Level Final: No policy levels below the one containing this code group are considered when checking code group membership and granting permissions; machine policy is the highest level of policy, followed by user policy, then application domain policy. For example, if the LevelFinal attribute is set for a code group in machine policy and some code matches the membership condition of this code group, neither the user level policy nor the application domain policy would be applied to that code.

A code group can be marked with both the Exclusive and LevelFinal attributes.


Permission Grants

The runtime grants permissions to both application domains and assemblies. The process the runtime follows when granting permissions can involve two steps, which are discussed in the remainder of this topic.

Computing the Allowed Permission Set

  • The runtime determines at load time the set of permissions that each type of policy allows the code to have. This task is accomplished by examining the code group hierarchy for each relevant policy level, using evidence to determine group membership.
  • The runtime intersects the allowed permission sets for each relevant policy level, resulting in one set of allowed permissions.

Determining the Granted Permissions

  • The runtime compares this final set of allowed permissions with the permissions that the assembly requests, resulting in a set of permissions that is granted to the assembly. For permission grants for application domains, this step does not apply.


Computing the Allowed Permission Set

The runtime attempts to discover what permissions policy allows code to have by traversing the code group hierarchies for the relevant policy levels. For application domains, the relevant policies are machine and user policy; for assemblies, the machine, user, and application domain policies are used. The runtime discovers which code groups the code is a member of, based on the identity information provided by the evidence. If the code is a member of a code group, that group is said to be a "match." The permission sets associated with the matching code groups determine which permissions security policy allows the code to have.

The search for a "match" in the code group hierarchy begins at the top of the hierarchy in the "all code" group. The levels in the code group hierarchy are searched, looking for matches in child groups as long as a match is found in some parent group. For code groups without attributes the process works like this: if a parent code group is a match, all of the matching group's children are examined to see if any of the child groups match, and so on. When all matches in the hierarchy have been identified, the permissions associated with each matching code group are unioned (combined in an additive manner), resulting in the set of permissions allowed by that level of policy.

Note: if exactly one matching code group in the policy level has the Exclusive attribute, only the permissions of the exclusive code group apply. In this case, the allowed permissions set for the policy level is simply the set of permissions belonging to the exclusive code group.

Once the allowed permission sets for each policy level have been determined, The code hierarchy search is repeated for each relevant policy level, and the allowed permission sets for each policy level are intersected. The resulting allowed permission set contains only the permissions that the allowed permission sets for all policy levels have in common.

If a code group has the LevelFirst attribute, the allowed permission set is computed differently: no policy levels below the one containing the exclusive code group are considered when checking for matches and granting permissions. For example, if a code group in the hierarchy for the machine policy has the LevelFirst attribute and some code matches the membership condition of this code group, neither the user level policy nor the application domain policy would be applied to that code.


Determining the Granted Permissions

For application domains, the granted permission set is simply the allowed permission set. For assemblies, the other factors described in this topic are considered at assembly load time when determining the grant set.

An assembly can contain declarative security requests that tell the runtime what permissions the code needs or would like to have. A code request can consist of the following three permission sets:

  • Required: specifies the minimum set of permissions the code must have in order to run.
  • Optional: identifies additional permissions it would like to have in addition to the minimum set.
  • Refused: specifies permissions it wants to ensure are never granted to it.

If all three permission requests are absent, the assembly is simply granted the permission set that policy allows. However, if at least one of the three permission requests is present, the requested permissions are considered when computing the granted permissions. First, the runtime checks to see if the set of required permissions is a subset of the allowed permission set. If this test fails, the code is not allowed to run and a PolicyException is thrown with a message naming the assembly that was not allowed to execute. Then, if the code is allowed to run, the runtime computes a set of permissions to grant the assembly, based on the allowed permission set and the requested permissions.

The granted permission set is computed as follows: The assembly's minimum permission request is added to the intersection of the assembly's optional request and the allowed permission set, and the refused set is subtracted from the result. In practical terms, this means that the permission set granted to an assembly is the assembly's required permission request and whatever subset of the optional request is allowed by policy, less any permissions that either policy or the permission request refused.


Default Security Policy

The following table shows how the machine policy is configured by default:

Code Group | Membership Condition | Named Permission Set
My Computer | from the MyComputer zone | LocalIntranet
Internet | from the Internet zone | Internet
Local Intranet | from the Intranet zone | LocalIntranet
Untrusted | from the Untrusted zone | Nothing
Trusted | from the Trusted zone | Internet
All Code | from any location | Nothing (Matching code groups are unioned to calculate allowed permissions; therefore, the fact that the All Code group maps to the Nothing permission set does NOT imply that all code gets no permissions.)


Administering Security Policy

Security policy configuration involves managing hierarchies of code groups, where each code group is defined by a membership condition, is associated with a permission set, and can have special attributes. To view or modify security policy, you must be granted the SecurityPermission that represents administrative access. Understanding the runtime's security policy model will help you administer security policy effectively.

You can administer security policy for machine policy or user policy, or both, using the caspol command line tool. The following tasks are supported:

  • Viewing Policy, Code Groups, or Permission Sets
  • Creating, Modifying and Removing Named Permission Sets
  • Adding, Modifying, and Deleting Code Groups
  • Assigning Permissions and Attributes to Code Groups
  • Analyzing Security Settings on Assemblies
  • Undoing Policy Changes

For detailed information on how to perform these tasks using the caspol utility, see the Runtime Code Access Security Administration specification.


Security Tools

The NGWS SDK supplies command-line utilities that help you perform security-related tasks and test your components and applications before you deploy them. The following table briefly describes each of these tools:

Tool Name | Description
assembly generation utility | Takes files containing IL and creates a PE (portable executable) file that contains an assembly manifest.
caspol | Enables you to view and configure security policy. You can see what permissions are granted to a specified assembly and which code groups that assembly belongs to.
cert2spc | Creates a Software Publisher's Certificate (SPC) from one or more X.509 certificates.
certmgr | Manages certificates, certificate trust lists (CTLs), and certificate revocation lists (CRLs).
chktrust | Checks the validity of a signed file.
makecert | Generates a X.509 certificate that can be used for testing purposes only.
permview | Allows you to view an assembly's requested permissions.
peverify | Determines whether the JIT compilation process can verify the type-safety of the assembly.
secutil | Obtains the X509Certificate and/or strong (shared) assembly names in a form that is easy to place in source files.
setreg | Changes registry settings.
signcode | Enables you to digitally sign an assembly.
sn | Helps with key management and signature verification for strong (shared) assembly names.


ASP+ Web Application Security

Most Web sites need to selectively restrict access to some portions of the site. A Web site can be somewhat analogous to an art gallery. The gallery is open for the public to come in and browse, but there are certain parts of the facility, such as the business offices, where the public is not allowed. Another example of the need for security is where the Web site must record a purchaser's credit card information. The file or database where such information is collected must be secured from public access. ASP+ security features can solve these and many other security needs.

ASP+, working in conjunction with Internet Information Services (IIS) 5.0, can authenticate user credentials (such as names and passwords) using any of five different authentication methods: Basic, Digest, Windows, Passport, and Cookie authentication. Access to site information can be controlled in either of two ways: by comparing authenticated credentials (or representations of them) to NTFS file system (NTFS) permissions, or by comparing them to an XML file containing authorized users, authorized roles (groups), or authorized HTTP verbs.

The following sections describe these capabilities in more detail:

How ASP+ Security Works

ASP+ Architecture

ASP+ Data Flow

ASP+ Authentication

ASP+ Authorization

ASP+ Impersonation

Designing Secure ASP+ Applications


How ASP+ Security Works

Securing Web sites is a critical issue for Web developers. It is also one of the most potentially confusing. A secure system requires careful planning, and Web site administrators and programmers must have a clear understanding of the options for securing their site. Security, in the context of an ASP+ application, involves performing three fundamental functions: authentication, authorization, and impersonation for a given request.

Authentication is the process of accepting credentials (various forms of identification, such as name and password) from a user and validating those credentials against some authority. If the credentials are valid, the entity that submitted the credentials can be considered an authenticated identity. Then, given an identity, authorization is the process of determining whether or not that identity has access to a given resource. Impersonation is when the application executes using the identity of the client making the request. Access authorization is then left up to Windows NT file access permissions, and/or URL authorization, as described in the sections that follow.

It is helpful to understand how all of the various security subsystems interact. This section, and the following sections, provide a brief overview of the subsystems involved, and more detail on the specifics of ASP+ security.

ASP+ works in concert with the NGWS frameworks and runtime and Internet Information Server (IIS) 5.0 to provide outstanding security capabilities that can be implemented by Web application designers. ASP+ provides these capabilities in three key areas:

  • User Authentication, which assures that the user is, in fact, who the person claims to be.
  • User Authorization, which limits access rights through user specific permissions.
  • User Impersonation, which allows the ASP+ application to assume the user's identity when the request is passed to the application from IIS. Then, when the ASP+ application tries to access protected resources, the NTFS file permissions determine whether it gains access.

Since ASP+ is built on the NGWS frameworks and runtime, the ASP+ application developer also has access to all of the built-in security features of the NGWS runtime, such as code access security and role-based, user access security. For details on NGWS runtime security capabilities, see the Code Access Security section of this document.


ASP+ Architecture

This section provides a high level overview of the ASP+ infrastructure and the subsystem relationships, as they relate to the subject of security. These relationships are shown in the following illustration:

As shown, all Web clients communicate with ASP+ applications through IIS. Among other things, IIS deciphers the request, does some authentication, finds the requested resource (such as an ASP+ application), and, if authorized, returns the appropriate resource to the client. In addition to the built-in ASP+ security features, an ASP+ application can use the NGWS runtime low-level security features. For more information on the NGWS runtime security features, see Key Concepts in Security.

Integrating with IIS

This release of ASP+ uses IIS 5.0 as the primary host environment. When considering ASP+ authentication, you should understand the interaction with IIS authentication services.

IIS always assumes that a set of credentials map to a Windows NT account and uses them to authenticate a user. There are three different kinds of authentication in IIS 5.0: Basic, Digest, and Windows authentication. The type of authentication used can be set in the IIS administrative services. For more information on these types of authentication, see the IIS documentation. If a URL containing an ASP+ application is requested, the request and authentication information gets handed off to the application.

ASP+ provides three additional types of authentication: Cookie authentication, Passport authentication, and customized Windows authentication. These will be discussed in later sections.

Using ASP+ Configuration Files

ASP+ configuration is done using XML configuration files. There are file sections for each major category of ASP+ functionality. The security section is as follows:

Config.web file
<security>
     <authentication mode=”[Windows/Cookie/Passport]”>
          <cookie cookie=”[name]” loginurl=”[url]” decryptionkey=”[key]”>
               <credentials passwordformat=”[Clear, SHA1, MD5]”>
                    <user name=”[UserName]” password=”[password]”/>
               </credentials>
           </cookie>
     </authentication>

     <authorization>
           <allow users=”[comma separated list of users]”
                  roles=”[comma separated list of roles]”/>
           <deny  users=”[comma separated list of users]”
                  roles=”[comma separated list of roles]”/>
     </authorization>

     <identity>
          <impersonation enable=”[true/false]”/>
     </identity>
</security>

The default settings for these elements are shown in the following table:

Element or Attribute | Default Value
<authentication mode= | "Windows"
<cookie cookie= | ".ASPXCOOKIEAUTH"
<cookie URL= | "login.aspx"
<cookie decryption key= | "autogenerate" Causes a machine-specific key, as provided by the Microsoft CryptoAPI, to be used.
<passwordformat= | "sha1"
<user name= | "" (empty)
<user password= | "" (empty)
<allow users= | "*" (all)
<allow roles= | "*" (all)
<deny users= | "" (empty)
<deny roles= | "" (empty)
<impersonation enable= | true

Notice that there are three major subsections: authentication, authorization, and identity. The values for each of the elements are usually set by overriding this section of the master configuration file with a similar section in an application configuration file placed in the application root. All subdirectories automatically inherit those settings. However, subdirectories can have their own configuration files that override their parent directory's settings.

Each of these security subsections is discussed in more detail in the document sections that deal with those topics. For more details on ASP+ configuration in general, see ASP+ Configuration Concepts.

The next topic provides the data flow details that show how security is handled by ASP+, and how the data flows through the subsystems.


ASP+ Data Flow

There are a number of different ways to design security into ASP+ applications, depending on the preferences of the designer. This section shows the security data flow for one of the more common scenarios.

Scenario 1 - Impersonation

In this scenario it is desired to have little or no security programming in the ASP+ application itself, but rather, rely on IIS authentication and Windows NT file access security. The data flow is shown in the following illustration:

As shown in this illustration, the sequence of events is as follows:

  1. A request for access comes in to IIS from a network client.
  2. IIS authenticates the client using Windows NT NTLM authentication.
  3. If the client is authenticated, IIS hands the authenticated request over to ASP+.
  4. The ASP+ application impersonates the requesting client (using the access token passed from IIS), and relies on NTFS file permissions for granting access. All that needs to be done for the ASP+ application is to verify that in the ASP+ configuration file, the "impersonation enabled" directive is set to true (the default); no ASP+ security code needs to be written.

    Notice that if impersonation is not enabled, the application runs with a "Local Machine" identity. Access at the Access Control List (ACL) level is normally allowed for ASP+ applications running with the Local Machine identity. If you want to restrict access, the ACLs must be tightened up or some other means of authorization, such as URL authorization, must be used.

    For more details on using impersonation in ASP+ applications, see the Impersonation section later in this document.

  5. If access is granted, the ASP+ application returns the requested page through IIS.


ASP+ Authentication

As explained earlier, authentication is the process of accepting credentials from a user and validating those credentials against some authority. If the credentials are valid, you have an authenticated identity. Then, given an identity, authorization is the process of determining whether that identity has access to a given resource.

There are a variety of methods commonly used to authenticate a request, including Basic, Digest, Windows, and Custom authentication. First, a brief overview of how these authentication schemes work is presented, and then ASP+ support for authentication and authorization is described.

The first version of ASP+ will ship with support for the following authentication providers:

  • Windows Authentication

    This method is used in conjunction with IIS authentication. Authentication is performed by IIS in one of three ways: Basic, Digest, or Windows authentication. ASP+ uses the authenticated identity to authorize access.

  • Passport Authentication

    Passport authentication is a centralized authentication service provided by Microsoft that offers a single sign-in and core profile services for member sites.

  • Cookie Authentication

    Cookie authentication is generally used to refer to a system whereby unauthenticated requests are redirected to an HTML form (using HTTP client-side redirection). The user provides credentials and submits the forms. If the application authenticates the request, the system issues a cookie that contains the credentials in some form or a key for reacquiring the identity. Subsequent requests are issued with the cookie in the request headers and they are authenticated and authorized by an ASP+ handler using whatever validation method the application developer specifies.


ASP+ Authentication Providers

ASP+ authentication providers are the code modules that contain the code necessary to authenticate the requestor's credentials. To enable a given authentication provider for an ASP+ application, you only need to create an entry for the application configuration file as follows:

// config.web file

<security>
     <authentication mode= "[Windows/Cookies/Passport]">
     </authentication>
</security>

The mode is set to one of the authentication methods: Windows, Cookies, or Passport. As is the case with other ASP+ modules, authentication modules are inherited by child directories in the URI space unless explicitly overridden. Each of the authentication providers is described in more detail in the sections that follow.

For more information on ASP+ configuration files, see ASP+ Configuration Concepts

The Windows Authentication Provider

The WindowsAuthenticationModule provider can use IIS authentication, or can be used to implement a custom Windows authentication scheme. In either case, the provider module constructs a WindowsIdentity object, and the default implementation constructs a WindowsPrincipal object and attaches it to the application context. The WindowsPrincipal object maps identities to Windows groups. For more information on these objects (classes), see the reference section of this documentation.

If it is desired to use IIS authentication, the provider module uses the authenticated identity passed in from IIS. The identity is authenticated by IIS using Basic, Digest, or Windows authentication, or some combination of them. Impersonation (in conjunction with NTFS ACL permissions) can be used to restrict or allow access to protected resources. This is the provider configuration you should use if you want to implement site security with a minimum of ASP+ coding.

You can also construct a custom authentication scheme using the WindowsAuthenticationModule, which is explained in the next topic.

Configuring the Windows Authentication Provider

An important reason to use the WindowsAuthenticationModule provider is to implement an impersonation scheme that can use any of the authentication methods that may have already been performed by IIS prior passing the request to the ASP+ application. To do this, set the authentication mode to "Windows", and confirm that the <impersonation enable> element is set to "true" (the default). For example:

<security>
     <authentication mode="Windows">
     </authentication>
     <identity>
          <impersonation enable="true"/>
     </identity>
</security>

Next you must set the NTFS ACLs to allow access only to the proper identities.

If you want to enable impersonation for only a short duration of time during request processing, you can do it by using an impersonation context and WindowsIdentity.Impersonate:

First, set the <impersonation enable> element to "false". Then, set up a context using the WindowsIdentity.Impersonate method:

ImpersonationContext context = WindowsIdentity.Impersonate();
// do whatever
context.Undo();

Notice that identity reversion can be done using context.Undo.

As mentioned earlier, you can implement a custom Windows authentication scheme. You can use a WindowsAuthenticate_OnAuthenticate event handler to implement a WindowsIdentity object, from which you can create a custom IPrincipal class (perhaps derived from WindowsPrincipal) that implements your custom scheme. The default implementation constructs a WindowsPrincipal object and attaches it to the application context. The WindowsPrincipal object maps identities to Windows groups. For more information on the WindowsPrincipal object, see the Reference section of this documentation.

The Passport Authentication Provider

Passport authentication is a centralized authentication service provided by Microsoft that offers a single sign-in and core profile services for member sites. This benefits the end user because it is no longer necessary to log in every time the user needs to access new protected resources or sites. If you want your site to be compatible with Passport authentication and authorization, this is the provider you should use. This document provides some introductory material on Microsoft Passport and the ASP+ support for it, but it is not a substitute for the information contained in the Passport documentation, which can be found at http://www.passport.com/business. Once there, you will have to get a passport and register.

Conceptually, Passport is a cookie-based authentication service. A sample conversation using Passport authentication might look similar to the following:

  • A client issues an HTTP GET request for a protected resource; for example, http://www.contoso.com/default.aspx
  • The request does not contain a valid Passport ticket (cookie), so the server returns 302 and redirects the client to the Passport login service, passing encrypted parameters about the original request on the query string.
  • The client follows the redirect and issues an HTTP GET to the login server with the supplied query string.
  • The Passport login service presents the client with a login form.
  • The client fills out the form and does a POST back to the login server using SSL.
  • The login server authenticates the user and redirects back to the original URI (http://www.contoso.com/default.aspx) with the authentication ticket encrypted in the query string.
  • The client follows the redirect and requests the original protected resource again with the ticket encrypted on the query string.

Back on the originating server, the PassportAuthenticationModule detects the absence of the cookie and the presence of the ticket on the query string and issues the authentication cookie, which consists of the authentication ticket and a path of / . The request is then authenticated.

Subsequent requests for protected resources at the site are authenticated at the originating server using the supplied ticket. Passport also makes provisions for ticket expiration and reusing tickets across other member sites.

The encryption scheme Passport uses is Triple DES. When member sites register with Passport, they are granted a site-specific key. The Passport login server uses this to encrypt and decrypt the query strings passed between sites.

It is important to remember that using Passport authentication requires site registration with the Passport service and installation of the Passport SDK prior to use (a fee is charged).

Configuring the Passport Authentication Provider

The PassportAuthenticationModule provider provides a wrapper around the Passport SDK for ASP+ applications. It requires installation of the Passport SDK and provides Passport authentication services and profile information from an IIdentity-derived class called PassportIdentity.

As is the case with the WindowsIdentity, the primary purpose of handling the PassportAuthentication_OnAuthenticate event is to attach a custom IPrincipal object to the context.

A special IIdentity-derived class called IPassportIdentity provides an interface to the Passport profile information as well as methods to encrypt and decrypt Passport authentication tickets.

The general process for implementing Passport authentication in an ASP+ application is as follows:

  1. Download, install, and configure the Passport SDK. It can be found at http://www.passport.com/business. You must register and pay a fee to obtain the SDK.
  2. Set up Passport as the authentication mode in the application configuration file as follows:
    <security>
     <authentication mode= "Passport">
     </authentication>
</security>
  3. Using the Passport documentation and the Passport SDK functionality, implement Passport authentication and authorization.

The Cookie Authentication Provider

Cookie authentication is generally used to refer to a system where unauthenticated requests are redirected to an HTML form (using HTTP client-side redirection). If you need to collect your own user credentials at logon time through HTML forms, this would be a good choice of providers. The user provides credentials and submits the forms. If the application authenticates the request, the system issues a cookie that contains the credentials in some form or a key for reacquiring the identity. Subsequent requests are issued with the cookie in the request headers and they are authenticated and authorized by an ASP+ handler using whatever validation method the application specifies.

It should be noted that cookie authentication is often used for personalization, where content is customized for a known user. In some of these cases, identification is the issue rather than authentication, so it is enough to merely store the user name (in a durable cookie) and use that cookie to access the user personalization information.

The CookieAuthenticationModule Provider

The CookieAuthenticationModule provider exposes cookie-based authentication services to ASP+ applications. The module allows the developer to optionally handle a CookieAuthentication_OnAuthentication event during the authentication process.

You must provide a logon URL that collects and authenticates credentials. If the credentials are valid, you can rely upon helper utilities provided to redirect the request to the originally requested resource with an appropriate authentication ticket cookie. For more information on authentication ticket cookies, see Creating an Authentication Ticket Cookie later in this section.

In the simplest case, the developer can just configure a login URL to redirect unauthenticated requests to, supply a minimal implementation of that file (customized from an example page), and supply a file containing valid credential pairs. The framework will take care of the rest. The following example code shows how this might be handled in an ASP+ configuration file:

// config.web file
<security>
     <authentication mode="cookie">
          <cookie cookie="401kApp"
                  loginurl="/login.aspx"
                  decryptionkey="1!#$$*13^">
               <credentials passwordformat=SHA1>
                    <user name="Mary" password="GASDFSA9823423BSD"/>
                    <user name="John" password="ZA#$34343443BSD44"/>
                    <user name="John" password="ZA#$34343443BSD44"/>
               </credentials>
          </cookie>
     </authentication>
</security>

The flow of control for a cookie authentication conversation is shown in the following example:

A user agent (browser) requests a protected resource from a server.

UA:

GET /default.aspx

In the absence of an authentication cookie, the server redirects the request to a login page to collect credentials. Information about the originating page is placed in the query string using RETURNURL as the key.

S:

302 Found
Location: https://samples.microsoft.com/login.aspx?RETURNURL=/default.aspx

The user agent follows the redirection to the login page.

UA:

GET /login.aspx?RETURNURL=/default.aspx

The server returns the login page. It is recommended that at least the POST back to the login page use SSL to protect the user’s credentials from being sent in cleartext.

S:

200 OK

The user enters their credentials into the login form.

UA:

POST /login.aspx?RETURNURL=/default.aspx

The server validates the credentials and, if the credentials are authenticated, redirects the UA to the original URL retrieved from the login ticket. The authentication ticket is encrypted in the query string.

S:

302 Found
Location: /default.aspx?ASPXTICKET=ABCDEFG12345

The user agent follows the redirection and requests the original resource again.

UA:

GET /default.aspx?ASPXTICKET=ABCDEFG12345

Access is granted (assuming the user is authorized) and the authentication cookie, which contains an authentication ticket, is granted. Future requests by the same browser session will be authenticated when the module inspects the cookie. It is possible to create a durable cookie that can be used for future sessions, but only until the cookie expiration date.

S:

200 OK
Set-Cookie: ASPXTICKET=ABCDEFG12345;Path=/

Notice that the cookie Path is set to /. Because cookies are case-sensitive, this is a safeguard against inconsistent case in URLs on the site. For example, if the path were set to /401K and a link contained /401k, the user would be forced to reauthenticate because the browser would not send the cookie.

Creating an Authentication Ticket Cookie

The authentication ticket is a linear representation of the AuthenticationTicket class suitable for encoding as an HTTP cookie or query string. The creation of an authentication ticket cookie from an AuthenticationTicket class is straightforward and detailed in the following steps:

  1. Convert AuthenticationTicket.IssueDate to a time_t value (seconds since the Epoch).
  2. Concatenate the fields in the order in which they are represented in the class (Name, IssueDate, IsPersistent, Version, Expiration).
  3. Compute the Message Authorization Code (MAC) by hashing the concatenation of the previous values and the site configured encryption key, and concatenate the MAC to the end of the data. For more information on MAC algorithms, see the CryptoAPI section of the Microsoft Platform SDK documentation, or the book, Applied Cryptography: Protocols, Algorithms, and Source Code in C, 2nd Edition by Bruce Schneier.
  4. Using TripleDES encryption (symmetrically), encrypt the concatenated result using a configurable site-specific key.
  5. Base64 encode the concatenated result to produce the cookie value.

When the cookie is validated, the CookieAuthenticationModule will take the cookie data, append the site encryption key, and produce a hash. The resulting hash will be compared to the hash in the cookie itself. If they match, the cookie is considered valid.

Cookie Authentication Credentials

You can allow the CookieAuthenticationModule to handle the authentication process from an application configuration file, as described in the following paragraphs.

As illustrated earlier, valid user/password pairs can be placed in the <credentials> section of a configuration file. The credentials collected from the user requesting logon privileges can be compared to the list of user/password pairs in the <credentials> section to determine if access should be granted. In the following example, users Mary and John could log in if they provide the correct password.

<credentials passwordformat=”SHA1” >
     <user name=”Mary” password=”GASDFSA9823598ASDBAD”/>
     <user name=”John” password=”ZASDFADSFASD23483142”/>
</credentials>

The credential pairs must be contained within a <credentials> section. The passwordformat attribute is required, and can be one of the following values:

Value | Description
MD5 | Passwords are stored using an MD5 hash digest. When credentials are validated, the user password will be hashed using the MD5 algorithm and compared for equality with this value. The cleartext password is never stored or compared when using this value. Use this algorithm for best speed as compared to SHA1.
SHA1 | Passwords are stored using the SHA1 hash digest. When credentials are validated, the user password will be hashed using the SHA1 algorithm and compared for equality with this value. The cleartext password is never stored or compared when using this value. Use this algorithm for best security.
clear | Passwords are stored in cleartext. The user password is compared directly against this value without further transformation.

The CookieAuthenticationModule is configured by the <cookie> element in a configuration file. The cookie, loginurl, and decryptionkey attributes of the <cookie> element can be set. See the example cookie configuration file shown earlier.

The cookie attributes can be set as follows:

Attribute | Description
Cookie= | Name of the HTTP cookie to use for the authentication ticket. By default, this value is .ASPXAUTH.
Loginurl= | The URL to which the request is redirected if it does not contain a valid authentication ticket. It is strongly suggested that this be a SSL URL (https://) to prevent credentials from being posted in cleartext. This need not be SSL-protected if the login form itself posts back to an SSL-protected resource.
Decryptionkey= | A key used to decrypt authentication tickets. Note that the default is "autogenerate", so a machine-specific key is used, and the cookies cannot be shared across server machines. This key is stored in cleartext.

For more information on ASP+ configuration, see ASP+ Configuration Concepts

Cookie Authentication Utilities

A helper class called CookieAuthentication provides some static helper methods for managing cookie authentication tickets. These methods are as follows:

System.ASP.Security
{
     public class CookieAuthentication
     {
          // encrypt and decrypt tickets
          public static CookieAuthenticationTicket Decrypt (
                         String cookie);

          public static String Encrypt (
                         CookieAuthenticationTicket ticket);

          public static bool Authenticate(String user, String password);

          public static void Redirect(HttpContext context,
                         CookieAuthenticationTicket ticket,
                         String originalURL);

          public static void Redirect(HttpContext context,
                         CookieAuthenticationTicket ticket);

     }
}

You can use the helpers to customize the way the module works. They can also be used in the login page handler to avoid the work of generating the redirection. A login page using these facilities can be as simple as the following example:

<html>
<head>
<script runat=server>
     Sub SubmitBtn_Click(ByVal Source as Object, ByVal E as EventArgs)
          ’pull credentials from form fields and try to authenticate
          If CookieAuthentication.Authenticate(UserName.Value,
                    UserPassword.Value) Then
          Set ticket = New CookieAuthenticationTicket(
                    UserName.Value, false)
          CookieAuthentication.Redirect context, ticket
          End If
     EndSub
</script>
</head>

<body>

<form method=post runat=server>
<table>
     <tr>
          <td>Name:</td>
          <td><input type=”text” id=”UserName” runat=server/>
     </tr>
     <tr>
          <td>Password:</td>
          <td><input type=”password” id=”UserPassword” runat=server/>
     </td>
</table>
<input type=”submit” OnServerClick=”SubmitBtn_Click”/ runat=server>
</form>
</body>
</html>

Applications that need granular control over the HTTP cookie properties can use the encryption helpers to encrypt the authentication ticket, but can construct the ticket and perform the redirection themselves.

Handling CookieAuthentication Events

You can choose to handle the CookieAuthenticate_OnAuthenticate event in order to do all cookie management in whatever way you want. Although in many applications you will want to take advantage of the framework support to manage this for you, some applications might have specific needs (for example, storing credentials in the cookie). These applications should handle the CookieAuthenticate_OnAuthenticate event to perform cookie management in whatever way desired.


ASP+ Authorization

The purpose of authorization is to determine whether an identity should be granted for the requested type of access to a given resource. Besides impersonation, there is one other authorization method offered by ASP+: URL authorization.

The URLAuthorizationModule maps users and roles to pieces of the URI namespace. This module implements both positive and negative authorization assertions. That is, the module can be used to selectively allow or deny certain sets, users, or roles access to arbitrary parts of the URI namespace.

The URLAuthorizationModule is available for use at any time. You only need to place a list of users and/or roles in the <allow> or <deny> elements of the <authorization> section of a configuration file.

To establish the conditions for access to a particular directory, a configuration file that contains an <authorization> section must be placed in that directory. The conditions set for that directory also apply to its descendants, unless they are overridden by configuration files in the descendant directories. The general syntax for this section is as follows:

<[element] [users] [roles] [verbs] />

The element is required. Either the users or the roles attribute must be included. Both can be included, but both are not required. The verbs attribute is optional.

The permissible elements are <allow> and <deny>, which grant and revoke access respectively. Each element supports three attributes, which are defined in the following table:

Attribute | Description
users | Identifies the targeted identities for this element.
roles | Identifies a targeted role for this element. The associated IPrincipal object for the request determines the role membership. Arbitrary IPrincipal objects can be attached to the context for a given request and they can determine role membership in whatever fashion they like. For example, the default WindowsPrincipal class uses Windows NT groups to determine role membership.
verbs | Defines the HTTP verbs to which the action applies (such as GET, HEAD, POST).

Anonymous users are also denied.

The following example grants access to Mary, while denying it to John:

<security>
     <authorization>
          <allow users=”Mary”/>
          <deny users=”John” />
          <deny users=”?” />
     </authorization>
<security>

Both users and roles may refer to multiple entities using a comma-separated list:

<allow users=”John, Mary, redmond\bar” />

In addition to identity names, there are two special identities: "*", which refers to all identities and "?", which refers to the anonymous identity. So, to allow John and deny everyone else, one might construct the following configuration section:

<security>
     <authorization>
          <allow users=”John” />
          <deny users=”*” />
     </authorization>
</security>

The following example lets everyone do a GET, but only Mary can use POST:

<security>
     <authorization>
          <allow verb=”GET” users=”*” />
          <allow verb=”POST” users=”Mary” />
          <deny verb=”POST” users=”*” />
     </authorization>
<security>

Rules are applied using the following heuristics:

  • Rules at lower levels take precedence over rules at higher levels. The system determines this by constructing a merged list of all rules for a URL, with the most recent (nearest in the hierarchy) rules at the head of the list.
  • Given a set of merged rules for a URL, the system checks rules starting at the head of the list until the first match is found. Note that the default configuration for ASP+ contains an <allow users="*"> element, which authorizes all users. If no rules match, the request is allowed unless otherwise denied. If a match is found and the match is a <deny> element, it returns 401. Applications or sites can easily configure a <deny users="*"> element at the top level of their site or application to prevent this behavior.

    In the case of an <allow> that matches, the module does nothing (letting the request be processed further).


ASP+ Impersonation

Impersonation refers to a concept whereby ASP+ applications execute with the identity of the client on whose behalf they’re operating. The usual reason for doing this is so that authentication and authorization issues do not have to be dealt with in the ASP+ application code. IIS is relied on to authenticate the user and either passes an authenticated token to the ASP+ application or, if unable to authenticate the user, passes an unauthenticated token. In either case, by default, the ASP+ application will impersonate whichever token is received. Then, the ASP+ application relies on the settings on the NTFS directories and files to allow it (now impersonating the client) to gain access or not.

It is highly recommended that the server file space be formatted as NTFS, so that access permissions can be set.

For ASP compatibility, ASP+ will do per-request impersonation by default. If impersonation is enabled for a given application, ASP+ will always impersonate the access token that IIS provides to ISAPI extensions. That token can be either an authenticated user token, or the token for the anonymous user (such as IUSER_MACHINENAME). The impersonation will occur regardless of the type of authentication being used in the application and whether the user is authenticated (in that case, it is the anonymous user token).

Applications that do not desire per-request impersonation can simply be configured to never impersonate and thus run with their original process token. Impersonation is complicated somewhat by the demands of dynamic compilation—at a minimum, authenticated users must be able to read (and possibly write) the codegen directory.

Configuring for Impersonation

To disable impersonation for a given application domain, add the following configuration directive to the desired application configuration:

<impersonation enabled=”false”/>

Adding configuration can be done by putting a configuration file in the application directory or by specifying a <webconfig> section with a location attribute specifying the application. For more information on the ASP+ configuration system, see the ASP+ Configuration Concepts section.

As is the case with other configuration directives, this directive applies hierarchically. It is respected by nested applications in the hierarchy, unless explicitly overridden. As stated previously, the default value for this setting is as follows:

<impersonation enabled=”true”/>

A minimal configuration file to enable impersonation for an application might look similar to the following example:

// config.web file
<security>
     <identity>
          <impersonation enable="true">
     </identity>
</security>


Designing Secure ASP+ Applications

A section describing best design practices for various scenarios - includes example code.

[To be supplied.]


Scenario 1 - Using IIS Authentication With ASP+ Impersonation

In this scenario, an administrator is setting up an application on an intranet Web site where employee information is to be posted. However, some of the information is for managers only. The manager information will be posted to a different directory (a subdirectory) than the general employee information, so that access to it can be limited. It is also assumed that:

  • The administrator is using a Windows NT or Windows 2000 server.
  • The hard disk is formatted for NTFS.
  • IIS 5.0 is the Web server.
  • All employees needing access are using Windows platforms.

The administrator might proceed as follows:

  1. The administrator creates the following files and directories:

    1. The administrator creates a Windows group called Managers that contains all users who should have access to the ManagerInfo.aspx file.
    2. The administrator uses the IIS administration tool (window) to set up Windows authentication.
    3. The administrator confirms that the <impersonation enabled> element in the ASP+ configuration file has not been changed from the default value of "true".
    4. The administrator sets the NTFS ACL for the ManagerInformation directory to allow access to only those identities that are in the Windows Manager group.

    The necessary security has now been applied to the EmployeeInformation application without the necessity of writing any code.


Scenario 2 - Cookie Authentication

In this scenario, a protected resource, default.aspx, is requested by the client. In the security section of the configuration file, the authentication mode is set to "Cookie", so ASP+ tries to find a cookie that is attached to the request. If it finds none, it redirects the request to a login page (login.aspx). There, the client user enters the required credentials (e-mail name and password). The page compares the entered credentials to a list of credentials in an XML file. If a match is found, the request is considered authenticated and the client is redirected to the originally requested resource (default.aspx). If no match is found, the request is redirected to the add user page (adduser.aspx). There, the just-entered credentials are encoded and added to the XML file.

There are five files associated with this example — three .aspx files (default.aspx, login.aspx, and adduser.aspx) and two configuration files (both named config.web, but one is located in the application root directory and the other is located in the adduser directory). All five files will be explained in the following discussion.

Application Root Directory Configuration File (config.web)

You should set up this configuration file to have the following entries and place it in the application root directory (the directory in which default.aspx resides).

<configuration>
   <security>
  1. Set the authentication mode to Cookie. Other possible values are Windows, Passport, and None (empty string) - but for this example it must be Cookie.
    <authentication mode="Cookie" />
  2. Set the cookie authentication attributes.
    <cookieauthentication
    1. Set the decryptionkey to autogenerate. This causes ASP+ to automatically generate a key for decrypting the cookies.
      decryptionkey = "autogenerate"
    2. Set the loginurl attribute to login.aspx. The is the URL to be used for redirection if ASP+ does not find a cookie with the request.
      loginurl = "login.aspx"
    3. Set the cookie name suffix.
      cookie = ".ASPXCOOKIEAUTH" />
  3. Deny unauthenticated users access to this directory.
        <authorization>
       <deny users="?" />
    </authorization>
  </security>
</configuration>

Adduser Directory Configuration File (config.web)

You should set up this file to have the following entries and place it in the file containing adduser.aspx. These settings allow anyone access to this directory (assuming there are no NTFS ACLs preventing it).

<configuration>
   <security>
      <authentication mode="None" />

      <authorization>
          <allow users="*" />
      </authorization>

   </security>
</configuration>

Default.aspx File

This is the requested, protected resource. It is a simple file that merely displays the string "Hello" plus the recorded (logged in) e-mail name, and a Signout button.

<%@ Page LANGUAGE="c#" %>
<html>
<head>
<title>Cookie Authentication</title>

<script runat=server>
    private void Page_Load(Object Src, EventArgs E )
    {
        Welcome.InnerHtml = "Hello, " + Context.User.Identity.Name;
    }
    private void Signout_Click(Object sender, EventArgs E)
    {
        CookieAuthentication.SignOut();
        Response.Redirect("login.aspx");
    }
</script>

<body>
<h3><font face="Verdana">Using Cookie Authentication</font></h3>
<span id="Welcome" runat=server/>
<form runat=server>
    <input type="submit" OnServerClick="Signout_Click" Value="Signout"
                                                       runat="server"/><p>
</form>
</body>
</html>

Login.aspx File

This is the file to which the request gets redirected if ASP+ does not find the cookie with the request. This URL was set up in the configuration file. A form is presented to the client user. It has two text boxes (e-mail name and password) and a submit button. The user enters the e-mail name and password, and clicks the submit button. The code then checks to see if this name and password is contained in an XML file located in this directory. If it is, then the user is connected to default.aspx. If it is not, the adduser.aspx file is called.

To implement this functionality, proceed as follows:

  1. Import the necessary name spaces.
    <%@ Page LANGUAGE="c#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SQL" %>
<%@ Import Namespace="System.Web.Security " %>
<%@ Import Namespace="System.IO" %>

<html>
<head>
<title>Cookie Authentication</title>
  2. Create a script section for the code.
    <script runat=server>
  3. Implement a Login_Click function
    private void Login_Click(Object sender, EventArgs E)
{
    1. If the page is not valid, tell the user.
      if( !Page.IsValid ) {
    Msg.Text = "Some required fields are missing";
    return;
}
    2. Set up a string named "cmd" that is initialized to "UserEmail='MyName'", where MyName is the user's e-mail name.
      String cmd = "UserEmail='" + UserEmail.Value + "'";
    3. Instantiate a new DataSet object.
      DataSet ds = new DataSet();
    4. Read in the XML file containing authenticated user name and password combinations. The retrieved data resides in "ds", the DataSet instantiated in the previous step.
    Note For the sake of simplicity, and the learning experience, the following code does not follow best design practice, in that it does not invoke any file locking or sharing flags. Also, in a real Web site, it would probably be desirable to use a relational database for the list of authenticated users.
    FileStream fs = new FileStream(Server.MapPath("users.xml"),
                                    FileMode.Open,FileAccess.Read);
StreamReader reader = new StreamReader(fs);
ds.ReadXml(reader);
fs.Close();
    1. Instantiate a new DataTable named "users" that is initialized to "ds".
      DataTable users = ds.Tables[0];
    2. Check for any matches between the Login name and the list of names in "users". For each match found, record the name in a DataRow[] named "matches"
      DataRow[] matches = users.Select(cmd);
    3. Check each of the name matches found in the previous step to see if there is a matching password for any of them.
      if( matches != null && matches.Length > 0 )
{
    4. If at least one name match is found, check for password matches.
          DataRow row = users.Rows[0];
    String pass = (String)row["UserPassword"];
    if( 0 != String.Compare(pass, UserPass.Value, false) )
    //**********************************************
    // If no password match is found, tell the user.
    //**********************************************
        Msg.Text = "Invalid Password: Please try again";
    else
    //*************************************************
    // If a password match is found, redirect the request
    // to the originally requested resource (Default.aspx).
    //*************************************************
        CookieManager.RedirectFromLoginPage(UserEmail.Value,
                                            PersistCookie.Checked);
}
else {
    5. If no name matches were found, redirect the request to the add users page. First a URL string must be constructed, and then the string is used in a Response.Redirect command.
              StringBuilder url = new StringBuilder();
        url.Append("adduser/adduser.aspx?");
        url.Append(Request.ServerVariables["QUERY_STRING"]);
        url.Append("&UserEmail=");
        url.Append(Server.UrlEncode(UserEmail.Value));
        url.Append("&UserPassword=");
        url.Append(Server.UrlEncode(UserPass.Value));
        Response.Redirect(url.ToString());
    }
}
</script>
<body>
  4. Display a form to collect the login information.
    <form runat=server>
    <h3><font face="Verdana">Login Page</font></h3>

    <table>
        <tr>
    1. Create a User E-mail Name text box.
              <td>Email:</td>
        <td><input id="UserEmail" type="text" runat=server/></td>
        <td><ASP:RequiredFieldValidator
            ControlToValidate="UserEmail"
            Display="Static"
            ErrorMessage="*"
            runat=server/>
        </td>

    </tr>
    <tr>
    2. Create a Password text box.
              <td>Password:</td>
        <td><input id="UserPass" type=password runat=server/></td>
        <td><ASP:RequiredFieldValidator
            ControlToValidate="UserPass"
            Display="Static"
            ErrorMessage="*"
            runat=server/>
        </td>
    </tr>
    <tr>
    3. Create a Persistent Cookie check box. If the persist cookie is set (checked), the cookie will be valid across browser sessions. Otherwise, when the browser is closed, the cookie is destroyed.
              <td>Persistent Cookie:</td>
        <td><ASP:CheckBox id=PersistCookie runat="server"
            autopostback="true" />
        </td>
        <td></td>
    </tr>

</table>
    4. Create a Submit button that causes the Login_Click event to be fired on postback.
      <input type="submit" OnServerClick="Login_Click" Value="Login"
                                             runat="server"/><p>
    <asp:Label id="Msg" ForeColor="red" Font-Name="Verdana"
                                  Font-Size="10" runat=server />

</form>
</body>
</html>

Add User.aspx File

When the login page cannot find the e-mail name in the "users" XML file, it redirects the request to this page, and if the user clicks the "Add User" button, the user name and password are added to the file. This page is implemented as follows:

  1. Import the necessary namespaces.
    <%@ Page LANGUAGE="c#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SQL" %>
<%@ Import Namespace="System.Web.Security " %>
<%@ Import Namespace="System.IO" %>
<html>
<head>
<title>Cookie Authentication</title>
  2. Create a script section tag
    <script runat=server>
  3. Implement a Page_Load function
    private void Page_Load(Object Src, EventArgs E )
{
    1. Get the UserEmail and UserPassword strings from the request.
      String email = Request.QueryString["UserEmail"];
String password = Request.QueryString["UserPassword"];
    2. If they are not null, set up the UserEmail and UserPassword values.
      if( null != email )
    UserEmail.Value = email;
if( null != password )
    UserPass.Value = password;
}
  4. Implement the AddUser_Click function.
    private void AddUser_Click(Object sender, EventArgs E)
{
    1. If the page is not valid, tell the user.
      if( !Page.IsValid ) {
    Msg.Text = "Some required fields are missing";
    return;
}
    2. Instantiate a new DataSet named ds.
      DataSet ds = new DataSet();
    3. Initialize a string named userFile with the path to the Users.xml file.
      String userFile = "../users.xml";
    4. Read in the XML file to a DataSet named "ds" (instantiated in step b).
      FileStream fs = new FileStream(Server.MapPath(userFile),
                                    FileMode.Open,FileAccess.Read);
StreamReader reader = new StreamReader(fs);
ds.ReadXml(reader);
fs.Close();
    5. Add the new name and password to the DataSet "ds".
      DataRow newUser = ds.Tables[0].NewRow();
newUser["UserEmail"] = UserEmail.Value;
newUser["UserPassword"] = UserPass.Value;
ds.Tables[0].Rows.Add(newUser);
ds.AcceptChanges();
    6. Write the new DataSet (with the new Name and Password) out to the XML file.
      fs = new FileStream(Server.MapPath(userFile), FileMode.Create,
                                 FileAccess.Write|FileAccess.Read);
StreamWriter writer = new StreamWriter(fs);
ds.WriteXml(writer);
writer.Close();
fs.Close();
    7. Redirect the request back to the originally requested resource (Default.aspx).
      CookieManager.RedirectFromLoginPage(UserEmail.Value,
                                            PersistCookie.Checked);
}
</script>
<body>
<form runat=server>
    <div style="background:#ccccff"><h3><font face="Verdana">Add New
                                              User</font></h3></div>
  5. With the exception of the button name, the following form is identical to the one described under the Login.aspx File heading.
        <table>
        <tr>
            <td>Name:</td>
            <td><input id="UserEmail" type="text" runat=server/></td>
            <td><ASP:RequiredFieldValidator
                ControlToValidate="UserEmail"
                Display="Static"
                ErrorMessage="*"
                runat=server/>
            </td>

        </tr>
        <tr>
            <td>Password:</td>
            <td><input id="UserPass" type=password runat=server/></td>
            <td><ASP:RequiredFieldValidator
                ControlToValidate="UserPass"
                Display="Static"
                ErrorMessage="*"
                runat=server/>
            </td>
        </tr>
        <tr>
            <td>Persistent Cookie:</td>
            <td><ASP:CheckBox id=PersistCookie runat="server"
                autopostback="true" />
            </td>
            <td></td>
        </tr>

    </table>
    <input type="submit" OnServerClick="AddUser_Click" Value="Add User" runat="server"/><p>
        <asp:Label id="Msg" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server />
</form>
</body>
</html>

To see an example run that has code very similar to this example, go to the ASP+ Quick Start and run sample "Forms-based/Cookie Authentication".


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Scenario 3 - URL Authorization

Presents code and step-by-step instructions.

[To be supplied.]


Developing Controls and Components

This section provides detailed conceptual and task related information for developers of custom controls. If you are interested in creating a custom control to be used in a Win Forms application, or a custom server control to be used on an ASP+ page, see the sections listed below. You will also find information on implementing design time support for your controls.

For information about | See Section
Background for component and control developers. | Component Programming Essentials
Developing a custom Win Forms control. | Developing Win Forms Controls
Developing a custom ASP+ server control. | Developing Web Forms Controls
Implementing a custom type converter, UI type editor, extender provider, or designer. | Enhancing Design-Time Support


Component Programming Essentials

This section provides background information for component and control developers.

Language Choices

Class vs. Component vs. Control

Essential Programming Constructs

Licensing Components and Controls


Language Choices

A programming language that targets the NGWS runtime is not required to guarantee interoperability with other languages. To enable language interoperability, NSWS runtime and frameworks has defined a programming language standard called the Common Language Specification (CLS). The CLS is a subset of language features supported by the NGWS runtime, and includes features that are common to major object-oriented programming languages. If you want your components and controls to be usable from other programming languages, you must author them in a CLS compliant language, and ensure that all public and protected members are CLS compliant. The NGWS SDK provides compilers for four CLS compliant languages – Visual Basic 7, C#, Managed Extension for C++, and Jscript 7. More information about CLS is provided in What is the Common Language Specification?.

See Also

Class vs. Component vs. Control | Essential Programming Constructs


Class vs. Component vs. Control

A class that targets the NGWS runtime can be versioned via assemblies. Additionally, if it exposes only Common Language Specification (CLS) compliant members, it can be used seamlessly from other CLS-compliant languages. A NGWS runtime or frameworks class thus satisfies the major requirements for software reuse. Why then is another entity called component defined, and what is its role?

A component is a class with emphasis on cleanup and containment. A component can be hosted in a container, and has the ability to query and get services from its container. A component ensures that resources are released explicitly through its Dispose method, without waiting for automatic memory management. During teardown of a container, it is guaranteed that all resources allocated to the container and its components will be released. Containment is logical and does not have to be visual. A middle tier container that sites database components is an example of logical containment.
Note: The Win Forms and Web Forms designers in Visual Studio 7.0 require that a class must be a component to be displayed in the designer. This requirement is based on considerations such as described above.

The definitions of a component and a control are given below.

Component
To be a component, a class must implement the System.ComponentModel.IComponent interface, and provide a constructor that requires no parameters. A class can also be a component if it derives from a class that implements System.ComponentModel.IComponent such as System.ComponentModel.Component. A component can be contained and sited by a container. When sited in a container, a component interacts with the container through its container provided site.
Control
A control is a component with user interface (UI) capabilities. NGWS frameworks has not defined interfaces that a class must implement in order to be a control. The only controls in NGWS frameworks are classes that derive from System.WinForms.Control or System.Web.UI.Control, and their subclasses. Both of these base classes are components that provide basic UI capabilities.
[C#]
//MyWinControl is a control since RichControl derives from
//System.WinForms.Control
using System.WinForms;
public class MyWinControl : RichControl {…}

//MyWebControl is a control since WebControl derives from
//System.Web.UI.Control
using System.Web.UI;
public class MyWebControl : WebControl {…}

If you are developing components and controls for Win Forms or Web Forms, you will not have to implement containers or sites. The Win Forms and Web Forms designers are containers for Win Forms and Web Forms controls. Containers are used for providing services. At design time, controls are sited in the designer and obtain services from the designer. For completeness, the definitions of a container and a site are given below.

Container
To be a container, a class must implement the System.ComponentModel.IContainer interface and provide a constructor that requires no parameters. A class can also be a container if it derives from a class that implements System.ComponentModel.IContainer. A container logically contains one or more components that are called the container's child components.
Site
Sites are provided by a container to manage and communicate with its child components. Typically, a container and a site are implemented as a unit. To be a site, a class must implement the System.ComponentModel.ISite interface.

A Win Forms control sited in Microsoft Internet Explorer 5.5, is another example of a component cited in a container. The browser provides services, such as ambient properties, to the control through its site.

See Also

Essential Programming Constructs


Essential Programming Constructs

This section provides an overview of constructs that are essential for programming components and controls.

Properties

Events

Custom Metadata Using Attributes


Properties

Classes can have members called properties. In its common usage, a property is logically equivalent to a private data member accompanied by accessor functions. A property is accessed syntactically as a field of a class. (Although properties can have public, protected, or private access, the discussion here will be limited to the more common case of public properties.)

Note:
  • Because properties have been available in several editions of Visual Basic, VB programmers may wish to skip this topic.
  • Visual designers allow properties, but not fields, to be edited visually.

A property definition generally consists of the following two pieces:

  1. Definition of a private data member.
    [C#]
private int number = 0;
  2. Definition of a public property using the property declaration syntax. This syntax associates the private data member with a public property through get and set accessor functions.
Note While a property definition generally includes a private data member, this is not required. The get method described below could return a value without accessing a private data member. An example is a property whose get method returns the System time. The idea behind properties is data hiding, whereby the accessor methods hide the implementation of the property.

The code fragment below defines a property called MyNumber

[C#]
public int MyNumber {
//retrieves number
get { return number; }
//assigns to number
set { number = value;}
}

The variable value is assigned to the property in the calling code. The type of value must the same as the declared type of the property to which it is assigned.

Note for C# Users The term property is not a keyword in C# and does not appear in the property declaration.

The following code example shows how the property MyNumber can be accessed.

[C#]
public class Foo {
   private int number = 0;
public int MyNumber {
//retrieves number
get { return number; }
//assigns to number
set { number = value;}
}
//other members
}
public class Bar{
   Foo example= new Foo();
//the set method is called with value = 5
   example.MyNumber = 5;
// the get method is called
   int anumber = example.MyNumber;
}

The get and set methods are generally no different from other methods. They can perform any program logic, throw exceptions, be overridden, and can be declared with any modifiers allowed by the programming language. Note however that properties can also be static. If a property is static, there are limitations on what the get and set methods can do. See your programming language reference for details.

Properties can be primitive types, collections of primitive types, user-defined types, or collections of user-defined types. For all primitive types, NGWS frameworks provide type converters that implement string-to-value conversions. When a type converter is available for a property, it can be displayed in the property window in the designer. If you define custom properties and want them to be displayed in the property window, you will need to implement custom type converters.

When the data type of a property is an enumeration, in a designer such as Microsoft Visual Studio 7.0, the property will be displayed as a drop-down list in the property window. If the data type of a property is a class that has properties, those properties are called sub properties of the defining property. In the property window in Visual Studio 7.0, a user can expands a property that has sub properties to display the sub properties.

See Also

Events

Custom Metadata Using Attributes


Events

This section gives an overview of the event model and shows how you can implement event functionality in your components. It includes the following topics:

Events and Delegates

Providing Event Functionality

Event Delegate Mini-Sample


Events and Delegates

An event is a message sent by an object to signal the occurrence of an "action". The action could be caused by user interaction, such as a mouse click, or it could be triggered by some other program logic. The object that raises (triggers) the event is called the event source, and the object that responds to the event is called the event receiver. The problem in event communication is that an event source does not know which object will want to receive the event, and an event receiver does not know where the event may originate. What is needed is an intermediary that will act as a messenger between the source and the receiver. The event model does just that, it uses classes called delegates to mediate event transmission.

What is a delegate? In the NGWS runtime and frameworks, a delegate is a class that can hold a reference to a method. Unlike other classes, a delegate class has a signature, and it can hold references only to methods that match its signature. A delegate is thus equivalent to a type-safe function pointer or a callback. Delegates have other uses besides event handling, but the discussion here will focus on the event functionality of delegates. A delegate class used as an event intermediary is called an event handler, because it holds references to methods that perform event handling logic. A declaration for an event delegate is shown below.

[C#]
//AlarmEventArgs is the class that holds event data for the alarm event.
//A class that holds event data derives form the base class for events,
//EventArgs.
//
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);

Event delegates have two parameters, the source that raised the event, and the data for the event. All event delegates are multicast, which means that they can hold references to more than one event handling method. This means that an event raised by one source can be dispatched to more that one receiver.

Note A delegate declaration is sufficient to define a delegate class. The declaration provides the signature of the delegate, and the implementation is provided by the NGWS runtime.

Delegates allow for flexibility and fine-grain control in event handling. A delegate acts as an event dispatcher for the class that raises the event by maintaining a list of registered event handlers for the event. To register as a receiver, a class must have an event handling method that matches the signature of the delegate. The class WakeMeUp below has a method AlarmRang that handles the alarm event.

[C#]
public class WakeMeUp {
// AlarmRang has the same signature as AlarmEventHandler
public void AlarmRang(object sender, AlarmEventArgs e){...};
...
}

To register an instance of WakeMeUp as a receiver of the alarm event:

  1. An AlarmEventHandler delegate must refer to it.
  2. The delegate must be registered with an alarm event source

Step 1 is accomplished by creating an instance of the AlarmEventHandler delegate that takes a reference to the AlarmRang method of the WakeUp instance in its constructor, as shown below.

[C#]
//Create instance of WakeMeUp
//
WakeMeUp w = new WakeMeUp();

//Instantiate the event delegate.
//The C# compiler provides a constructor for event handlers that takes
//only one parameter, the reference to the method that performs the
//event handling logic. The two parameter constructor for EventHandler
// provided in the class library is to be used by those who
// develop compilers and other tools that target the NGWS runtime.
//
AlarmEventHandler alhandler = new AlarmEventHandler(w.AlarmRang);

Now, whenever alhandler is called, it in turn calls the AlarmRang method of w.

Step 2, registering the delegate with the event source, is illustrated in the Event Delegate Mini-Sample.

Custom event delegates are needed only when the event generates event data. Many events, including some user interface events, do not generate event data. In such situations, the event delegate provided in the class library for the no-data event, System.EventHandler, is adequate. Its declaration is given below

[C#]
// EventArgs is the base class for event data. It does not have
// any data, and hence can be used as the event data class for events
// that do not generate data.
//
delegate void EventHandler(object sender, EventArgs e);

For details on using delegates to provide event functionality in your component or control, see Providing Event Functionality .


Providing Event Functionality

Event functionality is provided by several interrelated pieces: the event data, a delegate for the event, methods in your class that attach and remove event handlers, and a method to raise the event.

To provide event functionality

  1. Define a class that defines data for the event. This class must derive from System.EventArgs, which is the base class for event data.
    Note: This step is not needed if an event data class already exists for the event your class wants to raise, or if data is not generated by your event.
    [C#]
public class AlarmEventArgs : EventArgs {
      private readonly int nrings = 0;
      private readonly bool snoozePressed = false;
//properties
      public string AlarmText{   }
      public int NumRings{...}
      public bool SnoozePressed{...}
...
     }
  2. Declare a delegate to handle the event.
    Note: You do not have to declare a custom delegate if the event does not generate data. In that situation, the base event handler System.ComponentModel.Eventhandler is adequate.
    [C#]
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);
  3. Provide public methods (event hooks) in your class that attach (or remove) event handler delegates. If you want your components to work in the designer, in the PDC Technology Preview release of Visual Studio 7.0, these methods must be named AddOn<EventName> and RemoveOn<EventName>. The code example at the end of this topic shows an implementation of these methods.
    Note for C# and Visual Basic users In a future release, the AddOn and RemoveOn methods will be replaced by a simpler syntax for adding or removing event handlers. For details of the event syntax, see the C# or VB language documentation.
    [C#]
public class Alarm : Component {
//other class members

//Attaches an event handler for the alarm event
   public void AddOnAlarm(AlarmEventHandler) {...}

//Removes an event handler for the alarm event
   public void RemoveOnAlarm(AlarmEventHandler) {...}

}
  4. Provide a protected method in your class that raises the event. This method must be named On<EventName>. The On<EventName> raises the event by invoking the delegates. The code example at the end of this topic shows an implementation of the On<EventName> method.
    Note: The protected On<EventName>method also allows derived classes to override the event without attaching a delegate to it. A subclass must always call the On<EventName> method of the base class, to ensure that registered delegates receive the event.
    [C#]
public class Alarm : Component {
   //other class members
   public void AddOnAlarm(AlarmEventHandler) {...}
   public void RemoveOnAlarm(AlarmEventHandler) {...}
   protected virtual void OnAlarm(AlarmEvent e){…}
   }

A code example that includes all the elements described above is shown below. For a complete sample that implements and uses events, see the Event Delegate Mini-Sample.

[C#]
//Step 1. Class that defines data for the event
//
public class AlarmEventArgs : EventArgs {
public class AlarmEventArgs : EventArgs {

      private readonly bool snoozePressed = false;
      private readonly int nrings = 0;
      //constructor
      public AlarmEventArgs(bool snoozePressed; int nrings) {...)
      //properties
      public int NumRings{ get { return nrings;}}
      public bool SnoozePressed { get { return snoozePressed;}}
      public string AlarmText { get {...}}

   }

//Step 2. Delegate declaration
//
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);

//Component definition
//
public class Alarm : Component {
private AlarmEventHandler alarm = null;
// Step 3a. AddOnAlarm allows clients to attach an event handler.
// The Combine method of the Delegate class adds a delegate to the
// existing list of delegates.
//
public void AddOnAlarm(AlarmEventHandler handler) {
    alarm = (AlarmEventHandler)Delegate.Combine(alarm, handler);
        }
//Step 3b. RemoveOnAlarm allows clients to remove an event handler. The
//Remove method of the Delegate class removes a delegate from the
//existing list of delegates.
//
public void RemoveOnAlarm(AlarmEventHandler handler) {
     alarm = (AlarmEventHandler)Delegate.Remove(alarm, handler);
        }
//Step 4. The protected OnAlarm method raises the event by invoking
//the delegates. The sender is always this, the current instance of
//the class.
//
protected virtual void OnAlarm(AlarmEventArgs e) {
    if (alarm != null) {
       alarm(this, e);//Invokes the delegates.
          }
     }

See Also

Events and Delegates

Event Delegate Mini-Sample

Properties

Custom Metadata Using Attributes


Event Delegate Mini-Sample

The sample presented here builds on the various pieces discussed in Events and Delegates, and Providing Event Functionality. It consists of the following classes:

  • AlarmEventArgs defines the data for the alarm event. It is instantiated by the class that raises the event, which, in this example, is the Alarm class.
  • AlarmEventHandler is the delegate for the alarm event.
  • Alarm is the component that raises the alarm event.
  • WakeMeUp is a class that has a method AlarmRang that handles the alarm event.
  • AlarmDriver is a class that demonstrates how events are wired. It instantiates WakeMeUp, and then creates a delegate that will allow WakeMeUp to receive alarm events. The delegate is an instance of AlarmHandler that has a reference to the AlarmRang method of the WakeMeUp instance in its constructor. AlarmDriver completes the event wiring by registering the delegate with the instance of Alarm, using the AddOnAlarm method of Alarm.
Note There is no class named AlarmEvent. The data for the alarm event is in AlarmEventArgs.

To compile and run this sample

  • Copy the code below into a file named EventDelegateSample.cs.
  • From the directory that contains the EventDelegateSample.cs file, run the following command:

    csc EventDelegate.cs

  • Run the executable that is created, EventDelegateSample.exe.
[C#]
//EventDelegateSample.cs
//
namespace EventDelegateSample{

   using System;
   using System.ComponentModel;

   // Class that contains the data for
   //the alarm event. Derives from System.EventArgs
   //
   public class AlarmEventArgs : EventArgs {

      private readonly bool snoozePressed ;
      private readonly int nrings;

      //constructor
      //
      public AlarmEventArgs(bool snoozePressed, int nrings) {
         this.snoozePressed = snoozePressed;
         this.nrings = nrings;
      }

      //The NumRings property that returns the number of rings
      //that the alarm clock has sounded when the alarm event
      //is generated.
      //
      public int NumRings{

         get { return nrings;}

      }

      //The SnoozePressed property that indicates whether the snooze
      //button is pressed on the alarm when the alarm event is generated.
      //
      public bool SnoozePressed {
         get {
            return snoozePressed;
         }
      }

      //The AlarmText property that contains the wake-up message.
      public string AlarmText {
         get {
            if (snoozePressed)
            {
               return ("Wake Up!!! Snooze time is over.");
            }
            else
            {
               return ("Wake Up!");
            }
         }
      }

   }

   // Delegate declaration
   //
   public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);

   // The Alarm class that raises the alarm event.
   // This class does not have to be a component. In this example it
   // is, becasue this discussion is in the component developer's guide.
   //
   public class Alarm : Component {

      private bool snoozePressed = false;
      private int nrings = 0;
      private bool stop = false;

      //The Stop property indicates whether the
      //alarm should be turned off.
      //
      public bool Stop {
         get {
            return stop;
         }
         set {
            stop = value;
         }
      }

      //The SnoozePressed property indicates whether the snooze
      //button is pressed on the alarm when the alarm event is generated.
      //
      public bool SnoozePressed {
         get {
            return snoozePressed;
         }
         set {
            snoozePressed = value;
         }
      }

      // the AlarmEventHandler member
      //
      private AlarmEventHandler alh = null;

      // AddOnAlarm is the event hook that enables clients to
      // attach event handlers.
      // The Combine method of the Delegate class adds a delegate
      // to the existing list of delegates.
      //
      public void AddOnAlarm(AlarmEventHandler handler) {
         alh = (AlarmEventHandler)Delegate.Combine(alh, handler);
      }
      // RemoveOnAlarm allows clients to remove event handlers.
      // The Remove method of the Delegate class removes a delegate from
      // the existing list of delegates.
      //
      public void RemoveOnAlarm(AlarmEventHandler handler) {
         alh = (AlarmEventHandler)Delegate.Remove(alh, handler);
      }
      // The protected OnAlarm method raises the event by invoking
      // the delegates. The sender is always "this", the current instance
      // of the class.
      //
      protected virtual void OnAlarm(AlarmEventArgs e) {
         if (alh != null) {
            alh(this, e);//Invokes the delegates.
         }
      }

      //This is a cheap alarm clock that does not have
      //a user interface.
      //To simulate the alarm mechanism it has a loop
      //that raises the alarm event at every iteration
      //with a time delay of 300 milliseconds,
      //if snooze is not pressed. If snooze is pressed
      //the time delay is 1000 milliseconds.
      //

      public void Start(){

         for (;;)

         {
            nrings++;

            if (stop)
            {
               break;
            }

            else if (snoozePressed)
            {
               System.Threading.Thread.Sleep(1000);
               {
                  AlarmEventArgs e = new AlarmEventArgs(snoozePressed,
                     nrings);
                  OnAlarm(e);
               }
            }
            else
            {
               System.Threading.Thread.Sleep(300);
               AlarmEventArgs e = new AlarmEventArgs(snoozePressed,
                  nrings);
               OnAlarm(e);
            }

         }
      }
   }

   //The WakeMeUp class that has a method AlarmRang that handles the
   //alarm event.
   //
   public class WakeMeUp
   {

      public void AlarmRang(object sender, AlarmEventArgs e)
      {

         Console.WriteLine(e.AlarmText +"\n");

         if (!(e.SnoozePressed))
         {
            if (e.NumRings % 10 == 0)
            {
               Console.WriteLine(" Let alarm ring? Enter Y");
               Console.WriteLine(" Press Snooze? Enter N");
               Console.WriteLine(" Stop Alarm? Enter Q");
               String input = Console.ReadLine();

               if (input.Equals("Y") ||input.Equals("y")) return;

               else if (input.Equals("N") || input.Equals("n"))
               {
                  ((Alarm)sender).SnoozePressed = true;
                  return;
               }
               else
               {
                  ((Alarm)sender).Stop = true;
                  return;
               }
            }
         }
         else
         {
            Console.WriteLine(" Let alarm ring? Enter Y");
            Console.WriteLine(" Stop Alarm? Enter Q");
            String input = Console.ReadLine();
            if (input.Equals("Y") || input.Equals("y")) return;
            else
            {
               ((Alarm)sender).Stop = true;
               return;
            }
         }
      }
   }

   //The driver class that hooks up the event handling method of
   //WakeMeUp to the alarm event of an Alarm object using a delegate.
   //In a Forms based application, the driver class is the
   //form.
   //
   public class AlarmDriver{

      public static void Main (string[] args)
      {
         //instantiates the event receiver
         WakeMeUp w= new WakeMeUp();

         //Instantiates a delegate that holds a reference to
         //the AlarmRang method of the WakeMeUp instance.
         AlarmEventHandler alh = new AlarmEventHandler(w.AlarmRang);

         //Instantiates the event source.
         Alarm al = new Alarm();

         //Registers the event handler with the event source so
         //that the WakeMeUp instance can receive alarm events.
         al.AddOnAlarm(alh);
         al.Start();
      }
   }
}


Custom Metadata Using Attributes

This section gives a brief overview of attributes and describes why controls authors should use attributes in their controls.

Attributes Overview

Common Attributes for Properties and Events


Attributes Overview

In NGWS runtime and frameworks, attributes are declarative tags that allow additional information to be available at runtime through reflection. Attributes can be applied to almost any language element — types, properties, methods, and so on. The following code fragment shows an attribute applied to a property declaration.

[C#]
//attribute declaration is enclosed in square brackets
[ DefaultValue(null), Description("The image associated with the control"),Category("Appearance")]
        public Image MyImage {
            get{…}
            set {…}
             }

Within the attribute declaration, in square brackets above, are calls to constructors of attribute classes. A class that derives from System.Attribute is an attribute class. DefaultValueAttribute, DescriptionAttribute, and CategoryAttribute in the attribute declaration above are classes in the System.ComponentModel namespace.

Note for C# users In C#, an attribute class named <ClassName>Attribute, can be referenced simply as <ClassName> in the attribute declaration.

More than one attribute can be included in an attribute declaration as shown above, or each attribute can have its own declaration as shown below.

[C#]
[ DefaultValue(null)]
[Description("The image associated with the control")]
[Category("Appearance")]
        public Image MyImage {
            get{…}
            set {…}
             }

Attributes and Designer Support

Attributes are essential for displaying your control and its members correctly at design time, as they provide valuable information to the forms designer. As a control developer, you must apply attributes to your class (control), and to class members. The code fragments below show how attributes can be used.

The Browsable attribute determines whether a member will be displayed in the toolbox.

[C#]
[Browsable(false)]
    public string Name {
         get {…}
    }

A Designer attribute is a more advanced attribute that informs the forms designer which designer class should be used to display the control. Win Forms and Web Forms controls are always associated with default designers. You will need this attribute only if you define a custom designer for your component or control.

[C#]
//Associates the designer class SimpleControl.Design.SimpleDesigner
//with Simple control
[Designer(typeof(SimpleControl.Design.SimpleDesigner))]
    public class Simple : WebControl {…}

See Also

Common Attributes for Properties and Events


Common Attributes for Properties and Events

The commonly used attribute classes from the System.ComponentModel namespace are listed below. These classes can be extended.

Attributes for Properties and Events

Note Attributes for events are applied to the AddOn<EventName> method that attaches event delegates to the event. When the attribute is provided, the form designer is informed how the event named <EventName> should be displayed in the events window.
BrowsableAttribute
Specifies whether a property or an event should be displayed in the property window. To hide a property or event from the property window, set BrowsableAttribute.NO.
[C#]
[Browsable(false)]
CategoryAttribute
Specifies the name of the category that a given property or event should be grouped in. When categories are used, component properties and events can be displayed in logical groupings in the property window.
[C#]
[Category("Appearance")]
DescriptionAttribute
Defines a small block of text to be displayed at the bottom of the property window when the user selects a property.
[C#]
[Description("The image associated with the control")]

Attributes for Properties

BindableAttribute
Specifies whether a property is appropriate to bind data to.
[C#]
[Bindable(true)]
DefaultPropertyAttribute
Specifies the default property for the component.
DefaultValueAttribute
Sets a simple default value. DefaultValueAttribute results in the following:
  • There is visual indication in the property window if a property has been modified.
  • The user can right-click a property and choose Reset to restore the property to its default value.
  • More efficient code is generated.

The Visual Studio Forms Designer supports two-way editing. The user can modify a form in the Forms Designer or through code. When the form is modified in the Forms Designer, it generates code and inserts it into the user’s file. To avoid generating code for every property in every control, the Forms Designer will always check to see if the property has been tagged as changed. If it has not been tagged, then code for the property will not be persisted in the form.

[C#]
//The constructor for DefaultValueAttribute is overloaded.
//Constructors are provided for all primitive types, as well
//as for user defined types.
[DefaultValue("")]
public string GetHelpText{...}
LocalizableAttribute
Specifies that a property may be localized. Any properties that have this attribute will be automatically persisted into the resources file, when users choose to localize forms. No other changes are needed to the component to support localization, thus simplifying the localization process.
[C#]
[Localizable(false)]
PersistableAttribute
Specifies whether a property displayed in the property window should be persisted into code. To disable persistence, set PersistableAttribute.NO.
[C#]
[Persistable(PersistableSupport.None)]

Attributes for Events

DefaultEventAttribute
Specifies the default event for the component.

In addition to using the attribute classes defined in the class library, you can also define your own attribute classes. Please refer to your programming language documentation for information about creating your own attribute classes.


Licensing Components and Controls

NGWS frameworks provides a licensing model that is the same for Win Forms controls, Web Forms controls, and all components. It is fully compatible with licensing for ActiveX controls.

Licensing allows control authors to protect intellectual property by checking that a user is authorized to use the control. This check is more important at design time, when the control is incorporated into an application, than at run time. When a licensed control is used legally at design time (and it cannot be used otherwise), the application gets a run time license that can be freely distributed.

The licensing model enables the above scenario, and allows many other levels of licensing support. This is accomplished by separating the validation logic from the component or control. The granting of licenses and the validation logic is performed by a license provider, which is a class that derives from System.ComponentModel.LicenseProvider. The steps that a component author must take in order to enable licensing are simple.

To enable licensing for your component you must

  • Add a LicenseProviderAttribute to the class.
  • Call LicenseManager.Validate or LicenseManager.IsValid in the constructor.
  • Call Dispose on any granted license in the finalizer of the class or before the finalizer is called.

A Win Forms control that implements a simple case of licensing is shown in the code example below.

[C#]
[LicenseProvider(typeof(LicFileLicenseProvider))]
public class MyControl : RichControl {
    private License license = null;
    public MyControl () {
        license = LicenseManager.Validate(typeof(MyControl), this);
    }
    public override void Dispose() {
        if (license != null) {
            license.Dispose();
            license = null;
        }
    }
    protected override void Finalize() {
        Dispose();
        base.Finalize();
    }
}

A Web Forms control that implements a simple case of licensing is shown in the code example below.

[C#]
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

[LicenseProvider(typeof(LicFileLicenseProvider))]
public class MyControl : WebControl {
   private License license;
   public MyControl() {
      license = LicenseManager.Validate(
                  typeof(MyControl),
                  this);
   }
   protected override void Dispose() {
      if (license != null) {
          license.Dispose();
          license = null;
      }
   }
}

A sample of Licensing is provided in the Win Forms Quickstart.


Developing Win Forms Controls

The Win Forms framework provides many controls such as buttons, check boxes, drop-down lists, and many more that can be used to build sophisticated client-side Windows applications. However, customers often have a need for additional custom controls. This section describes how you can develop a custom Win Forms control by building on the basic user interface functionality that is provided by the Win Forms framework.

The section contains the following topics:

Overview of Win Forms

Win Forms Component Hierarchy

Walkthrough: Developing a Simple Win Forms Control

Properties in Win Forms Controls

Events in Win Forms Controls

Packaging and Deployment

Databinding in Win Forms Controls

Security in Win Forms Controls

Win Forms controls can be used in Win Forms applications, hosted in Microsoft Internet Explorer 5 and higher, and can be packaged as Active X controls.

The custom controls that you create can be licensed, a designer such as Microsoft Visual Studio 7.0 will verify the license before allowing a user to add the control to the form designer. For information about licensing see Licensing Components and Controls.


Overview of Win Forms

This section provides a high level overview of the Win Forms framework and how Win Forms controls are used in Win Forms applications.

The Win Forms framework encapsulates native Win32 APIs and exposes secure, managed classes for creating Win32 client-side applications. The Win Forms class library provides many controls such as buttons, check boxes, drop-down lists, combo boxes, data grid, and others that encapsulate user interface and other client-side functionality. Application development is greatly simplified if you use a rapid application development (RAD) environment such as Visual Studio 7.0. In the Visual Studio Win Forms designer, the controls appear in a toolbox and are dropped on a design surface. This is accompanied by automatic code generation. The forms designer has a property window that displays properties and events. Using the property browser, it is easy to modify properties and to provide code for event handling methods. The Visual Studio 7 Win Forms designer thus generates all the generic code for an application developer, so that you can focus on writing the code that is specific to your application. While a designer simplifies application development, the Win Forms framework is designed so that it is not difficult to develop applications all in code, without using a designer.

Every Win Forms application

  • is a class that derives from System.WinForms.Form.
  • invokes the Run method of the System.WinForms.Application class in its Main method, with an instance of the form as the argument.

The Application.Run method processes messages from the operating system to the application.

Many examples of Win Forms applications are provided in the Win Forms Quickstart.

The code example below shows the essential elements of a Win Forms application.

[C#]
using System;
using System.WinForms;
// Every Win Forms application derives from System.WinForms.Form
public class MyForm : Form {

   public static int Main(string[] args) {
     MyForm aform = new MyForm();

// The Application.Run method processes messages from the operating system to // your application. If you comment out the next line of code
// and simply instantiate the form in Main
// your application will compile and execute, but because it is not in the // message loop, it will exit after the form is instantiated.
      Application.Run(aform);
      return 0;
   }

   public MyForm() {
      this.Text = "Hello World";
   }
}

The code example below shows how controls are used in a Win Forms application, and how events are handled. The example consists of three buttons on a form; each button changes the background to a different color when clicked.

[C#]
//Simple Win Forms application that shows how controls
//are used and how events are handled.
using System;
using System.ComponentModel;
using System.WinForms;
//using System.Resources;
using System.Drawing;

public class MyForm : System.WinForms.Form {

   private System.WinForms.Button red;
   private System.WinForms.Button blue;
   private System.WinForms.Button green;

   public MyForm() : base() {
// InitializeComponent is a helper method. You can do
// all the work here if you want. InitializeComponent is used
// in this example for consistency with the Visual Studio 7.0
// Win Forms designer.

      InitializeComponent();
   }

   public override void Dispose() {
      base.Dispose();
      //components.Dispose();
   }

// Helper method for the constructor.
   private void InitializeComponent() {
//      components = new System.ComponentModel.Container();

// Create a delegate for the click event of a button. The argument to
// the constructor contains a reference to the method that performs the // // event handling logic.
      EventHandler handler = new EventHandler(buttonClick);

//    Text, Location, and Size are properties of a Button control. These
// are set using the property window in the Win Forms designer.
      red = new Button();
      red.Text = "Red";
      red.Location = new System.Drawing.Point(100, 50);
      red.Size = new System.Drawing.Size(50, 50);

//The AddOnClick method registers the delegate with the click event
//of the button.
      red.AddOnClick(handler);

//The Form class has a Controls property that is a collection of child
//controls. The Add method adds your control to that collection.
      Controls.Add(red);

      blue = new Button();
      blue.Text = "Blue";
      blue.Location = new System.Drawing.Point(100, 100);
      blue.Size = new System.Drawing.Size(50, 50);
      blue.AddOnClick(handler);
      Controls.Add(blue);

      green = new Button();
      green.Text = "Green";
      green.Location = new System.Drawing.Point(100, 150);
      green.Size = new System.Drawing.Size(50, 50);
      green.AddOnClick(handler);
    Controls.Add(green);

      }

//The method that contains the event handling logic
   private void buttonClick(object sender, EventArgs e) {
            if (sender == red) this.BackColor = Color.Red ;
                  else if (sender == blue) this.BackColor = Color.Blue;
                  else this.BackColor = Color.Green;
        }

   public static void Main(string[] args) {
  Application.Run(new MyForm());
   }

}


Win Forms Component Hierarchy

The following diagram shows the inheritance hierarchy of components in the System.Win Forms namespace.

Base Classes

Win Forms has two base classes for controls:

  • System.WinForms.Control defines the minimum set of features and overhead to be a Win Forms control. A control derived from Control has basic properties, and only the most basic events, such as keyboard and mouse events.
  • System.WinForms.RichControl derives from System.WinForms.Control. It provides more of the features that developers expect in controls, such as advanced layout, threading support, and many more properties and events. RichControl also provides interoperability with COM.
    Note Controls written specifically for Win Forms should derive from RichControl.

The code fragment below defines a custom control named FlashTrackbar, that can be used to track the progress of an application.

[C#]
public class FlashTrackBar : RichControl {…}

Other Controls to Derive From

You can customize any existing Win Forms control by deriving from it and overriding its properties and methods as described in Extending an Existing Win Forms Control.

If you want to create a composite control by grouping together other controls, your control should derive from System.WinForms.UserControl. Details are provided in Creating a Composite Win Forms Control. Deriving from UserControl will ensure that your composite control will work not only in Win Forms, but also in Internet Explorer 5.0 and higher as well as in Visual Basic 6.0. The base class of UserControl, System.WinForms.ContainerControl understands keyboard routing so that child controls work as a group.

See Also

Walkthrough: Developing a Simple Win Forms Control


Walkthrough: Developing a Simple Win Forms Control

This section will walk you through the key steps that are required to create a custom Win Forms control. If you have created controls before, the steps may seem familiar. However, even if you are an experienced control developer, it is recommended that you browse through the steps described here. If you are not familiar with properties, events, and attributes, see Properties, Events, and Custom Metadata Using Attributes before executing the tasks below.

The control that you will develop in the walkthrough, merely allows the alignment of text to be changed.

To create a simple custom control

  1. Define a class that derives from System.WinForms.RichControl
    [C#]
public class FirstControl:RichControl{…}
  2. Define properties. (You are not required to define a property, but a custom control that does anything meaningful will generally need to define additional properties.) The code fragment below defines a property TextAlignment that will be used to format the Text property inherited from RichControl.
    [C#]
private ContentAlignment alignment = ContentlAlignment.Left;
public ContentAlignment TextAlignment {
         get {
            return alignment;
         }
         set {
            alignment = value;
//Invalidate invokes the OnPaint method described
//in step 3 below.
            Invalidate();
         }
      }
  3. Override the protected OnPaint method inherited from RichControl to provide paint logic to your control. Unless you override OnPaint your control will not be able to draw. In the code fragment given below, the OnPaint method displays the Text property inherited from RichControl with a default alignment. The code example at the end of this section shows how OnPaint can change the alignment to that specified by TextAlignment defined in step 2 above.
    [C#]
public class FirstControl : RichControl{
   public FirstControl() {…}
   protected override void OnPaint(PaintEventArgs e) {
      base.OnPaint(e);
      e.Graphics.DrawString(Text, Font, new SolidBrush (ForeColor),
                            ClientRectangle);
 }
}
  4. Provide attributes for your control. Attributes allow your control and its properties and events to be displayed appropriately at design-time. The code fragment shown below applies attributes to the TextAlignment property. In a designer such as Visual Studio 7.0, the Category attribute (applied below) will cause the property to be displayed under a logical category. The Description attribute will cause a descriptive string to be displayed at the bottom of the property window when the TextAlignment property is selected.
    [C#]
[
Category("Alignment"),
Description("Specifies the alignment of text.")
]
public ContentAlignment TextAlignment {…}
  5. (optional) Provide resources for your control. You can provide resources, such as a bitmap for your control, by packaging them with your control. In PDC Technology Preview release, this can be accomplished by using a compiler option (/res for C#). At run time, the resource can be retrieved using methods of the System.Resources.ResourceManager class.

The code for FirstControl is shown below. The control is created in the namespace CustomWinControls. A namespace provides a logical grouping of related types. You can create your control in a new or existing namespace. In C#, the using declaration allows types to be accessed from a namespace without using the fully qualified name of the type. For example, with the using declaration below you can access the class RichControl from System.WinForms simply by typing RichControl instead of System.WinForms. RichControl.

If you compile FirstControl and create an assembly (which will be a .dll file in this case), you will be able to add it to the toolbox of a Win Forms designer such as Visual Studio 7.0. An example that shows how to build and use a custom control is provided in the Win Forms Quickstart.

[C#]
  namespace CustomWinControls{
   using System;
   using System.ComponentModel;
   using System.WinForms;
   using System.Drawing;
   public class FirstControl:RichControl{
      private ContentAlignment alignment = ContentAlignment.Left;
   [
    Category("Alignment"),
    Description("Specifies the alignment of text.")
   ]
      public ContentAlignment TextAlignment {
         get {
            return alignment;
         }
         set {
            alignment = value;
        //Invalidate invokes the OnPaint method
            Invalidate();
         }
      }

    //OnPaint aligns text as specified by the
    //TextAlignment property, by passing a parameter
    //to the DrawString method of the Grahics object.
      protected override void OnPaint(PaintEventArgs e) {
         base.OnPaint(e);
         //TextFormat style = (TextFormat)0;
         StringFormat style = new StringFormat();
         style.Alignment = StringAlignment.Near;
         switch (alignment) {
            case ContentAlignment.Left:
               style.Alignment = StringAlignment.Near;
               break;
            case ContentAlignment.Right:
               style.Alignment = StringAlignment.Far;
               break;
            case ContentAlignment.Center:
               style.Alignment = StringAlignment.Center;
               break;
         }
   //Call the DrawString method of the System.Drawing class to write text.
   //Text and ClientRectangle are properties inherited from
   //RichControl and Control respectively.
         e.Graphics.DrawString(Text, Font, new SolidBrush (ForeColor), ClientRectangle, style);
      }
  }
}


Properties in Win Forms Controls

This section contains the following topics.

Inherited Properties

Defining a Property

ShouldPersist and Reset Methods

Property Changed Notifications


Inherited Properties

A control that derives from System.WinForms.RichControl inherits over twenty properties from RichControl. These include properties such as Font, ForeColor, BackColor and many others. Additionally, the control inherits over forty properties from the base class of RichControl, System.WinForms.Control. These include Bounds, ClientRectangle, DisplayRectangle, Enabled, Focused, Height, Width, Visible and others. For details about inherited properties, see the reference documentation for System.WinForms.RichControl and System.WinForms.Control. You can override the inherited properties in your control.

See Also

Defining a Property


Defining a Property

For an overview of properties see Properties. There are a few important considerations to remember when defining a property:

  • Attributes must be provided for the properties you define, as described in Custom Metadata via Attributes. Attributes inform the designer how it should display the property.
  • If changing the property will affect the visual display of the control, you should call the Invalidate method from the set method after setting the property. Invalidate in turn calls the OnPaint method that redraws the control. Multiple calls to Invalidate result in a single call to OnPaint for efficiency.
  • NGWS frameworks provides type converters for common data types such as integers, decimal numbers, boolean values and others. The purpose of a type converter is generally to provide string to value conversion (conversion from string data to other data types). Standard data types are associated with default type converters that convert values to strings, and strings into the appropriate data types. If you define a property that is a custom (non standard) data type, you will have to apply an attribute that specifies the type converter to associate with that property. You can also associate a custom UI type editor with a property using an attribute. A UI type editor provides a user interface for editing a property or data type. A color picker is an example of a UI type editor. Examples of attributes are given at the end of this section.
    Note If a type converter or a UI type editor is not available for your custom property, you can implement one as described in Enhancing Design-Time Support .

The following code fragment defines a custom property EndColor for the custom control FlashTrackBar.

[C#]
public class FlashTrackBar : RichControl {
...
//private data member endColor
private Color endColor = Color.LimeGreen;

//Attributes for the property that tell the designer to display
//it in the Flash grouping.
        [
        Category("Flash"),
        Description("The ending color of the bar.")
        ]
//public property EndColor that accesses endColor
        public Color EndColor {
            get {
                return endColor;
            }
            set {
                endColor = value;
                if (baseBackground != null && showGradient) {
                    baseBackground.Dispose();
                    baseBackground = null;
                }
//Invalidate calls the OnPaint method that redraws the control
                Invalidate();
            }
        }
...
}

The following code fragment associates a type converter and a UI type editor with the property Value. In this case Value is an integer and has a default type converter, but the idea is to display it as a percentage. The UI type editor, FlashTrackBarValueConverter, allows the percentage to be displayed visually. This example also shows that the type converter or editor specified by the TypeConverter or Editor attribute overrides the default converter.

[C#]
        [
        Category("Flash"),
        TypeConverter(typeof(FlashTrackBarValueConverter)),
        Editor(typeof(FlashTrackBarValueEditor), typeof(UITypeEditor)),
        Description("The current value of the track bar.  You may enter an
        actual value or a percentage.")
        ]
        public int Value {…}


ShouldPersist and Reset Methods

ShouldPersist and Reset are optional methods that you can provide for a property that you define, if the property does not a have simple default value. If the property has a simple default value, you should apply the DefaultValueAttribute and supply the default value to the attribute class constructor instead. Either of these mechanisms enable the following features in the designer:

  • The property provides visual indication in the property browser if it has been modified from its default value.
  • The user can right click on the property and choose reset to restore the property to its default value.
  • More efficient code is generated.
Note You should either apply the DefaultValueAttribute or provide Reset<PropertyName> and ShouldPersist<PropertyName> methods, but not both.

The Reset<PropertyName> methods causes a property to be set to its default value as shown below.

[C#]
public void ResetFont() {
        Font = null;
    }
Note If a property has neither a Reset method defined, nor a DefaultValueAttribute, nor a default value supplied in its declaration, the reset setting for that property will be grayed in the context menu of the property browser of the Win Forms designer of Visual Studio.

The ShouldPersist<PropertyName> is used by a designer such as Visual Studio to check if a property has changed from its default value. Code is written into the form only if a property is changed, thus allowing for more efficient code generation. For example:

[C#]
//Returns true if the font has changed and false otherwise.
//Code will be written to the form only if true is returned.
public bool ShouldPersistFont() {
        return thefont != null;
    }

A complete code example is given below.

[C#]
using System;
using System.WinForms;
using System.Drawing;
public class MyControl : RichControl {
//Declare an instance of Font
//and set its default value to null.
//You do not have to use the fully qualified name
//because the using System.Drawing.Font declaration
//is used.
private Font thefont = null;

    public Font Font {
//Note that the Font property never
//returns null.
//
        get {
            if (thefont != null) return thefont;
            if (Parent != null) return Parent.Font;
            return Fonts.DefaultGui;
        }
        set {
            thefont = value;
        }
    }

    public bool ShouldPersistFont() {
        return thefont != null;
    }

    public void ResetFont() {
        Font = null;
    }
}

This example also shows how ambient properties are implemented in Win Forms. In this case, even though the default value of the private variable accessed by the Font property is null, the property browser will never display null, but the Font property of the parent (if non-null), or the default Font for controls. Thus the default value for Font is not "simple", and a DefaultValueAttribute cannot be applied. Instead, the ResetFont and ShouldPersistFont methods must be implemented.


Property Changed Notifications

When a property is changed, it may be necessary to update other dependent properties, or the control may need to perform actions such as redrawing some or all of the display area. Any logic that you want the control to perform when a property is changed should be implemented by overriding the protected OnPropertyChanged method that your custom control inherits from System.WinForms.Control. The data for the property changed event is stored in the PropertyChangedEventArgs class.

The code fragment below shows how the OnPropertyChanged method is overriden in the custom control FlashTrackBar. The complete source code is in Win Forms Control Sample. The Text, BackColor, and BackgroundImage properties are inherited from RichControl. The showGradient variable is a private member that is set and retrieved by the ShowGradient custom property of FlashTrackBar. The variable baseBackground is a private member of type System.Drawing.Brush.

[C#]
public class FlashTrackBar : RichControl {
……..
private bool showGradient = true;
private Brush baseBackground = null;

    ..
        [
        Category("Flash"),
        Description("Determines if the track bar should display a color gradient indicating the current value.")
        ]
        public bool ShowGradient {…}

// Looks for specific properties that have changed and updates the control.

 protected override void OnPropertyChanged(PropertyChangedEventArgs e) {
// Call the method of the base class so that regeistered delegates will //receive the event.
            base.OnPropertyChanged(e);
//If the text property is changed, redraw the control
            if (e.PropertyName.Equals("Text")) {
                Invalidate();
            }
//Free the brush resource after changing the backgroung color or image,
//else redraw the control if the trackbar is displying progress.
            else if (!showGradient) {
                if (e.PropertyName.Equals("BackColor") ||
                    e.PropertyName.Equals("BackgroundImage")) {

                    if (baseBackground != null) {
                        baseBackground.Dispose();
                        baseBackground = null;
                    }

                    Invalidate();
                }
            }
        }
}


Events in Win Forms Controls

This section contains the following topics.

Inherited Events

Handling User Input

Defining an Event


Inherited Events

A control that derives from System.WinForms.RichControl inherits over fifteen semantic events from RichControl. The Paint event that causes a control to be drawn is provided by RichControl.

Additionally, the control inherits over twenty-five events from System.WinForms.Control. The events inherited from Control are related to displaying a window, such as the Resize and Layout events. Many low level mouse and keyboard events are also inherited from Control. These are described in Handling User Input. Some low level events are synthesized by Control into semantic events such as Click and DoubleClick.

Details about inherited events are provided in the reference documentation for System.WinForms.Control and System.WinForms.RichControl .

If your custom control wants to override inherited event functionality, you should override the inherited On<EventName> method instead of attaching a delegate. If you are not familiar with the event model of NGWS runtime and frameworks, see Events.


Overriding the Paint Event

The basic steps for overriding any inherited event are the same. and involve:

  • Override the protected On<EventName> method.
  • Call the On<EventName> method of the base class from the overridden On<EventName method, so that registered delegates will receive the event.

The paint event is discussed in detail because every Win Forms control that you develop must override the paint logic provided by RichControl. This is because RichControl does not know how a control wants to be drawn, and cannot provide implementation that your control can use. The OnPaint method of RichControl simply dispatches the event to registered event receivers.

If you worked through the Walkthrough: Developing a Simple Win Forms Control, you have seen an example of overriding the OnPaint method. The code fragment from the walkthrough is reproduced below.

[C#]
public class FirstControl:RichControl{
   public FirstControl() {…}
   protected override void OnPaint(PaintEventArgs e) {
//call the base class's OnPaint method
   base.OnPaint(e);
//call methods of the System.Drawing.Graphics object
      e.Graphics.DrawString(Text, Font, new SolidBrush (ForeColor),
                            ClientRectangle);
 }
}

The PaintEventArgs class contains data for the Paint event. It has two properties as shown below.

[C#]
public class PaintEventArgs : EventArgs {
…
    public System.Drawing.Rectangle ClipRectangle {…}
    public System.Drawing.Graphics Graphics {…}
…
}

ClipRectangle is the rectangle where painting is to be done, and the Graphics property refers to a System.Drawing.Graphics object. The classes in the System.Drawing namespace are managed classes that encapsulate the functionality of the new graphics library, the Graphical Device Interface+ (GDI+). The Graphics object has methods to draw points, strings, lines, arcs, ellipses, and many other shapes.

A paint event is generated whenever the visual display of the control changes; it results in a call to the OnPaint method of your control.


Handling User Input

The main keyboard and mouse events provided by System.WinForms.Control are listed below., When handling an event, control authors should override the protected On<EventName> method as opposed to attaching a delegate to the event. For a review of events see Events.

Note that if there is no data associated with the event the base class EventArgs is used for "event data", and passed as an argument to the On<EventName> method.

Keyboard Events

The common keyboard events that your control may need to handle are KeyPress, KeyDown, and KeyUp.

Event Name | Method to Override | Description of Event
KeyDown | void OnKeyDown(KeyEventArgs) | Raised only when a key is initially pressed.
KeyPress | void OnKeyPress (KeyPressEventArgs) | Raised every time a logical key press occurs. If a key is held down, a KeyPress event is raised at the repeat rate defined by the operating system.
KeyUp | void OnKeyUp(KeyEventArgs) | Raised only when a key is released.

Mouse Events

The mouse events that your control may want to handle are: MouseDown, MouseEnter, MouseHover, MouseLeave, MouseMove, and MouseUp.

Event Name | Method to Override | Description of Event
MouseDown | void OnMouseDown(MouseEventArgs) | Raised when the mouse is pressed over the control.
MouseEnter | void OnMouseEnter(EventArgs) | Raised when the mouse first enters the region of the control.
MouseHover | void OnMouseHover(EventArgs) | Raised only when the mouse hovers over the control.
MouseLeave | void OnMouseLeave(EventArgs) | Raised when the mouse leaves the region of the control.
MouseMove | void OnMouseMove(MouseEventArgs) | Raised when the mouse moves in the region of the control.
MouseUp | void OnMouseUp(MouseEventArgs) | Raised when the mouse is released over the control.

The MouseDown event is overriden in the following code fragment.

[C#]
protected override void OnMouseDown(MouseEventArgs e) {
            base.OnMouseDown(e);
            if (!allowUserEdit) {
                return;
            }
            Capture = true;
            dragging = true;
            SetDragValue(new Point(e.X, e.Y));
        }

The MouseMove event is overriden in the following code fragment.

[C#]
protected override void OnMouseMove(MouseEventArgs e) {
            base.OnMouseMove(e);
            if (!allowUserEdit || !dragging) {
                return;
            }
            SetDragValue(new Point(e.X, e.Y));
        }

The MouseUp event is overriden in the following code fragment.

[C#]
protected override void OnMouseUp(MouseEventArgs e) {
            base.OnMouseUp(e);
            if (!allowUserEdit || !dragging) {
                return;
            }
            Capture = false;
            dragging = false;
            value = dragValue;
            OnValueChanged(EventArgs.Empty);
        }

The complete source code for the FlashTrackBar sample is in Win Forms Control Sample.


Defining an Event

For an overview of how to create a custom event, see Providing Event Functionality. If the event that your control will raise does not have any associated event data, you can use the base class System.ComponentModel.EventArgs for the event "data". In such a case, you do not have to declare a delegate class, but can use the System.ComponentModel.EventHandler as your event handler. All that remains to be done is to provide public AddOn<EventName> and RemoveOn<EventName> methods that attach a delegate or remove a delegate from your event, and a protected On<EventName> method that raises the event.

The following code fragment shows how event functionality for a custom event, ValueChanged, is provided in the control FlashTrackBar. The complete code for the FlashTrackBar sample is provided in Win Forms Control Sample.

[C#]
public class FlashTrackBar : RichControl {

//The event does not have any data, so EventHandler is adequate
//as the event delegate.
private EventHandler valueChanged;
        //
        // <doc>
        // <desc>
        //Adds an event handler for the value changed event.  This
        //event is raised when the value of the track bar changes.
        // </desc>
        // </doc>
        //
        public void AddOnValueChanged(EventHandler handler) {
            valueChanged = (EventHandler)Delegate.Combine(valueChanged, handler);
        }

        //
        // <doc>
        // <desc>
        // The protected method of our value changed event.  This is the
        // method that raises the event when the value has actually
        // changed. Deriving controls may override this.
        // </desc>
        // </doc>
        //
        protected virtual void OnValueChanged(EventArgs e) {
            if (valueChanged != null) {
                valueChanged(this, e);
            }
        }
//
        // <doc>
        // <desc>
        //      Removes the given event handler.
        // </desc>
        // </doc>
        //
        public void RemoveOnValueChanged(EventHandler handler) {
            valueChanged = (EventHandler)Delegate.Remove(valueChanged, handler);
        }
}


Methods in Win Forms Controls

There is nothing special about methods in controls. Define methods as you would in any class. The get and set property functions, and the methods for event functionality are discussed in Properties in Win Forms Controls and Events in Win Forms Controls. Methods other than property and event methods are generally private, helper methods of the control, or protected methods to be overridden by derived controls.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Databinding in Win Forms Controls

[To be supplied.]


Extending an Existing Win Forms Control

Creating a control that overrides or adds to the behavior of an existing Win Forms control is straightforward. Simply create a class that derives from the control that you are interested in and override properties, events and methods, as necessary. You can also add properties, events, and methods. An example of an extended control is presented in the Win Forms Quickstart.


Creating a Composite Win Forms Control

A composite Win Forms control is a control that consists of other Win Forms controls. An example is a control that has a text field with two buttons, a reset and a submit button. To create a composite control, you should define a class that derives from System.WinForms.UserControl. This will ensure that keyboard routing works for all the child controls and that the child controls receive focus. Composite controls are most easily created using a designer such as the Visual Studio 7 Win Forms designer. An example is presented in the Win Forms Quickstart.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Win Forms Control as an ActiveX control

[To be supplied.]


Debugging a Win Forms Control

Debugging the run time of a Win Forms control is straightforward. Simply add the control to a form, and debug as you would any Win Forms application. The debugger will step through the code for the control.

Debugging the design time of a control is more complicated. Information about design time debugging will be available in a future release.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Packaging and Deployment

[To be supplied.]

See Also

Versioning, Packaging, and Deployment


Security in Win Forms

All Win Forms base classes are secured with Code Access Security. Controls that derive from System.WinForms.RichControl are the only controls that can be hosted in ActiveX containers (such as Visual Basic 6.0) and managed control containers (such as Internet Explorer 5. or higher).

Win Forms controls are secure when hosted as managed controls. The security model is layered, and controlled by policy. There is limited, but rich, functionality for internet components. There is more functionality for intranet components, and there is full functionality for local components. Win Forms applications run as fully trusted local applications.


WinForms Controls Sample

NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Win Forms Control Sample

FlashTrackBar is a control that can be used to show the level or the progress of an application. It uses a gradient to visually represent progress. To download the source code for FlashTrackBar and to see an example of how it is used, see the Win Forms QuickStart.

FlashTrackBar defines the following custom properties:

AllowUserEdit
Indicates if the user can change the value of the flash track bar by clicking and dragging on it.
EndColor
Specifies the ending color of the track bar.
DarkenBy
Specifies how much to darken the background with respect to the foreground gradient.
Max
Specifies the maximum value of the track bar.
Min
Specifies the minimum value of the track bar.
StartColor
Specifies the starting color of the gradient.
ShowPercentage
Indicates whether a percentage should be displayed over the gradient.
ShowValue
Indicates whether the current value should be displayed over the gradient.
ShowGradient
Indicates whether the track bar should display a color gradient showing the current value.
Value
Specifies the current value of the track bar.

FlashTrackBar defines the following members for a custom event:

valueChanged
The event handler for the value changed event. This event is raised when the value of the track bar is changed.
AddOnValueChanged
The method that attaches a delegate for the value changed event.
OnValueChanged
The method that raises the value changed event.
RemoveOnValueChanged
The method that removes a delegate for the value changed event.
Note FlashTrackBar uses the EventArgs class for event "data", becasue the value changed event does not have associated event data. Because there is no data, it is not necessary to declare a custom delegate, and the EventHandler delegate type is adequate.

FlashTrackBar overrides the following methods inherited from System.WinForms.RichControl:

OnPaint
Provides the paint logic for FlashTrackBar.

FlashTrackBar overrides the following methods inherited from System.WinForms.Control:

OnMouseDown
Handles mouse movements.
OnMouseMove
Handles mouse movements
OnMouseUp
Handles mouse movements
OnPropertyChanged
Updates the control when specific properties are changed.
OnResize
Provides logic for resizing the control.

FlashTrackBar provides a custom Validate method that hides the Validate method inherited from Control.

The complete source code for FlashTrackBar is listed below.

[C#]
namespace SampleControlPack {
    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Drawing.Design;
    using System.WinForms;
    using System.Diagnostics;

    //
    // <doc>
    // <desc>
    // FlashTrackBar is a track bar that uses a gradient fill to
    // render the track value.
    // </desc>
    // </doc>
    //
    public class FlashTrackBar : RichControl {
        private const int LeftRightBorder = 10;
        private int value = 0;
        private int min = 0;
        private int max = 100;
        private bool showPercentage = false;
        private bool showValue = false;
        private bool allowUserEdit = true;
        private bool showGradient = true;
        private int dragValue = 0;
        private bool dragging = false;
        private Color startColor = Color.Red;
        private Color endColor = Color.LimeGreen;
        private EventHandler valueChanged;
        private Brush baseBackground = null;
        private Brush backgroundDim = null;
        private byte darkenBy = 200;

        //
        // <doc>
        // <desc>
        //      Creates a new FlashTrackBar instance.
        // </desc>
        // </doc>
        //
        public FlashTrackBar() {

            // There are two styles on this control.  The first,
            // Opaque,specifies that the entire contents
            // of the control will be painted.  This avoids flicker
            // The second, ResizeRedraw, specifies that the control
            // must be redrawn whenever
            // the size of our control changes.
            //
            SetStyle(ControlStyles.Opaque, true);
            SetStyle(ControlStyles.ResizeRedraw, true);

            ForeColor = Color.White;
            BackColor = Color.Black;

            // Configure a default width and height.
            //
            Width = 100;
            Height = 30;
        }

        //
        // <doc>
        // <desc>
        //This property determines if the user can change the value
        //of the flash track bar by clicking and dragging on it.
        // </desc>
        // </doc>
        //
        [
        Category("Flash"),
        Description("Determines if the user can change the value of the
        FlashTrackBar by clicking and dragging on it.")
        ]
        public bool AllowUserEdit {
            get {
                return allowUserEdit;
            }
            set {
                if (value != allowUserEdit) {
                    allowUserEdit = value;
                    if (!allowUserEdit) {
                        Capture = false;
                        dragging = false;
                    }
                }
            }
        }

        //
        // <doc>
        // <desc>
        // This property is the end color of the bar.  The end color
        // is the color the bar will have when the value is equal to
        // the Max property.
        // </desc>
        // </doc>
        //
        [
        Category("Flash"),
        Description("The ending color of the bar.")
        ]
        public Color EndColor {
            get {
                return endColor;
            }
            set {
                endColor = value;
                if (baseBackground != null && showGradient) {
                    baseBackground.Dispose();
                    baseBackground = null;
                }
                Invalidate();
            }
        }

        //
        // <doc>
        // <desc>
        // The amount to darken the background by.  The background is
        // painted in the same gradient as the foreground value.
        // This determines how dark the background will be with
        // respect to the foreground.
        // Notice that this property uses a special UI editor.
        // The Editor attributes informs the designer which
        // editor to use.
        // </desc>
        // </doc>
        //
        [
        Category("Flash"),
        Editor(typeof(FlashTrackBarDarkenByEditor), typeof(UITypeEditor)),
        Description("How much to darken the background with respect to the
        foreground gradient.")
        ]
        public byte DarkenBy {
            get {
                return darkenBy;
            }
            set {
                if (value != darkenBy) {
                    darkenBy = value;
                    if (backgroundDim != null) {
                        backgroundDim.Dispose();
                        backgroundDim = null;
                    }
                    Invalidate();
                }
            }
        }

        //
        // <doc>
        // <desc>
        // The maximum value of the track bar.
        // </desc>
        // </doc>
        //
        [
        Category("Flash"),
        Description("The maximum value of the track bar.")
        ]
        public int Max {
            get {
                return max;
            }
            set {
                if (max != value) {
                    max = value;
                    Invalidate();
                }
            }
        }

        //
        // <doc>
        // <desc>
        // The minimum value of the track bar.
        // </desc>
        // </doc>
        //
        [
        Category("Flash"),
        Description("The minimum value of the track bar.")
        ]
        public int Min {
            get {
                return min;
            }
            set {
                if (min != value) {
                    min = value;
                    Invalidate();
                }
            }
        }

        //
        // <doc>
        // <desc>
        // The starting color of the gradient.
        // </desc>
        // </doc>
        //
        [
        Category("Flash"),
        Description("The starting color of the track bar gradient.")
        ]
        public Color StartColor {
            get {
                return startColor;
            }
            set {
                startColor = value;
                if (baseBackground != null && showGradient) {
                    baseBackground.Dispose();
                    baseBackground = null;
                }
                Invalidate();
            }
        }

        //
        // <doc>
        // <desc>
        //  Determines if the percentage should be displayed on top of the
        //  track bar gradient.
        // </desc>
        // </doc>
        //
        [
        Category("Flash"),
        Description("Determines if a percentage should be displayed over
        the track bar gradient."),
        RefreshProperties(RefreshProperties.Repaint)
        ]
        public bool ShowPercentage {
            get {
                return showPercentage;
            }
            set {
                if (value != showPercentage) {
                    showPercentage = value;
                    if (showPercentage) {
                        showValue = false;
                    }
                    Invalidate();
                }
            }
        }

        //
        // <doc>
        // <desc>
        // Determines if the actual value should be displayed on top of
        //  the trackbar gradient.
        // </desc>
        // </doc>
        //
        [
        Category("Flash"),
        Description("Determines if the current value should be displayed
        over the track bar gradient."),
        RefreshProperties(RefreshProperties.Repaint)
        ]
        public bool ShowValue {
            get {
                return showValue;
            }
            set {
                if (value != showValue) {
                    showValue = value;
                    if (showValue) {
                        showPercentage = false;
                    }
                    Invalidate();
                }
            }
        }

        //
        // <doc>
        // <desc>
        // Determines if the track bar should display a gradient
        // indicating the current value.
        // </desc>
        // </doc>
        //
        [
        Category("Flash"),
        Description("Determines if the track bar should display a color
        gradient indicating the current value.")
        ]
        public bool ShowGradient {
            get {
                return showGradient;
            }
            set {
                if (value != showGradient) {
                    showGradient = value;
                    if (baseBackground != null) {
                        baseBackground.Dispose();
                        baseBackground = null;
                    }
                    Invalidate();
                }
            }
        }

        //
        // <doc>
        // <desc>
        //      The current value of the track bar.
        // </desc>
        // </doc>
        //
        [
        Category("Flash"),
        Editor(typeof(FlashTrackBarValueEditor), typeof(UITypeEditor)),
        Description("The current value of the track bar.  You may enter an
        actual value or a percentage."),
        TypeConverter(typeof(FlashTrackBarValueConverter))
        ]
        public int Value {
            get {
                if (dragging) {
                    return dragValue;
                }
                return value;
            }
            set {
                if (value != this.value) {
                    int old = this.value;
                    this.value = value;
                    OnValueChanged(EventArgs.Empty);
                    Invalidate();
                }
            }
        }

        //
        // <doc>
        // <desc>
        // Adds an event handler for the value changed event.  This event
        // is raised when the value of the track bar changes.
        // </desc>
        // </doc>
        //
        public void AddOnValueChanged(EventHandler handler) {
            valueChanged = (EventHandler)Delegate.Combine(valueChanged,
                            handler);
        }

        //
        // <doc>
        // <desc>
        // This is a custom Invalidate method.  It computes the rectangle
        // between oldValue and newValue and invalidates just that part
        // to avoid flicker.
        // </desc>
        // </doc>
        //
        private void Invalidate(int oldValue, int newValue) {

            // First, do the position of the slider
            //
            int oldPos = (int)((float)oldValue / ((float)Max - (float)Min)
                          * Width);
            int newPos = (int)((float)newValue / ((float)Max - (float)Min)
                          * Width);

            Rectangle invalidRect = new Rectangle(
                Math.Min(oldPos, newPos),
                0,
                Math.Abs(oldPos - newPos),
                Height);

            Invalidate(invalidRect);

            // If any text is changed,
            // invalidate that area too.
            //
            if (ShowPercentage || ShowValue) {
                string toDisplay = string.Empty;
                int displayValue = Math.Max(oldPos, newPos);

                if (ShowPercentage) {
                    toDisplay = Convert.ToString((int)(displayValue * 100
                                / Width)) + "%";
                }
                else if (ShowValue) {
                    toDisplay = Convert.ToString(displayValue);
                }

                Graphics g = CreateGraphics();
                SizeF textSize = g.MeasureString(toDisplay, Font.NewFont,
                                 new SizeF(0, 0));
                g.Dispose();

                // The + and - 1 factors here are to account for
                // floating point round
                // off errors.
                //
                invalidRect.X = (int)(((float)Width -
                               (float)textSize.Width) / 2.0) - 1;
                invalidRect.Y = (int)(((float)Height -
                                 (float)textSize.Height) / 2.0) - 1;
                invalidRect.Width = (int)textSize.Width + 1;
                invalidRect.Height = (int)textSize.Height + 1;

                Invalidate(invalidRect);
            }
        }

        //
        // <doc>
        // <desc>
        //      Override from control. Handles mouse movements if
        //  user edit is set to true.
        // </desc>
        // </doc>
        //
        protected override void OnMouseDown(MouseEventArgs e) {
            base.OnMouseDown(e);
            if (!allowUserEdit) {
                return;
            }
            Capture = true;
            dragging = true;
            SetDragValue(new Point(e.X, e.Y));
        }

        //
        // <doc>
        // <desc>
        // Override from control. Handle mouse movements if user edit
        // is set to true.
        // </desc>
        // </doc>
        //
        protected override void OnMouseMove(MouseEventArgs e) {
            base.OnMouseMove(e);
            if (!allowUserEdit || !dragging) {
                return;
            }
            SetDragValue(new Point(e.X, e.Y));
        }

        //
        // <doc>
        // <desc>
        // Override from control. Handle mouse movements if user edit
        // is set to true.
        // </desc>
        // </doc>
        //
        protected override void OnMouseUp(MouseEventArgs e) {
            base.OnMouseUp(e);
            if (!allowUserEdit || !dragging) {
                return;
            }
            Capture = false;
            dragging = false;
            value = dragValue;
            OnValueChanged(EventArgs.Empty);
        }

        //
        // <doc>
        // <desc>
        // Override from control.  This paints the user interface
        //  for our control.
        // </desc>
        // </doc>
        //
        protected override void OnPaint(PaintEventArgs e) {
            base.OnPaint(e);

            // The background brush is cached for speed.
            // If it isn't set here, create it.

            if (baseBackground == null) {
                if (showGradient) {
                    baseBackground =
                    new LinearGradientBrush(new Point(0, 0),
                    new Point(ClientSize.Width, 0),StartColor,
                                                    EndColor);
                }
                else if (BackgroundImage != null) {
                    baseBackground = new TextureBrush(BackgroundImage);
                }
                else {
                    baseBackground = new SolidBrush(BackColor);
                }
            }

            // To dim the background, paint over the top of the gradient
            // with an alpha-blended black brush.
            //
            if (backgroundDim == null) {
                backgroundDim = new SolidBrush(Color.FromARGB(DarkenBy,
                Color.Black));
            }

            // Calculate the location of the elements.
            //
            Rectangle toDim = ClientRectangle;
            float percentValue = ((float)Value / ((float)Max -
            (float)Min));
            int nonDimLength = (int)(percentValue * (float)toDim.Width);
            toDim.X += nonDimLength;
            toDim.Width -= nonDimLength;

            // Compute the dimensions of the text to render, if
            // text is to be rendered.
            //
            string text = Text;
            string toDisplay = null;
            RectangleF textRect = new RectangleF();

            if (ShowPercentage || ShowValue || text.Length > 0) {

                if (ShowPercentage) {
                    toDisplay = Convert.ToString((int)(percentValue *
                     100f)) + "%";
                }
                else if (ShowValue) {
                    toDisplay = Convert.ToString(Value);
                }
                else {
                    toDisplay = text;
                }

                SizeF textSize = e.Graphics.MeasureString(toDisplay,
                                      Font.NewFont, new SizeF(0, 0));
                textRect.X = (ClientRectangle.Width - textSize.Width) / 2;
                textRect.Y = (ClientRectangle.Height - textSize.Height) /
                              2;
                textRect.Width = textSize.Width;
                textRect.Height = textSize.Height;
            }

            // Now draw the gradients and the text.
            //
            e.Graphics.FillRectangle(baseBackground, ClientRectangle);
            e.Graphics.FillRectangle(backgroundDim, toDim);
            if (toDisplay != null && toDisplay.Length > 0) {
                e.Graphics.Flush();
                e.Graphics.DrawString(toDisplay, Font.NewFont, new
                                   SolidBrush(ForeColor), textRect);
            }
        }

        //
        // <doc>
        // <desc>
        // Override from control. Look for specific properties that have
        //  changed and update our control.
        // </desc>
        // </doc>
        //
        protected override void OnPropertyChanged
            (PropertyChangedEventArgs e) {
            base.OnPropertyChanged(e);
            if (e.PropertyName.Equals("Text")) {
                Invalidate();
            }
            else if (!showGradient) {
                if (e.PropertyName.Equals("BackColor") ||
                    e.PropertyName.Equals("BackgroundImage")) {

                    if (baseBackground != null) {
                        baseBackground.Dispose();
                        baseBackground = null;
                    }

                    Invalidate();
                }
            }
        }

        //
        // <doc>
        // <desc>
        // Override from control. Get rid of our background
        // brush if resized, because it will have to be recreated
        // for the new size.
        // </desc>
        // </doc>
        //
        protected override void OnResize(EventArgs e) {
            if (baseBackground != null) {
                baseBackground.Dispose();
                baseBackground = null;
            }
            base.OnResize(e);
        }

        //
        // <doc>
        // <desc>
        // The protected part of our value changed event.  This is the
        // method to call when the value has actually changed.  Deriving
        // controls may override this.
        // </desc>
        // </doc>
        //
        protected virtual void OnValueChanged(EventArgs e) {
            if (valueChanged != null) {
                valueChanged(this, e);
            }
        }

        //
        // <doc>
        // <desc>
        // Removes the given event handler.
        // </desc>
        // </doc>
        //
        public void RemoveOnValueChanged(EventHandler handler) {
            valueChanged = (EventHandler)Delegate.Remove(valueChanged,
                            handler);
        }

        //
        // <doc>
        // <desc>
        // Private function that sets the Value property given the current
        // mouse location.
        // </desc>
        // </doc>
        //
        private void SetDragValue(Point mouseLocation) {

            Rectangle client = ClientRectangle;

            if (client.Contains(mouseLocation)) {
                float percentage = (float)mouseLocation.X /
                              (float)ClientRectangle.Width;
                int newDragValue = (int)(percentage * (float)(max - min));
                if (newDragValue != dragValue) {
                    int old = dragValue;
                    dragValue = newDragValue;
                    Invalidate(old, newDragValue);
                }
            }
            else {
                if (client.Y <= mouseLocation.Y && mouseLocation.Y <=
                 client.Y + client.Height) {
                    if (mouseLocation.X <= client.X && mouseLocation.X >
                           client.X - LeftRightBorder) {
                        int newDragValue = min;
                        if (newDragValue != dragValue) {
                            int old = dragValue;
                            dragValue = newDragValue;
                            Invalidate(old, newDragValue);
                        }
                    }
                    else if (mouseLocation.X >= client.X + client.Width &&
            mouseLocation.X < client.X + client.Width + LeftRightBorder) {
                        int newDragValue = max;
                        if (newDragValue != dragValue) {
                            int old = dragValue;
                            dragValue = newDragValue;
                            Invalidate(old, newDragValue);
                        }
                    }
                }
                else {
                    if (dragValue != value) {
                        int old = dragValue;
                        dragValue = value;
                        Invalidate(old, value);
                    }
                }
            }
        }
    }
}


Developing Web Forms Controls

This section describes how you can develop custom Web Forms controls by building on the basic user interface functionality that is provided by the Web Forms framework.

This section contains the following topics:

Introduction To Web Forms Controls

Writing a Custom Web Forms Control

ASP+ Control Development Choices

Compositional and Non-Compositional Controls

Properties in Web Forms Controls

Events in Web Forms Controls

View State Management

Customizing Controls With Inline Templates

Building a SlideShow Control

Working with Data in Web Forms Controls

Extending Existing Controls

Passing Configuration Information to a Control

Debugging The Control

The Visual Studio Development Environment

Web Forms Control Examples


Introduction To Web Forms Controls

One of the most significant features of the ASP+ programming model is the ability to create custom controls based on the ASP+ Web Forms Framework. Reusable custom controls can encapsulate common functionality and dramatically reduce the amount of code a page developer must author.

ASP+ was designed to allow developers to extend or replace the supplied controls with custom-written controls. ASP+ custom server controls can be based on the supplied intrinsic controls, which can be modified and extended, or they can be totally new creations.


What is a Web Forms Control?

A Web Forms control is a reusable software component, executing on the server, that provides functionality to one or more Web pages. Controls are independent of the Web pages that use them and can be created, modified, and maintained separately from the pages. Controls usually, but not always, provide visual output that becomes part of each page that uses the control.

Web Forms controls are reusable and can be used by any number of other ASP+ pages. The public fields, properties, and methods of a Web Forms control can be accessed programmatically by the containing control or page. Web Forms controls are hierarchical and can inherit from, contain, and extend other controls.

There are two types of Web Forms controls:

  • Pagelet controls, which are pages composed of HTML text, tags, and possibly other HTML controls (buttons, text controls, and so on) that are compiled on demand just like any other ASP+ page.
  • Precompiled controls, which are written in a NGWS runtime compatible language such as C#, Managed Extensions for C++, or Microsoft Visual Basic 7.0, and compiled into .dll files. To ensure that controls will be usable from other programming languages, they must be authored in a Common Language Specification (CLS)-compliant language. For more information on CLS-compliant languages, see Language Choices.

Both types of custom controls can be used on an ASP+ page by simply placing a tag for the control on the page.


When to Use Custom Web Forms Controls

Adding Custom Functionality to a Web Application

Although the controls provided with ASP+ provide a broad range of functionality, you will eventually want to add features to a Web site that are not readily accomplished using the resources available out of the box. Using the ASP+ Web Forms framework, you can create new controls or modify and extend existing controls. You can write your own control in any language supported by the NGWS runtime, compile it to an assembly, deploy it on a Web server, and use it in your ASP+ pages.

Partitioning an Application Into Code and Content

With ASP+, it is no longer necessary to mix literal HTML and executable code together on the same page. Web page developers and programmers can now work independently on their respective pieces of a Web application.

Creating Reusable Code

Web Forms controls can be designed to be completely self-contained so that a page developer can simply drop an HTML tag onto one or more pages to gain the functionality provided by a single control.

Simplifying the Application Programming Model

Using the ASP+ programming model, Web pages can be developed in a much more intuitive way than in traditional Web applications. With functionality encapsulated in a custom control, page developers can use that functionality by setting control properties and responding to events. The Slideshow control example presented in Building a SlideShow Control is a good example of a self-contained control that handles all user interaction, visual display, and program logic.


Writing a Custom Web Forms Control

The Web Forms framework provides the set of classes and interfaces used to build custom controls. The classes are contained in these namespaces:

System.Web Namespace

System.Web.UI Namespace

System.Web.UI.HtmlControls Namespace

System.Web.UI.WebControls Namespace

You build controls by creating new classes that inherit appropriate base classes within these namespaces, and overriding the base class methods. The base class implementation handles all the work in your control.

The System.Web.UI.Control Class

The root of the server control class hierarchy is the System.Web.UI.Control base class, from which all server controls inherit. This class provides the methods and properties for managing page execution life cycle including view state management, postback data handling, postback event handling, and output rendering. To create your own server control, you only need to derive from this class and override the methods you want to implement or customize.

The Page Execution Life Cycle

Understanding the page execution life cycle will help you understand the role of the classes and interfaces you must implement as you develop custom controls.

The Page class (which inherits from the Control class) instantiates and populates a tree of server control instances representing the appropriate declarative .aspx file on each incoming Web request. Once the tree is created, the Page class begins the following execution sequence that enables both ASP+ page code and server controls to participate in the request processing and rendering of the page.

Init

Controls can use Init to initialize any settings that will be needed during the lifetime of the incoming Web request (for example, loading external template files, setting up event wirings, and so on). View state information is not available for use at this stage.

LoadViewState

A control’s view state information is automatically populated within the Control.State collection. The State methods allow you to programmatically restore internal state settings between page views.

PostBack Data Processing

Controls use the PostBack Data Processing stage to process any incoming form data and update their object modelsand internal state appropriately. (Note that change notification events are not raised during this stage; this occurs later in the life cycle execution.) This enables you to automatically (and accurately) reflect the state of the control on the client. (For example, if a user typed their name into a text box on the client, the text box control on the server should also reflect the text value of their name at the end of this stage of page execution).

Load

Controls use Load to perform any actions common to each request (for example, setting up a database query or updating a timer on a page). At this point in the life cycle, all the server controls in the tree have been created, initialized, and populated with the appropriate view state values, and all form controls have been populated with the appropriate client-side data values.

PostBack Change Notification

Controls fire appropriate change notification events during the PostBack Change Notification phase in response to form and control value changes that occurred on the client between the previous and current postbacks to a page.

For example, a text box server control whose initial value was “Bob” when a page was first rendered, would raise an OnChange event to all registered listeners if the value of the text box came back as “Fred” on the next postback.

PostBack Event Processing

Controls use the PostBack Event Processing phase to correctly handle client actions that cause a page postback and to raise appropriate events on the server.

For example, a button server control could handle a postback from the client in response to a user clicking it, and then raise an appropriate OnClick event on the server.

PreRender

Controls use PreRender to perform any last minute update operations that must take place immediately before page/control state is saved and output rendered.

SaveViewState

A control’s State information is automatically transferred from the Control.State collection into a string object immediately after the SaveViewState stage of execution. To prepare for this, your control can override the SaveViewState event to modify the State collection.

Render

Controls use the Render class to generate HTML output to the browser client. This is accomplished through a hierarchical, top-down, tree walk of all server controls.

Dispose

Controls can use Dispose to perform any final cleanup work (for example, closing files or database connections). It is very important that expensive resources (such as database connections) are explicitly closed. Otherwise, they remain open until the next garbage collection (GC) occurs. On a heavily loaded server (which might process hundreds of requests between GCs), this can quickly cause resource exhaustion (for example, hundreds of open connections to a database server).


ASP+ Control Development Choices

The two development models for Web Forms Controls are the The Pagelet Model and the The Pre-Compiled Model. pagelets are lightweight controls made up of of an HTML page consisting of literal HTML and script. Pre-compiled controls are written in a CLS-compliant language such as Visual Basic or C# and compiled into an assembly before being deployed on the Web server. Both types of controls are used on Web pages through the use of HTML tags placed on the pages.


The Pagelet Model

A pagelet is an ASP+ page containing HTML tags and executable code. Pagelet controls provide an easy way to partition and reuse simple, common UI functionality across a Web application. Pagelets are not precompiled but, because all ASP+ pages are compiled when requested, they are compiled on demand and cached in server memory.

The pagelet file name is subject to the same naming conventions as ASP+ page files. You can use the .aspx extension or an .aspc extension if you want to distinguish pagelet control files from regular ASP+ files.

Differences Between a Page and a Pagelet

Because pagelets are included in existing pages, they do not contain <head>, <body>, or <form> tags. Those tags are included in the ASP+ page that uses the pagelet control.

Defining a Pagelet

The following simple example shows how a pagelet could be used to encapsulate a navigation bar that remains constant on all pages in an application.

<%@ Page Description="A Simple User Control" %>
<table>
   <tr><td>< a href="news.aspx">News</a></td></tr>
   <tr><td>< a href="weather.aspx">Weather</a></td></tr>
   <tr><td>< a href="sports.aspx">Sports</a></td></tr>
</table>

Implementing the Pagelet

To insert this pagelet control in another page, use an @ Register directive that specifies the source (file name) of the control and a friendly tagprefix:tagname pair that is used to declare an instance of the control on the containing page. Then a control tag with the attribute runat=server is placed in the desired spot on the containing page.

For example, if the pagelet control above was saved as navigation.aspc, a page would use it like this:

<%@ Page Description="Sample ASP+ Page Consuming a Pagelet Control" %>
<%@ Register TagPrefix="UserCtl" TagName="NavBar" Src="navigation.aspc" %>

<html>
   <body>
      < UserCtl:NavBar id="MyNavBar" runat="server" />
   </body>
</html>

For a more complex pagelet control example, see the example in Using Pagelet Controls.


The Pre-Compiled Model

The precompiled control model refers to modules written in a NGWS runtime, CLS-compliant language (C#, Visual Basic 7.0, Managed Extensions for C++), compiled with a command-line compiler to create a .dll, and deployed on the server.

The simple server control class and .aspx file below show how you could take advantage of code-behind page development to cleanly separate logic and presentation code. The example supports a code-behind development mode in which page logic is contained within an external NGWS frameworks class that is compiled prior to deployment and linked to the .aspx file at run time.

Creating the Code Module

The code-behind module can be developed using any language supported by the NGWS runtime. Languages currently supported include C#, Visual Basic 7.0, and Managed Extensions for C++.

/* New class (MyPage) written in C#
*/
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;

public class MyPage : Page {

   protected HtmlContainerControl  Message  = null;
   protected HtmlButton            MyBtn    = null;

   protected override void Init() {
      if (!IsPostBack) {
         Message.InnerHtml = "This is the first time the page has been hit by you!";
      }
   }

   protected void MyBtn_Click(Object source, EventArgs e) {
      Message.InnerHtml = "Thanks! -- You just pushed the button!";
   }
}

Creating the ASP+ file

The ASP+ page that uses a custom server control must include an Inherits directive specifying the code-behind class the ASP+ page inherits.

The following ASP+ page uses the MyPage class developed above.

<%-- ASP+ page uses the MyPage class to respond to a button click. --%>
<%@ Page Description="Uses sample code-behind page" Inherits="MyPage"%>

<html>
   <body>
      <form runat=server>
         <h1><span id="Message" runat=server/></h1>
         <input type=submit value="Push" OnServerClick="MyBtn_Click" runat=server>
      </form>
   </body>
</html>


Compositional and Non-Compositional Controls

Code modules that build a custom server control containing other controls can be built in two basic methods: compositional and noncompositional.

Compositional controls are so named because they include child controls that are added to the controls collection through code in the compiled control. The child controls are responsible for rendering themselves on the client and handling postback events.

The following code example illustrates a compositional control module:

// Example of a compositional control.
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace SampleControl {
   public class Sample : Control, INamingContainer
      // Add a property.to set and get textbox text.
      public int Value {
         get {
            return Int32.Parse(((TextBox)Controls[1]).Text);
         }
         set {
            ((TextBox)Controls[1]).Text = value.ToString();
         }

      protected override void CreateChildControls() {
         // Add Literal Control - text box label.
         this.Controls.Add(new LiteralControl("<h3>Value: "));
         // Add a Textbox
         TextBox box = new TextBox();
         box.Text = "0";
         this.Controls.Add(box);
         // Add Literal Control
         this.Controls.Add(new LiteralControl("</h3>"));
         // Add an "Add" Button
         // Clicking the button will increment the text box value.
         Button AddButton = new Button();
         AddButton.Text = "Add";
         AddButton.AddOnClick(new EventHandler(this.AddBtn_Click));
         this.Controls.Add(AddButton);
      }
      // Add the event handler for the Add button.
      private void AddBtn_Click(Object sender, EventArgs e) {
         this.Value++;
      }
   }
}

Noncompositional controls use the Render method of the Control class to write HTML and associated client-side event handling script to the client. While somewhat more difficult to write, noncompositional controls are preferred over compositional controls because of the overhead involved in instantiating child controls on compositional controls.

The following example shows how to override the Render method of the Control class to add some literal HTML text, a text control, and two button controls to a server page. This example creates a control with output equivalent to the previous example.

// Example of a non-compositional control.
using System;
using System.Web;
using System.Web.UI;
using System.Collections;
using System.Web.UI.WebControls;

namespace SampleControl {

   public class Sample : Control, IPostBackDataHandler, IPostBackEventHandler {

      private int _value = 0;

      public int Value {
         get {
            return _value;
         }
         set {
            _value = value;
         }
      }

      public bool LoadPostData(String postDataKey, NameValueCollection values) {
         _value = Int32.Parse(values[this.UniqueID]);
         return false;
      }

      public void RaisePostDataChangedEvent() {
         // Part of the IPostBackDataHandler contract.  Invoked if we ever returned true from the
         // LoadPostData method (indicates that we want a change notification raised).  Since we
         // always return false, this method is just a no-op.
      }

      public void RaisePostBackEvent(String eventArgument) {
         if (eventArgument == "Add") {
            this.Value++;
         }
         else {
            this.Value--;
         }
      }

      protected override void PreRender() {
         Page.RegisterPostBackScript();
      }

      protected override void Render(HtmlTextWriter output) {
         output.Write("<h3>Value: <input name=" + this.UniqueID + " type=text value=" + this.Value + "> </h3>");
         output.Write("<input type=button value=Add OnClick=\"jscript:"+ Page.GetPostBackEventReference(this, "Add")+ "\"> |");
         output.Write("<input type=button value=Subtract OnClick=\"jscript:"+ Page.GetPostBackEventReference(this, "Subtract")+ "\">");
      }
   }
}

Note the call to the method GetPostBackEventReference, which automatically generates JavaScript event handling code that is sent to the client. The JavaScript code generates the HTTP POST in response to a button click event on the client.

The following ASP+ page, named SampleHtml.aspx, can use either the compositional or noncompositional control:

<%@ Register TagPrefix="SampleControl" Namespace="SampleControl" %>
<html>
   <body>
      form method="POST" action="SampleHtml.aspx" runat=server>
         <SampleControl:Sample id="MyControl runat=server/>
      </form>
   </body>
<html>


Properties in Web Forms Controls

You can easily implement custom properties in your custom Web Forms controls. Property arguments are set within the declarative server tag element that adds a control to a page. Properties are specified using a property name/value pair syntax where the (case-insensitive) attribute name represents the property name and the value represents the property value to assign:

<TagPrefix:Classname attributename = "propertyvalue" runat=server />

For Example:

<Samples:SampleControl Message="Hello There" MessageSize="small" Iterations=2 runat=server/>

Properties are accessed using Get/Set accessor methods in the class defining the property. A call is made to the class Set method when the control is instantiated.

The following C# example sets and retrieves the Message, MessageSize, and Iterations properties. The overridden Render method will output a line of text defined by Message in the size defined by MessageSize the number of times defined by Iterations when an ASP+ page containing the above tag is viewed in a browser.

using System;
using System.Web;
using System.Web.UI;

namespace Samples {

   public enum MessageSize {
      Small  = 0,
      Medium = 1,
      Large  = 2
   }

   public class SampleControl : Control {
      private String      _message;
      private MessageSize _messageSize;
      private int         _iterations = 1;

      public String Message {
         get {
            return _message;
         }
         set {
            _message = value;
         }
      }

      public MessageSize MessageSize {
         get {
            return _messageSize;
         }
         set {
            _messageSize = value;
         }
      }

      public int Iterations {
         get {
            return _iterations;
         }
         set {
            _iterations = value;
         }
      }

      protected override void Render(HtmlTextWriter output) {
         String htmlSize;
         if (_messageSize == MessageSize.Small)
            htmlSize="H5";
         else if (_messageSize == MessageSize.Medium)
            htmlSize="H3";
         else
            htmlSize="H1";
         for (int i=0; i<_iterations; i++) {
            output.Write("<" + htmlSize + ">" + _message + "</" + htmlSize + ">");
         }
      }
   }
}

Accessing View State Through a Private Property

The SlideShow control defines the SlideIndex property:

      private int SlideIndex {
         get {
            return (int) State["Index"];
         }
         set {
            State["Index"] = value;
         }
      }

The private SlideIndex property provides a convenient means of saving and retrieving view state values used internally by the control and stored in the member of the Control.State collection named Index. Since the SlideIndex property is declared as private, it cannot be accessed externally.

Child Properties

Child properties are subproperties of control properties where the subproperties are identified with a dash ("-"). For example:

<TagPrefix:Classname propertyname-subpropertyname="propertyvalue" runat=server />

For Example:

<ASP:label font-name="Arial" runat=server />

The following example shows subproperties used to encapsulate font subproperties within the font property:

using System;
using System.Web;
using System.Web.UI;

namespace SimpleControlSamples {

   public class Formater {
      public int     Size;
      public String  Color;

      public Formater(int size, String color) {
        this.Size  = size;
        this.Color = color;
      }
   }

   public class SimpleSubProperty : Control {
      private Formater _format  = new Formater(3, "black");
      private String   _message = null;

      public String Message {
         get {
            return _message;
         }
         set {
            _message = value;
         }
      }

      public Formater Format {
         get {
            return _format;
         }
      }

      protected override void Render(HtmlTextWriter output) {
         output.Write("<font size=" + Format.Size + " color=" + Format.Color + ">");
         output.Write(_message);
         output.Write("</font>");
      }
   }
}

The ASP+ page that uses the SimpleSubProperty control defined above sets font Color and Size properties in the <SimpleControlSamples:SimpleSubProperty> tag:

<%@ Register TagPrefix="SimpleControlSamples" Namespace="SimpleControlSamples" %>

<html>
   <body>
      <form method="POST" action="HelloWorld.aspx" runat=server>
          <SimpleControlSamples:SimpleSubProperty Message="Hello There" Format-Color="red" Format-Size="3" runat=server/>
      </form>
   </body>
</html>


Events in Web Forms Controls

You write custom events in your Web forms controls to give your Web site the look and feel of a real-time, interactive application. Custom event handlers can be written for all of the WebControl intrinsic controls and HTML server controls as well as for custom controls.


Defining an Event in a Web Form

You specify event wirings on server controls using a declarative attribute name/value pair syntax, where the attribute name represents the control’s event name and the value represents the method on the Page that should be called when the event is fired. For example:

<ASP:Classname EventName="EventHandlerMethodName" runat=server />

At run time, Web Forms will dynamically wire the appropriate, type-safe, NGWS frameworks delegate object between the control event adder method and the target page method. (Note that the target method can either be defined declaratively within a <script runat=server></script> block or in a pre-compiled, code-behind file).

For example, the following declarative syntax would automatically wire the OnServerClick event on a button control:

<html>
   <script runat=server>
      Sub MyButton_Click (Sender as Object, Evt as EventArgs)
         ' Do something when someone clicks the button.
      End Sub
   </script>

   <body>
      <form action="default.aspx" method="post" runat=server>
         <ASP:button text="Hi" OnServerClick="MyButton_Click" runat=server/>
      </form>
   </body>
</html>


Postback Event Handling

Web Forms provides a built-in event infrastructure that enables controls to capture and process postback form submits from a client. This enables control developers to better encapsulate functionality (for example, a calendar control could automatically handle the next month and previous month button events from the client, without requiring code on the ASP+ page to execute). It also enables control consumers to more intelligently interact with the controls (for example, a button control could capture a postback and then expose an OnServerClick event that it raised on the server when a client user clicked it).

Controls indicate that they are interested in capturing postback events by implementing the IPostBackEventHandler interface. Postback event notifications, which are indicated by a call to the IPostBackEventHandler.RaisePostBackEventmethod, can then be routed to the control in response to either a postback from a standard HTML form submit (that is, from something like a submit or image button), or as a result of a custom ECMAScript method that initiates a submit on the client.

Standard Form Submit Postback Handling

HTML supports two intrinsic tag controls—<input type=submit> and <input type=image>—that, when clicked, cause their containing form to be submitted to the server. Note that the name/value pair of one of these HTML elements is only included in the posted contents if it is the actual control initiating the submit; otherwise, its value is excluded. For example:

<form action="Test.aspx" method="GET">
   Name: <input type="text" name="Name">
   <input type=submit name="Button1">   |   <input type=submit name=Button2>
</form>

If Button1 is clicked on the above form, the following querystring will be posted:

   Test.aspx?name=scott&Button1

If Button2 is clicked on the above form, the following querystring will be posted:

   Test.aspx?name=scott&Button2

Web Form controls can easily render the intrinsic HTML types, setting each type's name property to that of its UniqueId, to capture postbacks on the server. For example:

public class MyButton : Control, IPostBackEventHandler {
   private EventHandler   clickHandler;

   // Register delegate to external "OnServerClick" event listener
   public void AddOnServerClick(EventHandler value) {
      clickHandler = (EventHandler) Delegate.Combine(clickHandler, value);
   }

   // Remove delegate to external "OnServerClick" event listener
   public void RemoveOnServerClick(EventHandler value) {
     clickHandler = (EventHandler) Delegate.Remove(clickHandler, value);
   }

   protected void OnServerClick(EventArgs e) {
     if (clickHandler != null) {
        clickHandler(this, EventArgs.Empty);
     }
   }

   // Process and raise appropriate incoming events
   public void RaisePostBackEvent(String eventArgument) {
     OnServerClick(EventArgs.Empty);
   }

   // Generate html button that will cause a postback to the control
   public override void Render(HTMLTextWriter output) {
     output.Write("<input type=submit name=" + this.UniqueId + ">");
   }
}

When a postback event on the page occurs, the page framework will iterate over every posted value in the appropriate form/querystring collection, searching for a control in the hierarchy tree that implements the IPostBackEventHandler interface and whose UniqueID matches the value key. If found, the page framework will invoke its RaisePostBackEvent method (passing null as the argument value). The server control is then free to do anything it wants and/or raise appropriate control events (for example, OnServerClick, OnMoveNext, OnCancel, and so on) to external listeners.


PostBack Data Handling Services

ASP+ pages can be activated either as a result of a navigation request to the page (for example, following a link from another page or site), or as a result of a postback from a previously rendered instance of the same page on the client (for example, a registration form with multiple text fields that must be resubmitted and validated).

Web Forms provides a built-in event infrastructure that enables controls to capture and process postback form submits from a client. This enables you to better encapsulate functionality (for example, a calendar control could automatically handle the next month and previous month button events from the client without requiring user code to execute), and enables control consumers to more intelligently interact with the controls.

The view state services described previously enable each server control to provide enough state information at the end of each request so that the control can restore itself to its last used condition on the next request. (Although this might sound extremely expensive, it typically entails saving only a minimum amount of data). The end result is a programming model that provides page developers with the perception that they are programming against a page that remains consistent on the server across multiple client round trips.

When a postback occurs, it is important that any data modified by the user on the client is mapped back to the appropriate server control and reflected to the Web developer on the server. This is handled within the Web Forms framework by the IPostBackDataHandler interface.

When a postback occurs, the Web Forms framework searches the posted contents for all values whose names match the UniqueID of a server control that implements the IPostBackDataHandler interface. If a match is found, the framework calls the IPostBackDataHandler.LoadPostData method on the control, and supplies it with a HTTPValueCollection containing the appropriate post values. The control then consumes the values, compares them to its current state (set either from declarative initialization values or view state), and returns a Boolean value indicating whether it has changed as a result of the postback. The page framework accumulates a list of all changed controls, and after Load completes, uses the list to invoke the IPostBackDataHandler.RaisePostDataChangedEvent method on all of the controls that have changed. These controls can then optionally fire an appropriate change event to any event listeners. For example:

public class InputBox : Control, IPostBackDataHandler {

   private EventHandler   changeHandler;
   private String         text;

   public String Text {
      get { return State["text"] }
      set { State["text"] = value; }
   }

   // Register delegate to external "OnChange" event listener
   public void AddOnChange(EventHandler value)    {
      changeHandler = value;
   }

   // Return true if posted value does not match value on last page request
   public boolean LoadPostData(HTTPValueCollection values) {
      text = values[this.GetUniqueID()];
      return (text == State["previoustext"]));
   }

   // Raise appropriate change event to any "OnChange" listeners
   public void RaisePostDataChangedEvent() {
      if (changeHandler != null) {
         changeHandler(new EventArgs());
      }
   }
}

<%-- An ASP+ page that consumes the InputBox class --%>

<html>
   <script language="VB" runat=server>

      Sub Name_Change(ByVal Source as Object, ByVal E as EventArgs)
         Message.Text = Name.Text & " is " & Age.Text & " years old!"
      End Sub

   </script>

   <body>
      <form action="Postback.aspx" method="POST" runat=server>

         Name: <input id=Name type=text OnServerChange=Name_Change runat=server>
         Age: <input id=Age type=text OnServerChange=InputBox_Change runat=server>

         <input type=submit value="Click Me" runat=server> <br><br>

         <span id=Message runat=server/>

      </form>
   </body>
</html>
Note The data postback process is deliberately split over two methods, LoadPostData and RaisePostDataChangedEvent. This ensures that developers are always working against a consistent model of the page when the OnChange event fires. If the controls fired the OnChange events from within LoadPostData instead, it would be possible for a page developer responding to an OnChange event from one input box to use an old value pulled from another input box whose LoadPostData method had not yet been called.


View State Management

The core HTTPRuntime infrastructure of ASP+ provides a collection of server-only state management services that enable you to store state information at either an application or user session level (see other specifications for details). The Web Forms Page framework augments these options by enabling page and control developers to also persist view state information (state associated with a particular view of an HTML page).

View state information is persisted using the Control.State dictionary. This instance of the ViewStateBag class automatically maintains a localized dictionary view of view state data, which means two controls on the same page could persist a value with a common key without colliding (the page framework uses a control’s UniqueID to avoid conflicts).

You can create code to read from the State collection at any time during a page request (although values cannot be written into the collection during or after the Render portion of page execution). The best way to accomplish this is by creating properties that represent the values you want to persist and read or write the current state of the values using get and set.

The following example, which creates the new class Table, shows how to read and write the BGColor item in the State collection and make it available programmatically through the property.BGColor.

public class Table : Control {

   // Expose a public "BGColor" property on the table control.
   //Store the value in view state to ensure that the value is
   //automatically round-tripped back to the server on postback.

   public String BGColor {
      get {
         return State["BGColor"];
      }
      set {
         State["BGColor"] = value;
      }
   }
}

At render time, the collective view state of all server controls on a page is output by server-side form controls into a specially named hidden form variable (__VIEWSTATE) on the generated HTML page.


Client-Side Java Script Generation

Unfortunately, not all HTML controls can generate form submits back to the server. To enable UI controls other than standard buttons and image buttons to participate in the postback framework, Web Forms supports a custom client-JavaScript eventing architecture that can also be used to initiate form postbacks to the server.

This is accomplished using two hidden form fields and a small script function that are rendered on demand (by the HTML form control) to the client. The hidden form fields indicate which server control should be posted to, and optionally specify an argument to be passed. The small JavaScript function is used to set the hidden form field contents and initiate the actual form submit to the server.

For example, the following HTML code shows some sample output from a page with two linkbutton server controls that support postback events from HTML anchor style output:

<script language="JavaScript">
   function __doPostBack(eventTarget, eventArgument) {
     var theForm = document.HtmlForm0;
     theForm.__EVENTTARGET = eventTarget;
     theForm.__EVENTARGUMENT = eventArgument;
     theForm.submit();
   }
</script>

<form name="Form1">
   <input type="hidden" name="__EVENTTARGET" value="">
   <input type="hidden" name="__EVENTARGUMENT" value="">

   Click below links to cause postbacks:

   <a href="jscript:__doPostBack('LinkButton1', 'Click')"> LinkButton1 </a>
   <a href="jscript:__doPostBack('LinkButton2', 'Click')"> LinkButton2 </a>
</form>

When a postback event on the page occurs, the page framework will examine the control tree hierarchy for a server control whose UniqueId property matches that specified in the __EVENTTARGET hidden field. The page framework will then query the target control for the IPostBackEventHandler interface and, if found, invokes the IPostBackEventHandler.RaisePostBackEvent method, passing the eventArgument field as an argument. For example:

public class MyLinkButton : Control, IPostBackEventHandler {

   private EventHandler   clickHandler;

   // Register delegate to external "OnServerClick" event listener, and then notify
   // the page instance that a client-side postback script block needs to be generated
   public void AddOnServerClick(EventHandler value) {
     clickHandler = (EventHandler) Delegate.Combine(clickHandler, value);
     Page.RegisterPostBackScript();
   }

   // Process and raise appropriate incoming events
   public void RaisePostBackEvent(String eventArgument) {
     if (clickHandler != null)
        clickHandler(this, EventArgs.Empty);
   }

   // Generate html button and use appropriate helper method to get call back script
   public override void Render(HTMLTextWriter output) {
     output.Write("<a href="javascript:" + Page.GetPostBackEventReference(this)" + ">");
     output.Write("    " + this.UniqueId + " \n");
     output.Write("</a>");
   }
}

Note that the client-side JavaScript function is not generated by default during page rendering. Rather, a control must explicitly indicate that it wants this to be rendered by calling the Page.RegisterPostBackScript method. They can then use the Page.GetPostBackEventReference helper methods to generate calls to the function (with appropriate parameters) at run time.


Customizing Controls With Inline Templates

The Web Forms Template Customization Architecture provides a customization infrastructure that enables you, as a control developer, to build controls that are as lookless as possible, allowing you to separate control behavior from actual control appearance. Web page developers can take advantage of this functionality by designing custom templates that enable rich customization of controls, allowing precise control over the look and feel of controls and the pages that use them. Using custom templates, page developers might even create scenarios not envisioned by the original control developers.

ITemplate Instances

You can expose template customization support within your controls using get and set properties of type ITemplate. ITemplate objects provide a factory definition for dynamically customizingand creating a control instance by invoking the ITemplate.Initialize method.

For example, the following sample shows how a repeater control could have a custom ItemTemplate declaratively specified within a page:

<html>
   <script language="C#" runat=server>
      public void Page_Load(Object sender, EventArgs e) {
         ArrayList myList = new ArrayList();
         myList.Add("DataItem One");
         myList.Add("DataItem Two");
         myList.Add("DataItem Three");
         MyRepeater.DataSource = myList;
         MyRepeater.DataBind();
      }
   </script>

   <body>
      <asp:repeater id="MyRepeater" runat=server>
         <template name="itemtemplate">
            Here is the value in a text box:
            <input type=text value="<%#Container.DataItem%>" runat=server>
         </template>
      </asp:repeater>
   </body>
</html>

The repeater control could then be implemented as follows:

public class RepeaterItem : Control, INamingContainer {
   public Object   DataItem;
   public int      Index;
   public RepeaterItem(Object dataItem, int index) {
      this.DataItem = dataItem;
      this.Index = index;
   }
}

public class Repeater : Control, INamingContainer {
   private ITemplate    itemTemplate;
   private ICollection  datasource;
   // Customizable template for each Item within the repeater
   public ITemplate ItemTemplate {
      get { return itemTemplate; }
      set { itemTemplate = value; }
   }

   // Datasource property to use to dynamically create rows within list
   public ICollection DataSource {
      get { return datasource; }
      set { datasource = value; }
   }

   // Framework method to dynamically create and initialize item rows in response
   // to a DataBind() call.

   protected override void OnDataBind(EventArgs e) {
      IEnumerator iterator = DataSource.GetEnumerator();
      int index = 0;
      while (iterator.GetNext()) {
         // Construct a new RepeaterItem control instance – settings its dataitem
         // and index properties dynamically using appropriate values
         RepeaterItem item=new RepeaterItem(iterator.GetObject(), index);
         // Customize the item instance using the declarative template
         ItemTemplate.Initialize(item);
         // Add the control instance into the hierarchy tree
         this.Controls.Add(item);
      }
   }
}


Building a SlideShow Control

The following example builds a slideshow control that lets a user scan forward and backward through a series of images. The SlideShow control provides all event handling and rendering of the visual interface. The control is used on an ASP+ page merely by placement of an HTML tag.

This ASP+ page uses the new SlideShow control:

<%--===================================================================
This page uses the slide show control. The declarative line below
registers the control by referencing the namespace (Acme) within that
file that contains the classes that define the control. The "Acme" TagPrefix is an arbitrary name used to reference the control on the ASP+ page.
===================================================================--%>

<%@ Register TagPrefix="Acme" Namespace="Acme" %>

<html>
<body bgcolor="black">
   <br><br>
   <center>
      <h3><font face="Verdana" color="white">Slide Show</font></h3>
      <form runat=server>
           <Acme:SlideControl id="SlideControl" runat="server"/>
      </form>
   </center>
</body>
</html>

This custom server control, written in C#, provides a user interface, maintains view state between requests, and displays the previous or next slide in response to a user clicking on forward or backward buttons.

/*=====================================================================
Slideshow control example
Filename: SlideShow.cs.
This control is compiled into SlideShow.dll and placed in the "bin" directory under the virtual root directory holding the ASP+ pages that use it.
=====================================================================*/

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections;

namespace Acme {

   public class SlideControl : Control, IPostBackEventHandler {
      private int index = 1;
      private ArrayList ImageSource = new ArrayList();

      private int SlideIndex {
         get {
            return (int) State["Index"];
         }
         set {
            State["Index"] = value;
         }
      }

      protected override void Init() {
         ImageSource.Add (new Slide("images/comp1.jpg", "My Composition #1"));
         ImageSource.Add (new Slide("images/comp2.jpg", "My Composition #2"));
         ImageSource.Add (new Slide("images/comp3.jpg", "My Composition #3"));
         ImageSource.Add (new Slide("images/comp4.jpg", "My Composition #4"));
         ImageSource.Add (new Slide("images/comp5.jpg", "My Composition #5"));
         ImageSource.Add (new Slide("images/comp6.jpg", "My Composition #6"));
      }

      public void RaisePostBackEvent(string eventArgument) {
         index = this.SlideIndex;
         int oldindex = index;
         if (eventArgument == null) {
            return;
         }
         if(string.Compare("NextSlide", eventArgument,true) == 0) {
            if ( index <  ImageSource.Count ) {
               index++;
            }
         }
         if(string.Compare("PreviousSlide", eventArgument,true) == 0) {
            if(index > 1) {
               index--;
            }
         }
         this.SlideIndex = index;
      }

      protected override void PreRender() {
         Page.RegisterPostBackScript();
      }

      protected override void Render(HtmlTextWriter output) {
         Slide  slidefile  =  (Slide)ImageSource[index -1];
         string  image     =  slidefile.source;
         string  caption   =  slidefile.caption;
         output.WriteLine("<table bgcolor=gray border=1 valign=top>");
         output.WriteLine("<tr><td>");
         output.WriteLine("<center>");
         if (index ==1 ) {
            output.WriteLine("<a>");
         }
            else
         {
         output.WriteLine("<a href=\"jscript:" + Page.GetPostBackEventReference(this,"PreviousSlide") +"\">");
         }
         output.WriteLine( "<img src='images/left4.gif' width=15 height=15 border= 0 ></a>");
         output.WriteLine("<span STYLE='font-family:Verdana;font-size:11pt;font-weight:bold'>" + caption + "</span>");

         if (index==ImageSource.Count) {
            output.WriteLine("<a>");
         }
         else {
            output.WriteLine("<a href=\"jscript:" + Page.GetPostBackEventReference(this,"NextSlide") +"\">");
         }

         output.WriteLine("<img src='images/right4.gif' width=15 height=15 border=0 ></a>");
         output.WriteLine("</center>");
         output.WriteLine("</td></tr>");
         output.WriteLine("<tr valign=top><td valign=top>");
         output.WriteLine("<table STYLE='font-family:Verdana;font-size:9pt'border=0 width=300 cellpadding=2 bgcolor=white>");
         output.WriteLine("<tr> <td>");
         output.Write("<img src='"+ image+"' /> <BR>") ;
         output.WriteLine("</td> </tr>");
         output.WriteLine("</table> </td> </tr></table>");
      }
   }

   public class Slide {
      public string source;
      public string caption;
      public Slide(string source, string caption) {
         this.source = source;
         this.caption = caption;
      }
   }
}


Working with Data in Web Forms Controls

Web Forms supports a hierarchical data binding model that allows page developers to declaratively associate one-way data binding expressions between data sources and server control properties. Any server control property can be data bound (the property is specified declaratively as an attribute name on the server control).

Data binding expressions are defined using <%# %> syntax fragments that can be written either on the value side of a property definition or inline, within literal text. For example:

Value Side

<servercontrol propertyname="<%# DataBinding Expression %>" runat=server/>

Inline

This is the value you data bound: <%# DataBinding Expression %>

Data Binding Expression Scope

Data binding expressions are written in the default language of the page (declared using the Page directive) and can evaluate against any public field or property either on the page itself, or on their immediate naming container (by referencing the strongly typed Container instance pushed into the namespace of each binding expression).

Data Binding Expression Evaluation

Data binding expressions are evaluated at run time in response to a developer explicitly invoking the Control.DataBind method on a control in the tree hierarchy. This call causes a recursive tree walk from that control down through the tree, causing the OnDataBind event to be raised on each server control in the hierarchy and data binding expressions on the control to be evaluated accordingly.

Data Binding Example

<html>
   <script language="C#" runat=server>
      public void Page_Load(Object sender, EventArgs e) {
         ArrayList myList = new ArrayList();
         myList.Add("DataItem One");
         myList.Add("DataItem Two");
         myList.Add("DataItem Three");
         MyRepeater.DataSource = myList;
         MyRepeater.DataBind();
      }
   </script>

   <body>
      <asp:repeater id="MyRepeater" runat=server>
         <template name="itemtemplate">
            Here is the value in a text box:
            <input type=text value="<%#Container.DataItem%>" runat=server>
        </template>
      </asp:repeater>
   </body>
</html>


Extending Existing Controls

A new Web Forms control can be based on an existing control. You may want to add functionality to a control that is not quite right for your application. To extend an existing control, create a new control class that inherits from the existing control's class. You can then override the properties and methods of the base control class to change existing behavior. Add new functionality by adding new properties and methods to the new class.


Passing Configuration Information to a Control

ASP+ includes a rich and flexible configuration system that enables extensible configuration data to be defined and used throughout an application, site, and machine. For detailed information on the configuration system, see ASP+ Configuration Concepts.


Debugging The Control

ASP includes three separate debugging tools:

  • Tracing and Profiling

    A powerful trace mode feature that enables both control and page developers to include instrumentation messages throughout their code.

  • NGWS SDK Debugger

    A lightweight debugger that supports debugging only on the local computer; analogous to the Script Debugger provided with traditional ASP. This debugger is provided with the NGWS SDK.

  • Visual Studio Debugger

    A feature-rich debugging tool integrated with the Microsoft Visual Studio development environment. This tool provides several features over the SDK debugger, including support for debugging on remote computers , auto-attach to the ASP+ runtime, and the ability to edit source files within the debugger source window.


Tracing and Profiling

ASP+ provides a powerful trace mode feature that enables both server control and page developers to include instrumentation messages throughout their code. These instrumentation messages can be used to simplify the process of identifying exactly what occurs during a given Web request, and to help locate resulting problems.

Writing to the Trace Log

Control and page developers can write to the trace log using the Trace property exposed on the Control base class. Developers can organize each output message using a provided category string.

Sample Control Using Tracing

public class MyButton : Control {
   public override void Render(HTMLTextWriter output) {
      Trace.Write("Render", "Button class is about to render….");
      output.Write("<button>Simple Button</button>");
      Trace.Write("Render", "Button class has just finished rendering….);
   }
}

Sample Page Using Tracing

<%@ Page Language="VB" Trace=True %>
<html>
   <script runat=server>
      Sub Page_Load(ByVal Sender as Object, ByVal E as EventArgs)
         Trace.Write("We’re at Load()!!!")
      End Sub
   </script>
   <body>
      <form runat=server>
         <span id="Message" runat=server/>
      </form>
   </body>
</html>

Output Trace Messages

By default, tracing is not enabled on a page. This means that Trace.IsEnabled will return false and no messages written to the trace log will be output during a Web request.

You can optionally enable tracing by setting the Trace attribute on the Page directive to true. For example:

<%@ Page Language="VB" Trace=True %>

In addition to causing the Trace.IsEnabled property to return true, this will cause the Page class to automatically output an HTML table at the conclusion of page rendering that details all the assorted category trace logs. For example:

<html>
   <body>
      <form runat=server>
         <span id="Message" runat=server/>
      </form>
   </body>
</html>

<table>
   <tr>
      <td colspan=2><h1>Trace Log</h1></td>
   </tr>

   <tr>
      <td><h3>Dev1 Category</h3></td>
      <td> We’re at Load()!!! </td>
   </tr>

   <tr>
      <td><h3>Render Category</h3></td>
      <td>
         Button class is about to render…. <br>
         Button class has just finished rendering….
      </td>
   </tr>
</table>

You can optionally toggle the order/presentation of trace messages using the tracemode attribute exposed on the Page directive. A value of SortByTime will cause the trace messages to be output in the order in which they were written. A value of SortByCategory will cause the trace messages to be output alphabetically by category. A value of ShowInline will cause the messages to be emitted immediately after Trace.Write is called.

Note that in addition to outputting user-provided trace content, the page class will also automatically include additional performance data, tree structure information, and state management content.


Global Trace Functionality

In addition to the exposing a generalized trace facility to developers, the ASP+ runtime can also collect helpful request information globally for an application. Collecting this additional data must be explicitly enabled for the application through the configuration system. Unlike custom user-defined Trace statements, these internal trace categories are not exposed in the client browser, but are displayed by the trace viewer application instead. The trace viewer displays trace information for a series of requests to the application, so metrics for each request must be stored in memory while tracing is enabled. This is made possible by inserting an IHttpModule-derived class, TraceModule, that participates in the HTTP Pipeline execution of each request to the application.

Enabling Global Trace through Configuration

Global tracing is enabled through the configuration system at the application level only, by the presence of a <tracing> configuration section with the enabled property set to true. Internal trace information is collected for each request to the application, up to a specific number of requests (the default is 10).

Enabling global tracing has the side effect of enabling page-level trace functionality for the entire application, overriding any settings in page-level directives that explicitly disable Trace. That is, any user-defined Trace.Write or Trace.Warn statements in application code will be executed and stored for viewing in the Trace Viewer. Optionally, the user can define a PageOutput property to force Trace statements to also be rendered in the browser client, as they would if tracing were enabled in a page-level directive. Pages for which tracing is already enabled by a page-level directive are not affected by the absence of this property.

For example:

<configuration>
   <configsections>
      <add name="trace" type="System.Web.Config.TraceConfigHandler">
   </configsections>
   <tracing enabled="false" requestlimit="15" pageoutput="true" />
</configuration>

Tracing Configuration Parameters

Property | Description
Enabled | True | false, indicates whether Tracing is enabled for the application (default is false)
PageOutput | Indicates whether trace information should be rendered at the end of each page – or only accessible via the trace utility
RequestLimit | Number of trace requests to store on the server (default is 10)


The NGWS SDK Debugger

The NGWS SDK debugger is a command-line utility that can help tools vendors and application developers find and fix bugs in programs that target the NGWS runtime. This utility uses the NGWS runtime Debug API to provide debugging services. Currently, the debugger can only be used for debugging managed code -- there is no support for debugging unmanaged code.

For more information about the NGWS runtime's debugging services, see:

Runtime Debugger (cordbg.exe)


The Visual Studio Debugger

Microsoft will ship a Visual Studio debugger for ASP+ applications with the Visual Studio version 7.0 release. The Visual Studio Debugger will support debugging of ASP+ code on a remote computer, a feature that allows auto-attachment to the ASP+ runtime, and the ability to edit source files within the debugger source window. For more information on the Visual Studio debugger, see To Be Supplied.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

The Visual Studio Development Environment

To be supplied.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Web Forms Control Examples

To be supplied.


Enhancing Design-Time Support

A designer such as Visual Studio 7.0 provides rich design time support for your components and controls. However, the available design-time functionality may not be adequate in certain situations. For instance, you might define a custom property and want to provide a custom editor to edit that property in the property browser. Or you might want your control to be displayed in a special way at design time. The frameworks class library provides classes and interfaces that make it relatively easy to add design-time support to your components. This section describes how you can use the base functionality and provide additional design-time support.

Many of the design time enhancements discussed here apply both to Win Forms controls and to Web Forms controls. However, there are differences, since rendering is done using HTML in Web Forms and using the Windows graphics library (GDI+) in Win Forms. Those differences will be noted where applicable. This section contains the following topics:

Implementing a Type Converter

Implementing a UI Type Editor

Implementing an Extender Provider

Implementing a Custom Designer


Implementing a Type Converter

A type converter allows a given data type to be converted to another data type. Type converters are primarily used for string-to-value conversions and for validation at design-time and at run time. In a host such as a property window in the forms designer, type converters allow a property value to be represented as text to the user, and convert text entered by the user to the appropriate data type.

Most native data types (Int32, string, enumeration types, and others) have default type converters that provide string-to-value conversions and perform validity checks. The default type converters are in the System.ComponentModel namespace and are named <TypeConverterName>Converter. You must implement a custom type converter when the default functionality is not adequate, or when you define a custom type that does not have an associated type converter.

Note
A TypeConverterAttribute must be applied to a property or a data type to associate it with a custom type converter.
The implementation of a type converter is independent of any user interface functionality. Hence any type converter can be applied to both Win Forms properties and Web Forms properties.

To implement a simple type converter

  • Define a class that derives from System.ComponentModel.TypeConverter.
  • Override the CanConvertFrom method that specifies which type the converter can convert from. This method is overloaded.
  • Override the ConvertFrom method that implements the conversion. This method is overloaded.
  • Override the CanConvertTo method that specifies which type the converter can convert to. It is not necessary to override this method for conversion to a string type. This method is overloaded.
  • Override the ConvertTo method that implements the conversion. This method is overloaded.
  • Override the IsValid method that performs validity checking. This method is overloaded.

The following code example implements a type converter that converts from a string type to a System.Drawing.Point type and from System.Drawing.Point to string. The CanConvertTo and IsValid methods listed above are not overriden in this example.

[C#]
using System;
using System.ComponentModel;

public class PointConverter : TypeConverter {

//Overrides the CanConvertFrom method of TypeConverter
// The ITypeDescritorContext interface provides the context for the
// conversion. Typically this interface is used at design time to
// provide information about the design time container.
   public override bool CanConvertFrom(ITypeDescriptorContext context,
               Type sourceType) {

      if (sourceType == typeof(string)) {
         return true;
      }
      return base.CanConvertFrom(context, sourceType);
   }
//Overrides the ConvertFrom method of TypeConverter.
   public override object ConvertFrom(ITypeDescriptorContext context,
               object value,
               object[] arguments) {

      if (value is string) {
         string[] v = ((string)value).Split(new char[] {‘,’});
         return new Point(int.Parse(v[0]), int.Parse(v[1]));
      }
      return base.ConvertFrom(ITypeDescriptorContext context,
               object value,
               object[] arguments);
   }
//Overrides the ConvertTo method of TypeConverter
public override object ConvertTo(ITypeDescriptorContext context,
               object value,
               Type destinationType,
               object[] arguments) {

      if (destinationType == typeof(string)) {
         return ((Point)value).X + “,” + ((Point)value).Y;
      }
      return base.ConvertTo(ITypeDescriptorContext context,
               object value,
               Type destinationType,
               object[] arguments);
   }
}

A complete sample is presented in Type Converter Sample.


Type Converter Sample

The complete source code for a Type Converter for the FlashTrackBar sample in the Win Forms Control Sample is given below. To download the source code, see the Win Forms Quickstart.

namespace SampleControlPack {

    using System;
    using System.ComponentModel;
    //
    // <doc>
    // <desc>
    // This is a type converter for the flash track bar value property.
    // It demonstrates a typical scenario -- accepting input in
    // multiple formats and converting it to the desired type.
    //It accepts a decimal or integer number for the value property,
    // and also a percentage.
    // </desc>
    // </doc>
    //
    public class FlashTrackBarValueConverter : TypeConverter {

        //
        // <doc>
        // <desc>
        // Override this method to indicate that
        // FlashTrackBarValueConverter can convert from
        // string data types. In this case, it is not really
        // necessary to override CanConvertTo
        // because, by default, all types can be converted to string.
        // </desc>
        // </doc>
        //
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
            if (sourceType == typeof(string)) {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }

        //
        // <doc>
        // <desc>
        //Does the actual conversino from string to value. You will
        //support an optional percentage value
        //here, and also attempt to constrain the value to the min and
        //max values of the control.
        // </desc>
        // </doc>
        //
        public override object ConvertFrom(ITypeDescriptorContext context, object value, object[] arguments) {

            if (value is string) {
                int convertedValue;
                string text = ((string)value).Trim();

                // Does the value end in a percentage?
                //
                if (text.EndsWith("%")) {
                    text = text.Substring(0, text.Length - 1).Trim();
                    float percent = float.Parse(text);

                    // Ok, you have a percentage.  But, a percentage of
                    // what? To find that out you need to get at the
                    // context object and see if you
                    // can find the flash track bar we are editing.
                    if (context != null && context.Instance is
                    FlashTrackBar) {
                        FlashTrackBar t = (FlashTrackBar)context.Instance;
                        convertedValue = (int)((float)(t.Max - t.Min) *
                        percent) / 100 + t.Min;
                    }
                    else {
                        // Otherwise, fall back to just assigning the
                        // value out.
                        convertedValue = (int)percent;
                    }
                }
                else {
                    convertedValue = int.Parse(text);
                }

                // Now that you have the converted value, constrain it by
                // the min and max properties of the control.
                // Notice that here, as in above, you always
                // check that context has what you are looking for.  The
                // context parameter that is passed to this method is
                // entirely optional, as are all of the properties on the
                // context object.  Because of this, you must check
                // everything and be prepared to
                // fall back to less-specific behavior.

                if (context != null && context.Instance is FlashTrackBar)
                {
                    FlashTrackBar t = (FlashTrackBar)context.Instance;
                    convertedValue = Math.Max(convertedValue, t.Min);
                    convertedValue = Math.Min(convertedValue, t.Max);
                }

                return convertedValue;
            }

            return base.ConvertFrom(context, value, arguments);
        }
    }
}


Implementing a UI Type Editor

In some situations, a simple value-to-string conversion that allows a property to be displayed as text in the property window might not be adequate. For instance, in the case of a color property, a visual representation is more desirable. A UI type editor allows such a representation. A UI type editor is intended for use with property browsers and other advanced design-time hosts.

Note An Editor attribute must be applied to a property of a control to associate it with a UI type editor.

The implementation of a UI type editor is dependent on the host where it will be supported, the following discussion applies to Win Forms only.

An editor of the type System.Drawing.Design.UITypeEditor is supported by the Win Forms property browser.

To implement a custom UI type editor for Win Forms

  • Define a class that derives from System.Drawing.Design.UITypeEditor.
  • Override the EditValue method to set up properties of the user interface. This method is overloaded.
  • Override the GetEditStyle method to inform the property browser about the editing support you will provide.

A complete sample is provided in the UI Type Editor for Win Forms Sample.


UI Type Editor for Win Forms Sample

The following code implements a UI type editor for the Value property of the FlashTrackBar control in the Win Forms Control Sample. To download the source code, see the Win Forms Quickstart.

namespace SampleControlPack {
    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Core;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing. Drawing2D;
    using System.Drawing.Design;
    using System.WinForms;
    using System.WinForms.ComponentModel;
    using System.WinForms.Design;

    //
    // <doc>
    // <desc>
    // This is a UI Type Editor for the value property of the
    // FlashTrackBar control.
    // A UI Type Editor provides custom UI in the property browser
    // for any property it is connected to.
    // Here, you will provide a custom UI that consists of a FlashTrackBar
    // so that the user may visually edit the value of the track bar.
    // </desc>
    // </doc>
    //
    public class FlashTrackBarValueEditor : UITypeEditor {

        private IWinFormsEditorService edSvc = null;

        //
        // <doc>
        // <desc>
        // This is a protected method that is used to set up the
        // properties of the flash track bar you
        // will display as the custom user interface.
        // </desc>
        // </doc>
        //
        protected virtual void SetEditorProps(FlashTrackBar
        editingInstance, FlashTrackBar editor) {
            editor.ShowValue = true;
            editor.StartColor = Color.Navy;
            editor.EndColor = Color.White;
            editor.ForeColor = Color.White;
            editor.Min = editingInstance.Min;
            editor.Max = editingInstance.Max;
            editor.Height = 23;
        }

        //
        // <doc>
        // <desc>
        // This is called by the property browser to display the custom
        // user interface  for editing the value.
        // Here you can simply display your own editor, or you can ask the
        // given service object provider for an editing service.
        // You will choose the latter so that you
        // can provide a combo box like drop-down to the user.
        // </desc>
        // </doc>
        //
        public override object EditValue(ITypeDescriptorContext context, IServiceObjectProvider provider, object value) {

            if (context != null
                && context.Instance != null
                && provider != null) {

                edSvc = (IWinFormsEditorService)provider.GetServiceObject(typeof(IWinFormsEditorService));

                if (edSvc != null) {
                    FlashTrackBar trackBar = new FlashTrackBar();
                    trackBar.AddOnValueChanged(new EventHandler(this.ValueChanged));
                    SetEditorProps((FlashTrackBar)context.Instance, trackBar);
                    bool asInt = true;
                    if (value is int) {
                        trackBar.Value = (int)value;
                    }
                    else if (value is byte) {
                        asInt = false;
                        trackBar.Value = (byte)value;
                    }
                    edSvc.DropDownControl(trackBar);
                    if (asInt) {
                        value = trackBar.Value;
                    }
                    else {
                        value = (byte)trackBar.Value;
                    }
                }
            }

            return value;
        }

        //
        // <doc>
        // <desc>
        // This is called by the property browser so it knows what type of
        // editing support to expect.
        //  You have provided a custom drop down box.
        // </desc>
        // </doc>
        //
        public override UITypeEditorEditStyle
        GetEditStyle(ITypeDescriptorContext context) {
            if (context != null && context.Instance != null) {
                return UITypeEditorEditStyle.DropDown;
            }
            return base.GetEditStyle(context);
        }

        //
        // <doc>
        // <desc>
        // This is an event handler that is connected to the ValueChanged
        // event of the flash track bar.  When the value changes,
        // you want to commit the value, so
        // you tell the editing service to close the drop down box.
        // </desc>
        // </doc>
        //
        private void ValueChanged(object sender, EventArgs e) {
            if (edSvc != null) {
                edSvc.CloseDropDown();
            }
        }
    }
}

The following code shows a UI type editor for the DarkenBy property of the FlashTrackBar control. This UI editor extends the FlashTrackBarValueEditor implemented above.

[C#]
namespace SampleControlPack {

    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Core;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Advanced;
    using System.Drawing.Design;
    using System.WinForms;
    using System.WinForms.ComponentModel;
    using System.WinForms.Design;

    //
    // <doc>
    // <desc>
    //This is a UI type editor that is used for the DarkenBy property.
    // It inherits from the default UI Type Editor for the Value property
    // of FlashTrackBar, and then modifies the min and max values of the
    // track bar.
    // </desc>
    // </doc>
    //
    public class FlashTrackBarDarkenByEditor : FlashTrackBarValueEditor {
        protected override void SetEditorProps(FlashTrackBar
         editingInstance, FlashTrackBar editor) {
            base.SetEditorProps(editingInstance, editor);
            editor.Min = 0;
            editor.Max = byte.MaxValue;
        }
    }
}


Implementing an Extender Provider

Extender providers add properties to other controls. The ActiveX world also had the concept of extender providers, however, special support was required in the programming language to support them. In the NGWS frameworks, no special language support is required for extender providers. In source code, an extender provider property exists on the actual extender provider object. Setting the value of the property on another object requires two pieces of information: the object to set the value on, and the new value of the property. For example, Win Forms has a ToolTip component that offers an extender property to other controls. The property it sets on other objects is a string that represents the tool tip the user sees when she hovers her mouse cursor over the control. The source code for setting the tool tip property on a control is shown below:

[C#]
tooltip1.SetToolTip(button1, “The tooltip text”);

At design time, extender properties show up in the property browser as properties on objects they extend, rather than the actual extender object. In the example above, the ToolTip property shows up on button1, not on tooltip1.

To implement an extender provider

  • Define a component that implements the System.ComponentModel.IExtenderProvider interface.
    [C#]
public MyExtender : IExtenderProvider {...}

    The definition of IExtenderProvider is given below.

    [C#]
public interface IExtenderProvider {
    bool CanExtend(IComponent component);
}
  • Implement the CanExtend method so that it returns true for each component/control that your extender wants to provide properties for.
  • Define a set of properties for your extender that it will provide to other components. The properties are really methods since they take an extra parameter that specifies the component to apply the property to.
    Note An ExtenderPropertyAttibute must be applied to an extender property.
        [C#]
       [
        DefaultValue(""),
        ExtenderProperty(typeof(Control))
       ]
//The Get method needs an attribute since it tells the
//designer to display this property.
       public string GetHelpText(Control control) {...}
//The Set method does not need an attribute.
       public void SetHelpText(Control control, string value) {

While an extender provider implemented as above can provide properties to any component, the implementation is often not so general, but includes features that make it usable only with a specific category of components.

Note The implementation of an extender provider for Win Forms controls is different from that for Web Forms controls.

A complete sample is provided in Win Forms Extender Provider Sample.


Win Forms Extender Provider Sample

The following sample is an extender provider that provides a help label extender property for the FlashTrackBar sample in the Win Forms Control Sample. To download the source code, see the Win Forms Quickstart.

The sample shows the following key steps.

  • The extender provider HelpLabel implements IExtenderProvider.
  • HelpLabel is itself a Win Forms control and hence derives from RichControl.
  • The CanExtend method returns true for any control except HelpLabel (because it is not meaningful to extend a property on itself).
  • HelpLabel has a method GetHelpText that gets the property, HelpText, that it makes available to other controls. This is tagged with the ExtenderPropertyAttribute. The SetHelpText method sets the value of the property.
[C#]
namespace SampleControlPack {
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Drawing;
    using System.WinForms;
    using System.WinForms.Design;

    //
    // <doc>
    // <desc>
    // Help Label offers an extender property called
    // "HelpText".  It monitors the active control
    // and displays the help text for the active control.
    // </desc>
    // </doc>
    //
    [
    ProvideProperty("HelpText"),
    Designer(typeof(HelpLabel.HelpLabelDesigner))
    ]
    public class HelpLabel : RichControl, IExtenderProvider {
        private Hashtable helpTexts;
        private Control activeControl;

        //
        // <doc>
        // <desc>
        //      Creates a new help label object.
        // </desc>
        // </doc>
        //
        public HelpLabel() {
            helpTexts = new Hashtable();
            BackColor = SystemColors.Info;
            ForeColor = SystemColors.InfoText;
            TabStop = false;
        }

        //
        // <doc>
        // <desc>
        //      Overrides the default tab stop property.
        //      Changes the default value to be false.
        // </desc>
        // </doc>
        //
        [
        DefaultValue(false)
        ]
        public bool TabStop {
            override get {
                return base.TabStop;
            }
            override set {
                base.TabStop = value;
            }
        }

        //
        // <doc>
        // <desc>
        // Overrides the text property of RichControl. The help label
        //ignores the text property,
        // so you have to add additional attributes here so that the
        // property does not show up in the property browser and is not
        // persisted.
        // </desc>
        // </doc>
        //
        [
        Browsable(false),
        Persistable(PersistableSupport.None)
        ]
        public string Text {
            override get {
                return base.Text;
            }
            override set {
                base.Text = value;
            }
        }

        //
        // <doc>
        // <desc>
        // This implements the IExtenderProvider.CanExtend method.  The
        // help label provides an extender property, and the design-time
        // framework will call this method once for each component to
        // determine if HelpLabel is interested in providing the extended
        // property for the
        // component. You return true here if the object is a control,
        // but is not a HelpLabel (because it not meaningful for
        // HelpLabel to add the property to itself).
        // </desc>
        // </doc>
        //
        bool IExtenderProvider.CanExtend(object target) {
            if (target is Control &&
                !(target is HelpLabel)) {

                return true;
            }
            return false;
        }

        //
        // <doc>
        // <desc>
        // This is the extended HelpText property.
        //  Extended properties are actually methods because they take an
        // additional parameter
        //  that is the object or control to provide the property for.
        // </desc>
        // </doc>
        //
        [
        DefaultValue(""),
        ExtenderProperty(typeof(Control))
        ]
        public string GetHelpText(Control control) {
            string text = (string)helpTexts[control];
            if (text == null) {
                text = string.Empty;
            }
            return text;
        }

        //
        // <doc>
        // <desc>
        // This is an event handler that responds to the OnControlEnter
        // event. You should attach this to each control you
        // are providing help text for.
        // </desc>
        // </doc>
        //
        private void OnControlEnter(object sender, EventArgs e) {
            activeControl = (Control)sender;
            Invalidate();
        }

        //
        // <doc>
        // <desc>
        // This is an event handler that responds to the OnControlLeave
        //event.  You should attach this to each control we are providing
        //help text for.
        // </desc>
        // </doc>
        //
        private void OnControlLeave(object sender, EventArgs e) {
            if (sender == activeControl) {
                activeControl = null;
                Invalidate();
            }
        }

        //
        // <doc>
        // <desc>
        // This is the extended property for the HelpText property.
        // </desc>
        // </doc>
        //
        public void SetHelpText(Control control, string value) {
            if (value == null) {
                value = string.Empty;
            }

            if (value.Length == 0) {
                helpTexts.Remove(control);

                control.RemoveOnEnter(new EventHandler(OnControlEnter));
                control.RemoveOnLeave(new EventHandler(OnControlLeave));
            }
            else {
                helpTexts[control] = value;

                control.AddOnEnter(new EventHandler(OnControlEnter));
                control.AddOnLeave(new EventHandler(OnControlLeave));
            }

            if (control == activeControl) {
                Invalidate();
            }
        }

        //
        // <doc>
        // <desc>
        // Overrides RichControl.OnPaint to draw the label.
        // </desc>
        // </doc>
        //
        protected override void OnPaint(PaintEventArgs pe) {

            // Let the base draw.  This will take care
            // of the back color
            // and set any image that the user may have
            // provided.
            //
            base.OnPaint(pe);

            // Draw a rectangle around the control.
            //
            Rectangle rect = ClientRectangle;

            Pen borderPen = new Pen(ForeColor);
            pe.Graphics.DrawRectangle(borderPen, rect);
            borderPen.Dispose();

            // Finally, draw the text over the top of the
            // rectangle.
            //
            if (activeControl != null) {
                string text = (string)helpTexts[activeControl];
                if (text != null && text.Length > 0) {
                    rect.Inflate(-2, -2);
                    Brush brush = new SolidBrush(ForeColor);
                    pe.Graphics.DrawString(text, NewFont, brush, rect);
                    brush.Dispose();
                }
            }
        }

        // <doc>
        // <desc>
        // Returns true if the backColor should be persisted in code gen.
       //  You override this to change the default back color.
        // </desc>
        // <retvalue>
        //     true if the backColor should be persisted.
        // </retvalue>
        // </doc>
        //
        public override bool ShouldPersistBackColor() {
            return(!BackColor.Equals(SystemColors.Info));
        }

        // <doc>
        // <desc>
        // Returns true if the foreColor should be persisted in code gen.
        // Override this to change the default foreground color.
        // </desc>
        // <retvalue>
        //     true if the foreColor should be persisted.
        // </retvalue>
        // </doc>
        //
        public override bool ShouldPersistForeColor() {
            return(!ForeColor.Equals(SystemColors.InfoText));
        }


Implementing a Custom Designer

A designer controls the behavior of a component at design time. The example below illustrates why a component’s appearance may need to be modified at design time.

  • The System.WinForms.Panel object may not have a visible border at runtime. Without a border, the panel is invisible to the user designing a form that contains a panel. Therefore, the designer for the System.WinForms.Panel object draws a dotted line border around the panel. While the goal of any WYSIWYG form designer should be to minimize differences between design-time and run time, sometimes special design-time cues are necessary.

In NGWS frameworks, a designer is simply an object that is attached to a component at design time. The designer host allows the designer object to change the behavior of the component during the design process. In the past, support for this type of special case logic was built into the form designer. It was the form designer, not the object, that was responsible for the design capabilities. In the ActiveX world, different tools offered their own form designers, with different user interfaces and capabilities. Because a product's fixed feature set cannot account for all controls, there was a limited level of custom design-time support for controls in the ActiveX world.

The design-time support for components in the NGWS frameworks, on the other hand, comes not from a tool such as Visual Studio, but from the components themselves. Tools such as Visual Studio 7.0 provide a set of base services for designers to use, but support for designers is a core part of the platform. The System.ComponentModel.Design namespace provides interfaces and base classes for design time support. The base functionality can be extended to provide varying levels of designer support.

The following topics are discussed in this section.

Base Designer Classes

Enabling Property Filtering

Creating Designer Verbs

Win Forms Designer Sample


Base Designer Classes

The base classes for designers implement the IDesigner interface described below and the IDesignerFilter interface described in Enabling Property Filtering.

IDesigner Interface

A designer object must implement the System.ComponentModel.Design.IDesigner interface:

[C#]
public interface IDesigner {
    void Dispose();
//other methods
....
    IComponent Component {
        get {…}
        set {…}
//other properties
....
    }
}

The Dispose method is used when this designer object should be destroyed. It is called when a component is removed from the design container.

The Component property provides access to the component to which the designer is attached. This property is set once when the designer is first created. The component has a site associated with it, and the designer can use this site to obtain services from the designer’s host.

ComponentDesigner Base Class

System.ComponentModel.Design.ComponentDesigner is the base class for all component designers. It implements the IDesigner and IDesignerFilter interfaces. Other designers are derived from this base class.

A designer object is associated with a component through a class attribute named DesignerAttribute. This attribute takes the fully qualified name of the designer class in its constructor. Typical code for associating a designer with a class is as follows:

[C#]
[DesignerAttribute(typeof (System.WinForms.Design.ControlDesigner))]
public class Control : Component {
}

Designer namespaces have the following convention. They follow the same class hierarchy as the components they design, except that the namespace Design is inserted into the hierarchy. For example, the designer for the class System.WinForms.Control is System.WinForms .Design.ControlDesigner. To add design-time support to a component from which you derive, you can inherit from that component’s designer.

Base Class for Win Forms Controls Designers

The base class for Win Forms controls designers is System. WinForms. Design.ControlDesigner. This class derives from ComponentDesigner. It allows a custom user interface to be provided at design time for any Win Forms control. While this provides base functionality designers for RichControls shoul derive from System. WinForms. Design.RichControlDesigner.

Base Class for Web Forms Controls Designers

The base class for Win Forms controls designers is System.Web.UI.Design.HtmlControlDesigner. This class derives from ComponentDesigner and implements IWebFormsControlDesigner. HtmlControlDesigner allows for custom design-time HTML rendering, and template editing support.

Note Win Forms has an integrated design-time and runtime, while Web Forms has a strong separation between design-time and runtime.

See Also

Enabling Property Filtering | Creating Designer Verbs | Win Forms Designer Sample


Enabling Property Filtering

Property filtering allows a designer to replace properties, add new properties, or modify the set of attributes or events that a component provides. For example, System.WinForms.Control has a Visible property that determines if the control is visible. At design time, however, the control should always remain visible, irrespective of the value of this property, so that the developer can position it on the design surface. The designer for Control replaces the Visible property with its own version, and stores the runtime value of the property.

To provide property filtering, a designer must implement the System.ComponentModel.Design.IDesignerFilter interface. The IDesignerFilter interface provides a way for a designer to fine-tune the filtering of the properties, events and attributes that its component reports.

[C#]
public interface IDesignerFilter {
    void PostFilterAttributes(List attributes);
    void PostFilterEvents(List events);
    void PostFilterProperties(List properties);
    void PreFilterAttributes(List attributes);
    void PreFilterEvents(List events);
    void PreFilterProperties(List properties);
}

See Also

Base Designer Classes | Creating Designer Verbs | Win Forms Designer Sample


Creating Designer Verbs

Verbs offer a way for a designer to add specific commands to a component at design time. For example, the Win Forms TabControl object adds two verbs: Add Tab and Remove Tab. Other controls can add other verbs.

Designer verbs are provided by the Verbs property in the IDesigner interface.

[C#]
public interface IDesigner {
    DesignerVerb[] Verbs {
        get {…}
//Other properties and methods
.....
    }
}

The Verbs property on IDesigner provides a way for the designer to add menu items to the context menu of its component. Visual Studio 7.0 adds these verbs as links to the surface of the property browser.

See Also

Base Designer Classes | Enabling Property Filtering | Win Forms Designer Sample


Win Forms Designer Sample

The sample below is a designer for the HelpLabel extender provider control described in the Win Forms Extender Provider Sample. To download the source code, see the Win Forms Quickstart.

        // <doc>
        // <desc>
        // This is a designer for the HelpLabel.  This designer provides
        // design time feedback for the label.  The help label responds
        // to changes in the active control, but these events do not
        // occur at design time.  In order to provide some usable feedback
        // that the control is working the right way, this designer
        // listens to selection change events and uses those events to
        // trigger active control changes.

        // </desc>
        // </doc>
        //
        public class HelpLabelDesigner : RichControlDesigner {

            //
            // <doc>
            // <desc>
            //  Overrides Dispose.  Here you remove the handler for the
            //   selection changed  event. With designers, it is critical
            // that they clean up any events the have attached.
            // Otherwise, during the course of an editing session many
            // designers may get created and never destroyed.
            // </desc>
            // </doc>
            //
            public override void Dispose() {
                base.Dispose();

                ISelectionService ss =
           (ISelectionService)GetServiceObject(typeof(ISelectionService));
                if (ss != null) {
                  ss.RemoveOnSelectionChanged(new
                  EventHandler(OnSelectionChanged));
                }
            }

            //
            // <doc>
            // <desc>
            // Overrides initialize.  Here you add an event handler to the
            // selection service. You have to be  careful not to assume
            // that the selection service is available.
            // It is entirely optional that a service is available and you
            // should always degrade gracefully if a service is not found.
            // </desc>
            // </doc>
            //
            public override void Initialize(IComponent component) {
                base.Initialize(component);

                ISelectionService ss =
           (ISelectionService)GetServiceObject(typeof(ISelectionService));
                if (ss != null) {
                    ss.AddOnSelectionChanged(new
                    EventHandler(OnSelectionChanged));
                }
            }

            //
            // <doc>
            // <desc>
            //  The handler for the selection change event.
            // Here you update the active control within
            // the help label.
            // </desc>
            // </doc>
            //
            private void OnSelectionChanged(object sender, EventArgs e) {

                ISelectionService ss = (ISelectionService)sender;
                object c = ss.GetPrimarySelection();
                if (c is Control) {
                    HelpLabel helpLabel = (HelpLabel)Component;
                    helpLabel.activeControl = (Control)c;
                    helpLabel.Invalidate();
                }
            }
        }
    }
}


Accessing the Internet

The NGWS frameworks provides an integrated and managed implementation of Internet services that can be integrated quickly and easily into your applications. The supplied Internet access classes can be used to implement both Web- and Internet-based applications.

Access to the Internet in the NGWS frameworks is through "pluggable protocols," an implementation of network services that can be used at a high level without needing to know the underlying protocol. Pluggable protocols are built on two abstract classes, WebRequest and WebResponse, that encapsulate the transaction between the client and the server, enabling them to communicate without worrying about the details of the protocol used. The protocol-specific implementations of these classes manage the details of making the actual connections to Internet services. Classes for data access via the HTTP protocol, HttpWebRequest and HttpWebResponse, are provided; additional protocol-specific classes can easily be derived from WebRequest and WebResponse.

For applications that need to access network services at the socket level, managed versions of these services are available as well. The System.Net.Socket namespace provides an implementation of the Windows Sockets interface. At a higher level, TCP and UDP services can be built on the TCPClient, TCPListener, and UDPClient classes. All high-level Internet access classes in System.Net are built on the socket-level classes in System.Net.Socket.


Requesting Data

Developing applications that run in a distributed operating environment like today’s Internet requires an efficient, easy-to-use method for retrieving data from resources of all types. Pluggable protocols let you develop applications that use a single interface to retrieve data using multiple Internet protocols.

Client applications request data from servers using the WebRequest class and its descendents. The WebRequest class encapsulates the details of connecting to the server, sending the request, and receiving the response. WebRequest is an abstract class that defines a set of properties and methods that are available to all applications using pluggable protocols. Descendents of WebRequest, such as HttpWebRequest, implement the properties and methods defined by WebRequest in a way that is consistent with the underlying protocol.

The WebRequestFactory class creates protocol-specific instances of WebRequest descendents using the value of the URI passed to its Create method to determine the specific derived class instance to create.

The request is made to the Internet resource by calling the GetResponse method on the WebRequest instance. The GetResponse method returns a WebResponse derived instance that matches the WebRequest derived instance.

WebRequest req = WebRequestFactory.Create("http://www.microsoft.com/");
WebResponse resp = req.GetResponse();


Creating Internet Requests

Applications create WebRequest instances through the WebRequestFactory class. This is a static class that exists to create a descendent WebRequest instances based on the URI scheme passed to its Create method.

The NGWS frameworks provides an HttpWebRequest class that descends from the WebRequest class to handle HTTP protocol requests to the Internet. You can typecast WebRequest objects created by the WebRequestFactory to HttpWebRequest to access the HTTP protocol-specific properties of the request.

For other Internet protocols, such as FTP, you will need to derive a descendent of WebRequest to handle requests made to URIs with that scheme. See Deriving from WebRequest for more information

The HttpWebResponse class is also provided by the NGWS frameworks to handle the responses from HTTP protocol requests to the Internet. Typecast WebResponse to HttpWebResponse to access the HTTP-specific properties of the HttpWebResponse class.

You will also need to derive a descendent of WebResponse to handle requests for other Internet protocols, such as FTP. See Deriving from WebRequest for more information.


Using Internet Request and Response Classes

The steps for requesting data from the Internet and reading the response are:

  1. If you are accessing a resource such as a Web page using HTTP, simply create a WebRequest by calling Create on the WebRequestFactory with the URI of the resource you want to use. If you are accessing a non-HTTP resource, first register the WebRequest descendent that will handle the protocol you want to use by calling Register on WebRequestFactory, and then calling Create. The following code will create a WebRequest instance for an HTTP resource:
WebRequest req = WebRequestFactory.Create("http://www.microsoft.com/")
  1. Set any property values that you need in the WebRequest instance. In most cases the WebRequest instance will be sufficient to send and receive data. In the event that you need to set protocol-specific properties, typecast the WebRequest instance to the protocol specific instance. For example, to access the HTTP-specific properties of HttpWebRequest, typecast the WebRequest to HttpWebRequest. The following code will set the UserAgent property:
HttpWebRequest(req).UserAgent="NGWS Frameworks Example Client";
  1. To download a resource from the Internet, call the GetResponse method of your WebRequest instance to make the request to the Internet resource and return the response. When sending or uploading data to a resource, call the GetRequestStream method of your WebRequest and use the resulting Stream object to write the data. When you've finished uploading, GetResponse can be called to ensure that the data was received correctly by the server. The actual class of the WebResponse instance returned is determined by the scheme of the requested URI.
WebResponse resp = req.GetResponse();
  1. Use the GetResponseStream method of the WebResponse instance to get the stream containing the response data. Reading the returned stream provides the data from the network resource. You can also access the properties of the WebResponse instance, or typecast the WebResponse instance to a protocol-specific instance to read protocol-specific properties. For example, to access the HTTP-specific properties of HttpWebResponse, typecast the WebResponse to HttpWebResponse. The following code illustrates accessing an HTTP-specific property and reading the response stream:
// Read an HTTP specific property:
DateTime Updated = HttpWebRequest(resp).LastModified;
// Get the response stream:
Stream resp = resp.GetResponseStream();
// Create a buffer to hold the response data:
int BufferSize = 512;
Byte[] Buffer = new Byte[BufferSize];
// Read the stream to access the data:
int BytesRead = respstrm.Read(Buffer, 0, BufferSize);
while (BytesRead != 0)
{
   Console.Write(Encoding.ASCII.GetString(Buffer, 0, BytesRead));
       BytesRead = respstrm.Read(Buffer, 0, BufferSize);
}


Using an HTTP Proxy

If your site uses an HTTP proxy to provide access to the Web, you must configure a global proxy instance to enable your application to communicate with the Web proxy. The following code will create a proxy instance that will enable a WebRequest to use a proxy to communicate with the Internet. It assumes that the proxy host is named "webproxy" and that it communicates on port 80, the standard HTTP port:

DefaultControlObject proxyObject =
     new DefaultControlObject("webproxy",80);
GlobalProxySelection.Select = proxyObject;


Using Streams on the Network

The NetworkStream class provides the underlying data stream sent to or received from the network.

You send data to the network using the stream returned by calling GetRequestStream on your WebRequest instance. Data can then be sent to the Internet resource by calling the BeginWrite, EndWrite, or Write methods on the returned stream.

You receive data from the network by calling GetResponseStream on your WebResponse instance. You can then read data from the Internet resource by calling the BeginRead, EndRead, or Read methods on the returned stream.

When using streams from network resources, keep in mind the following points:

  • The CanSeek property will always return false since the NetworkStream class cannont change its position in the stream. The Seek and Position methods will return a NotSupportedException.
  • The stream will be read-only for streams instances created by calling GetResponseStream and write-only for stream instances created by calling GetRequestStream.
  • Use the Streamreader class to make encoding easier, as shown in the following example of reading an ASCII encoded stream from a WebResponse instance (creating the request is not shown):
// Create a response object
WebResponse response = request.GetResponse;
// Get a readable stream from the server
StreamReader sr =
   new StreamReader(response.GetResponseStream(), Encoding.ASCII);


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Working with Enhanced Features

[To be supplied.]


Deriving from WebRequest and WebResponse

The abstract WebRequest and WebResponse classes provide the base for the pluggable protocols. By deriving protocol-specific classes from WebRequest and WebResponse, an application can request data from an Internet resource and read the response without needing to be aware of the specific protocol used.

Before a WebRequest instance can be created, its Create method must be registered with the WebRequestFactory. You use the Register method of the WebRequestFactory to register a WebRequest descendent to handle a set of requests to a particular Internet scheme.

In most cases you will be able to send and receive data using the methods and properties of the WebRequest class. However, if you need to access protocol-specific properties, you can typecast a WebRequest to a specific derived class instance:

HttpWebRequest httpreq =
   (HttpWebRequest) WebRequestFactory.Create("http://www.microsoft.com/");


Deriving from WebRequest

Two criteria must be met in order for a protocol-specific class to be used as a pluggable protocol: The class must implement the IWebRequestCreate interface that is registered with the WebRequestFactory. The class must override the abstract methods and properties of WebRequest to provide the pluggable interface.


Method Property

The Method property specifies the activity that application is performing. The default value of the Method property should be "GET," which instructs the server to return the resource specified by the URI property.

If the protocol changes the actual method that is used by the protocol to perform the action, the value of the Method property should be changed to reflect the actual method used.


RequestUri Property

The RequestUri property identifies the resource that the application is requesting. This value is set by the Create call to the WebRequestFactory and will always be non-null.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Headers Property

The Headers property stores header information as name-value pairs.


Error Handling

The WebRequest and WebResponse classes throw both system exceptions (such as InvalidArgumentException) and Web specific exceptions in the WebException class.

Each WebException includes a Status property that contains a value from WebStatus. You can examine the Status property to determine the error that occurred and take the proper steps to resolve the error. The possible values for the Status property are:

Status | Value | Description
Success | 0 | No error was encountered.
Error | -1 | General error status.
NameResolutionFailure | -2 | The name resolver service could not resolve the host name.
ConnectFailure | -3 | The remote service could not be contacted at the transport level.
ReceiveFailure | -4 | A complete response was not received from the remote server.
SendFailure | -5 | A complete request could not be sent to the remote server.
ResponseTimeout | -6 | No response was received within the timeout settings in the Connection class.
RequestCanceled | -7 | The request was canceled.
ProtocolError | -8 | Thre response received from the server was complete but indicated an error at the protocol level.
ConnectionClosed | -9 | The connection was prematurely closed.
TrustFailure | -10 | A server certificate could not be validated.
SecureChannelFailure | -11 | An error occurred in a secure channel link.

When the Status property is WebStatus.ProtocolError, a WebResponse is available that contains the response from the server. This response an be examined to determine the actual source of the protocol error.

Applications that use the Socket class must handle any error codes returned by the Socket class. These error codes are contained in the SocketErrors class. The TCPClient, TCPListener, and UDPClient classes are built on top of the Socket class, and will return SocketErrors as well.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Connection Management

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Using Application Protocols

[To be supplied.]


HTTP

In most cases, WebRequest and WebResponse will be the ideal classes for sending and retrieving data from Internet resources using HTTP. However, the System.Net namespace includes an HTTP protocol-specific implementation of the WebRequest and WebResponse classes for use when needed. These classes encapsulate a standard HTTP request and response transaction, and provide access to common HTTP headers through properties. Custom headers and headers not provided through properties can be accessed by storing them in the Headers property.

HttpWebRequest is the default class used by WebRequestFactory and does not need to be registered before passing an HTTP Uniform Resource Identifier (URI) to the WebRequestFactory.Create method.

You application can automatically follow HTTP redirects by setting the AllowAutoRedirect property true. When the request is redirected, the ResponseURI property of the HttpWebResponse will contain the actual Web resource that responded to the request. When AllowAutoRedirect is false, your application must be prepared to handle redirects as an HTTP protocol error.

HTTP protocol errors are returned by throwing a WebException with the Status set to WebStatus.ProtocolError. The Response property contains the WebResponse sent by the server and it can be examined to find the actual HTTP error encountered.


Using a Proxy

If your site uses an HTTP proxy to provide access to the Web, you can configure the global proxy selection to enable your application to communicate with the Web proxy. The following code will create a global proxy instance that will enable any HttpWebRequest instance to use this proxy to communicate with the Internet. It assumes that the proxy host is named "webproxy" and that it communicates on port 80, the standard HTTP port:

DefaultControlObject proxyObject =
   new DefaultControlObject("webproxy",80);
GlobalProxySelection.Select = proxyObject;

Each HttpWebRequest can specify an individual proxy by setting its Proxy property to an instance of the ProxyData class. This individual proxy setting will override the global proxy selection when OverrideSelectProxy is true; otherwise the global proxy selection will be used.

// Create the ProxyData object
ProxyData proxyObject = new ProxyData();
// Set the proxy host and port.
proxyObject.HostName = "webproxy";
proxyObject.Port = 80;
// Override the global proxy setting
proxyObject.OverrideSelectProxy = true;

req.Proxy = proxyObject;


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

TCP/UDP

Applications can use TCP and UDP services with the TCPClient, TCPListener, and UDPClient classes.


Sockets

The System.Net.Sockets namespace contains an implementation of the Windows Sockets interface. All other network access classes in the System.Net namespace are built on top of this implementation of sockets.

The NGWS frameworks Socket class is a managed code version of the socket services provided by the WinSock32 API. Developers familiar with the WinSock API should have no problems developing applications using the Socket class.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Internet Security

[To be supplied.]


Using Secure Sockets Layer

WebRequest and WebResponse classes use Secure Sockets Layer (SSL) automatically. The decision to use SSL is made by the WebRequest class based on the URI it is given. If the URI begins with "https:" then SSL will be used, otherwise, if the URI begins with "http:" SSL will not be used.


Internet Authentication

The following standard Internet authentication methods are supported:

  • Basic
  • Digest
  • NTLM
  • Kerberos
  • Custom


Basic and Digest Authentication

The System.Net implementation of Basic and Digest authentication complies with RFC2617 – HTTP Authentication: Basic and Digest Authentication. To use these forms of authentication, the application must provide a user name and password in the Credentials property of the WebRequest object used to request data from the Internet.


NTLM and Kerberos Authentication

Default NTLM authentication and Kerberos authentication uses the Windows NT user credentials associated with the calling application to attempt an authentication with the server. When using non-default NTLM authentication, the authentication type is set to AuthenticationTypes.NTLM and a SingleCredential object is used to pass in the username, password and domain.


Web and Socket Permissions

Internet security for applications using System.Net is provided by the WebPermissions and SocketPermissions classes. The WebPermissions class controls an application's right to request data from a URI or to serve a URI to the Internet. The SocketPermissions class controls an application's right to accept data on a local port or to contact applications via a transport protocol at another address based on the host, port number, and transport protocol.

You should choose the permission class based on your application type. Applications that use WebRequest and its descendents should use the WebPermissions class to manage permissions. Applications using socket-level access should use the SocketPermissions class to manage permissions.

WebPermissions and SocketPermissions define two permissions, accept and connect. Accept grants the application the right to answer an incoming connection from another party. Connect grants the application the right to initiate a connection to another party.

For SocketPermissions, accept means that an application can accept incoming connections on a local transport address; connect means that an application can connect to some remote (or potentially local) transport address.

For WebPermissions, accept means that an application can export that URI to the world; connect means that an application can access that URI (be it remote or local).


Best Practices for Net Classes

The following recommedations will help you use the classes contained in System.Net to their best advantage:

  • Use WebRequest and WebResponse whenever possible instead of typecasting to descendent classes. Applications that use WebRequest and WebResponse can take advantage of new Internet protocols without needing extensive code changes.
  • When writing ASP+ applications that run on a server using the System.Net classes, it is often better from a performance standpoint to use the asynchronous methods for GetResponse and GetResponseStream.
  • The number of connections opened to an Internet resource can have a significant impact on network performance and throughput. System.Net uses two connections per application per host by default. This number can be increased by setting the ConnectionLimit property in the ServicePoint instance for your connection.
  • When writing socket level protocols, try to use TCPClient or UDPClient whenever possible instaed of writing directly to Sockets. These two client classes encapsulate the creation of TCP and UDP sockets without requiring you to handle the details of the connection.
  • When accessing sites that require credentials, use the CredentialCache class to create a cache of credentials rather than supplying them with every request. The CredentialCache class will search the cache to find the appropriate credential to present with a request, relieving you of the responsibility of creating and presenting credentials based on the Uniform Resource Locator.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Handling and Throwing Exceptions

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

What is an exception?

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Exception Handling Fundamentals

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Throwing Exceptions

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Catching Exceptions

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Exception Filters

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Best Practices for Handling Exceptions

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Enumerating Exceptions in an Exception Hierarchy

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Obtaining a Message for an Exception

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Customizing Error Messages

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Creating and Working with Services

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Manipulating Strings

This section contains the following topics:

NGWS Frameworks Regular Expressions


NGWS Frameworks Regular Expressions

This section contains the following topics:

Introduction to Regular Expressions

Regular Expressions as a Language

Regular Expressions Language Elements

Details of Regular Expression Behavior

Regular Expression Examples


Introduction to Regular Expressions

Regular expressions provide a powerful, flexible, and efficient method of processing text. The extensive pattern-matching notation of regular expressions allow you to parse large amounts of text to find matching character patterns. Regular expressions can be used to extract, edit, replace, or delete substrings, or to just add the extracted strings to a collection in order to generate a report. For many applications that deal with strings (such as HTML processing, log file parsing, and HTTP header parsing), regular expressions are an indispensable tool.

NGWS frameworks regular expressions incorporate the most popular features of other regular expression implementations such as those in Perl and awk. NGWS Frameworks regular expressions have been designed to be compatible with Perl 5 but also include features not yet seen in other implementations, such as right-to-left matching and compilation to native code.

The classes that define NGWS frameworks regular expressions are part of the NGWS frameworks base class library and can be used with any language or tool that adopts the NGWS runtime. Regular expressions can be used with ASP+ and the Microsoft Visual Studio 7.0 languages for Internet-based or standalone desktop applications.


Regular Expressions as a Language

Regular expressions are a mini-language designed and optimized to manipulate text. They are composed of two basic character types: literal, or normal, text characters, and metacharacters. The set of metacharacters give regular expressions their processing power.

You are probably familiar with the metacharacters ? and * used with the DOS file system to represent any single character or any group of characters. The DOS file command COPY *.DOC A: commands the file system to copy files of any name but with a .DOC filename extension to the disk in drive A. The metacharacter "*" matches any file name in front of the file name extension ".DOC". Regular expressions extend this basic idea many times over, and provide a large set of metacharacters that make it possible to describe very complex text-matching expressions with relatively few characters.

For example, the regular expression "\\s2000" when applied to a body of text will match all occurrences of the string "2000" that is preceded by a space in that text.

NOTE: In the above expression, the special escaped character \s is preceded by a backslash to signal that the backslash in the escaped character should be treated as a literal character. Otherwise, the backslash and the s will be treated as two separate operators. All special escaped characters must appear this way in regular expression strings.

For a more complex example, the regular expression (?<char>\\w)\\k<char>, using named groups and backreferencing (explained in the following sections), looks for adjacent paired characters. When applied to the following string, it will find matches in the words "I'll", "tall" and "latte".

I'll have a double tall latte.

The following sections detail the set of metacharacters that define the NGWS frameworks regular expression language and show how to use the regular expression classes to implement regular expressions in your applications.


Using the Regular Expression Classes

The classes you will implement to use NGWS frameworks regular expressions are described below. The code fragments are written in C#.

Regex

The Regex class represents an immutable, compiled regular expression. It also contains static methods that allow use of other regular expressions classes without explicitly instantiating objects of the other classes.

The following example declares an object of type Regex and defines a simple regular expression when the Regex object is initialized.

   Regex r; // Declare object variable of type Regex.
   r = new Regex("\\s2000") // Instantiate a Regex object and define
                            // its regular expression.

Match

The Match Class represents the results from a single regular expression matching operation. This example uses the static Match method of the Regex class to find the first match. The Match.Success property is used to test if a match was found.

   Regex r = new Regex("abc"); // Instantiate a new Regex object.
   Match m = r.Match("123abc456"); // Find a single match in the string.
   if (m.Success){
      // Print out character position where match was found
      // (it will be 3 in this case).
      Console.Write("Found match at position " + m.Index);
   }

MatchCollection

The MatchCollection class represents a sequence of successful nonoverlapping matches. The collection is read-only. This example uses the static Matches method of the Regex class to fill a collection with all matches found in the input string.

   MatchCollection mc;
   int i;
   String[] matchresult = new String[20];
   int[] matchposition[] = new int[20];

   Regex r = new Regex("abc");     // Instantiate a new Regex object and
                                   // define the regular expression.
   mc = r.Matches("123abc4abcd");  // Use the Matches method to find all
                                   // matches in the input string.
   // Loop through mc collection to retrieve all matches and positions.
   for (i=0; i<mc.Count; i++) {
      results[i] = mc[i].ToString;    // Add match string to string array.
      matchposition[i] = mc[i].Index; // Record character position where
   }                                  // match was found.

Group

The Group class represents the results from a single capturing group. A capturing group can capture zero, one, or more strings in a single match because of quantifiers, so Group supplies a collection of Capture objects. A Group object is immutable and has no public constructor. Instances are returned by the property Match.Group(groupnum), or Match.Group("groupname") if the (?<groupname>) grouping construct is used.

This example uses nested grouping constructs to capture groups of varying strings. To determine the number of groups returned, count the opening parenthesis, going from left to right.

   String[] results = new String[20];
   r = new Regex("(a(b))c"); // Define three groups: "abc", "ab", and "b".
   m = r.Match("abdababcabcabcabcabc";);
   for (i=0; m.Group(i).ToString() != "" ; i++) {
      results[i]=m.Group(i).ToString();
      matchposition[i] = m.Group(i).Index;
   }

This returns the following:

   results[0] = "abc"
   results[1] = "ab"
   results[2] = "b"

The next example uses named grouping constructs to capture substrings from a string containing data in a "DATANAME:VALUE": format that will be split at the colon (":").

   String inputString="Section1:119900";
   r = new Regex("^(?<name>\\w+):(?<value>\\w+)+");
   m = r.Match(inputString);

This returns the following:

   m.Group("name").ToString() = "Section1"
   m.Group("value").ToString() = "119900"

CaptureCollection

Represents a sequence of captured substrings. The object is used to return the set of captures done by a single capturing group. A static Captures property is provided as a member of the Match and Group classes and will usually be accessed through Match.Captures or Group.Captures.

Capture

The Capture class represents the results from a single subexpression capture. The object represents only one substring for each successful capture.

RegexGroupNameCollection

This collection represents the set of names appearing as capturing group names in a regular expression. The collection is read-only and has no public constructor. Instances are returned using the static GroupNames property in the Regex class. For example:

int namecount;
namecount = r.GroupNames.Count


Using Backreferences

Backreferences provide a convenient way to find repeating groups of characters. They can be thought of as shorthand for "match the same thing again".

A previous example presented the regular expression (?<char>\\w)\\k<char>, to find repeating adjacent characters such as the "ll" in the word "tall". The regular expression used the metacharacter \w to find any single "word character". The grouping construct (?<char> ) enclosed the metacharacter to force the regular expression engine to "remember" a sub-expression match (which in this case will be any single character) and save it under the name "char".

The backreference construct \k<char> caused the engine to compare the current character to the previously matched character stored under "char". The entire regular expression will successfully find a match wherever a single character is the same as the preceding character.

To find repeating whole words, the grouping sub-expression can be modified to search for any group of characters preceded by a space instead of simply searching for any single character. The sub-expression \w+ that matches any group of characters can be substituted for \w, and the metacharacter \s can be used to match a space preceding the character group. This yields the regular expression (?<char>\\s\\w+)\\k<char>. This regular expression will find any repeating words such as "the the" but it will also match similar words like "the" followed by "theory".

Adding the metacharacter \b after the repeat match will verify that the second match is on a word boundary. The resulting regular expression to find repeating whole words then becomes: (?<char>\\s\\w+)\\k<char>\\b


Regular Expression Solutions to Common Requirements

Searching for Text

[To be supplied.]

Replacing Text

[To be supplied.]

Generating Reports

[To be supplied.]


Regular Expressions Language Elements

Most of the important Regular Expression language operators are unescaped single characters. The escape character \ (a single backslash) signals the Regular Expression parser that the character following the backslash is not an operator. For example, Regular Expression treats * as a repeating quantifier and \* as the Unicode character 002A.

Regular Expression supports lazy (minimal matching) quantifiers, lookahead, and nonbacktracking subexpressions.


Character Escapes

The character escapes listed in this table are recognized both in a regular expression and in a replacement pattern.

ordinary characters | Characters other than .$^{[(|)*+?\ are matched to themselves.
\a | Matches a bell (alarm) \u0007.
\b | Matches backspace \u0008 in [] character class; otherwise see below.
\t | Matches a tab \u0009.
\r | Matches a carriage return \u000A.
\v | Matches a vertical tab \u000B.
\f | Matches a form feed \u000C.
\n | Matches a newline \u000D.
\e | Matches an escape \u001B.
\040 | Matches an ASCII character as octal (up to 3 digits); numbers with no leading 0 will be backreferences if they have only one digit or if they correspond to a capturing group number. (See Backreferences, below.)
\x20 | Matches an ASCII character using hexadecimal (exactly two digits).
\cC | Matches an ASCII control character, for example, \cC is control-C.
\u0020 | Matches a Unicode character using hexadecimal (exactly four digits).
\ | When followed by a character that is otherwise not recognized as an \ escape, matches that character. For example \* is the same as \x2A.

The escape \b is a special case. Inside a regular expression, \b means word boundary (between \w and \W characters) except in a [] character class, where \b refers to the backspace character. In a replacement pattern, \b always means backspace.


Substitutions

Substitutions are allowed only within a replacement pattern. For similar functionality within a regular expression, use a backreference (for example, \1; see the Backreference Constructs section).

Character escapes and substitutions are the only special constructs recognized in a replacement pattern. All the syntactic constructs described in the sections that follow are allowed only in regular expressions and not recognized in replacement patterns. For example, the replacement pattern a*${txt}b inserts the string a* followed by the substring matched by the "txt"capturing group, if any, followed by the string "b". The * character is not recognized as a metacharacter within a replacement pattern. Similarly, $ patterns are not recognized within a regular expression matching pattern. Within a regular expression, $ means "end of string". For example:

$123 | Substitutes the last substring matched by group number 123 (decimal).
${name} | Substitutes the last substring matched by a (?<name> ) group.


Character Classes

This table summarizes matching character syntax.

. | A dot matches any character except \n. (Or any character.- See the s option in Regular Expression Options.)
[aeiou] | Matches a single character in the specified list of characters.
[^aeiou] | Prefix of ^ matches a single character not in the specified set.
[0-9a-fA-F] | Use of – allows the specification of contiguous character ranges.
\w | Word character. Same as [a-zA-Z_0-9].
\W | Nonword character. Same as [^a-zA-Z_0-9].
\s | Whitespace character. Same as [\n\r\t\f].
\S | NonWhitespace character. Same as [^ \n\r\t\f].
\d | Decimal digit. Same as [0-9].
\D | Nondigit. Same as [^0-9].


Regular Expression Options

A regular expression pattern can be modified by any of several options when created. Options are passed as a string of letters in a second argument to the Regular Expression("pattern","options") constructor, or by the (?imnsx-imnsx: ) or (?imnsx-imnsx) constructs.

An option is disabled by including it after a "-". For example, (?ix-ms) turns on "i" and "x" and turns off "m" and "s".

Supported options are listed below. Note that the options "r" and "c" apply only to an expression as a whole, and are not allowed inline. (They can only be specified as the second argument to Regular Expression.)

i | Case-insensitive match.
m | Multiline mode. Changes the meaning of ^ and $ so that they match at the beginning and end, respectively, of any line, not just the beginning and end of the whole string.
n | Only captures explicitly named or numbered groups of the form (?<name>…). This allows naked parentheses to act as noncapturing groups without the syntactic clumsiness of the (?:…).
c | Compiles. Generates IL code for the regular expression; yields faster execution at the expense of startup time.
s | Single-line mode. Changes the meaning of dot (.) so that it matches every character (instead of every character except \n).
x | Eliminates unescaped white space from the pattern and enables "#" comments. (Note that # comments are currently experimental.)
r | Searches from right to left instead of from left to right. A regular expression compiled with this option will move to the left of the starting position instead of to the right. (Therefore, the starting position should be specified at the end of the string instead of the beginning.) This option cannot be specified in mid-stream. This prevents the possibility of crafting regular expressions with infinite loops. Nevertheless, the (?<) lookbehind constructs provide something similar that can be used as a subexpression.


Atomic Zero-Width Assertions

These metacharacters do not cause the engine to advance through the string or consume characters. They simply succeed or fail depending on the current position in the string. For instance, ^ specifies that the current position is at the beginning of a line or string. So the regular expression ^FTP will only return those occurrences of the character string "FTP" that occur at the beginning of a line.

^ | Beginning of string (or beginning of line; see the m option in Regular Expression Options).
$ | End of string, or before a \n at the end of the string (or end of line; see the m option).
\A | Beginning of string (ignores the m option).
\Z | End of string, or before the \n at the end of the string (ignores the m option).
\z | Exactly the end of the string (ignores the m option).
\G | Where the current search started (often, this is where the last search ended).
\b | On a boundary between \w (alpha-numeric) and \W (non alpha-numeric) characters. Returns true at the first and last characters in words separated by spaces.
\B | Not on a \b boundary.


Quantifiers

These regular expression modifiers add optional quantity data to a regular expression.

* | Zero or more matches; for example, \w* or (abc)*. Same as {0,}
+ | One or more matches; for example, \w+ or (abc)+. Same as {1,}
? | Zero or one matches; for example, \w? or (abc)?. Same as {0,1}
{n} | Exactly n matches; for example, (pizza){2}.
{n,} | At least n matches; for example, (abc){n,}.
{n,m} | At least n, but no more than, m matches.
*? | Lazy *. Finds the first match that consumes as few repeats as possible.
+? | Lazy +. As few repeats as possible, but at least one.
?? | Lazy ?. Zero repeats if possible, or one.
{n}? | Lazy {n}. Equivalent to {n}.
{n,}? | Lazy {n,}. As few repeats as possible, but at least n.
{n,m}? | Lazy {n,m}. As few repeats as possible, but between n and m.


Grouping Constructs

You might need to obtain more information than just if the regular expression found a match.

( ) | Captures the matched substring (or noncapturing group – see the n option). Captures using () are numbered automatically based on the order of the left parentheses, starting from 1.
(?<name> ) | Captures the matched substring into a group name or number name. The name must not contain any punctuation. Single quotes can be used as an alterative to the angle brackets; for example, (?'name' ).
(?: ) | Noncapturing group.
(?imnsx-imnsx: ) | Applies or disables the specified options within the parentheses. For example, (?i-s: ) turns on case insensitivity and disables single-line mode. See the Regular Expression Options section.
(?= ) | Zero-width positive lookahead assertion. Continues match only if the subexpression matches at this position on the right. For example, \w+(?=\d) matches a word followed by a digit, without matching the digit. This construct does not backtrack
(?! ) | Zero-width negative lookahead assertion. Continues match only if the subexpression does not match at this position on the right. For example, \b(?!un)\w+\b matches words that do not begin with "un".
(?<= ) | Zero-width positive lookbehind assertion. Continues match only if the subexpression matches at this position on the left. For example, (?<=19)99 matches instances of "99" that follow "19". This construct does not backtrack.
(?<! ) | Zero-width negative lookbehind assertion. Continues match only if the subexpression does not match at this position on the left.
(?> ) | Nonbacktracking subexpression. The subexpression is fully matched once, and then does not participate piecemeal in backtracking. (That is, the subexpression only matches strings that would be matched by the subexpression alone.)


Backreference Constructs

These optional parameters add backreference modifiers to a regular expression.

\number | Backreference. For example, (\w)\1 finds doubled word characters.
\k<name> | Named backreference. Example: (?<char>\w)\k<char> finds doubled word characters. The expression (?<43>\w)\43 does the same. Single quotes can be used as an alternative delimiter to the angles; for example, \k'char'.

Note the ambiguity between \number backreferences and octal codes. For more information see Backreferences.


Alternation Constructs

These special characters modify a regular expression to allow either/or matching.

| | Either/or, for example cat|dog|tiger. Leftmost successful match wins.
(?(expression)yes|no) | Matches the "yes" part if the expression matches at this point; otherwise, matches the "no" part. The "no" part can be omitted. The expression can be any valid subexpression, but it is turned into a zero-length assertion, so this syntax is equivalent to (?(?=expression)yes|no). Note that if the expression is the name of a named group or a capturing group number, the alternation is interpreted as a capture test (the next case). To avoid confusion in these cases, you can spell out the inside (?=expression) explicitly.
(?(name)yes|no) | Matches the "yes" part if the named capture string has a match; otherwise matches the "no" part. The "no" part can be omitted. If the given name does not correspond to the name or number of a capturing group used in this expression, the alternation is interpreted as an expression test (the previous case).


Miscellaneous Constructs

These sub-expressions modify a regular expression.

(?imnsx-imnsx) | Sets or disables options in mid-pattern. This allows options such as case insensitivity to be turned on or off in the middle of a pattern. For information on specific options, see the Regular Expression Options section. Options changes are effective until the end of the enclosing group. See also the grouping construct (?imnsx-imnsx: ), which is a cleaner form.
(?# ) | Comment. The comment terminates at the first right parentheses character.
# [to end of line] | X-mode comment. The comment begins at an unescaped # and continues to the end of the line. (Note that the "x" option must be activated for this kind of comment to be recognized. (This construct is currently experimental).


Details of Regular Expression Behavior

This sections includes the following topics:

Matching Behavior

Backreferences

Nonbacktracking Lookahead and Lookbehind

Quantifiers and Empty Matches

Next Match After an Empty Match

Compilation and Reuse

Thread Safety


Matching Behavior

Regex is a backtracking regular expression matcher that incorporates a traditional Nondeterministic Finite Automation (NFA) engine such as used by Perl, Python, Emacs, and Tcl. This distinguishes it from faster, but more limited, pure regular expression Deterministic Finite Automation (DFA) engines such as those found in awk, egrep, or lex. This also distinguishes it from standardized, but slower, POSIX NFAs.

The three types of regular expression engines

Why does Regex implement a traditional NFA matcher? Here is an overview of the three types of engines.

DFA engines run in linear time because they do not require backtracking (and thus they will never test the same character twice). They can also guarantee matching the longest possible string. However, since a DFA contains only finite state, it cannot match a pattern with backreferences, and because it does not construct an explicit expansion, it cannot capture subexpressions.

Traditional NFA engines run "greedy match" backtracking algorithms, testing all possible expansions of a regular expression in a specific order, and accepting the first match. Because a traditional NFA constructs a specific expansion of the regular expression for a successful match, it can capture subexpression matches and matching backreferences. However, because a traditional NFA backtracks, it can visit exactly the same state multiple times if the state is arrived at over different paths. As a result, it can run exponentially slowly in the worst caseBecause a traditional NFA accepts the first match it finds, it can also leave other (possibly longer) matches undiscovered.

POSIX NFA engines are like traditional NFA engines, except that they are "patient" — they continue to backtrack until they can guarantee that they have found the longest match possible. As a result, a POSIX NFA engine is slower than a traditional NFA engine, and when using a POSIX NFA it is not possible to favor a shorter match over a longer one by changing the order of the backtracking search.

Traditional NFA engines are favored by programmers because they are more expressive than either DFA or POSIX NFA expressions. Although in the general worst-case they can have exponential running time, by using patterns that reduce ambiguities and limit backtracking, they can be "steered" to find matches in linear or polynomial time.

Regex provides the best steering

Taking the benefits of a traditional NFA engines to heart, Regex includes the most complete set of constructs to allow programmers to steer the backtracking engine. These constructs can be used to find matches faster or to favor specific expansions over others

Other Regex features include:

  • Lazy quantifiers: ??, *?, +?, {n,m}?. These tell the backtracking engine to search the minimum number of repetitions first. In contrast, ordinary greedy quantifiers try to match the maximum number of repetitions first.
  • Positive lookahead. This allows the backtracking engine to return to the same spot in the text after matching a subexpression. It is useful for pruning through the search tree by verifying multiple patterns starting from the same position.
  • Negative lookahead. This adds the ability to succeed in a match only if a subexpression fails to match at a specific spot. This is particularly powerful for pruning a search, because often an expression for a case that should be eliminated is much simpler than an expression for cases that must be included. (For example, try writing an expression for words that do not begin with "non").
  • Conditional evaluation. This allows alternate patterns to be used, depending on the results of previous subexpression matches. This allows a more powerful form of backreference that permits, for example, matching a right parenthesis when a left parenthesis was previously captured in a subexpression.
  • Greedy subexpressions. This allows the backtracking engine to guarantee that a subexpression matches only the first match found for that subexpression, as if the expression were running independently of its containing expression. Without this construct, the behavior of a subexpression can change when incorporated in a larger expression due to backtracking “invasions” from the larger expression.
  • Right-to-left matching. This is useful when searching from right-to-left instead of from left-to-right, or in cases where it is more efficient to begin a match at the rightmost part of the pattern rather than at the left.
  • Positive and negative lookbehind. Similar to lookahead. The latest version of Perl supports lookbehind, but only for fixed-width strings. Because the Regular Expression engine allows complete right-to-left matching, Regular Expressions allow complete generality in their lookbehinds.


Backreferences

Parsing details. The expressions \1 through \9 always refer to backreferences, not octal codes. Multidigit expressions \11 and upward are considered backreferences if there is a backreference corresponding to that number; otherwise, they are interpreted as octal codes (unless the starting digits are 8 or 9, in which case they are treated as literal "8" and "9"). If a regular expression contains a backreference to an undefined group number, it is considered a parsing error. Users should note that if the ambiguity is a problem, the \k<n> notation is unambiguous and cannot be confused with octal character codes; similarly, hexadecimal codes \xdd are unambiguous and cannot be confused with backreferences.

Matching details. A backreference refers to the most recent definition of a group (when matching left-to-right, that is the definition most immediately to the left). Specifically, when a group makes multiple captures due to quantifiers or multiple parentheses, a backreference refers to the most recent capture. For example, (?<1>a)(?<1>\1b)* matches aababb, with capturing pattern (a)(ab)(abb). Looping quantifiers do not clear group definitions.

If a group has not captured any substring, a backreference to that group is undefined and never matches. For example, the expression \1() never matches anything, but the expression ()\1 matches the empty string.


Nonbacktracking Lookahead and Lookbehind

Matching details. Positive lookahead and lookbehind do not backtrack. That is, their contents are treated in the same way as the contents of a nonbacktracking (?> ) group.

Since lookahead and lookbehind are always zero-length, backtracking behavior is only visible when capturing groups appear within positive lookahead and lookbehind. For example, (?=(a*))\1a will never match because group 1, which is defined within the lookahead, consumes as many "a" characters as there are, then \1a requires one more. Because the lookahead expression is not backtracked, group 1 is never retried with fewer "a"s.


Quantifiers and Empty Matches

Matching details. Quantifiers *, +, {n,m} (and their lazy counterparts) never repeat after an empty match when the minimum number n has been matched. This rule prevents quantifiers from entering infinite loops on empty matches when m is infinite (although the rule applies even if m is not infinite).

For example, (a?)* matches "aaa" and captures substrings in the pattern (a)(a)(a)(). Note that there is no fifth empty capture, because the fourth empty capture causes the quantifier to stop repeating.

Similarly, (a\1|(?(1)\1)){0,2} matches the empty string rather than "a" because it never tries the expansion ()(a): the {0,2} quantifier only allows empty matches in the last iteration. In contrast, (a\1|(?(1)\1)){2} actually matches "a", because it does try ()(a) because the minimum number of iterations, 2, forces the engine to repeat after an empty match.


Next Match After an Empty Match

Matching details. When a match is repeated through a string, either by calling NextMatch or by using the collection returned by Regex.Matches, empty matches are given special treatment.

Usually, NextMatch begins the next match exactly where the last match left off. However, after an empty match, NextMatch advances by one extra character before trying the next match. This rule guarantees that progress is made through the string. (If one did not advance an extra character, the next match would start in exactly the same place as the previous match, and it would match the same empty string repeatedly.)

For example, when searching for "a*" in the string "abaabb", the following sequence of matches is returned:

"a", "", "aa", "", "", ""

Here is another view, in context:

(a)()b(aa)()b()b()

The first match gets the first a. The second match starts exactly where the first match ended, before the first b; it finds zero occurrences of a and returns the empty string.

The third match does not begin exactly where the second match ended, because the second match returned the empty string. Instead, applying the rule, it begins one character later, after the first b. The third match finds two occurrences of a and returns "aa".

The fourth match begins where the third match ended, before the second b, and finds the empty string. Then the fifth match gets begins before the last b and finds the empty string again. Similarly, the sixth match begins after the last b and finds the empty string yet again.


Compilation and Reuse

By default, Regex compiles a regular expression to a sequence of internal regular expression byte codes (these are high-level codes that are different from intermediate language (IL)). When a regular expression is executed, the byte codes are interpreted.

If a Regex object is constructed with the c option, it compiles the regular expression to explicit IL code instead of high-level Regex byte codes. This allows the NGWS runtime Just-in-Time (JIT) engine to convert the expression to native machine code for higher performance.

However, generated IL cannot be unloaded. The only way to unload code is to unload an entire application domain (that is, unload all of your application’s code.). Effectively, once a regular expression is compiled with the c option, the resources used by the compiled expression are never released, even if the Regex object itself is released and garbage-collected.

Because of this limitation, you must be careful to limit the number of different regular expressions you compile with the c option. If an application must use a large or unbounded number of regular expressions, each expression should be interpreted, not compiled. On the other hand, if a small number of regular expressions are used repeatedly, they should be compiled with c for higher performance.

Regex maintains an internal hashtable of compiled regular expressions. Once a regular expression is compiled with c, the code will be reused whenever the same regular expression is subsequently requested.


Thread Safety

The Regex class itself is thread-safe and immutable. That is, Regex objects can be created on any thread and shared between threads; matching methods can be called from any thread and never alter any global state.

However, result objects (Match and MatchCollection) returned by Regex should be used on a single thread. Although many of these objects are logically immutable, their implementations may delay computation of some results to improve performance, and as a result, callers must serialize access to them.

If there is a need to share Regex result objects on multiple threads, these objects can be converted to thread-safe instances by calling their synchronized methods. With the exception of enumerators, all classes in this specification are thread-safe or can be converted to thread-safe objects by a synchronized method.

Enumerators are the only exception. An application is responsible for serializing calls to collection enumerators itself. Here is the rule: if a collection can be enumerated on more than one thread simultaneously, use of enumerator methods should be synchronized on the root object of the collection traversed by the enumerator.


Regular Expression Examples

This section includes the following regular expression examples:

Scanning for HREFS

Changing Date Formats

Extracting URL Information


Example: Scanning for HREFS

The following example searches an input string and prints out all the href="…" values and their locations in a string. It does this by constructing a compiled Regex object, and then using a Match object to iterate over all the matches in the string.

To understand the example, it is important to understand that the metacharacter \s matches any space characterand that \S matches any non-space.

void DumpHrefs(String inputString)
{
   Regex r;
   Match m;

   r = new Regex("href\\s*=\\s*(?:\"(?<1>[^\"]*)\"|(?<1>\\S+))", "i");
   for (m = r.Match(inputString); m.Success; m = m.NextMatch()) {
      Console.Write("Found href " + m.Group(1) + " at " +.Group(1).Index);
   }
}

Compiled Pattern

In the example, a Regex object was created before beginning the loop to search the string. The Regex object is where the compiled pattern is stored. Because parsing, optimizing, and compiling a regular expression takes time, we want to do this work outside the loop so that it is only done once.

Objects of class Regex are immutable; each one corresponds to a single pattern and is stateless. This allows a single Regex instance to be shared between functions or even between different threads.

Match Result Class

After a search is done, the results are stored in the Match class. This class provides access to all the substrings extracted by the search. It also remembers the string being searched and the regular expression being used, so it can be used to do another search, starting where the last one ended.

Explicitly Named Captures

In traditional regular expressions, capturing parentheses are automatically numbered sequentially. This leads to two problems: first, if a regular expression is modified by inserting or removing a set of parentheses, all the code that refers to the captures must be rewritten to reflect the new numbering. Second, because often different sets of parentheses are used to match two alternative expressions for the same concept, the result we are seeking is sometimes in one of two result buckets, and it usually requires some trickery to determine which one.

Regex supports the syntax (?<name>…) for capturing a match into a specified slot (the slot can be named using a string or an integer; integers can be recalled faster). That way, alternative matches for the same thing can all be directed to the same place. In case of a conflict, the last match dropped into a slot wins. (Actually, there is more: in case there are multiple matches for a single slot, either due to repeated quantifiers or aliasing, a complete list of all of them is available. See the Group.Captures collection for details.)


Example: Changing Date Formats

The following example uses the Regex.Replace method to replace dates of the form mm/dd/yy with dates of the form dd-mm-yy.

String MDYToDMY(String input) {
   return Regex.Replace(input, "\b(?<month>\d{1,2})/(?<day>\d{1,2})
      /(?<year>\d{2,4})\b",null, "${day}-${month}-${year}");
}

Regex Replacement Pattern

This example shows the use of named backreferences within the replacement pattern for Regex.Replace. For example, the replacement expression ${day} inserts the substring captured by the group (?<day>…).

The Regex.Replace function is one of several static functions provided so that regular expression operations can be done without creating an explicit regular expression object. This is convenient when a compiled regular expression is not going to be kept.


Example: Extracting URL Information

The following example uses Match.Result to extract a protocol and port number from a URL. For example, "http://www.contoso.com:8080/letters/readme.html" would return “http:8080”.

String Extension(String url) {
   static Regex r = new Regex("^(?<proto>\\w+)://[^/]+(?<port>:
      \\d+)?/","c" ,"${proto}${port}");
   return r.Match(url).Result;
}


Discovering Type Information at Runtime

The Reflection API provides access to type information at run time (see Type). The primary users of the Reflection services are script engines, type viewers, compilers, and serializers.

The term reflection refers to the ability to reflect (as a mirror would) the type information of an object. This includes details about the assembly in which the type is defined as well as the members of the type. The following are examples of details obtainable through reflection: the signature of a method, information about a property, information about a field, a list of the methods defined within a class, and a list of classes contained within a module. Using this information you can, for example, create an instance of a class, or bind to an existing object and invoke its methods or access its fields.

The primary means of accessing type information is the Type class. The runtime creates a Type when it loads a class. The runtime creates only one Type per type. Type objects are accessible through the various methods provided by Type and Assembly, as well as through the Object.GetType method. Once a reference to a Type object is obtained, you can access information about the members of a type and invoke those members through instances of the MemberInfo class and its subclasses.

In addition to the ability to reflect type information at run time, there are classes that enable types to be built at run time. These classes belong to System.Reflection.Emit namespace (see the System.Reflection.Emit overview). Intermediate language (IL) is actually written at run time and can be persisted in the NGWS runtime file format. This feature is used by scripting engines, and results in a performance gain because scripts are compiled once and executed many times.


Design

Reflection in the runtime provides read-only access to the metadata associated with runtime objects. Reflection is a low-level concept that returns the metadata-defined objects.

Reflection allows programmatic discovery of the metadata. Metadata describes information about the methods, fields, properties, and events associated with a class. Additional information is provided about interfaces supported by an object, what an object's super class is, and information about the namespaces. Reflection also provides late binding services. These include the ability to create call methods and access fields and properties.

The general design of the runtime reflection classes is to model various concepts as objects. For example, information about a class is found in the Type object, information about a method of a class is found in the MethodInfo object, and so on. Reflection provides a rich set of objects.


Navigational Diagram

The Reflection API uses a design pattern for retrieval and filtering. The following table shows when one class is a container for a number of other classes.

Member | Description
Xxx[] FindXxx(XxxFilter filter, Object filterCriteria) | Finds and returns the Xxx instances that match the criteria specified by filter. That is, if filter.invoke(x) returns true, then include x in the resultant array. Otherwise, omit it.
Xxx GetXxx(<parameters>) | Return the Xxx instance that is uniquely specified by <parameters>. If no such instance exists, return null. Note that <parameters> uniquely specifies an Xxx instance.
Xxx[] GetXxxs() | Return all of the Xxx instances. If no Xxx instances exist, return an array containing zero elements.
Xxx[] GetXxxs(<parameters>) | Return all of the Xxx instances. If no Xxx instances exist, return an array containing zero elements. Note that <parameters> does not necessarily specify a unique Xxx instance.

The diagram below shows this design pattern in action for Module (which may contain zero or more classes), Type (which may contain zero or more methods) and MethodInfo.


Type

Type is the fundamental root of the reflection information for an object. Object is the root class for all runtime classes and supports direct access to the Type objects through the GetType method. From Type, all the metadata for an object can be discovered.


Namespace and Assembly

At a higher level, runtime reflection supports retrieving a string that identifies the namespace through the Type.Namespace property. Assembly level information is provided in the Assembly class. A namespace can span several modules and an assembly can be part of one or more namespaces.


Dynamic Invocation

Runtime reflection provides the ability to dynamically invoke methods. The nonstatic methods require a parameter indicating the instance to invoke the method on. The following C# code fragment demonstrates this method.

using System;
using System.Reflection;

public class MyRandom
{
  public static void Main(string[] args)
  {
    Type type = Type.GetType("System.Random");
    // Create an instance of the Random class using the default
    // constructor.
    Object obj = Activator.CreateInstance(type);
    try {
      // Create an array of Type objects that represent
      // the parameter list for list for the Random.Next method
      // that takes an int as a parameter.
      Type[] paramTypes = new Type[1];
      paramTypes[0] = Type.GetType("System.Int32");
      // Get the Random::Next method that takes an int as a
      // parameter.
      // This method generates a random number between 0 and the
      // value of the argument passed in.
      MethodInfo method = type.GetMethod("Next", paramTypes);
      // Call the Random::Next method with an initial value of 100,
      // and then repeatedly call the method with the result of the
      // previous invocation.  This will result in a decreasing
      // sequence of numbers, which will be written to the console.
      int i = 0;
      Object randomNumber;
      Object[] randomArgs = new Object[1]; // argument list
      randomArgs[0] = 100;
      do {
   // Invoke this method with the given values
   randomNumber = method.Invoke(obj, randomArgs);
   // Print the return value
   Console.Write("Random Number (");
   Console.Write(i++);
   Console.Write("): ");
   Console.WriteLine(randomNumber.ToString());
   randomArgs[0] = randomNumber;
      }
      while (Convert.ToInt32(randomNumber) > 0);
    }
    catch (TargetInvocationException e) {
      Console.Write("Target throw this exception: ");
      Console.WriteLine(e.InnerException);
    }
  } // end main
}

Of course, you can invoke methods that take arguments as well. However, the arguments supplied must match exactly the arguments to the method being called. No automatic narrowing conversions are performed. Only widening conversions are done automatically. These include class to base class conversions and standard widening conversions of intrinsic types that are detailed below:

Please see the Base Data Type Specification for a list of the standard conversions we do in reflection.


Delegates and Reflection

Delegates are object-oriented versions of function pointers. A delegate contains two references: one to an object instance and another to the method on that object. Delegates do not understand the type of the class they point to; they simply care about the signature of the method they are defined for.

Delegates are used in the Reflection classes as callback methods. This allows user control of some of the information provided by some methods. Typically, delegates are used in methods that return arrays of objects and allow for filtering of the results set. The NGWS Frameworks provides a set of standard delegates that provide common filtering.

For more information, see Delegates.


DBNull, Missing and Empty

System.DBNull, System.Missing and System.Empty are special singleton classes; that is, there is only one instance of each of these types in the system at any given time. That instance is stored in the static member named “Value” on each of these types. These singleton classes have special semantics when used in Reflection:

DBNull

A DBNull is never equal to anything (including another DBNull). DBNull is similar to database null. It is used in database applications to indicate the absence of a known value. The System.Convert.ToXXX methods throw an InvalidCastException when called with a DBNull argument. Convert.IsDBNull(Object obj) returns true if the argument is a DBNull. DBNull is used internally to indicate that a parameter does not have a default value. This is necessary because a parameter can have a default value that is null.

Missing

Error. Used in reflection for default arguments. All of the System.Convert.ToXXX methods throw an InvalidCastException when called with a Missing argument. The method argument Missing indicates that the default value for this argument should be used.

Empty

The universal zero, which represents values such as 0, 0.0, "", and so on. For the integral types, its value is 0; for characters, it is ‘\000’; for floating and fixed-point types, it is 0.0, and for object references, it is null. The method argument Empty is automatically coerced to any type as a “zero” value as previously defined.

When a managed object is passed to COM, the runtime’s marshaling service automatically converts the object to a COM Variant with the proper variant type. Marshaling is also done when COM variants are passed into managed code. In that case, the COM variant is converted to the proper managed object. The following table shows Variant.DBNull, Variant.Empty, and Variant.Missing are marshaled by the runtime's marshaling service.

NGWS Type | COM variant type
System.DBNull | VT_NULL
System.Empty | VT_EMPTY
System.Missing | VT_ERROR with scode = DISP_E_PARAMNOTFOUND


Security

Reflection provides the means to discover, bind to, and invoke members at run time. The different levels of trust control which members code is allowed to discover, bind to, and invoke. Allowing access to private information on a type potentially exposes information and access to code and data to untrusted users. This document describes the security that is built into the Reflection services and how it is used. Access to information through reflection is controlled at three levels:

  • Untrusted code can inspect the metadata for public types, invoke public members, invoke family access members of the calling code’s base classes, invoke assembly access members of the calling code’s assembly, and the logical combination of these in FamilyAndAssembly and FamilyOrAssembly accessibility.
  • In addition to the functionality described above, code with ReflectionPermission can view the metadata for a nonpublic types, inspect the metadata for nonpublic types, and walk up and back down the containment chain to access other types in the calling code’s module or its assembly.
  • The SerializationPermissions offers the ability to get and set any nontransient data members of serializable types. This permission allows code to discover and alter the private state of an instance.

The following topics describe each of the above in greater detail:

  • Untrusted Code
  • ReflectionPermission
  • SerializationPermission


Untrusted Code

Code with no special level of trust (perhaps code downloaded from the Internet) is allowed to invoke anything it would have been able to invoke in an early bound way.

In short, this means:

  • Inspect the metadata for public types and members.
  • Invoke public members.
  • Invoke family access members of the calling code’s base classes.
  • Invoke assembly access members of the calling code’s assembly.
  • The logical combination of these in FamilyAndAssembly and FamilyOrAssembly accessibility.

Notice that untrusted code cannot access nonpublic members (even of its own class) through the Get methods on type (GetMembers, GetMethods, and so on) or the Get methods on Assembly and Module. However, untrusted code can access nonpublic types and members using:

  • The ldtoken IL instruction.
  • The Type.GetType (String name) or Type.GetType (RuntimeTypeHandle).
  • MethodBase.GetCurrentMethod.
  • Environment.StackTrace.

Even though private members of a class may be exposed, they cannot be executed unless the access rules allow it.

These rules apply to:

  • Inspect: Type.GetMembers, Type.GetMethod, Type.GetField, Type.GetEvent, Type.GetConstructor, and so on.
  • Invoke: Type.InvokeMember, Activator.CreateInstance, MethodInfo.Invoke, FieldInfo.SetValue, FieldInfo.GetValue, PropertyInfo.SetValue, PropertyInfo.GetValue, ConstructorInfo.Invoke, and son on.

Untrusted code can discover which module and assembly the code is in (as that is part of the code’s identity) but the code cannot discover which other types belong to the code’s module, or which other modules belong to the code’s assembly. This rule applies to: Assembly.Module, Assembly.GetType, Module.GetTypes, Module.FindTypes, Module.GetMethods, Module.GetType.


ReflectionPermission

ReflectionPermission controls access to metadata through the Reflection APIs. With ReflectionPermission, code can access all the types in its application domain, assembly, and module. It can find out all kinds of information about the public, family, and even private members of any type it can get to. At best, this access reveals implementation details; at worst, it exposes intellectual property. Even though private members of a class may be exposed, they cannot be executed from semi-trusted code unless the access rules allow it — even if that code has ReflectionPermission. It would be prudent not to grant ReflectionPermission to Internet code.

ReflectionPermission grants two primary capabilities:

  • The ability to read the metadata for nonpublic members of any type. (Type.GetMethods, and so on, return information about nonpublic members).
  • The ability to find peer classes in the module and peer modules assembly.

The specific ReflectionPermission flags are as follows:

TypeInformation. This flag is required to reflect on nonpublic members of a type. Note that by default, Reflection will not allow you to access Family members of a type.

MemberAccess. This flag controls invocation of operations on type members. A caller needs this permission to invoke operations on members that are inaccessible according to the Virtual Object System (VOS) rules. For example, the caller needs this permission to call Type.InvokeMember on a private method of a type.

ReflectionEmit. The permission is required for Reflection Emit operations.


SerializationPermission

SerializationPermission offers the ability to get and set any nontransient data members of serializable types. This permission allows code to discover and alter the private state of an instance.

If code has the SerializationPermission, it can access all the nontransient fields of any instance of a serializable type it gets a hold of regardless of its accessiblity. For example, to get the private instance field f of a type t all the following must be true:

  • The caller must have SerializationPermission.
  • The type t must have the tdSerializable bit set to true.
  • The field f must have the fdNonSerializable bit set to false.


Inheritance in Reflection

The following table shows which members of a base class are returned by the GetXXX methods when reflecting on a type:

Member Type | Static | Non Static
Constructor | No | No
Field | No | Yes. A field is always hide-by-name-and-signature.
Event | Not applicable | Same as property.
Method | No | Yes. A method (both virtual and non-virtual) can be hide-by-name or hide-by-name-and-signature.
Nested Type | No | No
Property | Not applicable | VOS states the inheritance is the same as that of the methods that implement the property. Reflection treats properties as hide-by-name-and-signature. See Note 2.

Notes

  1. Hide-by-name-and-signature considers all of the parts of the signature: custom modifiers, return type, parameter types, sentinel, unmanaged calling convention. It is a binary comparison.
  2. For Reflection, properties and events are hide-by-name-and-signature. So, if you have a property Foo with a Getter and a Setter in the base class but the subclass has only a Getter method, the subclass property hides the base class property; that is, you will not be able to access the Setter on the base class.
  3. Custom attributes are not part of VOS. See the CustomAttributes specification for a description of the inheritance rules for custom attributes.


Default Member Support in Reflection

A type member may be marked as a default member. There can be only one default member per class. This member is executed when no name is given. For example:

Dim i As Integer
Dim c As MyClass
i = MyClass

The default member of MyClass is executed.


Marking Default Members

Default members are marked with the System.Reflection.DefaultMemberAttribute defined below:

[Attribute (AttributeTargets.Classes)]
public class DefaultMemberAttribute
{
  public DefaultMemberAttribute (String memberName);
  public MemberName {get;}
}

Only one DefaultMemberAttribute can be attached per class. The inheritance of the DefaultMemberAttribute is up to the compilers


Getting the Default Members

There are two ways to get the default members of a class: through custom attributes and through a helper method on type.

As noted above, the default member data is stored in a custom attribute. This custom attribute can be found on a type using the normal custom attribute mechanism.

   DefaultMemberAttribute defMem = (DefaultMemberAttribute) t.GetCustomAttribute (typeof(DefaultMemberAttribute))[0];
   MemberInfo[] = t.GetMember (defMem.MemberName);

There is also a helper method on Type that should yield exactly the same result.

   MemberInfo[] = t.GetDefualtMembers ();

This method will throw an InvalidOperationException if there is more than one default member defined on this type.


Invoking the Default Member

Type.InvokeMember() with String.Empty ("") as the member name will invoke the Default member. InvokeMember will retrieve the DefaultMemberAttribute from the type (not its base type or an interface type). The name of the retrieved member will be used as the name to pass to InvokeMember.


Default Arguments Support in Reflection

Many languages (such as managed extensions to C++, Microsoft Visual Basic) support optional arguments through assigning default values to some arguments. For example:

void Foo (int a, double b = 1.2, int c = 1)
{
   if ( a == c) //…
}
The caller can optionally provide values for b and c.  All of the following are valid calls:
Foo (10, 55.3, 12);
Foo (10, 1.3); // c == 1
Foo (11) // b = 1.2, c == 1


Marking Default Values in the Metadata

For details on the metadata APIs for specifying default values, see the Metadata API Specification. For details on the Reflection Emit APIs for specifying default values, see the System.Reflection.Emit namespace overview.


Getting the Default Value of a Parameter

The default member can be found by getting the ParameterInfo for the parameter. This property will return the default value and type:

public Object DefaultValue {get;}

If there is no default value, this property will return Value.DBNull. For example,

MethodInfo m = t.GetMethod (“foo”);
ParameterInfo [] ps = m.GetParameters();
for (int i = 0; i < ps; I++)
{
   if (ps[i].DefaultValue != Value.DBNull)
   {
      Console.WriteLine (“Default Value == {0}[{1}]”, ps[i].DefualtValue, ps[i].DefualtValue.VariantType);
   }
}


Invoking a Method with Default Arguments

Methods with default values can be invoked either by specifying exactly which arguments are defaults or by leaving off trailing default arguments.


Explicitly

Through the use of a parameter attribute, a parameter value can explicitly be requested to be the default value. Passing Type.Missing as a value to Invoke tells the late binding service to use the default value persisted in the metadata for this value.

If Type.Missing is passed in for a parameter value and there is no default value for that parameter, an ArgumentException is thrown.

Notice that with this mechanism the default values do not have to be trailing.

It is completely up to the binder to respect these rules for Type.Missing. Some binders may choose not to support this functionality or to treat Type.Missing in some other special way.

The following code fragment is from the example in the overview:

void Foo (int a, double b = 1.2, int c = 1)
This method can be called via Type.InvokeMember as:
t.InvokeMember (“foo”, new Object[] {10, 55.3, 12});
t.InvokeMember (“foo”, new Object[] {10, 1.3, Type.Missing});
t.InvokeMember (“foo”, new Object[] {10, Type.Missing, Type.Missing });


Implicitly

Trailing default arguments are considered even when no value is specified at the call site. This is the common case usage of default arguments.

The following code fragment is from the example in the overview:

void Foo (int a, double b = 1.2, int c = 1)

This method can be called using Type.InvokeMember as follows:

t.InvokeMember (“foo”, new Object[] {10, 55.3, 12});
t.InvokeMember (“foo”, new Object[] {10, 1.3});
t.InvokeMember (“foo”, new Object[] {10});


Through MethodInfo.Invoke

Only the explicit method described previously is supported for MethodInfo.Invoke. You know what method you are invoking when you call MethodInfo.Invoke. You do not know when you are calling InvokeMember. Therefore, in the MethodInfo.Invoke case, you can create an object array that contains Type.Missing for all the parameters for which you do not have values.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Redefining Classes at Runtime

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Interoperating with Unmanaged Code

To be supplied.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Calling into the Win32 API and Other Unmanaged Code

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Platform Invocation Services

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Accessing Win32 Resource Data

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Performance Considerations

[To be supplied.]


COM Interop

The NGWS frameworks and runtime is in the logical line of progression from COM. COM Interop enables legacy COM components to interoperate with NGWS components.

The Microsoft Component Object Model (COM) differs significantly from the NGWS object model. For example, clients of COM objects must manage the lifetime of those objects; the NGWS runtime manages the lifetime of objects in its environment. Clients of COM objects discover whether a service is available by requesting an interface that provides that service and getting back an interface pointer or not. Clients of NGWS objects can obtain a description of an object’s functionality using Reflection.

Further, NGWS objects reside in memory that is managed by the NGWS execution environment. The NGWS execution environment can move objects around in memory for performance reasons and update all references to the objects it moves. Unmanaged clients, having obtained a pointer to an object, rely on the object’s remaining at the same location. These clients have no mechanism for dealing with an object whose location is not fixed.

To simplify interoperation between the NGWS runtime and COM and to ease the migration path, the NGWS runtime conceals from both clients and servers the differences in these object models. If you have a code base that uses COM, your code can interact with NGWS objects as if they were COM objects. Similarly, NGWS objects can interact with COM objects as if they were NGWS objects.

For example, COM objects pass HRESULTs, while managed objects throw exceptions. When a NGWS client calls a method on a COM object, the NGWS runtime converts any failure HRESULT it obtains into an exception. It then throws this exception to the managed client, and the client catches the exception just as if it were thrown by a managed server object. The managed client code needs no awareness of HRESULTs, nor does the unmanaged COM object code need any awareness of exceptions.

This guide to interoperation explains the following:

  • Registering a NGWS Class with COM
  • Providing Metadata for a COM Class to the NGWS runtime
  • Namespaces and COM Objects
  • Calling a NGWS Object from a COM Client
  • Calling a COM Object from a NGWS Client


Registering a NGWS Class with COM

Any instance of a public class that can be created and consumed within the NGWS execution environment can also be created and consumed by unmanaged COM clients, provided that you register them. A NGWS class that does not have a public constructor cannot be instantiated by managed clients, so it cannot be instantiated by unmanaged COM clients, either.

To register or unregister a NGWS class with COM, you must run a command-line utility called RegAsm. The RegAsm utility adds information about the class to the system registry so COM clients can use the NGWS class transparently.

Under the HKCR/CLSID registry key, RegAsm creates a new entry for the CLSID of the NGWS class. If you run RegAsm on the same class more than once, the class will always have the same CLSID in COM.

Under the HKCR\CLSID\{…} key, the default value is set to the ProgID of the class, and two new named values are added. The class value is a string that contains the fully qualified name of the NGWS class that provides the implementation. If a class with the specified name is not already loaded, the runtime reads the assembly value from the registry and passes it on to the runtime's assembly resolver. The assembly resolver attempts to locate the assembly based on the assembly name. Before the assembly resolver can locate the assembly, the assembly must be signed and installed in the runtime's assembly cache or it must be found along the application path. Assemblies loaded from the application path are only accessible from that application. Shared assemblies must be signed and installed in the assembly cache.

The RegAsm utility also creates the InProcServer32 key under the HKCR\CLSID\{0000…0000} key. The default value for the key is set to the name of the DLL that contains the NGWS runtime. Clients can call DllGetClassObject on the runtime DLL to get a class factory and create objects, just as they would with any other COM component.

See Also

COM Registration Utility (RegAsm.exe)


Providing Metadata for a COM Class to the NGWS runtime

Any class that can be instantiated and used by a COM client can be created and used by a NGWS client. Just as NGWS classes must be registered before COM clients can instantiate them, so COM classes must provide metadata before NGWS clients can instantiate them.

The TlbImp command-line utility converts the classes and interfaces contained in a COM type library to NGWS metadata. Once the class' metadata is available, NGWS clients can create an instance of the COM class and call the instance’s methods, just as if it were a NGWS object. TlbImp converts an entire type library at one time. You cannot use TlbImp to generate type information for a subset of the types defined in a type library.

For reference information on command-line switches for TlbImp, see Type Library Importer (TlbImp.exe).

A COM class' metadata is available through a NGWS namespace, just like any other NGWS class. This section explains Namespaces and COM Objects.

COM classes without a type library are also accessible to NGWS clients through a special class called __COMObject. __ComObject, an internal class in the System namespace, is derived from MarshalByRefObject. __ComObject is the root class for all COM wrappers, which wrap COM objects accessed from the NWGS runtime. This class defines only the basics, and exists solely to make COM classes with no type library available to NGWS clients. For more details, see Accessing a COM Object that has no Type Library.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Importing Metadata from a Type Library

[To be supplied.]


Namespaces and COM Objects

In this release of the NGWS runtime, a COM class' metadata is placed in a namespace with the same name as that of the type library the metadata was created from. For example, if you convert a type library named Server1 to a metadata file named MyServer.dll and that type library contains a class named MyCOMObj, the namespace created would be named Server1, and the class' fully resolved name would be Server1.MyCOMObj. You can change the name of the namespace by using the /out option on TlbImp.

For example, if Corpath=C:\bin, creating a C:\bin\classic subfolder and placing the DLL containing the Server1 type library in it changes the fully resolved name of the MyCOMObj class to Classic.Server1.MyCOMObj.

Renaming the subfolder C:\bin\classic to c:\bin\modern changes the namespace name to Modern.Server1. Therefore, the MyCOMObj class' fully resolved name changes to Modern.Server1.MyCOMObj.


Calling a NGWS Object from a COM Client

Once a NGWS class is registered with COM, COM clients can create instances of that class and request services from the resulting objects just as if they were any other COM object. Further, the NGWS objects receive requests from COM clients just as if they were requests from any other NGWS client.

From the point of view of the client and server code, then, the differences between COM and the NGWS runtime are largely invisible. Microsoft Visual Basic clients can view NGWS objects in the object browser, which exposes the object's methods and their syntax, its properties, and its fields exactly as if they were any other COM object.

For C++ clients, there are some important differences. For example, COM classes are defined using the Microsoft Interface Definition Language (MIDL). The MIDL compiler generates C++ headers, which declares an object's interfaces and methods and makes them available to client programs. The NGWS runtime does not use headers to define types, nor does the RegAsm utility generate them. In order to get header information for a NGWS class, use TblExp.exe on the managed assembly and then import the resulting type library into your C++ program using the C++ #import preprocessor directive. Microsoft Visual C++ automatically generates a .tlh file from the type library.

This section explains the following:

  • HRESULTs and Exceptions
  • Object Lifetimes and NGWS
  • COM Interfaces Exposed by NGWS Classes


HRESULTs and Exceptions

COM methods report errors by returning HRESULTs, while NGWS methods report them by throwing exceptions. The NGWS runtime handles the transition between the two. Each exception class in NGWS maps to an HRESULT. User-defined exception classes can specify whatever HRESULT is appropriate, and they can dynamically change the HRESULT to be returned when the exception is thrown by setting the ErrorCode property on the exception object. Additional information about the exception is provided to the client through the IErrorInfo interface, which is implemented on the NGWS object in the unmanaged process space.


Object Lifetimes and NGWS

The COM client is responsible for managing the reference count on each COM interface it obtains on a NGWS object, just as it is on any other COM object. On the unmanaged side, the NGWS object is unavailable as soon as its reference count goes to zero, just like any other COM object. As far as the COM client is concerned, the object has been destroyed as it should be.

The only perceptible difference is that if the NGWS object is an in-process server, the memory allocated for that object may not be freed immediately. Within the managed execution environment, the object will remain in existence as long as any managed objects hold a reference to it. If the COM client holds the only reference, the object will still remain in memory until the garbage collector frees it.


COM Interfaces Exposed by NGWS Classes

In addition to the interfaces that are explicitly implemented by a NGWS class in the managed environment, the NGWS runtime supplies implementations of the following interfaces:

  • IUnknown
  • IDispatch
  • IDispatchEx
  • ITypeInfo
  • IProvideClassInfo
  • ISupportErrorInfo
  • IErrorInfo
  • IConnectionPoint
  • The class (_classname) interface

The class interface, which is generated by the runtime and not explicitly defined, is an interface that exposes all public interfaces, methods, properties, and fields that are explicitly exposed on the NGWS object. This interface is always a dual interface. The class interface receives the name of the NGWS class itself, preceded by an underscore. For example, for class MyClass, the class interface is named _MyClass.

For subclasses, the class interface also exposes all public interfaces, methods, properties, and fields on the base class. Additionally, the subclass exposes a class interface for each inherited class. For example, if class MyClass extends class MySuperclass, which itself extends Object, the NGWS object will expose to COM clients three class interfaces named _MyClass, _MySuperclass, and _Object.

For any interface that is explicitly implemented on the NGWS class itself, the COM client can obtain a pointer to a vtable. Other interfaces can obtain a pointer, too, depending on the interface type

Some NGWS classes also provide implementation of the following interfaces, as described in the following table:

Interface ID | Interface Description
IEnumVariant | For collection type classes, an interface for enumerating the objects in the collection if the class implements IEnumerable.
IDispatchEx | If the class implements IExpando and performs late binding to objects, an extension of the IDispatch interface that, unlike IDispatch, allows for enumeration, addition, deletion, and case-sensitive calling of members.
IConnectionPoint | For objects that support delegate-based events (an interface for registering event subscribers) and ICPC, but only if the class sources its events.

For the most part, the COM client will get exactly the expected behavior from these COM interfaces. There are some special considerations to be aware of in using the class interface and overloaded methods on the NGWS object. The following topics discuss these considerations in detail:

  • The Class Interface
  • COM Clients and Overloaded Methods


The Class Interface

All functionality and state on COM objects are encapsulated in interfaces, which expose the object's functionality and state through methods. COM objects in Visual Basic can expose properties that appear like any other Visual Basic property, but to C++ objects (and also in the Visual Basic runtime), these properties, too, are exposed through methods. For objects in the NGWS runtime, interfaces are optional: they can have public fields, properties, and methods that are exposed either directly on the object or through interfaces implemented on the object.

To COM clients, the NGWS runtime implements a dual interface that exposes all the public methods and properties of the NGWS class and its base classes, if any. This interface is commonly referred to as the class interface. The class interface has the same name as the class, prefixed with an underscore. For example, a NGWS class MyClass would implement a class interface named _MyClass.

Fields on a NGWS class are not exposed to COM clients.

For example, consider the following NGWS class:

public class Mammal // implicitly extends System.Object
{
   void  Eat();
   void  Breathe():
   void  Sleep();
}

The COM client can obtain a pointer to a class interface named _Mammal, which is described in the type library that the TlbExp utility generates for Mammal as follows:

[uuid(0000…0000), version(1.0)]
library AnimalLib
{
   [odl, uuid(0000…0000), hidden, dual, nonextensible, oleautomation]
   interface _Mammal : IDispatch
   {
      [id(0x00000000), propget] HRESULT ToString([out, retval] BSTR* pRetVal);
      [id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval] VARIANT_BOOL* pRetVal);
      [id(0x60020002)] HRESULT GetHashCode([out, retval] long* pRetVal);
      [id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
      [id(0x6002000d)] HRESULT Eat();
      [id(0x6002000e)] HRESULT Breathe([out, retval] long* p1):
      [id(0x6002000f)] HRESULT Sleep([out, retval] double* p1);
   }
   [uuid(0000…0000)]
   coclass Mammal
   {
      [default] interface _Mammal;
   }
}

If the Mammal class did happen to implement one or more interfaces, the methods of the interfaces would also be available through the class interface, as well as through the interfaces themselves.

Because the class interface inherits IDispatch, late-bound clients can call the methods of the class interface through IDispatch::Invoke. They can also obtain complete type information about the NGWS class through IDispatch::GetTypeInfo.

The NGWS runtime will generate unique DISPIDs on demand for the methods and properties of the class. The DISPIDs are guaranteed to always be the same for any instance of the same version of the same class. The DISPIDs are used by IDispatch::Invoke and are supplied by IDispatch::GetIdsOfNames and by the type information obtained through IDispatch::GetTypeInfo.


COM Clients and Overloaded Methods

Because the NGWS runtime supports overloaded methods and the IDispatch interface relies solely on method names, rather than complete method signatures, for binding, IDispatch cannot support overloaded methods. In order to provide access to overloaded methods of a class, the ITypeInfo implementation provided by the runtime decorates the names of overloaded methods so that each method name is unique. For example, the System.StringBuilder class has several overloads of the Append method. The COM signature for the methods (as exported by RegAsm) appears as a single Append method followed by a series of decorated Append_x methods, where x starts from 1 and is incremented for each additional overloaded form of the method, as shown in the following table:

NGWS signature | COM signature
Append(Object value) | Append(Object value)
Append(byte value) | Append_1(byte value)
Append(short value) | Append_2(short value)
… | …

Object browsers display all the decorated forms of the overloaded method, so developers can choose whatever form of the method they require.


Calling a COM Object from a NGWS Client

When a COM class has a type library that has been converted to metadata by the Type Library Importer utility (TlbImp), it appears to the NGWS client exactly like any other NGWS class. The client creates a new instance in the usual way, and it obtains class information through metadata as it would for any other NGWS class. Method syntax can be exposed through an object viewer or obtained using reflection, just as it can with any other NGWS class. When the COM object returns a failure HRESULT, the NGWS client catches a corresponding exception. Obtaining and releasing a reference to a running COM object is just like obtaining and releasing a reference to any other running NGWS object. When NGWS clients obtain and release references to a COM object, the NGWS runtime maintains the reference count on the COM object just as any other COM client would, and NGWS clients can behave as if the object were subject to garbage collection, just as they would for any other NGWS server object.

For COM objects with no type library, you must instantiate a special class called __COMObject. __ComObject, an internal class in the System namespace, is derived from MarshalByRefObject. __ComObject is the root class for all COM wrappers, which wrap COM objects accessed from the NGWS runtime. This class defines only the basics, and exists solely to make COM classes with no type library available to NGWS clients.

This section explains the following topics:

  • Accessing a COM Object that has no Type Library
  • Inheritance, Aggregation, and Containment

See Also

Type Library Importer (TlbImp.exe)


Accessing a COM Object that has no Type Library

For COM classes that have no type library, the NGWS client can create an instance of System.Type and obtain an instance of the COM object by calling the Type.GetTypeFromProgID method, followed by a call to Type.CreateInstance. These calls will load the appropriate class factory and create an instance of the __COMObject class.

__ComObject, an internal class in the System namespace, is derived from MarshalByRefObject. __ComObject is the root class for all COM wrappers, which wrap COM objects accessed from the NGWS runtime. This class defines only the basics, and exists solely to make COM classes with no type library available to NGWS clients. In this case, the client calls methods on the COM server through the Type object’s InvokeMember method, which takes as arguments a method name, object name, and argument string.


Inheritance, Aggregation, and Containment

COM reusability is accomplished through containment and aggregation. Managed objects can participate in both models as the inner object in the relationship. Inheritance is used in special cases. Use the inheritance, aggregation, or containment models under the following circumstances:

Model | Use For
Inheritance | Exposing the managed object as the outer object.
Aggregation | Enabling the outer object to expose another object's implementation of an interface without modification.
Containment | Enabling the outer object to modify the behavior of the inner object.

Inheritance

Inheritance is typically used when you want the managed object to be the outer object. When managed interfaces are exposed to COM, they always extend IUnknown or IDispatch even when the interface is inherited from another interface on the managed side. The same applies for the class interface that is generated for managed classes. For example:

In Managed C++:

Interface IBase {
    void m1();
}

Interface IDerived : implements IBase {
    void m2();{
}

class CDerivedImpl : implements IDerived {
    void m1();
    void m2();
}

In an Exported Type Library:

Interface IBase : public IDispatch {
    void m1();
}

Interface IDerived : public IDispatch {
    void m2();
}

Interface _CDerivedImpl : public IDispatch {
    Boolean Equals();
    int GetHashCode()
    // … plus all other methods of object

    void m1();
    void m2();
}

coclass CDerivedImpl {
    interface IBase;
    interface IDerived;
    interface _CDerivedImpl;
    interface _Object;
}

Aggregation

To expose the interfaces of one COM class as though they were implemented on a second COM class, the second class aggregates the first. A COM object can aggregate a NGWS object, in which case all of the object's interfaces, including its class interface, are available through the outer object. The inner NGWS object delegates calls to its IUnknown methods to the controlling IUnknown.

The analogous behavior in the NGWS runtime is for the first class to inherit the second. When a COM class is imported into the NGWS runtime, it is marked as a static final class. This means that a COM class cannot be a base class for a NGWS class.

Aggregation is slightly more complex than containment. It is typically used to enable the outer object to expose another object's implementation of an interface without modification. All managed objects automatically support COM-style aggregation with the managed object being used as the inner object. In order to aggregate a managed object, the unmanaged outer object creates the managed inner object by calling CoCreateInstance, passing the outer object's IUnknown as an OuterUnknown parameter. When an outer IUnknown is passed to a managed object during construction, the managed object caches the interface and uses it as follows:

  • The outer IUnknown is not reference-counted, since the inner object's life is scoped within the lifetime of the outer object.
  • If the inner object is queried for an interface that it does not support, the inner object delegates the call to the outer object's IUnknown interface.
  • All calls to the inner object's AddRef and Release methods are delegated to the outer object's IUnknown.

These three behaviors make it possible to aggregate any managed object. With this type of aggregation relationship, it is possible to have a single COM object that is partly implemented in managed code (the inner portion) and partly in unmanaged code (the outer portion).

Containment

A NGWS object can contain a COM object by importing its metadata into a NGWS component file, then declaring a data member of that type within another class. As with normal COM containment, you can call the COM object's interfaces in your own interface implementations, but the contained object is not exposed outside the class. Containment is simpler than aggregation. Containment is typically used when the outer object needs to modify the behavior of the inner object. To do so, the outer object simply creates an instance of the inner object during constructor and delegate calls to the inner object as necessary. The outer object can choose which calls to delegate and which calls to handle directly. There are no special requirements of the runtime in order for objects to support containment.

A COM object can also contain a NGWS object. Behavior with respect to clients of the COM object will be exactly the same as if the contained object was any other COM object.


COM+ 1.0 Interop

COM+ 1.0 run-time services make it easier for you to create distributed applications that are timely and cost-effective. These services work on your application's behalf to manage transaction processing, object pooling, threading, security, and each object's lifecycle.

A NGWS class can request COM+ 1.0 services, providing that you have prepared the class to do so. The table below identifies the sequence of actions required to mark a managed type so that it can access services.

Type of Action | Description
Decision | Identify the services you intend to include in your application. See COM+ 1.0 Services for a summary of services and links to additional information.
Programming task | Add the COMEmulate custom attribute to your class. Classes must include this attribute to expose COM+ 1.0 services. See Marking a ComEmulate Custom Attribute for additional information.
Programming task | Declare services programmatically by inserting the service-related attribute in the emulated class. The RegSvcs utility configures the service for you. See Marking a ComEmulate Custom Attribute for a sample. You can omit this step and configure services manually after registering your class. Some services, such as object pooling, must be configured manually.
Programming task | Access and control services programmatically with interfaces, methods, and properties in the Microsoft.ComServices namespace. For example, you can direct an object to commit or abort a transaction by adding the SetComplete or SetAbort members to the internal class. See Marking a ComEmulate Custom Attribute for a sample of this implementation.
Administrative task | Run the RegSvcs utility to load, register, and configure NGWS classes. This command-line utility combines several tasks into a single step. See Deploying and Configuring COM+ 1.0 Services.
Administrative task | Use the Component Services snap-in administrative tools or write a script to manually configure any COM+ 1.0 services. See the COM+ 1.0 Programming Guide for instructions on configuring individual services.

See Also

COM+ 1.0 Contexts


COM+ 1.0 Contexts

The Microsoft.ComServices namespace contains familiar interfaces, methods, and properties to access COM+ 1.0 context objects.

A context is essentially a set of run-time properties maintained for a collection of one or more objects, and represents the smallest execution context for a component. Every COM object is associated with precisely one context as it runs (that is, between its activation and deactivation).

COM+ 1.0 uses context properties as the basis for providing run-time services. These properties hold state that determines how the execution environment will perform services for objects within the context. In some cases, you will interact directly with an object's context properties to indicate some state relevant to a service being provided for it. For example, you do this when an object that is participating in an automatic transaction votes on the outcome of the transaction.


COM+ 1.0 Services

COM+ 1.0 interoperability provides access to the services that are available in Microsoft Windows 2000 Services. The following table lists these services and their availability in the PDC Technology Preview.

COM+ 1.0 Service | Description | Status | Configuration
Just-In-Time Activation | Activates an object on a method call and deactivates when call returns. | Available | Administrative tool: Activation tab. Programmatically: JustInTimeActivationAttribute
Synchronization (Activity) | Manages concurrency. | Available | Administrative tool: Concurrency tab. Programmatically: SychronizationAttribute.
Automatic Transactions | Applies transaction-processing features. | Available | Administrative tool: Transactions tab. Programmatically: TransactionAttribute.
Object Pooling | Provides a pool of ready-made objects. | Available | Administrative tool: Activation tab.
Role-Based Security | Applies security permission based on role. | Available | Administrative tool: Security tab.
Queued Components | Provides asynchronous message queuing. | Not exposed for PDC Technology Preview | N/A
Events | Manages object-based events. | Not exposed for Tech Preview | N/A
DTC/TIP transactions | Enables transactions that are optimized for the Internet. | Not exposed for Tech Preview | N/A
Compensating Resource Manager (CRM) | Applies Atomicity and Durability properties to non-transactional resources. | Not exposed for Tech Preview | N/A
COM Transaction Integrator (COMTI) | Encapsulates CICS and IMS applications in Automation objects. | Not exposed for Tech Preview | N/A
Bring Your Own Transaction (BYOT) | Enables an automatic transaction to enlist in a non-DTC transaction. | Not exposed for Tech Preview | N/A

See Also

Marking a ComEmulate Custom Attribute


Marking a ComEmulate Custom Attribute

You can include COM+ 1.0 services in your NGWS classes by marking the class with the ComEmulate custom attribute. This attribute guarantees that the NGWS execution environment will create each object of a marked class using standard COM interoperability.

For the PDC Technology Preview, the NGWS runtime has same fidelity as COM. For the product release, the runtime will provide the same rich object-model fidelity that exists in the NGWS runtime.

Objects from a class marked with the ComEmulate attribute are created in the current appdomain of the executing thread and behave as though they have not aggregated the free threaded marshaler.

The following example shows how to implement the ComEmulate attribute within a NGWS class. In this example, the EmulAccount class is configured to require a transaction and points to the Account class for its implementation.

// -----------------------------------------------------------------
// Component.cs
// -----------------------------------------------------------------
using System;
using System.Runtime.InteropServices;
using Microsoft.ComServices;

[guid("CE2A4888-82BD-40ad-9BBA-8D129747886F")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
public interface IAccount
{
   void Debit(int amount);
}

[guid("557B96C4-F7E6-4d48-BBC3-10981E0CA664")]
[ComEmulate("Component.Account")]
[Transaction(TransactionOption.Required)]
public class EmulAccount
{
}

[guid("871D8D5C-ECC6-40a5-BD82-D5E3B1740095")]
internal class Account : IAccount
{
  public void Debit(int amount)
  {
     //… do some Db work
     if (ok) ContextUtil.SetComplete();
     else ContextUtil.SetAbort();
  }
}

See Also

InterfaceTypeAttribute Class


Deploying and Configuring COM+ 1.0 Services

Before a managed class can acquire COM+1.0 services, you must load and register the assembly hosting the class; generate, register, and install the type library into an existing application; and configure each service. The RegSvcs utility reduces this effort by combining these activities into a single tool.

Syntax

RegSvcs assemblyName.dll applicationName [tblName.tbl]

This command-line utility requires a NGWS source assembly file and the name of the destination application in Component Services. If you plan to install your class into a new COM+ 1.0 application, create the application first before running the RegsSvcs utility. Optionally, you can specify a type library name or allow the utility to use the assembly name as the default.

The table below lists each step performed by the RegsSvcs utility and describes the failures that are possible at each step.

Step | Possible failure
Loads the assembly | If the assembly fails to load, the utility displays an error message and failure description.
Registers the assembly | If the type registration fails, the utility displays the list of exceptions that were generated. TypeLoadExceptions can result from an improperly specified COMEmulate attribute or assembly.
Generates a type library | If the library generation fails, the utility provides a list of exceptions. As with the previous step, an improperly specified COMEmulate attribute or assembly can result in TypeLoadExceptions.
Registers the type library | The registration process uses the oleaut LoadTypeLibrary call and throws expected exceptions when it fails.
Installs the type library into the requested application | If the utility cannot find the specified application, it will fail. The error message, One of the objects could not be found, indicates that you should verify the presence of the specified type library and application.
Configures the class | The utility will detect a service attribute mismatch and display an error. For example, if you specify a transaction by setting the TransactionAttribute to Required and setting the SynchronizationAttribute to None, the utility will identify a conflict.

To obtain online help, enter regsvcs/? or regsvcs /h at the command line.


COM+ 1.0 JIT Activation

Just-In-Time activation and deactivation is a required attribute for automatic transactions. If you configure a class to support, require, or require a new transaction, Windows 2000 Component Services will configure JIT activation by default.

The following class defines the JIT attribute.

[AttributeUsage(AttributeTargets.Class)]
public class JustInTimeActivationAttribute : Attribute
{
  public JustInTimeActivationAttribute();
  public JustInTimeActivationAttribute(bool mode);
}


COM+ 1.0 Synchronization (Activity)

Synchronization is a required attribute for automatic transactions. If you configure a class to support, require, or require a new transaction, Windows 2000 Component Services will match the transaction setting by default.

The following class defines the Synchronization attribute.

public enum SynchronizationOption
{
    Ignored = 0,
    None = 1,
    Supported = 1,
    Required = 2,
    RequiresNew = 3
}
[AttributeUsage(AttributeTargets.Class)]
public class SynchronizationAttribute : Attribute
{
  public SynchronizationAttribute();
  public SynchronizationAttribute(SynchronizationOption policy);
}


COM+ 1.0 Automatic Transactions

Automatic transactions require JIT activations and synchronization, which Windows 2000 Component Services configures by default.

The following class defines the Transaction attribute.

public enum TransactionOption
{
      Ignored = 0,
      None = 1,
      Supported = 1,
      Required = 2,
      RequiresNew = 3
}
[AttributeUsage(AttributeTargets.Class)]
public class TransactionAttribute : Attribute
{
  public TransactionAttribute();
  public TransactionAttribute(TransactionOption policy);
}

The default value for this attribute is None.

See Also

Transaction Processing Fundamentals


COM+ 1.0 Object Pooling

In the NGWS runtime, objects can be pooled through COM+ 1.0 interoperability. When an object is activated, it is pulled from the pool. When the object is deactivated, it is placed back into the pool to await the next request. Pooled objects cannot participate in automatic transactions.

Pooling related attributes can be configured only through the Component Services Snap-in administrative tool. First you must enable object pooling for the class, and then you can specify the minimum and maximum pool sizes.


COM+ 1.0 Security

A NGWS application can extend an existing application using COM+1.0 security services. To accommodate this need, the NGWS runtime provides mechanisms to integrate managed code with COM+1.0 security services. This functionality is only available on Windows 2000 and it relies on the NT token associated with executing code as the basis for identity.

The NGWS and COM+1.0 role-based security mechanisms are independent and you may use only one mechanism within a single application.

To simplify use of COM+1.0 security, the NGWS runtime provides a managed code wrapper so a managed application can easily access the COM+1.0 ISecurityContext. Applications using this mechanism will need to:

  • Mark any types accessing this functionality with the ComEmulate custom attribute.
  • Register these types with the COM+1.0 services registration database.

Developers should be aware of the following issues:

  • Managed types marked as ComEmulate are indistinguishable from standard COM objects; therefore, all managed callers are required to have permission to access unmanaged code.
  • COM+1.0 security relies on NT accounts and process/thread impersonation. If the managed code provides authentication services it will need to obtain an NT security token and do an impersonation prior to calling any COM objects.
  • Any calls to managed types not marked as COMEmulate are opaque to COM+1.0 security services. That is, they are treated no differently than calls from COM objects to Win32 APIs in unmanaged DLLs.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Transition Costs

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Limitations

[To be supplied.]


Working with Transactions

When you purchase a book from an online bookstore, you exchange money (in the form of credit) for a book. If your credit is good, a series of related operations ensures that you get the book and the bookstore gets your money. However, if a single operation in the series fails during the exchange, the entire exchange fails. You don’t get the book and the bookstore doesn’t get your money.

The technology responsible for making the exchange balanced and predictable is called transaction processing. Transactions ensure that data-oriented resources are not permanently updated unless all operations within the transactional unit complete successfully. By composing a set of related operations into a unit that either completely succeeds or completely fails, you can simplify error recovery and make your application more reliable.

This guide to transaction processing explains the following:

Topic | Description
Transaction Processing Fundamentals | Introduces basic transaction processing terms and concepts.
Transaction Models | Compares and contrasts two models for adding transactions to your NGWS application.


Transaction Processing Fundamentals

Transaction processing is a well-established technology. Although commonly associated with large-scale systems in telecommunications, transportation, and manufacturing enterprises, transaction-processing systems are becoming a foundation of electronic commerce, where the number of transactions per minute can significantly affect the cost of doing business. Commercial transaction-processing systems, or TP systems, deliver either local transactions (sometimes called database transactions) or distributed transactions.

Most database management system (DBMS) products provide transaction-processing features, although they seldom provide the full capabilities of a dedicated TP system. A transaction served up by a DBMS typically spans only those databases located on that server, giving the transaction a local quality. The advantage of a local transaction is the speed of access to and control of data. Unfortunately, local transaction facilities do not scale well since the transaction is restricted to a single server.

Message queuing systems have similar constraints with regard to local or internal transactions. Although internal transactions to and from a queue are extremely efficient, the local aspect of the system prevents a queued transaction from flowing to a database or other data store.

While some DBMS products do support distributed transactions, the support often extends only to a single database vendor. Distributed TP systems, on the other hand, are designed to facilitate transactions that span heterogeneous, transaction-aware resources in a distributed environment. Thus, supported by a distributed TP system, your application can combine in a transactional unit such diverse activities as retrieving a message from a Microsoft Message Queue Server (MSMQ) queue, storing the message in an SQL Server database, and removing all existing references to the message from an Oracle Server database.

The following topics explain transaction-processing concepts in greater detail.

Topic | Description
ACID Properties | Lists and describes the properties considered essential to all transactions: atomicity, consistency, isolation, and durability.
Transaction Boundaries | Defines the scope of a transaction in terms of manual and automatic transaction boundaries.
Distributed Transactions | Identifies the key elements that allow a distributed transaction processing system to maintain ACID properties.


ACID Properties

The term ACID is useful in conveying the role transactions play in mission-critical applications. Coined by transaction processing pioneers, ACID stands for atomicity, consistency, isolation, and durability.

These properties, which are described below, ensure predictable behavior, reinforcing the role of transactions as all-or-none propositions designed to reduce the management load when there are many variables.

Atomicity

A transaction is a unit of work in which a series of operations occur between the BEGIN TRANSACTION and END TRANSACTION statements of an application. A transaction executes exactly once and is atomic — all the work is done or none of it is.

Operations associated with a transaction usually share a common intent and are interdependent. By performing only a subset of these operations, the system could compromise the overall intent of the transaction. Atomicity eliminates the chance of processing a subset of operations.

Consistency

Since a transaction preserves the consistency of data, transforming one consistent state of data into another consistent state of data, you can regard a transaction as a unit of integrity.

Consistency requires that data bound by a transaction be semantically preserved. Some of the responsibility for maintaining consistency falls to the application developer who must make sure that all known integrity constraints are enforced by the application. For example, in developing a money-transfer application, you should avoid arbitrarily moving decimal points during the transfer.

Isolation

A transaction is a unit of isolation — allowing concurrent transactions to behave as though each was the only transaction running in the system.

Isolation requires that each transaction appear to be the only transaction manipulating the data store even though other transactions may be running at the same time. A transaction should never see the intermediate stages of another transaction.

Transactions attain the highest level of isolation when they are serializable. At this level, the results obtained from a set of concurrent transactions are identical to the results obtained by running each transaction serially. Because a high degree of isolation can limit the number of concurrent transactions, some applications reduce the isolation level in exchange for better throughput.

Durability

A transaction is also a unit of recovery. If a transaction commits, the system guarantees that its updates will persist, even if the computer crashes immediately after the commit. Specialized logging allows the system's restart procedure to complete unfinished operations, making the transaction durable.


Transaction Boundaries

A transaction has a beginning, an end, and occurs exactly once. As a transaction executes, various transaction-aware resources may participate or enlist into the transaction. If within the scope of a transaction your application connects to a database, for example, the transaction flows to that resource and extends the transaction boundary to include the database server. Within a given transaction boundary, all participating resources share the same transaction identifier.

You can design transactions to span processes and computers. Thus, a transaction boundary is an abstraction for managing consistency across process and computer boundaries.

Your control over a transaction boundary varies depending on the transaction model you select for your application: manual or automatic. In a manual transaction, you control transaction boundaries with explicit instructions to begin and end the transaction. From within one transaction boundary, you can begin a second transaction, called a nested transaction. The parent transaction does not commit until all its subordinate transactions commit.

An automatic transaction manages transaction boundaries for you, based on a declarative attribute set for each component. A transaction automatically flows to objects instructed to participate in a transaction and bypasses objects instructed to execute outside a transaction. You cannot nest transactions when using the automatic transaction model.

See Also

Transaction Models


Distributed Transactions

A distributed TP system consists of computer hardware and software hosting a transaction-oriented application that performs those routine transactions necessary to conduct business. Examples include systems that manage sales order entry, airline reservations, payroll, employee records, and shipping.

When a TP system provides distributed capabilities, you can design transactions that span transaction-aware data stores, such as databases and message queues. Even though they span multiple data resources, distributed transactions maintain the ACID properties required to run robust, distributed applications.

Distributed TP systems consist of several cooperating entities, as described in the following sections.

Transaction Processing (TP) Monitors

A TP monitor is software that sits between a transaction-aware application and a collection of services. It maximizes operating system activities, streamlines network communications, and connects multiple clients to multiple applications, which potentially access multiple data resources.

Instead of writing an application that manages a multi-user, distributed environment, you write an application that consists of single transaction requests. The monitor scales your application as required.

Transaction Managers

In a distributed transaction, each participating resource has a local transaction manager (TM) to track incoming and outgoing transactions on that computer. The TP monitor assigns one TM the additional task of coordinating all activities among local TMs. The TM that coordinates transaction activities is called the root or coordinating TM.

A TM coordinates and manages all transaction-processing functions, such as the two-phase commit, but it is not equipped to manage data directly. Resource managers handle data-related activities.

Resource Managers

A resource manager (RM) is a system service that manages persistent or durable data in databases, durable message queues, or transactional file systems. It is the resource manager that knows how to store data and perform disaster recovery.

Resource Dispensers

A resource dispenser manages nondurable state that can be shared. The ODBC Resource Dispenser, for example, manages pools of database connections, reclaiming each connection when it is no longer needed.


Transaction Models

Two or more NGWS objects can participate in the same transaction. One or more NGWS objects can participate in the same transaction as one or more COM+ 1.0 objects. All objects must be hosted in Microsoft Windows 2000 Component Services in order to receive services such as transaction processing.

There are several considerations you must address in designing transactions that span COM+ 1.0 and NGWS objects. For example, in order for a NGWS object to participate in an automatic transaction, the NGWS class must be registered with Window 2000 Component Services. However, not all transactions are automatic. The activities you perform when programming transactions depend on the transaction model you choose.

The NGWS runtime supports both manual and automatic transaction models, as described in the following topics.

Topic | Description
Manual Transactions | Identifies the characteristics of a manual transaction using ADO+ and MSMQ.
Automatic Transactions | Introduces declarative transactions and automatic enlistment of data drivers, data providers, and resource managers.


Manual Transactions

Transaction-based applications have been available for years. Early TP applications were extremely specialized programs that required a developer to have an in-depth knowledge operating systems and business logic. These systems were expensive, reliable, and costly to modify.

Since those early days, TP programming has become less specialized and more widespread. ADO, OLE DB, ODBC, and MSMQ resource APIs enable manual transaction processing, which is one of two transaction models available to NGWS developers.

A manual transaction allows the developer to explicitly begin a transaction boundary, control each connection and resource enlistment within the boundary, determine the outcome of the transaction (commit or abort), and end the transaction. Although this model offers measured control over a transaction, it lacks some of the ease built into the automatic transaction model. For example, there is no automatic enlistment and coordination between data stores in a manual transaction. Further, transactions do not flow from object to object, as is the case in automatic transactions.

If you choose to control a distributed transaction manually, you must manage recovery, concurrency, security, and integrity. In other words, you must apply all the programming technique necessary to maintain the ACID properties associated with transaction processing.

The following topics describe NGWS runtime support for the manual transaction model:

  • Manual Transactions and ADO+
  • Manual Transactions and MSMQ Class Libraries

See Also

Automatic Transactions

ACID Properties


Manual Transactions and ADO+

The ADO+ managed provider and the ADO+ SQL Server managed provider support manual transactions in the NGWS runtime.

ADO+ managed provider

The ADO+ managed provider uses native OLE DB through COM Interoperability to enable data access. The following NGWS class exposes methods that you can use to manage manual transactions:

public class System.Data.ADO.ADOConnection
{
  //. . .
  public int BeginTransaction();
  public void CommitTransaction();
  public void RollbackTransaction()
  //. . .
}

ADO+ SQL Server Managed Provider

This adapter uses the private protocol called tabular data stream (TDS) to communicate with SQL Server, instead of using OLE DB, ADO or ODBC. It is all managed code.

The following class exposes transaction-related methods:

public class System.Data.SQL.SQLConnection
{
  public int BeginTransaction();
  public int BeginTransaction(String transactionName);

  public void CommitTransaction();
  public void RollbackTransaction();
  public void RollbackTransaction(String transactionName);
  public void SaveTransaction(String savePointName);
  int TransactionLevel {get;}
}

See Also

Automatic Transactions and NGWS Classes


Manual Transactions and MSMQ Class Libraries

NGWS objects written in a managed language, such as Microsoft Visual Basic 7.0, can send and receives messages from a MSMQ queue.

MSMQ is a technology that implements message queuing in an application. With MSMQ, you can create or delete message queues; send or receive messages; and manage message queues.

Transactions are an important part of enterprise systems, which frequently require the asynchronous capabilities of MSMQ. The NGWS runtime supports manual transactions through MSMQ class libraries for both internal and external transactions.

Internal Transactions

Internal transactions are simple, optimized for performance, and limited in scope. These transactions are called internal because they are handled entirely inside the MSMQ and are unsupported by the Distributed Transaction Coordinator (DTC) or any other transactional mechanism. DTC is Microsoft's transaction monitor.

Internal transactions have a very simple programming model. The following code fragment shows two messages composed in a single transaction. MSMQ ensures the delivery of both messages, or the transaction fails and neither message is delivered.

try
{
   mq.BeginTransaction();
   messageA = mq.Receive(1000);
   messageB = mq.Receive(1000);
   mq.CommitTransaction();
}
catch(Exception e)
{
mq.RollbackTransaction();
}

External Transctions

Although simple to program, internal MSMQ transactions exclude database operations. Thus, if your application requires multiple operations, such as receiving a message from a message queue and putting the message in a database, and you want to execute these operations in the scope of one transaction, you will have to use an external transaction.

The NGWS runtime provides the following class, which gives MSMQ access to distributed transactions served up by DTC:

namespace System.Messaging
{
  public class MessageQueue
  {
    //. . .
    public void BeginTransaction();
    public void CommitTransaction();
    public void RollbackTransaction();
    //. . .
  }
}

An external transaction through the MessageQueue class is manual because you explicitly begin the transaction, commit or roll back the operation, and manually enlist each resource manager through DTC.

The following table lists and describes the methods exposed by the MessageQueue class.

Method | Description
BeginTransaction | Creates a new Microsoft Message Queue Server (MSMQ) internal transaction.
CommitTransaction | Commits a pending internal transaction.
RollbackTransaction | Rolls back the pending internal transaction.

See Also

Automatic Transactions and NGWS Classes


Automatic Transactions

Microsoft Transaction Server (MTS), COM+ 1.0, and the NGWS runtime support the same automatic, distributed transaction model.

Once an ASP+ page, WebMethod, or NGWS class is marked to participate in a transaction, it will automatically execute within the scope of a transaction. You can control an object's transactional behavior by setting a transaction attribute value on the page, Webmethod, or class. The attribute value, in turn, determines the transactional behavior of the instantiated object. Thus, based on the declared attribute value, an object will automatically participate in an existing or ongoing transaction, be the root of a new transaction, or never participate in a transaction at all. (The syntax to declare the transaction attribute varies slightly in a NGWS class, an ASP+ page, and a WebMethod.)

The declarative transaction attribute predicts how an object participates in a transaction, and is something you configure programmatically. Although this declarative level represents the logic of a transaction, it is one step removed from the physical transaction. A physical transaction occurs when a transactional object accesses a data resource, such as a database or message queue. The transaction boundary associated with the object automatically flows to the appropriate resource manager. An associated driver, such as OLE DB, ODBC, or ADO, looks up the transaction in the object's context and enlists in the transaction through the Distributed Transaction Coordinator (DTC). The entire physical transaction occurs automatically.

The following topics describe support for automatic transactions:

  • Automatic Transactions and ASP+
  • Automatic Transactions and Web Services
  • Automatic Transactions and NGWS Classes
  • Voting in an Automatic Transaction


Automatic Transactions and ASP+

The ASP+ framework supports automatic transactions on Windows 2000. By inserting a transaction directive in your ASP+ page, you can instruct the page to participate in an existing transaction, begin a new transaction, or never participate in a transaction.

The table below lists and describes the transaction directives available in ASP+.

Directive | Description
Not_Supported | This value indicates that the page does not run within the scope of transactions. When a request is processed, its object context is created without a transaction, regardless of whether there is a transaction active.
Supported | The page will run in the context of an existing transaction, if one exists. If not, it will run without a transaction.
Required | The page requires a transaction. It will run in the context of an existing transaction, if one exists. If not, it will start one.
Requires_New | The page requires a transaction and a new transaction will be started for each request.

You can indicate the level of transaction support on a page by placing the directive in your code. For example, you can ensure that the page activities always execute in the scope of a transaction by inserting the following directive:

<@% TRANSACTION=”Required” %>

The default transaction state is None, which indicates that the transaction context will be ignored by the ASP+ framework. There is no explicit directive for this value; in other words, you cannot add TRANSACTION=”None” to your page. Instead, simply omit the transaction directive to apply the default value.

See Also

Voting in an Automatic Transaction


Automatic Transactions and Web Services

ASP+ provides built-in support for creating and exposing Web Services — using a programming abstraction that is consistent and familiar to both ASP+ Web Forms and Visual Basic developers. The resulting model is scalable, extensible, and embraces HTTP, XML, SOAP, and SDL open Internet standards, among others. By supporting open standards, Web services can be accessed and consumed by any client or Internet-enabled device.

Like ASP+ Web Forms, ASP+ Web Services offers a developer the option to execute their code within the scope of an automatic transaction. A transaction ensures that all interactions with resource managers like SQL Servers, MSMQ Servers, Oracle Servers, and SNA Servers maintain the ACID properties required to run robust distributed applications.

You can declare an automatic transaction by using the custom enumeration property, Transaction, on the WebMethod metadata attribute of a WebMethod. The declaration begins a new transaction each time a client calls the WebMethod.

The following code fragment shows a service that exposes a single WebMethod, called DeleteDatabase. This WebMethod performs a database operation that is scoped within an automatic transaction.

<%@ WebService Language=”C#” %>
using System;
using System.Data;
using System.Data.SQL;
using System.Web.Services;

public class TransactionStuff : WebService
{
  [WebMethod(Transaction=Transaction.Required)]
  public void DeleteDatabase()
  {
    SQLConnection sqlConn = new SQLConnection(
                          "Bar", "sa", "", "northwind");
    SQLDataSetCommand sqlAdapter1 = new SQLDataSetCommand(
                         "delete orders", sqlConn);
    SqlConn.Execute();
  }
}

Important

A transaction will begin only when the activating WebMethod, which is the method called from the client, has transaction metadata. If the activating WebMethod does not carry the appropriate transaction metadata, subsequent WebMethods can neither participate in an existing transaction nor begin a new transaction.


Automatic Transactions and NGWS Classes

Instances of a NGWS class can participate in an automatic transaction, as long as you prepare the class to do so. Each resource accessed by a class instance, or object, enlists in the transaction. For example, if an object uses ADO+ to post money on an account in a database, the resource manager for the database determines if it should execute in a transaction and, if so, it enlists the database in the transaction automatically.

See COM+ 1.0 Interop for instructions on preparing your class to access Windows 2000 services.

Programming Model

[Transaction(TransactionOption.Required)]
[ComEmulate()]
public class Bar()
{
  //. . .
}

TransactionAttribute Constructor Variations

[TransactionAttribute(TransactionOption.NotSupported)]
[TransactionAttribute(TransactionOption.Support)]
[TransactionAttribute(TransactionOption.Required)]
[TransactionAttribute(TransactionOption.RequiresNew)]

You can use Transaction, transaction, TransactionAttribute, and transactionattribute interchangeably.

The following table lists and describes each constructor variation.

Attribute Value | Description
NotSupported | This value indicates that the object does not run within the scope of transactions. When a request is processed, its object context is created without a transaction, regardless of whether there is a transaction active. [Transaction(TransactionOption.NotSupported)]
Supported | The object will run in the context of an existing transaction, if one exists. If not, it will run without a transaction. [Transaction(TransactionOption.Supported)]
Required | The object requires a transaction. It will run in the scope of an existing transaction, if one exists. If not, it will start one. [Transaction(TransactionOption.Required)]
RequiresNew | The object requires a transaction and a new transaction will be started for each request. [Transaction(TransactionOption.RequiresNew)]

Sample Class

namespace BankClassLibrary
{
[Guid(“1237e50f-6085-49f7-8293-3febb8580e78”)]
public interface IMoveMoney
{
/*
lngPrimeAccount -   "From" Account
lngSecondAccount -  "To" Account
lngAmount -         Amount of transaction
lngTranType -       Transaction Type
1 = Withdrawal,
2 = Deposit,
3 = Transfer)
Returns:  String -        Account Balance
*/

  public String Perform (
int lngPrimeAccount,
int lngSecondAccount,
int lngAmount,
int tranType);
}

[Transaction(TransactionOption.Required)]
[Guid(“b963fbbd-d1b1-4a2f-8501-89252dcb116f”)]
[ComEmulate(“BankClassLibrary.MoveMoneyInternal\0”)]
public class MoveMoney
{
}
internal class MoveMoneyInternal : IMoveMoney
{
  public String Perform (
         int lngPrimeAccount,
         int lngSecondAccount,
         int lngAmount,
int tranType)
  {
    // Use IAccount to move money
    // between two accounts
  }
}

[Guid(“41ee971a-93f9-4111-89ee-4e0f4e0fca0a”)]
public interface IAccount
{
   public String Post (
              int lngAccountNo,
              int lngAmount);
}
[Transaction(TransactionOption.Required)]
[Guid(“41ee971a-93f9-4111-89ee-4e0f4e0fca0a”)]
[ComEmulate(“BankClassLibrary.AccountInternal\0”)]
public class Account
{
}

internal class AccountInternal : IObjectControl
{
  public String Post(
          int lngAccountNo,
          int lngAmount)
  {
    // Use ADO+ to post money into
    // the account in the database.
  }

  void Activate()
  {
     //. . .
  }

  void Deactivate()
  {
     //. . .
  }

  bool CanBePooled()
  {
     //. . .
  }
}


Voting in an Automatic Transaction

NGWS classes and ASP+ pages can vote to commit or abort their current transaction. The absence of an explicit vote in your code casts a commit vote by default. The default commit, however, may decrease the performance of your application by lengthening the time it takes for each transaction to release expensive resources.

Explicit voting also allows your class or page to abort a transaction if it encounters a significant error. Again, you can improve your application's performance by catching a fatal error early in the transaction, ending the transaction, and releasing resources.

You can use Microsoft.ComServices.ContextUtil, which exposes the SetComplete or SetAbort members, to explicitly commit or abort a transaction. SetComplete indicates that your object votes to commit its work; SetAbort indicates that your object encountered a problem and votes to abort the ongoing transaction.

The following code fragment shows the SetAbort method in use.

// try to do something crucial to transaction completing
if( !DoSomeWork() )
{
  ContextUtil.SetAbort();
}


Working with I/O

The System.IO namespace contains types that allow synchronous and asynchronous reading from and writing to data streams and files.

The difference between a file and a stream is not always hard and fast, but the following distinctions are useful. A file is an ordered and named collection of a particular sequence of bytes having persistent storage. Therefore, with files, one thinks in terms of directory paths, disk storage, and file and directory names.

Streams provide a way to write and read bytes to and from a backing store that can be one of several storage mediums. Just as there are several backing stores other than disks, there are several kinds of streams other than file streams. For example, there are network, memory, and tape streams.

The abstract base class Stream integrates asynchronous support. Stream provides default implementations defining synchronous reads and writes in terms of their corresponding asynchronous methods, and visa versa.

All classes that represent streams inherit from the Stream class. The Stream class and its subclasses provide a generic view of data sources and repositories, isolating the programmer from the specific details of the operating system and underlying devices.

Streams involve these fundamental operations:

  • Streams can be read from. Reading is the transfer of data from a stream into a data structure, such as an array of bytes.
  • Streams can be written to. Writing is the transfer of data from a data structure into a stream.
  • Streams can support seeking. Seeking is the querying and modifying of the current position within a stream. Seek capability depends on the kind of backing store a stream has. For example, network streams have no unified concept of a current position, and therefore typically do not support seeking.

Depending on the underlying data source or repository, streams might support only some of these capabilities.

The System.IO classes provide the following functionality:

  • Creation, deletion, and manipulation of directories and files (File and Directory).
  • Reading and writing of single bytes or byte blocks from and to streams (StreamReader and StreamWriter).
  • Reading and writing of multibyte (also known as wide stream) characters from and to streams (StreamReader and StreamWriter, when multibyte characters are encoded by a subclass of System.Encoding).
  • Reading and writing of characters from and to streams (StreamReader and StreamWriter).
  • Reading and writing of characters from and to strings, allowing strings to be handled as streams of characters (StringReader and StringWriter).
  • Reading and writing of data types and objects from and to streams (BinaryReader and BinaryWriter).
  • Random access to files (FileStream).
  • Optimization of performance (MemoryStream and BufferedStream).
  • Enumeration of file and directory attributes (FileAccess, FileMode, FileShare, FileAttributes, and DirectoryAttributes).
  • Enumeration of entry points for seeking (SeekOrigin).

The Memory Stream class is a non-buffered stream whose encapsulated data is directly accessible in memory. This stream may reduce the need for temporary buffers and files in an application.

The FileAccess, FileMode, and FileShare enumerators define constants used by some of the FileStream and IsolatedStorageFileStream constructors and some of the File.Open overloaded methods. These constants affect the way in which the underlying file is created, opened, and shared.

The SeekOrigin enumerator is used by the Seek methods that are supported by streams. SeekOrigin defines constants that specify the point of entry for random access to files. These constants are used with byte offsets.

The FileAttributes and DirectoryAttributes enumerators are used by the File.Attributes and Directory.Attributes methods, respectively. FileAttributes and DirectoryAttributes define constants that represent Windows file attributes such as Archive, Hidden, and ReadOnly, which correspond to the attributes defined in WinNT.h.

All streams can support reading, writing, and seeking (random access).

The Stream, TextReader, and TextWriter classes are abstract classes. They cannot be directly instantiated, but are used only as base classes of other classes. Classes derived from Stream, TextReader, and TextWriter must include actual implementations of all inherited abstract methods and accessors by overriding them.

The Directory and File classes are sealed classes. These can be instantiated, but can have no subclasses.

All other documented System.IO members are public classes or enumerators.


Basic File I/O

The following examples show some typical uses of the IO classes:

Creating a Directory Listing

Reading and Writing to a Newly Created Data File

Opening and Appending to a Log File

Reading Text from a File

Writing Text to a File

Writing Data to a File


Creating a Directory Listing

This example shows how to use the IO classes to create a listing of a directory.

using System;

using System.IO;

class DirectoryLister

{

public static void Main(String[] args)

{

Directory dir = new Directory(".");

foreach (File f in dir.GetFiles("*.cs")) {

String name = f.FullName;

long size = f.Length;

DateTime creationTime = f.CreationTime;

Console.WriteLine("{0,-12:N0} {1,-20:g} {2}", size, creationTime, name);

}

}

}


Reading and Writing to a Newly Created Data File

This example shows the basics of creating a file stream, creating the associated BinaryReader and BinaryWriter, and doing some file I/O.

FileStream fs = new FileStream("data.txt", FileMode.CreateNew);
BinaryWriter w = new BinaryWriter(fs);
BinaryReader r = new BinaryReader(fs);
for (int i = 0; i < 11; i++)
  {
    w.Write( (int) i);
  }
//Set the file pointer to the beginning.
w.GetStream().Seek(0,SeekOrigin.Begin);
for (int i = 0; i < 11; i++)
  {
    Console.WriteLine( r.ReadInt32() );
  }


Opening and Appending to a Log File

The following Managed Extensions for C++ example opens a file or creates a file if it does not already exist, and appends information to the end of the file. The contents of the file are then written to standard output for display.

using System;
using System.IO;
class Directory
{
   public static void Main(String[] args)
   {
     FileStream fs = new FileStream("log.txt", FileMode.OpenOrCreate, FileAccess.Write);
// Create a Char writer.
     StreamWriter w = new StreamWriter(fs);
// Set the file pointer to the end.
     w.BaseStream.Seek(0, SeekOrigin.End);
     Log ("Test1", w);
     Log ("Test2", w);
// Close the writer and underlying file.
     w.Close();
     fs = new FileStream("log.txt", FileMode.OpenOrCreate, FileAccess.Read);
// Create a Char reader.
     StreamReader r = new StreamReader(fs);
// Set the file pointer to the beginning.
     r.BaseStream.Seek(0, SeekOrigin.Begin);
     DumpLog (r);
  }
  public static void Log (String logMessage, StreamWriter w)
  {
     w.Write("\r\nLog Entry : ");
     w.WriteLine("{0} {1}",
       DateTime.Now.ToLongTimeString(),
       DateTime.Now.ToLongDateString());
     w.WriteLine("  :");
     w.WriteLine("  :{0}", logMessage);
     w.WriteLine ("-------------------------------");
// Update the underlying file.
     w.Flush();
  }
  public static void DumpLog (StreamReader r)
  {
// While not at the end of the file, read and write lines.
     while (r.Peek() > -1)
     {
        Console.WriteLine(r.ReadLine());
     }
     r.Close();
  }


Reading Text from a File

The following example reads a string in from an existing text file.

using System;
using System.IO;
public class TextFromFile
{
    public static void Main(String[] args)
    {
        StreamReader sr = File.OpenText("MyFile.txt");
        String input = sr.ReadLine();
        Console.WriteLine (input);
        sr.Close();

    }
}

This code creates a stream reader that points to MyFile.txt through a call to File.OpenText.You can now read a line from the file by calling StreamReader.ReadLine.


Writing Text to a File

The following example creates a new text file and writes a string to it.

using System;
using System.IO;
public class TextToFile
{
    public static void Main(String[] args)
    {
        StreamWriter sr = File.CreateText("MyFile.txt");
        sr.WriteLine ("This is my file");
        sr.WriteLine ("I can write ints {0} or floats {1}, etc.", 1, 4.2);
        sr.Close();

    }
}


Writing Data to a File

The following example writes some data to a file.

using System;
using System.IO;
public class DataToFile
{
    public static void Main(String[] args)
    {
        int myInt = 5;
        File f = new File("MyData.data");
        Stream s = f.Open(FileMode.OpenOrCreate);
        BinaryWriter d = new BinaryWriter (s);
        d.Write(myInt);
        s.Close();
    }
}

This code creates a file object that points to MyData.data. Next, a FileStream is created through a call to File.Open, returning a FileStream object. A BinaryWriter is then created and passed the FileStream as its argument. BinaryWriter.Write writes the integer myInt to the file.


Composing Streams

A backing store is a storage medium, such as a disk or memory. All the different backing stores implement their own stream as an implementation of the Stream class. Each stream type reads and writes bytes from and to its given backing store. Streams that interface to backing stores are called base streams. Base streams have constructors that have the parameters necessary to connect the stream to the backing store. For example, a file stream might have a parameter that is a file path or file handle.

The design of the System.IO classes provides simplified stream composing. Base streams can be attached to one or more pass-through streams that provide the desired functionality. A reader or writer can be attached to the end of the chain so that types other than bytes can be read or written easily.

The following example composes a stream with buffering and then reads from it.

public static void Main(String[] args)
{
File f = new File(“MyFile.text”);
FileStream s = File.Open(FileMode.Read);
BufferedStream b = new BufferedStream(s);
StreamReader t = new StreamReader(b);
String s = t.ReadLine();
b.Close();
}

This example creates a File object that points to MyFile.text. Next, a FileStream is created through a call to File.Open, returning a FileStream object. A buffer is then composed by creating a BufferedStream and passing the file stream to it as its constructor argument. A TextReader is then created and passed the BufferedStream as input. Finally, the user reads a line from the file by calling the ReadLine method of the TextReader.


Creating a Stream

This example creates a stream, possibly to connect to a different backing store.

public abstract class MyStream: Stream{
// Here, the user would provide their own implementations of the methods on the Stream abstract base class. They would only need to implement one set of read/write methods as the other implementation is inherited from Stream.
// The constructor should be typed to take a Stream, and the class should extend Stream.
}


Creating a Writer

The following example creates a writer, which is a class that can take data of some type and convert it to a byte array that can be passed to a stream.

public class MyWriter
{
private Stream s;
public MyWriter(Stream stream) {
s = stream;
} // end MyWriter(Stream stream)
public void WriteDouble(double myData) {
byte[] b = myData.GetBytes();
// GetBytes is a binary representation of a double data type.
s.Write(b,0,b.Length);
} // end WriteFoo(double myData)
public void Close()
{
s.Close();
}
}

In this scenario, you create a class that has a constructor with a stream argument. From here, you can expose whatever write methods are necessary. You must convert whatever you are writing to a byte[]. After obtaining the byte[] b, it is written to the stream s by the Write method.


Asynchronous File I/O

The Stream class supports the mixing of synchronous and asynchronous reads and writes on the same stream, regardless of whether the operating system allows this. Stream provides default implementations of asynchronous read and write operations in terms of their synchronous implementations, and provides default implementations of synchronous read and write operations in terms of their asynchronous implementations.

When implementing a subclass of Stream, it is necessary to provide an implementation for either the synchronous or asynchronous Read and Write methods. Overriding Read and Write is sufficient; the default implementations of the asynchronous methods (BeginRead, EndRead, BeginWrite, and EndWrite) will work with your implementation of the synchronous methods. Similarly, the synchronous Read and Write methods will work correctly if you provide an implementation of the asynchronous methods. The default implementations of ReadByte and WriteByte call the synchronous Read and Write methods with a one-element byte array. When subclassing Stream, if you have an internal byte buffer, it is strongly recommended that you override these methods to access your internal buffer for better performance.

A stream that connects to a backing store overrides either the synchronous or asynchronous read and write methods to get the functionality of the other by default. If a stream does not support asynchronous or synchronous operations, the implementer need only make the appropriate methods throw exceptions.


Isolated Storage

Basic file I/O functionality provides the ability to access, store, and manipulate data that is stored in hierarchical file systems whose files are referenced using unique paths. For some applications, such as web applications, using standard mechanisms for storing data in files complicates the development process because of the need for isolation, configurability, ease of deployment, and security:

Isolation

When an application stores data in a file in a file system, the file name and storage location must be carefully chosen to minimize the possibility that the storage location will be known to another application and, therefore, vulnerable to corruption. Individual application developers have been responsible for creating and using ad hoc techniques that minimize storage conflicts.

Configurability

When web applications handle persistent data, it can be difficult, if not impossible, for the application to ensure that the files it uses can be easily configured by administrators.

Ease of Deployment

It can be difficult to deploy an application that requires a file to reside in a particular location, especially when that location must be secured from access by other applications.

Security

Traditional file systems have mechanisms, such as access control lists (ACLs), for restricting the access that users have to files, but they do not restrict what code can do when running in a user's context. Also, ACL-based security mechanisms are useful only when an administrator can properly configure the ACLs on all the systems on which the application will run; with web applications, this approach is often not feasible.

Isolated storage is a data storage mechanism that provides isolation, configurability, ease of deployment, and security. It is similar to browser cookies but more flexible and powerful. Isolated storage provides developers with a controlled mechanism for isolating and storing data in files. There is no longer a need for applications to invent unique paths to try to prevent data storage from overlapping with another application's storage area. Using isolated storage enables partially trusted applications to store data in a secure, controlled way that is likely to be allowed by security policy. Although security policy rarely grants permission to access the file system using standard I/O mechanisms, it is not unusual for security policy to allow code to access or create isolated storage.

For more information, see Introduction to Isolated Storage.


Introduction to Isolated Storage

When using isolated storage, data is always isolated by user. Data can also be isolated by assembly, based on credentials such as the origin or the strong name of the assembly, and it can be isolated by application domain, using similar credentials.

When using isolated storage, applications save data to a unique data compartment that is associated with a specific user and with some aspect of the code's identity, such as its Web site, publisher, or signature. The data compartment is an abstraction, not a specific storage location; it consists of one or more stores, which are the actual locations where data is stored. For example, a Web application might have a data compartment associated with it, and a directory in the file system would implement the store that actually preserves the data for that application. The data saved in the store can be any kind of data from user preference information to application state. For the developer, the location of the data compartment is transparent; stores can reside either on the client or on the server.

Administrators can limit how much isolated storage applications can use, based on the trust level appropriate for that application. In addition, administrators can easily remove the persisted data when uninstalling the application.

Code access security prevents untrusted managed code from successfully calling more highly trusted code to store data in isolated storage on its behalf. To create or access isolated storage, code must be granted the appropriate IsolatedStorageFilePermission.


Scenarios for Isolated Storage

Isolated storage is useful in many situations. Three of the most readily identifiable scenarios are described as follows:

  • Persistent application storage. Web applications can use isolated storage to store user personalization or preference information, as well as other persistent application data.
  • Shared component storage. Components that are shared between applications can use isolated storage to provide controlled access to data stores.
  • Server storage. Server applications can use isolated storage to provide individual stores for a large number of users making requests to the application. In this case, data is isolated based on the identity of the principal, which is the same identity the application uses to distinguish between its users.

Although isolated storage is well suited to the scenarios described previously, there are a few cases in which you should not use isolated storage:

  • Isolated storage should not be used to store high-value secrets, such as unencrypted keys or passwords, because isolated storage is not protected from highly trusted code, from unmanaged code, or from trusted users of the computer.
  • Isolated storage should not be used to store code.
  • Isolated storage should not be used to store configuration and deployment settings, which administrators control. (User preferences are not considered to be configuration settings because they are not controlled by administrators.)

Many of today's applications use a database to store and isolate data, in which case one or more rows in a database might represent storage for a specific user. You might choose to use isolated storage instead of a database when the number of users is small, when the overhead of using a database is significant, or when no database facility exists. Also, when the application requires storage that is more flexible and complex than what a row in a database provides, isolated storage can provide a viable alternative.


Types of Isolation

Access to isolated storage is always restricted to the user who created it. To implement this type of isolation, the runtime uses the same notion of user identity that the operating system recognizes, which is the identity associated with the process in which the code is running when the store is opened. This identity will be either an authenticated user identity or a unique anonymous user; impersonation can cause the identity of the current user to change dynamically.

In addition to isolation by user, access to isolated storage is restricted according to the identity associated with the application's domain and assembly or the assembly alone. The runtime obtains these identities in the following ways:

  • Domain identity represents the domain of the application, which in the case of a Web application is the full URL (i.e., "http://www.microsoft.com"). For shell-hosted code, the domain identity is based on the application directory path. For example, if the executable runs from the path C:\Office\MyApp.exe, the domain identity would be "C:\Office\MyApp.exe".
  • Assembly identity comes from a cryptographic digital signature, which can be the assembly's strong name or the software publisher of the assembly or its URL identity. If an assembly has both a strong name and a software publisher identity, then software publisher identity is used. If the assembly comes from the Internet and is unsigned, the URL identity is used.

By combining the concepts of user, domain, and assembly identity, isolated storage can isolate data in the following ways, each of which has its own usage scenarios:

  • Isolation by User and Assembly
  • Isolation by User, Domain, and Assembly

Note that isolated storage is always implicitly isolated by computer because it uses the storage facilities local to a given computer. Isolated storage is never shared between machines, nor is data in isolated storage accessible across computers.


Isolation by User and Assembly

When the assembly using the data store needs to be accessible from any application's domain, isolation by user and assembly is appropriate. Typically, in this situation isolated storage is used to store data that applies across multiple applications and is not tied to any particular application, such as the user's name or license information. To use storage isolated by user and assembly, code must be highly trusted because this type of isolation could enable the transfer of information between applications, which could compromise data isolation—especially in the case of Web applications.


Isolation by User, Domain, and Assembly

If an application uses a third-party assembly that requires a private data store, isolated storage can be used to store the private data. Isolation by user, domain, and assembly ensures that only code in a given assembly can access the data, and only when the assembly is used by the application that was running when the user created the store, and only when the application is run by the user who created the store. Isolation by user, domain, and assembly keeps the third-party assembly from leaking data to other applications. This isolation type should be your default choice if you know you want to use isolated storage but are not sure which type of isolation to use.


Quotas

A quota is a limit on the amount of isolated storage that can be used. The quota includes bytes of file space as well as the overhead associated with the directory and other information in the store.Isolated storage uses permission quotas, which are storage limits that are set using IsolatedStoragePermission objects. Security policy, which can be modified using the caspol utility, determines which permissions are granted to code. Code that has been granted IsolatedStoragePermission is restricted to using no more than the specified quota. However, because code can bypass permission quotas by presenting different identities, permission quotas serve as guidelines for how code should behave rather than as firm limits on code behavior.


IsolatedStorageFilePermission

To control access to isolated storage, the runtime uses IsolatedStorageFilePermission objects. Each IsolatedStorageFilePermission object has properties that specify the following values:

  • Allowed usage, which indicates the type of access that is allowed. The values are members of the IsolatedStorageContainment enumeration. See the table in Allowed Usage and Security Risks for more information about these values.
  • Storage quota.

The runtime decides whether to grant IsolatedStorageFilePermission to code, based on how much the code is trusted. If the permission is granted, the allowed usage and storage quota values are determined by security policy and by the code's request for IsolatedStorageFilePermission, if there is a request.


Allowed Usage and Security Risks

The allowed usage specified by the IsolatedStorageFilePermission determines the degree to which code will be allowed to create and use isolated storage. The following table shows how the allowed usage in the permission maps to types of isolation, and it also summarizes the security risks associated with each allowed usage.

Allowed Usage | Isolation Types | Security Impact
None | No isolated storage use allowed. | None.
DomainIsolationByUser | User, domain, and assembly. | Data is contained by domain. Each assembly has a separate substore within the domain. Potential denial-of-service.
AssemblyIsolationByUser | User and assembly. | Potential for leaking information between applications.
UnrestrictedIsolatedStorage | All users, domains, and assemblies. | Potential for total compromise.
AdministerIsolatedStorageByUser | View or delete a user's files or directories. | The most powerful allowed usage, which brings the greatest security risk. These risks include, but are not limited to, leaking information and data loss.


Using the IsolatedStorageFilePermission

As is the case with other code access permissions, the IsolatedStorageFilePermission protects a resource from unauthorized access. In this case, the IsolatedStorageFilePermission encapsulates the ability to create and use isolated storage.

Application code is not required to request this permission because the runtime will grant whatever IsolatedStorageFilePermission is appropriate based on security policy. However, there are good reasons to request permissions that your application needs, including the IsolatedStorageFilePermission. For more information, see Requesting Permissions.

Demands for this permission are made when a file is created in isolated storage. A demand causes all callers in the call stack to be checked to ensure that each caller has at least the appropriate allowed usage. The quota of the code that opens or creates the store is checked when a store is opened.


Using Isolated Storage

Several classes are provided to help you perform tasks that involve isolated storage. The IsolatedStorage abstract class provides access to stored assembly and application data. IsolatedStorageFile, which is derived from IsolatedStorage, is used to get access to a store and manipulate the isolated file system. IsolatedStorageFileStream, which is derived from FileStream, provides access to files in an IsolatedStorageFile, using the same methods as System.IO.FileStream. IsolatedStorageScope is an enumeration that enables you to create a store with the appropriate isolation type.

The isolated storage classes enable you to perform the following tasks:

Obtaining a store

Accessing files and data in a store

Managing stores


Obtaining a Store

A store exposes a virtual file system within a data compartment. To create a store or retrieve an existing one, you can call one of the static methods IsolatedStorageFile.GetUserStoreForDomain, IsolatedStorageFile.GetUserStoreForAssembly, or IsolatedStorageFile.GetStore, depending on which type of isolation you need. Each of these methods returns an IsolatedStorageFile object. To help you decide which isolation type is most appropriate for your situation, see Types of Isolation. You can create or delete directories, delete files, or get file and directory names using methods on the IsolatedStorageFile object.

There is no mechanism that prevents code from passing IsolatedStorageFile object references to objects that do not have the required permissions or identities. Domain and assembly identities and isolated storage permissions are checked only when a reference to an IsolatedStorage object is made (typically, in the IsolatedStorageFile constructor). Protecting references to IsolatedStorageFile objects is, therefore, the responsibility of the code that uses these references.


Accessing Files and Data in a Store

Having created a store, you can create and open a file so that you can store data in it. To create and open the file, you can use an IsolatedStorageFileStream constructor, passing in the file name, the FileMode.OpenOrCreate enumeration value, and the IsolatedStorageFile object you obtained when you created the store. Then, you can do the things you would typically expect to do with data in a file stream, such as read, seek, and write. You can also open the file for other purposes using the IsolatedStorageFileStream constructor. If you do not pass in a store to the IsolatedStorageFileStream constructor, the constructor will get the user-, domain-, assembly-isolated store for you.

To open a file called “options” in a store that is isolated by user, domain, and assembly and write the string "blue" to the file, you could write the following C# code:

IsolatedStorageFileStream stream;
string data = "blue";
stream = new IsolatedStorageFileStream("options", FileMode.OpenOrCreate);
StreamWriter writer = new StreamWriter(stream);
writer.WriteLine(data);
writer.Close(); // implicitly closes stream


Managing Stores

The isolated storage classes enable you to enumerate and delete isolated storage. The methods for performing these tasks are on the IsolatedStorageFile object. Some operations require you to have the IsolatedStorageFilePermission that represents the right to administer isolated storage and you might need to have operating system rights to access the file or directory. On Microsoft Windows NT and Microsoft Windows 2000, ACL settings on the file can prevent you from accessing isolated storage. TheStoreAdm tool can also be used for storage management tasks.


Enumerating Stores

You can enumerate stores for all isolated stores for the current user. To enumerate stores, you must have the IsolatedStorageFilePermission that specifies AdministerIsolatedStorageByUser.


Deleting Stores

To delete a store, use the Remove method on the IsolatedStorageFile class. No permissions are required for this operation. Any code that can access the store can delete any or all the data inside it.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Detecting File and Directory Changes

[To be supplied.]


Reacting to File System Events

Using the FileSystemWatcher component and language features, you can easily monitor actions occurring within directories on your system or on any computer to which you have network access.

For information about | See
File and directory watching features | Introduction to File System Components
Creating instances of FileSystemWatcher components | Creating FileSystemWatcher Components Instances
Setting properties to determine the behavior of FileSystemWatcher components | Configuring FileSystemWatcher Component Instances
Controlling the flow of file system events on systems with high activity | File Changes on High-Volume Systems
Creating event handlers that will automatically take action when file system events are raised | Creating Handlers for File System Events


Introduction to File System Components

You use the FileSystemWatcher component to monitor a file system and react when changes to it occur. This makes it possible for you to quickly and easily launch business processes when certain files or directories are created, modified, or deleted. For example, suppose you and a group of coworkers are collaborating on a document that is stored on a shared directory on your server. Using the FileSystemWatcher component, you can easily program your application to watch for changes to the shared directory. When a change is detected, the component can run processing that notifies each of the group members via e-mail.

You can configure the component to watch either an entire directory and its contents or a specific file or set of files within a given directory. The FileSystemWatcher component raises an event whenever a file or directory is created, deleted, renamed, or changed in some other way. The types of changes that the component monitors include changes in the directory’s or file’s properties, size, last write time, last access time, and security settings.

Directory-Level Events

When changes occur in a directory or file your application is monitoring, one or more events are raised, stored in a buffer, and ultimately passed to your component for processing. The FileSystemWatcher component gives you access to the following events:

  • Created — raised whenever a directory or file is created.
  • Deleted — raised whenever a directory or file is deleted.
  • Renamed — raised whenever the name of a directory or file is changed.
  • Changed — raised whenever changes are made to the size, system attributes, last write time, last access time, or security permissions of a directory or file.
Note Although some common occurrences, such as copying or moving a file, do not correspond directly to an event, these occurrences do cause events to be raised. When you copy a file, the system raises a Created event in the directory to which the file was copied but does not raise any events in the original directory. When you move a file, the server raises two events: a Deleted event in the source directory, followed by a Created event in the target directory.

Each change to a file in a directory raises a separate event. If a user makes multiple changes to a single file, the application raises a separate event for each change. For example, if you are watching a directory that contains five files and the security permissions for all of the files suddenly change, you will receive five Changed events. If the user renames and assigns new security permissions to each of those files, you will receive 10 event notifications — a Changed event and a Renamed event for each of the five files.

Requirements

You must meet the following restrictions to use the FileSystemWatcher component:

  • You can use the FileSystemWatcher component on computers running either Windows NT version 4.0 or Windows 2000. If you are using Windows 95, you cannot use the FileSystemWatcher component to monitor events on any local or remote computers.
  • You can create FileSystemWatcher components in Web applications and Windows applications built with the Common Language Runtime.

See Also

Creating FileSystemWatcher Component Instances | Configuring FileSystemWatcher Component Instances | Creating Handlers for File System Events | File Changes on High-Volume Systems


Creating FileSystemWatcher Component Instances

You create an instance of the FileSystemWatcher component when you want to build file system monitoring into your application. FileSystemWatcher components allow you to connect to directories and watch for specific changes within them, such as the creation of new files, addition of subdirectories, and renaming of files or subdirectories.

To create an instance of the FileSystemWatcher component programmatically

  1. Create an instance of the FileSystemWatcher base class in code. Your code might look like this:
    [Visual Basic]
Dim myWatcher as New FileSystemWatcher
[C#]
FileSystemWatcher myWatcher = new FileSystemWatcher ();
  2. Configure any other necessary properties for your component. For details, see Configuring FileSystemWatcher Component Instances.

See Also

Configuring FileSystemWatcher Component Instances | Introduction to File System Components


Configuring FileSystemWatcher Component Instances

There are several properties you set for your FileSystemWatcher component instances to determine how they behave. These properties determine what directories and subdirectories the component instance will monitor and the exact occurrences within those directories that will raise events.

Indicating the Directories to Watch

You use two properties to determine what directories the FileSystemWatcher component should watch: Path, and IncludeSubdirectories.

Path indicates the fully qualified path of the root directory you want to watch. This can be in standard directory path notation (c:\directory) or in UNC format (\\server\directory). You can set the Path property on an existing component instance, as shown in the following code:

[Visual Basic]

Dim MyWatcher as New System.IO.FileSystemWatcher
MyWatcher.Path = "c:\"

[C#]

System.IO.FileSystemWatcher MyWatcher = new System.IO.FileSystemWatcher("c:\\");

Or you can use a special constructor for the FileSystemWatcher component that takes the path as a parameter. In that case, your code would look like this:

[Visual Basic]
Dim MyWatcher as New System.IO.FileSystemWatcher("c:\")
[C#]
System.IO.FileSystemWatcher MyWatcher = new System.IO.FileSystemWatcher();
MyWatcher.Path = "c:\\";

The IncludeSubdirectories property indicates whether subdirectories within the root directory should be monitored. If the property is set to true, the component watches for the same changes in the subdirectory as it does in the root.

Specifying the Changes to Watch

In addition to specifying the directories and subdirectories to watch, you can indicate what specific files and types of changes you’re interested in watching for. In part, you determine the changes to watch for by defining handlers for only those events in which you’re interested. For example, if you only want to be notified when files are created, you would only handle the Created event. However, you can set a series of properties that restrict the component’s activities even further.

You can use the Filter property to indicate a wildcard matching pattern that restricts the component to watching only certain files or directories within the root directory. For example, if you want to watch for the creation of text files within the root, you could set the Filter property to “*.txt” and then create a handler for the Created event.

You can use the Target property to indicate whether the component should watch for changes to files within the root, files and directories within the root, or directories only. This narrows the scope of events that the component raises. By default, the Target property is set to watch for changes to both directory and file-level items.

Note Whenever the FileSystemWatcher component records a change in the root directory, it raises an event that is stored in an internal buffer. This buffer has a size limit and might overflow. You can use the Target property to reduce the number of events that are stored in the buffer. The Filter property does not affect the number of events stored in the buffer as it is applied after events are recorded. For more information, see Managing File Changes on High-Volume Systems.

Limiting the Scope of the Changed Event Handler

If one of the events you are handling in your code is the Changed event, you can use the ChangedFilter property to further limit the number of events the component instance will raise. The Changed event has the potential to raise a large number of events, because it will raise an event each time any change is made to the file in question; some standard operations like creating and moving files will actually cause several file change events to be raised as various attributes such as size, last write, and last access are altered.

The ChangedFilter property limits the number of file or directory changes for which the component will raise events. You can set up your component so that it will raise events only when an item in the root directory has its attributes change, when its size changes, when its last write or last access time changes, or when security changes. These values are all part of the ChangedFilter enumeration.

These properties all work together to determine how the FileSystemWatcher component behaves. For example, suppose you want to create a FileSystemWatcher instance that watches a drop directory called “reports” for the creation of new .txt files. You also want to monitor changes to existing reports that rerun dynamically when data changes. You would set up your component as follows:

[Visual Basic]
'Create a component instance and set its root path to the local c:\ drive.
Dim MyWatcher as New FileSystemWatcher
MyWatcher.Path = "c:\"
'Watch for changes only to *.txt files or directories
MyWatcher.Filter = "*.txt"
'Only pay attention to files, not directories or subdirectories
MyWatcher.Target = "File"
MyWatcher.IncludeSubdirectories = "False"
'Filter for Last Write changes
MyWatcher.ChangedFilter = ChangedFilters.LastWrite
[C#]
// Create a component instance and set its root path to the local c:\ drive.
FileSystemWatcher MyWatcher = new FileSystemWatcher();
MyWatcher.Path = "c:\\";
// Watch for changes only to *.txt files or directories
MyWatcher.Filter = "*.txt";
// Only pay attention to files, not directories or subdirectories
MyWatcher.Target = "File";
MyWatcher.IncludeSubdirectories = "False";
// Filter for Last Write changes
MyWatcher.ChangedFilter = ChangedFilters.LastWrite;

To configure an instance of the FileSystemWatcher component

  1. Create an instance of the FileSystemWatcher component. For details, see Creating FileSystemWatcher Components.
  2. Set the Path property to the fully qualified path of the root directory you want to watch.
    Tip You can enter this as a standard file path or a UNC path.
  3. Set any of the optional properties for your instance.
    Scenario | Property | Value
    To watch for changes of a specific file or directory type | Filter | A wildcard filter expression that restricts the monitoring activities of the instance
    To watch for changes to files only, directories only, or both | Target | File, Directory, or Both
    To watch for changes within any subdirectories the root director contains | IncludeSubdirectories | true or false
    To watch for only specific changes to a file or directory when handling the Changed event | ChangedFilter | Attributes, LastAccess, LastWrite, Security, or Size
  4. Enable your component instance by setting the Enabled property to true.

See Also

Creating FileSystemWatcher Component Instances | Introduction to File System Components


Creating Handlers for File System Events

The FileSystemWatcher component raises four events depending on the types of changes that occur in the directory it is watching. These events include:

  • Created — raised whenever a directory or file is created.
  • Deleted — raised whenever a directory or file is deleted.
  • Renamed — raised whenever the name of a directory or file is changed.
  • Changed — raised whenever changes are made to the size, system attributes, last write time, last access time, or security permissions of a directory or file.
    Note You can use the ChangedFilter property to limit the amount of events the Changed event raises. For more information, see Configuring FileSystemWatcher Component Instances.

You can define handlers for these events that automatically call methods in your code when a change occurs. An event handler is a method that is bound to an event for a component, form, or control. Each event handler provides two parameters that allow you to handle the event properly — the sender, which provides an object reference to the object responsible for the event, and the e parameter, which provides an object for representing the event and its information.

For more information on event handlers, see Event Handling.

To create a handler for a FileSystemWatcher event

  1. Create an instance of the FileSystemWatcher component. For details, see Creating FileSystemWatcher Components.
  2. Set the necessary properties for the component. For details, see Configuring FileSystemWatcher Component Instances.
  3. Use the appropriate AddOn method to create an event handler for your component that will call the appropriate procedure when an entry is written to the log.
    To handle this event | Use this method
    Changed | AddOnChanged
    Created | AddOnCreated
    Deleted | AddOnDeleted
    Renamed | AddOnRenamed
    Note For more information on this syntax, see Event Handling.
  4. Create the appropriate procedure that the handler will call and define the code you want to process the entries.
    To handle this event | Create this procedure
    Changed | FileSystemWatcher1_Changed
    Created | FileSystemWatcher1_Created
    Deleted | FileSystemWatcher1_Deleted
    Renamed | FileSystemWatcher1_Renamed

    The following example shows how to create the handler and create a procedure that interacts with the handler’s parameters.

    [Visual Basic]
MyWatcher1.AddOnChanged New System.IO.FileSystemEventHandler _
   (AddressOf Me.MyWatcher1_Created)
Private Sub MyWatcher1_Created(ByVal sender as System.Object, _
   ByVal e as FileSystemEvent)
     Dim watcher As FileSystemWatcher = sender
     Console.WriteLine "File was created in directory: " & watcher.Path
End Sub
[C#]

    5.

See Also

Creating FileSystemWatcher Components  |  Configuring FileSystemWatcher Component Instances  |  Introduction to File System Components


File Changes on High-Volume Systems

Your FileSystemWatcher component has the potential to receive an enormous number of events, particularly if you have set it to watch a very high-traffic directory. This can cause problems, as the FileSystemWatcher component uses an internal buffer of limited size to store the events. If too many changes occur, this buffer might overflow, causing all of the events the component was tracking to be lost.

To avoid this problem, you should do several things. First, you should set your buffer to an appropriate size for the approximate number of events you expect to receive. By default, the buffer is set to a size of 4 KB. A 4-KB buffer can track changes on approximately 80 files in a directory. Each event takes up 16 bytes in the buffer, plus enough bytes to store the name of the file that the event occurred on, in Unicode (2 bytes per character). You can use this information to approximate the buffer size you will need

You reset the buffer size by setting the InternalBufferSize property. If you are using Microsoft Windows 2000, you should increase the buffer size in increments of 4 KB. With any other operating system, you should increase the buffer size in increments that correspond to the operating system’s default page size.

Tip If you are unsure of the default page size for the operating system you are using, the safest way to proceed is to just double the original size of the buffer. This will maintain the original interval needed for your operating system.

In addition to setting an appropriate buffer size, you can use the following three properties to control the amount of information that the buffer receives:

  • Use the Mode property to determine whether the FileSystemWatcher component looks for all changes in a given directory or only changes in a specific file.
  • Use the IncludeSubdirectories property to indicate whether or not you want to include the subdirectories of the directory you are watching. If you turn this off when you don’t need it, you will receive fewer events than when it is turned on.
  • If you have set the FileSystemWatcher component to watch for the Changed event, you can use the ChangedEventFilter property to specify only those events in which you are interested.
Note Although you can also use the Filter property to specify files or subdirectories that you want watched, this property is applied after changes are added to your buffer and will not reduce the buffer size at all. Instead, use the Mode and ChangedEventFilter properties to control the amount of information written to the buffer.

See Also

Configuring FileSystemWatcher Component Instances | Introduction to File System Components


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Grouping Data in Collections

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

What is a Collection?

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Generic Collection Types

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Stack

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Queue

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

List

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Array

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Dictionary

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Struct

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Creating and Manipulating Collection Objects

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Remoting

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Designing Global Applications

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Creating Resource Files

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Encoding Types

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Codepages

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Unicode

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Locating and Using Resources for a Particular Culture

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Formatting Objects for a Specific Culture

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Sorting Data for a Specific Culture

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Obtaining Culture- or Resource-Specific Information

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Switching Locales on the Fly

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Working with Calendars

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Best Practices for Global Application Design

[To be supplied.]


Incorporating Messaging Into Applications

The MessageQueue component allows you to easily incorporate message-based communication into your applications. Using this component and its associated language features, you can send and receive messages, explore existing queues, create and delete queues, and perform a variety of other operations using a simple programming model.

For information about | See
Messaging features | Introduction to Messaging
The language elements used in MessageQueue component programming | MessageQueue Programming Architecture
Finding, creating, and deleting message queues in Server Explorer | Creating, Deleting, and Administering Queues
Creating components that interact with message queues | Creating MessageQueue Components
Sending simple and complex messages to a variety of queue types, requesting acknowledgment, using journals, and sending datasets in messages | Sending and Serializing Messages
Reading messages procedurally, handling asynchronous operations, and peeking at messages in a queue | Reading and Receiving Messages
Encrypting and authenticating the messages you send and receive | Message Queue Security


Introduction to Messaging

Microsoft Message Queuing makes it easy for application developers to communicate with application programs quickly and reliably by sending and receiving messages. Messaging provides you with guaranteed message delivery and a robust, fail-safe way to carry out many of your business processes. For example, suppose you have a retail point-of-sale application that must run 24 hours a day, 7 days a week. If the database system behind the application goes down your sales staff might need to start taking orders manually. Using Message Queuing, you can set up the system so that the orders that cannot be processed during the downtime are automatically put into a queue and processed as soon as the database comes back up.

In the NGWS frameworks, you can use an instance of the MessageQueue component to quickly and easily connect to existing message queues, examine their contents, and send and receive messages.

Interacting with Message Queues

Messaging and messages play a central role in the life of a distributed application. Essentially, interprocess communication between components of a server-based application is heavily dependent on messages (rather than direct calls between components). Reasons include:

  • Robustness — Messages are considerably less affected by component failures, because they are stored in queues and remain there until processed appropriately. Messaging is similar to transaction processing, because message processing is guaranteed.
  • Message prioritization — More urgent or important messages can be received before less important messages, so you can guarantee adequate response time for critical applications.
  • Offline capabilities — Messages are queued when they are sent and remain that way until they are successfully delivered. Developers can continue to perform operations when access to the necessary queue is unavailable for whatever reason. Each operation queues a message, which is sent to the queue when it becomes available. In the meantime, additional operations can proceed as if the message had already been processed, because the message delivery is guaranteed when the network connection is restored.
  • Transactional messaging — You can couple several related messages into a single transaction, ensuring that the messages are delivered in order, delivered only once, and are successfully retrieved from their destination queue. If any errors occur, the entire transaction is cancelled.
  • Security — The Message Queuing technology on which the MessageQueue component is based uses Windows® 2000 security to secure access control, provide auditing, and encrypt and authenticate the messages your component sends and receives.

Requirements

Your system must meet the following requirements to develop MessageQueue components:

  • To interact with queues programmatically, you must have Message Queuing installed on your client computer. Message Queuing is installed as part of Microsoft BackOffice.
    Note When you install Message Queuing, you select the primary Message Queuing server to which you want to connect. You cannot view or manipulate queues that reside on another primary Message Queuing server.
  • Message Queuing can be run in either a domain or a workgroup environment. In the context of Message Queuing, a domain environment includes domain controllers that provide a directory service such as Active Directory, and a workgroup environment is any environment that does not provide such a directory service.
  • You must have either Windows 2000 Server or Workstation version installed to work with Microsoft Message Queuing. You can use MSMQ 1.0 with Windows NT version 4.0.
  • You can create MessageQueue components in both Web applications and Windows applications built with the NGWS runtime.

See Also

Message Queues and Messaging Technology Backgrounder | Message Queuing and E-mail Systems | Creating MessageQueue Components


Message Queues and Messaging Technology Backgrounder

Microsoft Message Queuing makes it easy for application developers to communicate with application programs quickly and reliably by sending and receiving messages. If you are unfamiliar with MessageQueue technology, this topic provides a brief overview of essential terms and concepts.

Key Concepts in Messaging

A message is a unit of data sent between two computers. A message can be very simple, consisting of just a string of text, or more complex, possibly involving embedded objects.

Messages are sent to queues. A message queue is a container that holds messages while they are in transit. The message queue manager acts as the middleman in relaying a message from its source to its destination. A queue’s main purpose is to provide routing and guarantee the delivery of messages; if the recipient is not available when a message is sent, the queue holds the message until it can be successfully delivered.

Message Queuing, Microsoft’s messaging technology, provides messaging and message queue facilities for any application and among any combination of computers that have Microsoft Windows installed, regardless of whether they are on the same network or online at the same time. A Message Queuing network is any set of computers that are enabled to send messages back and forth to one another. Different computers in the network play different roles in ensuring that messaging proceeds smoothly. Some provide routing information to determine how messages are sent, some hold key information for the entire network, and some simply send and receive messages.

During Message Queuing setup, an administrator makes decisions about which servers can communicate with each other and sets up special roles for specific servers. The computers that make up this Message Queuing network are called sites, and they are connected to one another by site links. Each site link has an associated cost, determined by the administrator, that indicates how quickly messages can be passed across it.

The Message Queuing administrator also sets up one or more computers in the network to act as routing servers. A routing server makes decisions about how a message is delivered by looking at the cost of various site links and determining the quickest and most efficient way to deliver the message across multiple sites.

The following picture shows a typical configuration of Message Queuing sites and how they interact:

Message Routing Between Sites

All of these decisions occur behind the scenes; you do not need to worry about the routing of your messages. You can be assured that Message Queuing will handle the delivery of your messages in the most efficient manner possible.

Types of Queues

There are two main categories of queues — queues that you or other users of the network create, and system queues. User-created queues can be any of the following:

  • Public queues are replicated throughout the Message Queuing network and can potentially be accessed by all of the sites connected by the network.
  • Private queues are not published across the entire network. Instead they are displayed only on the local computer that contains them. Private queues can be accessed only by applications that know the full path name or label of the queue.
  • Administration queues contain messages acknowledging the receipt of messages sent within a given Message Queuing network. You specify the administration queue you want your MessageQueue components to use, if any.
  • Response queues contain response messages that are returned to the sending application when the message is received by the destination application. You specify the response queue you want your MessageQueue components to use, if any.

System-generated queues generally fall in one of the following categories:

  • Journal queues optionally store copies of messages that you send and copies of messages removed from a queue. A single journal queue on each Message Queuing client stores copies of messages sent from that computer. On the server, a separate journal queue is created for each individual queue. This journal tracks messages removed from that queue.
  • Dead letter queues store copies of undeliverable or expired messages. If the expired or undeliverable message was a transactional message, it is stored in a special kind of dead-letter queue called a transaction dead letter queue. Dead letters are stored on the computer on which the message expired. For more information on time-out periods and expired messages, see Default Message Properties .
  • Report queues contain messages that indicate the route a message took to its destination, and can also contain test messages. There can be only one report queue per computer.
  • Private system queues are a series of private queues that store administrative and notification messages that the system needs to process messaging actions.

Most of the work you do in your NGWS frameworks applications will involve accessing public queues and their messages. However, you will most likely use several different kinds of the system queues in your day-to-day operations, depending on your application's need for journal recording, acknowledgment, and other special processing.

Synchronous and Asynchronous Communication

Queue communication can be asynchronous. This means that the person who wants to receive a message can call the BeginReceive method for any given queue and then immediately go on to other tasks without waiting for a reply. This is very different from what is known as synchronous communication.

In synchronous communication, the sender of a request must wait for a response from the intended receiver before performing other tasks. The amount of time that the sender must wait depends entirely on the amount of time it takes for the receiver to process the request and send a response.

For more information on asynchronous processing in message queues, see Asynchronous Message Processing.

Message Security

You can help secure the content of messages that you send and receive in the following ways:

  • You can use authentication to verify the source of messages your applications receive.
  • You can use encryption to ensure that your messages cannot be read or used by unauthorized persons.
  • You can use access control rights to restrict users from sending messages to, or reading messages from, a particular queue on a computer.
  • You can use auditing to record which users attempt to access Message Queuing objects, the type of operation attempted, and if that access succeeded or failed.

For more information, see Message Queue Security.

Installing Message Queuing

You must have Message Queuing installed on your development computer if you want to work with message queues either in code. You can find instructions on installing Message Queuing in your Windows 2000 or Windows NT documentation. When you install, you must specify whether you are creating a new primary Message Queuing server that will act as the hub of a new network, an independent client of an existing network, or a dependent client of an existing network. In most cases you will not create a new network, but will join the existing Message Queuing network for your company or workgroup.

Where To Go For Background Reading

The remaining topics in this section provide you with the information you need to use Message Queuing components in the NGWS frameworks. If you need more information on messaging technologies outside the context of the NGWS frameworks, there are many sources of information available to you in MSDN and on Microsoft's Web site:

For information about | See this topic in your Windows 2000 Server documentation
Setting up a Message Queuing network | Installing Message Queueing
General Message Queuing concepts | Understanding Message Queuing
Fine-tuning Message Queuing performance | Administering Message Queuing

See Also

Message Queuing and E-mail Systems| Incorporating Messaging Into Applications


MessageQueue Programming Architecture

The MessageQueue component uses these portions of the NGWS frameworks namespaces:

If you are creating your MessageQueue components in code, you may need to add a reference to System.Messaging to your build command.

Key Programming Elements in the MessageQueue Base Class

You can use the following methods to interact with an instance of the MessageQueue component:

  • Use the Create method to create a new message queue using the path you specify, and use the Delete method to delete an existing queue.
  • Use the Exists method to see whether a particular message queue exists.
  • Use the GetPublicQueues method to locate message queues in your Message Queuing network.
  • Use the Peek or BeginPeek method to look at messages in a particular queue without removing the messages from the queue.
  • Use the Receive and BeginReceive methods to retrieve the message at the front of the specified queue and remove it from the queue.
  • Use the Send method to send a message to the specified queue.

You can get detailed information about the configuration of your message queue network by using Message Queuing Explorer, which is installed automatically with Message Queuing.

See Also

Incorporating Messaging Into Applications | Adding and Removing References | Creating MessageQueue Components


Creating, Deleting, and Administering Queues

You can use the MessageQueue component in the NGWS frameworks to create new queues on the Message Queuing network, delete queues, and perform some administrative actions on existing queues.

For information about | See
Creating your own message queues within an existing network | Creating Queues
Providing identifying elements for queues, or linking your MessageQueue components to existing queues | Queue Reference Recommendations
Deleting queues from the network | Deleting Queues
Clearing the contents of a queue | Purging a Queue's Contents


Creating Queues

You create a queue to provide a resource with which your messaging components can interact. For example, suppose you are building an order entry system that places orders into queues as they are received from the sales force or from direct customer interaction on a Web site. You might begin by creating an OrderEntry queue on your Message Queuing enterprise. All of the components you create to process orders will interact with this queue.

You can create a queue by using the Create constructor from your code. You can use the Windows 2000 Computer Management window to verify that your queue has been created successfully.

You can create a public queue on your own computer or any Message Queuing computer to which you have domain or enterprise administrative access rights. You can also create private queues on your local computer only. For more information on access rights, see Message Queue Security, or see "Access Control for Message Queuing" in the documentation for the Computer Management dialog box.

Note It is important to note the difference between creating a queue and creating an instance of the MessageQueue component. When you use the Create constructor, you are creating a new queue in the Message Queuing network, rather than a component in your project or application. When you create an instance of the MessageQueue component, you create a component inside your project that references an external queue.

You can also configure an installation component to create or locate an existing queue when your application is deployed. For more information, see Custom Actions Management in Deployment.

To create a public queue programmatically

  • Use the Create method to create a public queue on your computer. Your code should look like this:
    [Visual Basic]
     MessageQueue.Create("myMachine\MyQueue")
[C#]
     MessageQueue.Create (@"myMachine\MyQueue");

To create a private queue programmatically

  • Use the Create method, specifying the necessary syntax, Private$, in order to create a private queue on your computer. Your code should look like this:
    [Visual Basic]
      MessageQueue.Create("myMachine\Private$\MyPrivateQueue")
[C#]
      MessageQueue.Create (@"myMachine\Private$\MyPrivateQueue");

To verify that your message queue was created

You can use the Windows 2000 Computer Management window to verify that your message queue was created.

  • To see Message Queuing information in Windows 2000, follow these steps:
    • Right-click My Computer on the desktop, and then click Manage.
    • In the dialog box that appears, expand the Services and Applications node, and then expand Message Queuing. You can find your queue and its messages in this node.

See Also

Creating, Deleting, and Administering Queues | Creating MessageQueue Components


Queue Reference Recommendations

If you create an instance of the MessageQueue component, you need to indicate what queue you want the component to communicate with. There are three ways to refer to a queue:

  • By path—the path to a queue uniquely identifies the computer and queue name for the queue in which you're interested.
  • By format name—a unique identifier for the queue that is generated by MSMQ when the queue is created or generated later by the application.
  • By label—a descriptive and potentially non-unique name for the queue that is assigned by the queue administrator when the queue is created.

When you create a new queue, rather than a new instance of the MessageQueue component, you must use the path. Format name and label are assigned by the Message Queuing system after the queue is created. When you refer to an existing queue in code or from the toolbox, however, you can choose which method of referencing you want to use. Whatever method you choose is stored in the component's Path property.

Using Path to Refer to a Queue

A queue path takes the form servername\queuename. Paths to a queue are always unique. The following table lists the path information used for each type of queue:

Queue Type | Syntax Used in Path
Public queue | MachineName\QueueName
Private queue | MachineName\Private$\QueueName
Journal queue | MachineName\QueueName\Journal$
Machine journal queue | MachineName\Journal$
Machine dead letter queue | MachineName\Deadletter$
Machine transactional dead letter queue | MachineName\XactDeadletter$

The following are a few caveats about using a path to refer to your queues:

  • When the domain controller on a Message Queuing server receives an operation for a queue that is referenced by path, it must resolve that path and determine the format name of the queue in question. This may result in slightly slower performance than if you refer to the queue by format name directly.
  • If you are designing your component to be used as a Web service, you will get the best performance results by using the format name method of referring to queues rather than the path method.
  • You cannot refer to a queue by path if you want to send messages to it when the queue is disconnected. Disconnected messages must be sent using the format name.

You can retrieve the path for a queue by querying two separate properties in the MessageQueue base class—the MachineName property and the QueueName property.

Referencing a queue by Path looks like this:

[Visual Basic]
MessageQueue1.Path=”.\MyQueue”
[C#]
MessageQueue1.Path= @”.\MyQueue”;

Using Format Name to Refer to a Queue

Format names take the form of a string indicating whether a queue is public or private, followed by a generated GUID for the queue and other identifiers as necessary. The following table lists the path information used for each type of queue:

Queue Type | Syntax Used in Format Name
Public queue | PUBLIC=QueueGUID
Private queue | PRIVATE=MachineGUID\QueueNumber
Journal queue | PUBLIC=QueueGUID;JOURNAL or PRIVATE=MachineGUID\QueueNumber;JOURNAL
Foreign queues | DIRECT=AddressSpecification\QueueName

You do not assign a format name to a queue; instead, the queue manager generates this value when the queue is created. Referencing a queue by its format name is the most direct way to access a queue, because the domain controller on the server does not have to interpret the reference as it does when you refer to the queue by path.

The following are a few hints about referencing your queues by format name:

  • If you intend to use your component in a Web service or as part of a remote Web call, you should use format name rather than path to refer to your queues, because it is much faster for the queue manager to resolve the format name.
  • If you intend to send messages to a disconnected queue, you must refer to the queue by format name rather than path, because the path will not be available to resolve with if the queue is offline.
  • Be aware that format names may become invalid when your network topography changes or when a queue is deleted and recreated.

You can retrieve a format name for a queue by querying the FormatName property in the MessageQueue class.

Referencing a queue by format name looks like this:

Visual Basic]
MessageQueue1.Path=”.\MyQueue”
[C#]
MessageQueue1.Path= @”.\MyQueue”;

Using Label to Refer to Queues

You can also refer to a queue by its label, which is a descriptive text label given to the queue by its administrator. Labels are not always unique, so you will receive an error when you try to connect to a specific queue using its label and a name conflict exists.

Labels can be useful in a situation where you know you are going to be moving a queue from one computer to another. If you refer to the queue by label only, all of your operations will continue to work successfully after the queue has been moved to its new location, provided that there are no other queues with that label on the new computer. If there are, the Send method will produce an error.

You can retrieve the label for a queue by querying the Label property in the MessageQueue class.

Referencing a queue by label name looks like this:

[Visual Basic]
MessageQueue1.Path=”.\MyQueue”
[C#]
MessageQueue1.Path= @”.\MyQueue”;

See Also

Creating, Deleting, and Administering Queues | Setting Queue References | Creating Queues | Creating MessageQueue Components


Setting Queue References

You have three choices in the way you refer to a queue in your project—you can refer to it by the path to the queue, the queue's automatically generated format name, or by the non-unique descriptive label of the queue. Each of these ways of referencing a queue has its advantages and disadvantages. For more information, see Queue Reference Recommendations.

Note When you set a queue reference, you are merely linking the MessageQueue component you created to the existing queue. You are not changing the way the actual queue itself is identified on the server.

To set a queue reference

  1. Create an instance of the MessageQueue component. For details, see Creating MessageQueue components.
  2. Set one of the following properties on the MessageQueue component:
    • To refer to the queue by its path, set the Path property.
    • To refer to the queue by its format name, set the FormatName property.
    • To refer to the queue by its label, set the Label property.

See Also

Creating, Deleting, and Administering Queues | Queue Reference Recommendations.


Deleting Queues

You can delete queues that exist on your local computer programmatically. You can delete any public or private queues on your local computer, provided that you have the appropriate access rights. To delete a local queue, you must have either full control access rights assigned to you by the queue's owner, or explicit Message Queuing access rights to delete queue-related items from that machine. You cannot delete queues on other computers.

Note You do not need to purge a queue's messages before deleting the queue. When you delete a queue, all of its messages are deleted with it. These messages are not sent to a dead letter queue and cannot be retrieved. For more information on purging, see Purging a Queue's Contents.

To delete a message queue programmatically

  • Use the Delete method to delete the queue you want. Your code should look something like this:
    [Visual Basic]
MessageQueue.Delete("myMachine\MyQueue")
[C#]
MessageQueue.Delete (@"myMachine\MyQueue");
    Note This example shows the queue referenced by path. You can also refer to the queue you want to delete by format name or by label. For more information, see Queue Reference Recommendations.

See Also

Creating, Deleting, and Administering Queues | Queue Reference Recommendations| Purging a Queue's Contents


Purging a Queue's Contents

You can use the Purge method to clear the contents of any queue to which you have access rights in the Message Queuing system. For example, suppose you use a journal queue on your local Message Queuing client to record copies of all messages you send out. When that journal reaches its maximum size, you might use the Purge method to clear out the items you no longer need.

Note Messages stored in journal and dead letter queues count against the quota for the computer where the queue resides. When the quota is reached, these queues stop accepting new messages. It is important to periodically clear messages that are no longer needed from journal and dead letter queues if you are using these in your application.

You must have Receive rights in Message Queuing to delete the contents of a queue. When you purge a queue, all of the messages it contains are discarded without being sent. You can purge a queue's contents from code.

To purge a queue's contents programmatically

  • Use the Purge method to clear the contents of the appropriate queue. Your code should look something like this:
    [Visual Basic]
Dim MessageQueue1 as New System.Messaging.MessageQueue
MessageQueue1.Purge("myMachine\MyQueue")
[C#]
System.Messaging.MessageQueue MessageQueue1 = new System.Messaging.MessageQueue();
MessageQueue1.Purge (@"myMachine\MyQueue");

See Also

Creating, Deleting, and Administering Queues | Creating Queues


MessageQueue Component Creation

You create MessageQueue components when you want to build messaging functionality into your application. MessageQueue components allow you to connect to existing queues, send and receive messages, and otherwise add communication services to your application using a very small amount of code. For example, suppose you are building an order entry system that places orders into queues as they are received from the sales force or from direct customer interaction on a Web site. You might begin by adding an instance of the MessageQueue component to your project and configuring it to interact with an existing OrderEntry queue on your Message Queuing server.

MessageQueue components have no visual user interface.

For instructions on how to create MessageQueue components, see Creating MessageQueue Components .

See Also

Creating MessageQueue Components


Creating MessageQueue Components

  • You can create an instance of the MessageQueue component in code.

For an overview of the issues of creating MessageQueue components, see MessageQueue Component Creation.

To create an instance of the MessageQueue component programmatically

  1. Create an instance of the MessageQueue class in code. You code might look like this:
    [Visual Basic]
Dim myMQ as New System.Messaging.MessageQueue
[C#]
System.Messaging.MessageQueue myMQ =
   new System.Messaging.MessageQueue();
  2. Set the Path property to determine which existing queue you want your component to reference. Your code might look like this:
    [Visual Basic]
myMQ.Path = ".\MyNewQueue"
[C#]
myMQ.Path = @".\MyNewQueue";
    Note You can refer to the queue by the path to the queue, the queue's automatically generated format name, or by the non-unique descriptive label of the queue. Each of these ways of referencing a queue has its advantages and disadvantages. For details, see Queue Reference Recommendations.
  3. Configure any other necessary properties for your queue. For details, see Message Queue Configuration Properties .

See Also

MessageQueue Component Creation | Message Queue Configuration Properties | Accessing Server Explorer


Message Queue Configuration Properties

There are a variety of properties you can manipulate on the queues and MessageQueue components you create. You can define these properties to help you set values that guide the queue's behavior, or query these properties to retrieve information about a queue with which your component interacts.

Properties That Reference a Queue

You use the Path property to determine what queue your component references. The path can be identified in one of three ways—by the physical path to the queue, by the queue's format name, or by the queue's descriptive label. For a full explanation of the differences between path, format name, and label, see Queue Reference Recommendations.

Queues can also be classified according to a category. You can use the Category property to determine the type of queue you're working with. Category is a GUID that is defined by the owner of the queue. You can either use a GUID-generation tool or make up your own number for the GUID. Category GUIDs do not have to be unique except within a single category. For example, you might define the same Category for all of your order processing queues. This would allow you to retrieve all of them by that GUID.

Properties That Determine What Type of Data You Can Send

You use the Formatter property to determine how your message content will be serialized into a message before being sent to a queue and extracted from a message after being received from a queue. Setting the Formatter property determines what type of content you can send in a message. For more information, see Message Serialization.

Properties That Control Interaction With a Queue

You can use the following properties to control how your component interacts with a queue:

  • You can use the DenySharedReceive property to indicate that only one component at a time may look at the messages in a queue. This can be extremely useful in multi-user situations. When DenySharedReceive is set, the first component to peek at or receive messages off a queue is the only component that may perform these operations at that time. When the first component finishes its instance of that queue, it releases the queue either through garbage collection or by calling Close. For details about garbage collection, see Reference Counting, Garbage Collection, and Object Lifetime.
  • You can use the CanRead and CanWrite properties to determine if you have read and write access to a particular queue, or to set a queue to allow read and write access.
  • You can use the MaximumQueueSize and MaximumJournalSize properties to determine the capacity, in kilobytes, of messages that a queue can receive. Once this maximum has been reached, messages are no longer accepted by the queue. By default, the maximum queue size defaults to the queue size set by the system administrator for Message Queuing. If no limit has been set, default queue size is infinite.
  • You can use the UseJournalQueue property to determine whether copies of the messages retrieved from the queue are added to a journal queue.

For a full list of the properties associated with the MessageQueue class, see MessageQueue Members.

See Also

Creating MessageQueue Components


Sending and Serializing Messages

A message in Microsoft Message Queuing consists of a body and a set of properties. Message bodies can contain text or any form of binary information, and can be encrypted. In the NGWS frameworks, you set a message's body and properties programmatically, either by setting property values in the Property window or by setting the required values in code. A message, including its body and all specified properties, cannot be bigger than 4 megabytes (MB) in size.

You can create and send messages with your MessageQueue components. Messages created in the NGWS frameworks can be sent to any public, private, journal, dead letter, or transactional queue.

For information about | See
Sending simple messages as parameters of the Send method | Sending Simple Messages
Sending messages using the Message object to fine-tune the message content and processing | Sending Complex Messages
Sending a message to a queue that is not currently online | Sending Messages to Disconnected Queues
Requesting acknowledgment messages to indicate whether your message successfully reached its destination | Requesting Acknowledgment for a Message
Storing copies of the messages you send in a journal queue | Storing Messages in a Journal Queue
Sending a series of messages together in a transaction, in which all of the messages are either successfully delivered or not sent. | Processing Messages Transactionally
Sending a dataset as a message | Serializing a DataSet into a Message
Setting default properties to be applied to all messages you send | Default Message Properties
Serializing objects data into and de-serializing them out of a message | Message Serialization


Sending Simple Messages

Using an instance of the MessageQueue component, you can send simple messages to a queue in as little as two lines of code. When you send a simple message, you must perform the following actions:

  • Create a connection to the queue to which you want to send a message.
  • Specify the appropriate formatter for the data you want to send. The formatter controls what type of data can be sent in a message and how that data is persisted. In simple cases, the default formatter is acceptable. For more information, see Message Serialization.
  • Provide the data to be sent.

Creating a Connection to the Queue

After you decide which queue you want to communicate with, you need to create an instance of the MessageQueue component that references the queue you want to use. You can create this component by using the MessageQueue constructor.

To create a connection to the queue you want to communicate with

  1. Create an instance of the MessageQueue component. For details, see Creating MessageQueue Components.
  2. Use the component's Path property to connect to the queue with which you want to communicate by the queue path, format name, or label.
    Note If you created your component from Server Explorer, the Path property is set automatically to the queue path for that queue.

Providing the Data to Be Sent

Each MessageQueue component contains a series of default properties that are applied to all messages sent by that component, unless you specify otherwise in an individual message. In the simplest scenario, you can send a simple message to the queue using the default properties set for the component. For more information on these properties, see Default Message Properties.

You use the Send method to specify a message and send it to the queue. You can send objects, primitive data types, streams, and other kinds of data in a simple message.

The MessageQueue component takes the data you specify in the Send method's argument, persists it into a message, and sends the message to the specified queue.

Note You can also use the Message object to send far more complex messages to a queue. In addition, you can send messages as part of a transaction. For more information, see Sending Complex Messages.

To send a simple message

  1. Accept the default value for the Formatter property, or set the property to XMLMessageFormatter if you have changed the default value.
  2. Use the Send method to send a simple message to your queue, specifying the message as an argument of the method.

    Your code to send a simple message might look like this:

    [Visual Basic]
'Create a connection to the queue
Dim MessageQueue1 as new MessageQueue ("YourMachine\YourQueue")
'Send an integer
MessageQueue1.Send(1)
'Send a string
MessageQueue1.Send("Hello world")
'Persist an object and send it to the queue
MessageQueue1.Send(System.DateTime.Now)
[C#]
// Create a connection to the queue
MessageQueue mq = new MessageQueue (@"YourMachine\YourQueue");
// Send an integer
mq.Send (1);
// Send a string
mq.Send ("Hello world");
// Persist an object and send it to the queue
mq.Send (System.DateTime.Now);

See Also

Sending and Serializing Messages | Creating MessageQueue Components | Sending Complex Messages | Sending Messages Asynchronously | Transactional Message Processing


Sending Complex Messages

In addition to sending a simple message by specifying your message in the Send method's argument, you can gain more control over your messages by explicitly creating a Message object, rather than letting the shared Send method create and send one for you. By manipulating the properties of the resulting Message object, you can create more complex messages and exert more control over the way your messages are handled.

When you send a complex message, you must first create a connection to the queue with which you want to communicate, and then specify the data to send. However, as part of specifying the data to send, you will create an instance of the Message class, set the properties you need, and fine-tune the delivery mechanisms before sending your message. As with simple messages, the system persists your message object and sends it to the queue you specified.

For more information on the properties you can use to fine-tune your Message object, see Message Members.

Note Using the Message object, you can send messages as part of a transaction. For more information, see Transactional Message Processing.

To send a complex message using the Message class

  1. Create an instance of the MessageQueue component and set its Path property to the queue to which you want to refer. For details, see Creating MessageQueue Components.
  2. Create an instance of the Message object.
  3. Set the body of the message and specify any properties you want to change from their default values.
  4. Use the Send method to send your object to the queue.

    When you're finished, your code might look like this:

    [Visual Basic]
Dim newMessage as System.Messaging.MessageQueue.Message
newMessage = new Message("Hello again")
newMessage.Label = "This is the label"
MessageQueue1.Send(newMessage)
[C#]
System.Messaging.MessageQueue.Message newMessage;
newMessage = new System.Messaging.MessageQueue.Message("Hello again");
newMessage.Label = "This is the label";
MessageQueue1.Send (newMessage);

See Also

Sending and Serializing Messages | Creating MessageQueue Components | Sending Simple Messages | Sending Messages Asynchronously | Transactional Message Processing


Sending Messages to Disconnected Queues

There are two situations in which messages cannot readily be delivered to their queues: when the machine on which the queue resides is not available, or when the domain controller needed to route your message is not available. Message Queuing allows you to handle these situations so that if you are disconnected from the network or a necessary machine or controller is not available, you can continue to send messages. In these cases, the messages are temporarily stored on a queue on the local machine or a machine along the delivery route until the necessary resources are back online for delivery to be completed.

For example, suppose you have a central queue that records orders sent by your on-the-road sales force. The sales force works in disconnected mode much of the day, recording order information from customer sites, and dials in once a day to transfer all of this information to the central queue. Because messages can be sent to queues when the sender is disconnected, the sales force can send their messages immediately upon recording the customer's information, and the system caches those messages until the nightly call is placed.

Sending a message to a disconnected queue is almost identical to the process of sending a message to an available queue. You do not have to perform any special configuration in order for your component to store messages in a temporary queue when the queue to which you are sending is not available. There are two main differences to be aware of:

  • If the queue is unavailable but the domain controller is working, you must identify the queue you want to send to by the queue's format name, rather than the path name. When a queue is disconnected, the domain controller for the queue cannot resolve the path name correctly. For more information on format names, see Queue Reference Recommendations.
  • If you set a time-out interval on your message send operation, the time-out period will begin to count down as soon as the message is sent. This means that while the message sits on your local machine waiting for the queue to become available, the time-out period is active and the expiration time is approaching. If you want to use the time-out period, you must either set it to a large enough interval to allow for delivery or request negative acknowledgment for the message so you know if expiration has occurred. For more information on requesting acknowledgment when a message cannot be delivered, see Requesting Acknowledgment for a Message.

To send a message to a disconnected queue

  1. Create an instance of the MessageQueue component. For details, see Creating MessageQueue Components.
  2. Set the Path property for the component to the format name of the queue to which you want to send a message.
  3. Send your message normally.
    • For instructions on sending simple messages, see Sending Simple Messages

      – or –

    • For instructions on sending messages using the Message object, see Sending Complex Messages.

See Also

Sending and Serializing Messages | Requesting Acknowledgment for a Message | Default Message Properties | Sending Simple Messages | Sending Complex Messages | Creating MessageQueue Components


Message Acknowledgment

You can set up your MessageQueue component to generate acknowledgment messages that tell you whether the message was successfully delivered. There are two main types of acknowledgment you can receive:

  • Acknowledgment that the message reached its destination queue.
  • Acknowledgment that the message was removed from the queue by the destination application.

Within each of these scenarios, you can also receive positive or negative acknowledgment. In positive acknowledgment, you receive an acknowledgment message when your message successfully reaches its destination queue or application. In negative acknowledgment, you receive an acknowledgment message when your message fails to reach its destination queue or application. A message might fail to reach its destination if its time-out period expires or if the message can't be authenticated.

As with most operations in Message Queuing, acknowledgments are handled via sending a new message to a queue. In this case, the acknowledgment message is sent from the target queue to a special type of queue called an Admin queue. Acknowledgment messages differ from standard messages in that they contain no attached body; only the information in the message header is of interest in an acknowledgment.

Note In the NGWS frameworks, acknowledgment message are sent to whatever queue you specify in the AdministrationQueue property. You determine the type of acknowledgment you want to receive by setting the AcknowledgeType property to one of its predetermined values.

You can check acknowledgment messages in the same way you check any message on a queue — by peeking at the acknowledgment queue or removing messages from it. For more information, see Reading and Receiving Messages.

For instructions on setting up acknowledgment, see Requesting Acknowledgment for a Message .

See Also

Sending and Serializing Messages | Requesting Acknowledgment for a Message | Reading and Receiving Messages |


Requesting Acknowledgment for a Message

You can request positive acknowledgment, negative acknowledgment, or a combination of acknowledgment types on the messages sent by your MessageQueue components. For an overview of acknowledgment types, see Message Acknowledgment.

To request acknowledgment of a message

  1. Create an instance of the MessageQueue component and set its Path property to the queue you want to reference. For details, see Creating MessageQueue Components.
  2. Create the message you want to send. For details, see Sending and Serializing Messages.
  3. Set the AdministrationQueue property for your Message object to the queue to which you want to send acknowledgment messages.
  4. Set the Message object’s AcknowledgeType property to the appropriate value:
    • If you want acknowledgment when a message reaches or fails to reach the queue to which it was sent, set the property to FullReachQueue.
    • If you want acknowledgment when the message reaches or fails to reach the destination application, set the property to FullReceive. Note that this acknowledgment is generated by the queue manager, rather than by the destination application.
    • If you want acknowledgment only if a message you send fails to reach the destination queue, set the property to NotAcknowledgeReachQueue.
    • If you want acknowledgment only if a message you sent cannot be retrieved by its destination application, set the property to NotAcknowledgeReceive.
  5. Send the message, and then check the queue you specified in the AdministrationQueue property for the appropriate acknowledgment. For details, see Reading and Receiving Messages.

To prevent acknowledgment messages from being sent

  • Set the AcknowledgeType property for your component to None.

See Also

Sending and Serializing Messages | Message Acknowledgment | Creating MessageQueue Components | Reading and Receiving Messages | Sending and Serializing Messages |


Message Queue Journal Storage

Journal queues allow you to save copies of messages as they are processed. You can store copies of the messages you send from your computer in the local machine journal queue, or you can store copies of messages removed from a queue in that queue's journal on the server.

Journal storage can be useful if you need to re-send a message at a later point. For example, suppose you send a message to a queue and then receive negative acknowledgment that it could not be delivered. Acknowledgment messages do not contain the body of the original message; instead they give you some header information and a correlation ID that corresponds to the original message. If you have journal recording turned on, you can use this correlation ID to locate the original message in your machine's journal queue, recreate the message object, and resend it.

Note You can use the ReceivebyCorrelationID or the PeekbyCorrelationID methods to retrieve a message by its correlation ID.

There are a few caveats you should note about journal queues:

  • There is one global journal queue per machine. All messages sent from that machine will be recorded in that journal queue, whether they are delivered successfully or not. In addition, each queue has its own associated journal queue.
  • Journal queues have a maximum size, called a quota. This size is limited by the disk capacity of the computer on which the queue resides. After the queue capacity is reached, messages that would have been sent to the journal queue are no longer saved in the system. No errors are raised when this occurs. It is important that you periodically purge your journal queues to prevent this from occurring. For details, see Purging a Queue’s Contents.
  • Journal queues are silent—that is, they cannot create acknowledgment messages of their own, send removed messages to dead letter queues, or process time-outs.

For instructions on setting up journal storage for your messages, see Storing Messages in a Journal Queue.

See Also

Sending and Serializing Messages | Storing Messages in a Journal Queue | Purging a Queue’s Contents


Storing Messages in a Journal Queue

There are two ways you can set up the use of journal queues for sent messages—you can set a default property for your MessageQueue component so that all messages sent by it will use journals, or you can establish journal queue recording for individual messages you are sending.

For an overview of journal storage, see Message Queue Journal Storage.

To use journals for all messages sent by an instance of the MessageQueue component

  1. Create an instance of the MessageQueue component and set its Path property to the queue to which you want to refer. For details, see Creating MessageQueue Components.
  2. Set the UseJournalQueue property for your MessageQueue component to true.
    Tip You can do this in code by setting the UseJournalQueue property on the DefaultPropertiestoSend class for your component to true.

To set journal recording for an individual message

  1. Create the message you want to send.
  2. In code, set the UseJournalQueue property to true. Your code might look like this:
    [Visual Basic]
Message1.UseJournalQueue = True
[C#]
Message1.UseJournalQueue = true;

To set journal recording for messages removed from a queue

  1. Create an instance of the MessageQueue component and set its Path property to the queue to which you want to refer. For details, see Creating MessageQueue Components.
  2. Set the UseJournalQueue property for the MessageQueue component that references that queue to true. Your code might look like this:
    [Visual Basic]
MessageQueue1.UseJournalQueue = True
[C#]
MessageQueue1.UseJournalQueue = true;

To set the maximum size for a journal queue

  1. Create an instance of the MessageQueue component and set its Path property to the journal queue you want to refer to. For details, see Creating MessageQueue Components.
  2. Set the MaximumJournalSize property to the capacity, in kilobytes, that you want to use for your journal queue.

See Also

Sending and Serializing Messages | Message Queue Journal Storage | Purging a Queue's Contents | Default Message Properties | Requesting Acknowledgment for a Message | Sending and Serializing Messages | Creating MessageQueue Components


Default Message Properties

When you create an instance of the MessageQueue component, you have the option of setting a series of default properties that all messages sent by that component will use. This allows you to save time when defining your messages, and to have more control over the Send method without the need to create and configure Message objects.

You set default message properties by setting values in the DefaultPropertiestoSend section of the Properties window. These default properties control features such as whether acknowledgment messages are generated and where they are sent, how your messages are encrypted and authenticated, the message priority, and the time-out period for your messages.

This topic provides an overview of how you might set some of the more critical properties in this area. For a full description of the default properties, see DefaultPropertiesToSend Members.

Message Priority

Setting a default priority for your messages affects the order in which they are sent to their destination queues. You set the Priority property to determine the default priority for messages sent by your component. Priority ratings default to normal, with settings for lower and higher priorities.

The priority setting for a message determines where the message is placed in the queue. Messages sent with a higher priority are placed higher in the queue, and messages with a lower priority are placed lower in the queue. When the queue receives a set of messages with an identical priority setting, the messages are arranged in the queue according to the time when they were sent.

Note You cannot set priority on messages that are being sent to transactional queues, because the order in which messages are processed on a transactional queues are determined by the transaction itself.

Acknowledgment and Response Properties

There are several default properties you can set that determine whether responses and acknowledgments will be generated for your component’s messages and how these responses will be handled. The properties you can set as a default for all messaging operations include:

  • You can use the AcknowledgeType property and the AdministrationQueue properties to determine whether acknowledgment messages should be generated when your messages reach or fail to reach their destination queue, or when the messages are removed from the queue by the target application. For more information, see Requesting Acknowledgment for a Message
  • You can use the ResponseQueue property to indicate the return path for any messages that will be sent by the application removing your messages from the queue. These messages differ from acknowledgment messages in that they are generated by the destination application, rather than the queue manager.
  • You can determine whether copies of your outgoing messages should be stored in a journal queue by setting the UseJournalQueue property, and similarly, whether undeliverable messages should be sent to a dead letter queue by setting the UseDeadLetterQueue property.

Time-out Properties

There are two timer-related properties you can use to help maintain better control of your messages. Both of these determine how long a message can exist in the system before it is discarded. You use the TimeToReachQueue property to specify how long a message has to try to reach its destination queue. You use the TimeToBeReceived property to specify how long the message should remain in the system, from the time it is sent until the time it is removed from the queue by the destination application. If either timer interval expires, Message Queuing discards the message.

When a message is discarded due to an expired timer, the queue manager might take additional steps:

  • If you set the UseDeadLetterQueue property to true, a copy of the discarded message will be sent to the specified dead letter queue.
  • If you set the AcknowledgeType property to send negative acknowledgments, an acknowledgment message will be sent to the specified Administration queue.

By default, no time-out interval is set for either property. If you set a value for both properties, the value in the TimeToBeReceived property takes precedence.

See Also

Sending and Serializing Messages | Creating MessageQueue Components


Message Serialization

Serialization is the process of taking objects and converting their state information into a form that can be stored or transported. The basic idea of serialization is that an object writes its current state, usually indicated by the value of its member variables, to persistent storage. Later, the object can be re-created by reading, or de-serializing, the object’s state from the storage. Serialization handles all the details of object pointers and circular object references that are used when you serialize an object.

In the NGWS frameworks message queuing feature, serialization refers specifically to the process of converting an object or set of data into a message that can be sent to a queue, and then converting messages retrieved from the queue back into objects or data that your application can process.

A Formatter object handles message serialization in the NGWS frameworks. You choose the appropriate type of formatter for the kind of data you want to send and receive. The NGWS frameworks ship four predefined formatters, each designed to persist and de-persist a different type of data:

  • The Dataset formatter persists ADO+ datasets into messages and de-persists them out of messages. The messages created by the Dataset formatter are human-readable, which allows users to troubleshoot any messages that cannot be de-persisted after they are serialized.
  • The XML formatter persists objects and primitive data types into and out of messages using human-readable XML strings. This is the default formatter setting for MessageQueue components.
  • The Binary formatter persists one or more connected objects into serialized streams. The result is very compact and fast to parse, but not human-readable.
  • The ActiveX formatter persists primitive data types, allowing for interoperability with components that use previous versions of Message Queuing. The resulting serialization is very compact. This formatter is designed with Windows in mind, and does not produce human-readable results. However, it is an extremely fast method of serialization.

In addition, you can create your own formatters if you need to work with different types of data. You can import your formatter into a project and access it in code.

You choose the formatter you want to use by setting the Formatter property for the queue or MessageQueue component with which you're working.

Advantages of XML Message Formatting

One advantage of the XML formatter is that you can read the strings that are created when the object or data is serialized. This means that if something happens and the message cannot be de-persisted from the message queue, you can take a look at the message itself and likely fix the problem.

Another advantage is that messages serialized with this formatter don't have to be de-serialized by the same formatter object. That is, you do not need to have the same formatter class in the receiver as you had in the sender, as long as both parties know the data schema. In addition, messages serialized by the XML formatter do not necessarily have to be de-serialized at all. Most browsers and other XML viewers can parse XML messages.

See Also

Creating MessageQueue Components | Sending and Serializing Messages | Serializing a DataSet into a Message


Serializing a DataSet into a Message

The Dataset formatter allows you to easily persist a dataset into a message and send it to queues in your Message Queuing enterprise. When you send a dataset as a message, you first create your dataset using ADO+. You then create an instance of the MessageQueue component, set the formatter appropriately, and send your message like you would any other message.

To persist a dataset into a message

  1. Create your dataset.
  2. Create an instance of the MessageQueue component and set its Path property to the queue to which you want to refer. For details, see Creating MessageQueue Components.
  3. Set the Formatter property to DataSetMessageFormatter.
  4. Send your message normally:
    • For instructions on sending simple messages, see Sending Simple Messages
    • For instructions on sending messages using the Message object, see Sending Complex Messages.

See Also

Sending and Serializing Messages | Creating MessageQueue Components | Message Serialization


Reading and Receiving Messages

There are several ways you can read and receive messages from the queues in your system. First, you can either receive messages from the queue or you can peek at the messages on a queue without removing them. You can also create an event handler that will watch for a new message to arrive and automatically raise an event when the message arrives at the queue. The following topics explain each of these methods of working with queue messages.

For information about | See
Specifying the properties to retrieve when reading messages | Specifying Properties to Retrieve with a Message
Writing code to read and receive messages | Receiving Messages Programmatically
Looking at messages without removing them from the queue | Peeking at Messages
Retrieving messages asynchronously | Asynchronous Message Processing
Processing messages as part of a transaction | Transactional Message Processing
Retrieving collections of queues and messages | Queue and Message Collections


Specifying Properties to Retrieve with a Message

Messages have a large number of properties, and filtering the properties you want to receive can enable you to focus on only that information that is relevant to you. For example, if you are not working with transactional messages, you might not want to retrieve the properties associated with transactions. In this case, you would set the MessagePropertyFilter properties IsFirstInTransaction, IsLastInTransaction, TransactionID, and TransactionStatusQueue to False. Your MessageQueue component would not retrieve any of the property values corresponding to these properties whenever it gets a message from a queue.

When you create an instance of the MessageQueue component, you can set a series of properties to indicate what properties you want to retrieve when the component gets a message from a queue. These properties exist in a class called MessagePropertyFilter, and correspond to actual properties on the Message class. When you set the value for one of these properties to True, the component will retrieve the corresponding property each time a message is removed from the queue.

For a full list of all of the properties you can set, see the MessagePropertyFilter Class.

To specify the properties to retrieve with a message

  1. Create an instance of the MessageQueue component and set its Path property to the queue to which you want to refer. For details, see Creating MessageQueue Components.
  2. Set the value for the MessageReadPropertyFilter member’s properties you want to receive to true.
    Note You can use the SetAll method to set all the filter properties to true, or the ClearAll method to set all the filter properties to False.

See Also

Reading and Receiving Messages | Receiving Messages Programmatically | Peeking at Messages | Receiving Messages Asynchronously | Creating MessageQueue Components


Receiving Messages Programmatically

You can use a synchronous method called Receive to look at the contents of a queue. When you call Receive on a queue, it removes the first message from the queue and returns it to you. This message is no longer available to other components looking at the queue.

Note You can also peek at the first message on a queue without removing it from the queue. For more information, see Peeking at Messages.

If there are no messages available on the queue when you call Receive, the method will wait until a message arrives on the queue. You can specify a time-out period if you want the method to wait for only a specified interval. The time-out period is specified in milliseconds.

When you perform a Receive operation, you may want to temporarily prevent other users from also removing messages from the queue with which you're working. You can do this by setting the DenySharedReceive property on that queue to True. Setting DenySharedReceive prevents any other users from removing messages from the same queue until you release your usage of it, either through garbage collection or by calling Close.

You can also receive messages asynchronously. For more information, see Receiving Messages Asynchronously.

To receive a message programmatically

  1. Create an instance of the MessageQueue component and set its Path property to the queue to which you want to refer. For details, see Creating MessageQueue Components.
  2. Create an instance of the Message object to hold the retrieved message.
  3. Call the Receive method to remove the message from the queue. Optionally, to specify a time-out for the Receive method, enter the length of time (in milliseconds) that you want the method to wait as an argument of the Receive method.

    When you're finished, your code might look like this:

    [Visual Basic]
'Create a connection to the queue you want to communicate with.
Dim mq as new MessageQueue("YourMachine\YourQueue")
'Create a Message object.
Dim MyMessage as Message
'Call the Receive method, specifying a time-out of 1000 milliseconds.
MyMessage = mq.Receive(1000)
[C#]
// Create a connection to the queue you want to communicate with.
MessageQueue mq = new MessageQueue(@"YourMachine\YourQueue");
// Create a Message object.
Message MyMessage;
// Call the Receive method, specifying a timeout of 1000 milliseconds.
MyMessage = mq.Receive(1000);

See Also

Reading and Receiving Messages | Peeking at Messages | Receiving Messages Asynchronously | Creating MessageQueue Components


Peeking at Messages

You can use the Peek method to look at the first message on any queue without removing that message from the queue. This allows your component to get information from the queue without preventing other applications or components from retrieving messages they were intended to process.

Note Peek allows you to see only the first message on the queue. Because that message is not removed from the queue when you peek at it, you cannot then peek at subsequent messages. If you want to see all of the messages in a queue without removing them from the queue, you can use the GetMessages method or the GetMessagesEnumerator method. For more information, see Queue and Message Collections.

If there are no messages in the queue when you call Peek, the method waits until a message arrives. You can specify a time-out period if you want the method to wait only a specified period of time. The time-out period is specified in milliseconds. Most commonly, the time-out period will be set to either zero, in which case it checks for a message and does not wait at all, or to the default infinite setting, which waits indefinitely. You would set this in code using the following syntax:

[Visual Basic]

Message.TimeToBeReceived = Message.Infinite
[C#]
Message.TimeToBeReceived = Message.Infinite;

Peeking can be synchronous or asynchronous. For more information, see Peeking at Messages Asynchronously.

To peek at messages synchronously

  1. Create an instance of the MessageQueue component and set its Path property to the queue to which you want to refer. For details, see Creating MessageQueue Components.
  2. Create an instance of the Message object to hold the data the Peek method copies from the queue.
  3. Call the Peek method to get data about the first message on the queue. Optionally, to specify a time-out for the Peek method, enter the length of time (in milliseconds) that you want the method to wait as an argument of the method.

    When you're finished, your code might look like this:

    [Visual Basic]
'Create a connection to the queue you want to communicate with.
Dim mq as new MessageQueue("YourMachine\YourQueue")
'Create a Message object.
Dim MyMessage as Message
'Call the Peek method, specifying a time-out of 1000 milliseconds.
MyMessage = mq.Peek(1000)
[C#]
// Create a connection to the queue you want to communicate with.
MessageQueue mq = new MessageQueue(@"YourMachine\YourQueue");
// Create a Message object.
Message MyMessage;
// Call the Peek method, specifying a timeout of 1000 milliseconds.
MyMessage = mq.Peek(1000);

See Also

Reading and Receiving Messages | Receiving Messages Programmatically | Creating MessageQueue Components


Asynchronous Message Processing

You can retrieve messages asynchronously if you want to retrieve messages without tying up your application's processing. In asynchronous message processing, the method that starts a task is returned immediately without waiting for a result. The application can continue to do what it was originally doing while the task is completed. When the task completes, the server can notify the application that the message was successfully processed.

There are two types of asynchronous processes you can perform with the MessageQueue component:

  • You can asynchronously receive messages from a queue, using the BeginReceive and EndReceive methods. For instructions, see Receiving Messages Asynchronously.
  • You can asynchronously peek at a message on a queue, using the BeginPeek and EndPeek methods. For instructions, see Peeking at Messages Asynchronously.

Receiving Notification of Your Asynchronous Operation

There are two ways you can receive notification when your asynchronous receive or peek operation completes successfully:

  • You can create an event handler that will raise an event and return notification when the process completes.
  • You can use a callback to automatically watch for incoming messages and pass processing to your application when a message arrives.

When you use event notification, you create a method that handles your message processing and returns a notification when the processing completes. You then call the method that begins the asynchronous processing.

In the event notification scenario, BeginPeek or BeginReceive returns a single message and then stops processing. You must call BeginPeek or BeginReceive again for each message you want to retrieve.

An alternate way to process messages asynchronously is to use a callback. A callback identifies a delegate you want to associate with your BeginPeek or BeginReceive operations. In this scenario, the delegate continues to watch for new event notification after each message is processed.

See Also

Receiving Messages Asynchronously | Reading and Receiving Messages | Peeking at Messages Asynchronously | Peeking at Messages | Receiving Messages Programmatically


Receiving Messages Asynchronously

You can retrieve messages asynchronously in two ways: by using an event handler to receive notification when the operation finishes processing, or by using a callback. This procedure explains how to use event notification.

In event notification, you first bind an event handler to the procedure you want to run when the asynchronous call completes. You then call the BeginReceive method in your code. This starts the asynchronous processing and returns processing to your component. When the processing completes, an event you've defined is raised and the results of the retrieval are processed.

Note BeginReceive will retrieve only a single message. If you want to continue processing messages asynchronously, you must either call BeginReceive again or use the callback parameter on BeginReceive to call a delegate that will continue to watch for new messages arriving on the queue.

See Also

Asynchronous Message Processing | Peeking at Messages Asynchronously | Reading and Receiving Messages | Peeking at Messages | Receiving Messages Programmatically | Creating MessageQueue Components


Peeking at Messages Asynchronously

You can peek at messages asynchronously in two ways: by using an event handler to receive notification when the operation finishes processing, or by using a callback. This procedure explains how to use event notification.

In event notification, you first bind an event handler to the procedure you want to run when the asynchronous call completes. You then call the BeginPeek method in your code. This starts the asynchronous processing and returns processing to your component. When the processing completes, the an event you've defined is raised and the results of the retrieval are processed.

Note BeginPeek will retrieve only a single message. If you want to continue peeking at messages asynchronously, you must either call BeginPeek again or use the callback parameter on BeginPeek to call a delegate that will continue to watch for new messages arriving on the queue.

See Also

Asynchronous Message Processing | Receiving Messages Asynchronously | Reading and Receiving Messages | Peeking at Messages | Receiving Messages Programmatically | Creating MessageQueue Components


Transactional Message Processing

The NGWS frameworks MessageQueue component gives you the option of sending and receiving messages transactionally. When you send a transactional message, you group a set of related messages into a transaction. All the messages are either delivered together in the order in which they were sent (a committed transaction), or automatically not sent at all if any problems occur (an aborted transaction). Transactional processing allows you to ensure that the messages in a transaction are delivered in order, are delivered only once, and are successfully retrieved from the destination queue.

There are two main types of transactions you can create from your applications:

  • Internal transactions are transactions sent between two or more Message Queuing resources. Because no non-Message Queuing (MSMQ) resources are involved in the transaction, the entire transaction is governed by the Message Queuing engine. These transactions are faster and have a simpler programming model than external transactions.
  • External transactions are used if resources other than Message Queuing are needed to provide part of the transaction. Message Queuing is then used as a resource manager under the control of Microsoft's Distributed Transaction Coordinator (DTC). Using the DTC as the transaction coordinator for Message Queuing, you can create transaction-based applications and ensure that all operations either succeed or fail as a unit. For example, a Message Queuing application can send a message and update a database in the same transaction. The DTC ensures that either both actions succeed or neither action is executed.

See Also

Reading and Receiving Messages | Creating a Transactional Queue | Sending Messages Within an Internal Transaction


Creating a Transactional Queue

A transactional queue is one that is created for the specific purpose of receiving and processing transactional messages. In the NGWS frameworks, you are not limited to sending transactional messages only to transactional queues as you are in Microsoft Message Queuing, but you may still have cause to create transactional queues and manipulate their properties. For example, transactional queues provide some advantages over non-transactional queues in processing transactions: they are more reliable, ensure one-time delivery, and can coordinate transactions with other resource managers such as OLEDB and ODBC.

To create a transactional queue

  • Use the overloaded Create constructor to create a public, transactional queue, setting the second parameter to true to indicate that the queue should be transactional. Your code should look like this:
    [Visual Basic]
Dim mq2 as New System.Messaging.MessageQueue
mq2 = mq2.Create(".\MyTransQueue", True)
[C#]
System.Messaging.MessageQueue mq2 =
   new System.Messaging.MessageQueue();
mq2 = mq2.Create(@".\MyTransQueue","True");
    Note This syntax creates a public transactional queue. The exact syntax you use in the first parameter depends on the kind of queue you're trying to create. For more information, see Queue Reference Recommendations.

See Also

Reading and Receiving Messages | Transactional Message Processing | Sending Messages Within an Internal Transaction | Creating Queues


Sending Messages Within an Internal Transaction

Internal transactions involve only Message Queuing resources and are managed entirely from within Message Queuing. You use the following language elements to indicate an internal transaction:

Element | Used to
BeginTransaction | Indicate the start of a transaction that consists of one or more messages and operations
CommitTransaction | Used to commit the transaction if all of the message operations it contained were performed successfully
RollbackTransaction | Used to undo all of the previously performed messaging operations if one of the operations in the transaction cannot be completed successfully.
Note A disconnected or full queue is not considered a failure. You decide in your code for the RollbackTransaction method what constitutes a rollback.

To send messages within an internal transaction

  1. Create an instance of the MessageQueue component and set its Path property to the queue to which you want to refer. For details, see Creating MessageQueue Components.
  2. Call the BeginTransaction method on the component.
  3. Define each message you want to send after calling BeginTransaction. For details, see Creating MessageQueue Components.
  4. After the last message you want to send in the transaction, call CommitTransaction.
  5. Enclose the entire transaction in Try/Catch error-checking code to handle any errors that arise. If you detect an error, roll back the transaction by calling the RollbackTransaction method.
Your code might look like this:
[Visual Basic]
Try
  mq.BeginTransaction
  messageA = mq.Send(1000)
  messageB = mq.Send(1000)
  mq.CommitTransaction
Catch
  mq.RollbackTransaction
End Try
[C#]
try {
  mq.BeginTransaction();
  messageA = mq.Send(1000);
  messageB = mq.Send(1000);
  mq.CommitTransaction();
}
catch {
  mq.RollbackTransaction();
}

See Also

Transactional Message Processing | Creating a Transactional Queue | Creating MessageQueue Components | Sending and Serializing Messages


Queue and Message Collections

You can retrieve lists of the queues on your Message Queuing network and the messages they contain by using any of a variety of retrieval methods available in the MessageQueue class. You might retrieve lists of queues and messages if you wanted to perform administrative tasks, generate reports about queue contents, or search through the contents of a queue for messages that meet specific criteria.

There are two main ways you can retrieve both messages and message queues: you can retrieve a static snapshot of the messages or queues on the network, or you can iterate through messages and queues using an enumerator. Retrieving a static list of queues or messages is faster than retrieving an enumeration but doesn't give you as much accuracy in or control over the resulting set of information. You might use a static snapshot of retrieval if you need to perform an administrative task on all public queues in your network.

Enumerators give you finer control over how you interact with the retrieved results. Using an enumerator, you can locate a queue or message that meets certain criteria, instead of evaluating each and every item in the returned collection. For example, you might use an enumerator if you want to review the contents of a queue and work with only those messages for which a particular property has been set to true.

Message Queues Retrieval

You can retrieve information about message queues in the following ways:

  • You can retrieve a static array of all of the public or private queues on the network.
  • You can retrieve a static subset of all the public queues on the network by specifying certain criteria such as the date and time the queue was created or modified, or by specifying a category, label, or machine by which to retrieve queues.
  • You can iterate queues on the network using an enumerator, or you can iterate through a subset of all the queues on the network by specifying criteria such as machine name, date and time created, and category.

You can also use the Exists method to determine whether a queue you are interested in exists. The Exists method searches for a specific queue by a path you define.

For instructions on retrieving lists of message queues, see Retrieving Queues.

Message Retrieval

When you retrieve messages in an array or enumerator, you do not actually pull those messages off the queue as you do when you read or receive messages. Instead, you retrieve information about the items in the queue, while the actual messages remain available to the users who were intended to receive them.

You can retrieve messages in the following ways:

  • You can retrieve a static array of all the messages in a queue.
  • You can iterate messages in a queue using an enumerator.

For instructions on retrieving lists of messages, see Retrieving Messages.

Working with Items in an Enumerator

After you create an enumeration of messages or queues, the enumerator is conceptually positioned before the first item of the enumeration. You can call the GetNext method to move to the first item in the list, and to move through subsequent items. You can also use the Current method to return the item you're currently positioned on in the enumeration.

See Also

Retrieving Queues | Retrieving Messages


Retrieving Messages

You can use either static or dynamic retrieval mechanisms to retrieve a list of messages. When you retrieve a static list of messages, an array of Message objects representing all messages in the queue is returned. When you retrieve a dynamic list of messages, a MessagesEnumerator object representing all messages in the queue is returned.

To retrieve a static list of messages

  1. Create an instance of the MessageQueue component and set its Path property to the queue to which you want to refer. For details, see Creating MessageQueue Components.
  2. Create an array that references the Message class to hold the results of your query.
  3. Call the GetAllMessages method.
  4. Assign the results to the array you created. Your code might look like this:
    [Visual Basic]
Dim mq as New MessageQueue
Dim msg() as Message
mq.Path = ".\MyQueue"
msg = mq.GetAllMessages()
For I = 0 to uBound(msg)
  Debug.Print msg(i).Body
Next i
[C#]
MessageQueue mq = new MessageQueue();
Message[] msg();
mq.Path = @".\MyQueue";
msg = mq.GetAllMessages();

To retrieve a dynamic list of messages

  1. Create an instance of the MessageQueue component and set its Path property to the queue to which you want to refer. For details, see Creating MessageQueue Components.
  2. Create a MessagesEnumerator object to hold the results of your query.
  3. Call the GetMessagesEnumerator method on the MessageQueue class.
  4. Assign the results to the MessagesEnumerator object. Your code might look like this:
    [Visual Basic]
Sub Main
   Dim mq as New MessageQueue
   Dim msgEnum As MessagesEnumerator
   mq.Path = ".\MyQueue"
   msqEnum = mq.GetMessagesEnumerator
End Sub
[C#]
public void Main() {
   MessageQueue mq = new MessageQueue();
   MessagesEnumerator msgEnum;
   mq.Path = @".\MyQueue";
   msqEnum = mq.GetMessagesEnumerator();
}

See Also

Queue and Message Collections | Retrieving Queues


Retrieving Queues

You can use either static or dynamic retrieval mechanisms to retrieve a list of queues. When you retrieve a static list of queues, a MessageQueue object is returned with the results of your query. When you retrieve a dynamic list of queues, a MessageQueuesEnumerator object is returned with the results of the query.

Because the GetPublicQueues, GetPrivateQueues, and GetMessageQueuesEnumerator methods are static, you do not need to create an instance of an instance of the MessageQueue class before calling the method.

To retrieve a static list of public queues

  1. Create an array that references the MessageQueue object to hold the results of your query.
  2. Call the appropriate method on the MessageQueue class:
    • To retrieve all public queues without criteria, call GetPublicQueues.
    • To retrieve public queues with criteria, set the Criteria parameter to the appropriate value and call GetPublicQueues.
    • To retrieve only those public queues that share a category GUID, call GetPublicQueuesByCategory and specify the category GUID as the parameter.
    • To retrieve only those public queues that share a label, call GetPublicQueuesByLabel and specify the label as a parameter.
    • To retrieve only those public queues on a specific machine, call GetPublicQueuesByMachine and specify the machine name as a parameter.
  3. Assign the results to the array. Your code might look like this:
    [Visual Basic]
Sub Main
    'Create an array to hold the set of message queue objects
    Dim mqlist() As MessageQueue
    'Get an array containing messagequeue information
    mqlist = MessageQueue.GetPublicQueuesByMachine("machinename")
End Sub
[C#]
public void Main() {
    // Create an array to hold the set of message queue objects
    MessageQueue[] mqlist();
    // Get an array containing messagequeue information
    mqlist = MessageQueue.GetPublicQueuesByMachine("machinename");
}

To retrieve a static list of private queues

  1. Create an array that references the MessageQueue object to hold the results of your query.
  2. Call the GetPrivateQueuesbyMachine method, specifying the machine on which the queues exist as a parameter.
  3. Assign the results to the array. Your code might look like this:
    [Visual Basic]
Sub Main
    'Create an array to hold the set of message queue objects
    Dim mqlist() As MessageQueue
    'Get an array containing messagequeue information
    mqlist = MessageQueue.GetPrivateQueuesByMachine("machinename")
End Sub
[C#]

To retrieve a dynamic list of queues

  1. Create a MessageQueuesEnumerator object to hold the results of your query.
  2. Call the GetMessageQueuesEnumerator method on the MessageQueue class.
  3. To retrieve a subset of the queues on the network, set the Criteria parameter to the appropriate value.
  4. Set the results to the MessageQueuesEnumerator object you created. Your code might look like this:
    [Visual Basic]
Sub main()
  'Create an enumerator to hold the set of message queue objects
  Dim mqEnum as MessageQueuesEnumerator
  'Get the queues
  mqEnum = MessageQueue.GetMessageQueuesEnumerator()
End Sub
[C#]

See Also

Queue and Message Collections | Retrieving Messages


Message Queue Security

Message Queuing takes advantage of the various built-in security features of the Windows 2000 operating system. Specifically, Message Queuing uses access control, authentication, encryption, and auditing for security:

  • Access control is used to restrict user access to Message Queuing objects, and is implemented by assigning security descriptors to objects. Message Queuing objects include computer (MSMQ), queue, routing link, and Message Queuing Settings objects. A security descriptor lists the users and groups that are granted or denied access to an object, and the specific permissions assigned to those users and groups.
  • Authentication is implemented using public key certificates, the Kerberos V5 security protocol, and Windows NTLM (for compatibility with Message Queuing 1.0 running on Windows NT 4.0). Public key certificates are used for message authentication, which verifies the sender of a message (the client) to a Message Queuing server. Kerberos V5 and NTLM are used for server authentication, which verifies a Message Queuing server to a client.
  • Encryption is implemented using both public key (asymmetric and secret key symmetric) algorithms. Encryption is used by Message Queuing applications to encrypt messages sent between Message Queuing computers.
  • Auditing is used to record which users attempt to access Message Queuing objects in Active Directory. The security descriptor for an object specifies the various access events to be audited for the object.

By managing security properties for objects, you can set permissions, assign ownership, and monitor user access. For more information on the specifics of security in Message Queuing, see "Security Message Queuing" in the Message Queuing documentation.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Serialization

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Persisting Object State on Disk

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Saving Object State Remotely

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Threading and Synchronization

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Runtime Support for Threading

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Synchronization Techniques

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Synchronized Context

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Synchronized Code Regions

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Manual Synchronization

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Using Directory Services

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Migrating to NGWS

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Migrating COM Code to NGWS

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Migrating from ASP to ASP+

[To be supplied.]


Debugging, Optimizing, and Profiling

This section explains how to write to event logs and access performance counters. The following topics are provided:

Section | Description
Logging Application, Server, and Security Events | Explains how you can write entries to event logs, which record information about important events.
Monitoring Performance Thresholds | Describes how you can retrieve values from performance counters so you can capture and analyze the performance data that applications, services, and drivers provide.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Performance and Optimization Concepts

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Runtime profiling

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Optimizing Your Application

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Optimizing code

[To be supplied.]


Logging Application, Server, and Security Events

Windows event logs allow your applications and components to record information about important events that occur. You can use these records to audit the access to your system, troubleshoot problems, and recreate usage patterns.

Using the EventLog component, you can easily connect to Windows event logs on both local and remote computers and write entries to these logs. You can also read entries from existing logs and create your own custom event logs on computers that have Windows 2000 or Windows NT installed.

For information about | See
Event log features | Introduction to the EventLog Component
The language elements used in EventLog component programming | EventLog Programming Architecture
Deleting event logs and performing administrative tasks on event logs | Administering Event Logs
Creating and configuring EventLog components | Creating EventLog Components
Configuring your component to write entries to event logs | Writing Entries to Event Logs
Reading event log entries | Reading Event Log Entries
The effects of event log interactions on application security | Security Ramifications of Event Logs


Introduction to the EventLog Component

Event logging in Microsoft Windows provides a standard, centralized way for you to have your applications record important software and hardware events. When an error occurs, the system administrator or support technicians must determine what caused the error, attempt to recover any lost data, and prevent the error from recurring. It is helpful if applications, the operating system, and other system services record important events such as low-memory conditions or failed attempts to access a disk. The system administrator can use the event log to help determine what conditions caused the error and the context in which it occurred.

Windows supplies a standard user interface for viewing these event logs and a programming interface for examining log entries.

Using the EventLog component, you can easily connect to event logs on both local and remote computers and write entries to these logs. You can also read entries from existing logs and create your own custom event logs

Key Concepts in Windows Event Logs

An event, as defined in Windows, is any significant occurrence ¯ whether in the operating system or in an application ¯ that requires users to be notified. Critical events are sent to the user in the form of an immediate message on the screen. Other event notifications are written to one of several event logs that record the information for future reference. Every event log entry is classified into one of the following categories: errors, warnings, information, success audits, or failure audits.

There are three event logs available by default on computers running Windows 2000 or Windows NT 4.0:

  • a System log, which tracks events that occur on system components (for example, a problem with a driver);
  • a Security log, which tracks security changes and possible breaches;
  • an Application log, which tracks events that occur in a registered application.

In addition to these logs, other programs may create their own default logs. You can also create your own custom logs.

Choosing Events to Log

The following are examples of cases in which event logging can be helpful:

  • Resource problems. If an application gets into a low-memory situation (caused by a code bug or inadequate memory) that degrades performance, logging a warning event when memory allocation fails might provide a clue about what went wrong.
  • Hardware problems. If a device driver encounters a disk controller time-out, a power failure in a parallel port, or a data error from a network or serial card, logging information about these events can help the system administrator diagnose hardware problems. The device driver logs the error.
  • Bad sectors. If a disk driver encounters a bad sector, it may be able to read from or write to the sector after retrying the operation, but the sector will become inaccessible eventually. Therefore, if the disk driver can proceed, it should log a warning; otherwise, it should log an error event. If a file system driver finds a large number of bad sectors, fixes them, and logs warning events, logging information of this type might indicate that the disk is about to fail.
  • Information events. A server application (such as a database server) records a user logging on, opening a database, or starting a file transfer. The server can also log error events it encounters (cannot access file, host process disconnected, and so on), corruptions in the database, or whether a file transfer was successful.

Event logging consumes resources such as disk space and processor time. The amount of disk space that an event log requires and the overhead for an application that logs events depend on how much information you choose to log. Therefore, it is important to log only essential information. It is also best to place event log calls in an error path in the code rather than in the main code path, so as not to reduce performance.

Requirements

The following restrictions apply to EventLog components:

  • Event logs are installed as part of Windows 2000. You must have either Windows NT version 4.0 or Windows 2000 installed on the computer on which you do your development work in order to create and test EventLog components, view event logs in Server Explorer, or drag event logs from Server Explorer to your classes.
  • You can create EventLog components in both Web applications and Windows applications built with the NGWS runtime.

See Also

Types of Event Log Entries | Event Log References | EventLog Programming Architecture | Walkthrough: Creating EventLog Components


Types of Event Log Entries

When you write an entry to a log, you specify the type of entry you're making. The following table lists the types of events and their contents.

Entry Type | Contents
Information | An information event indicates an infrequent but significant successful operation. For example, when Microsoft® SQL Server™ successfully loads, it may be appropriate to log an information event stating that "SQL Server has started." Note that while this is appropriate behavior for major server services, it is generally inappropriate for a desktop application to log an event each time it starts.
Warning | A warning event indicates a problem that is not immediately significant but that may cause future complications. Resource consumption is a good candidate for a warning event. For example, an application can log a warning event if disk space is low. If an application can recover from an event without loss of functionality or data, it can generally classify the event as a warning event.
Error | An error event indicates a significant problem the user should know about, such as a loss of functionality or data. For example, if a service cannot be loaded as the system boots, it can log an error event.
Success audit | A success audit event is a security event that occurs when an audited access attempt is successful. For example, a successful logon attempt is a success audit event.
Failure audit | A failure audit event is a security event that occurs when an audited access attempt fails. For example, a failed attempt to open a file is a failure audit event.

By default, the event type is set to Information if you do not specify otherwise. You can query the EntryType property on the EventLogEntry class to see what type of entry you are working with.

See Also

Introduction to the EventLog Component | | Event Log References | EventLog Programming Architecture


Event Log References

There are three default event logs on any computer: an application event log, a security event log, and a system event log. In addition to these logs, there may be other default logs that are installed by applications on the server, and custom logs that are created by users. You identify which of these event logs you want to work with. The method of referencing a specific log differs depending on whether you want to read the log's entries or write entries to the log. The following illustration shows the overall process of referencing logs for read or write operations.

Event Log Referencing

Referencing a Log to Read Entries

When you read entries from an event log, you uniquely identify the log you want by machine name and log name. Machine name denotes the Windows 2000 server on which the event log exists, and log name is simply the name of the log. Machine name is optional; if you do not specify a machine, the local computer is assumed.

For example, suppose you have an instance of the EventLog component that you want to use to read entries from a custom log called "OrderEntrySystem, " which is stored on a server named "myserver". You would connect to this log by specifying that:

  • Machine name = "myserver"
  • Log name = "OrderEntrySystem"

You can retrieve the machine name of a log by querying the MachineName property. You can retrieve the log name by querying the Log property.

Note When you are referencing a log in an international application, you can query the DisplayName property to retrieve the localized name of the log. You must set the Log property to the non-localized (usually in English) version of the log name.

Referencing a Log to Write Entries

If you are writing entries to an event log, you must specify the Source property. The Source property registers your component with the event log as a valid source of entries. You can specify any string as the value for the Source property; however, it may be helpful to use the name of your project for this value.

Note If the source is already registered with the log, you do not need to set the log name property. You can also not set the machine name; in this case the local machine will be assumed.

For example, suppose you have an instance of the EventLog component that you want to use to write entries to a custom log called "OrderEntrySystem, " which is stored on the local computer. You would connect to this log by specifying that:

  • Machine name = "."
  • Log name = "OrderEntrySystem"
  • Source = string of your choice
Note In this case the period in the machine name indicates the local computer.

A single source can be associated with (and therefore write entries to) only one event log at a time, but a single event log can have many different sources writing to it at once. This means that each instance of the EventLog component can write to only one event log. If you want to write to multiple logs, you must use different instances or change the Source property of an existing instance.

Note When you are referencing a log in an international application, you can query the DisplayName property to retrieve the localized name of the log. You must set the Log property to the non-localized (usually in English) version of the log name.

See Also

Introduction to the EventLog Component


EventLog Programming Architecture

The EventLog component uses these portions of the NGWS frameworks namespaces:

EventLog Namespace

If you are creating your EventLog components in code, you may need to add a reference to System.Diagnostics to your build command.

Key Programming Elements in the EventLog Class

The EventLog component raises an event called EventWritten whenever an entry is written to the log you are watching. You can define a handler for this event. In addition, you can use the following methods to interact with an instance of the EventLog component:

  • You can use the Exists method to determine whether a particular event log exists.
  • You can use the CreateEventSource method to define a string that creates a relationship between a source of events and a log, and use the DeleteEventSource method to remove this association. You can also use this method to create new custom event logs. Just type a new log name as the log in which you want to create your source, and the system will create the log automatically.
    Note You can set the Source property to any string; it does not have to correspond to the name of your application. A single application can contain multiple components that use different source strings to identify themselves to event logs.
  • You can use the WriteEntry method to write event information to any event log to which you have access. The WriteEntry method automatically registers your component or application as a valid source if it is not already registered with the designated log.
  • You can use the SourceExists method to determine whether a particular source is already registered. This is useful when you are attempting to create a new source on a given computer, because the CreateEventSource method will cause a run-time error if the source is already registered on that computer.
  • You can use the GetEventLogs method to retrieve a list of all the event logs on a particular computer.
  • You can use the Delete method to delete an entire event log. Use this method with caution, as deleting a log will also delete any sources registered in it.

See Also

Introduction to the EventLog Component


Administering Event Logs

You can use the NGWS frameworks to perform administrative tasks on event logs on your local and remote computers, such as creating custom logs, deleting event logs, and clearing log entries.

For information about | See
Deleting an existing log and all of its entries | Deleting Event Logs
Querying to see if a specific log exists | Determining Whether Specific Event Logs Exist
Creating custom event logs on the local computer | Creating and Removing Custom Event Logs
Deleting entries from an event log | Clearing Event Logs


Deleting Event Logs

You can delete any event log on your local computer or a remote server, provided you have the appropriate registry rights. When you delete a log, the system first deletes the file that contains the log's contents and then accesses the registry and removes all of the event sources that were registered for that log. Even if you re-create the log at a later point, you will not be able to re-register the event sources without reinstalling the applications that created them, so some applications that previously were able to write entries to that log will not be able to write to the new log.

You must have registry rights on the machine on which you want to delete a log. For more information, see your Windows documentation.

Note Recreating an event log can be a difficult process. It is good practice to not delete any of the system-created event logs, such as the Application log. You can delete your custom logs and recreate them as needed.

To delete an event log on the local machine

  • Call the Delete method and specify the name of the log you want to delete. Your code should look like this:
    [Visual Basic]
EventLog.Delete ("MyCustomLog")
[C#]
EventLog.Delete ("MyCustomLog");
    Note Because the Delete method is static, you do not need to create an instance of the EventLog component before you call the method — instead, you can call the method on the EventLog class itself.

To delete an event log on a remote machine

  • Call the Delete method and specify the name of the log you want to delete and the machine on which it exists. Your code should look like this:
    [Visual Basic]
EventLog.Delete ("MyCustomLog", "myremotemachine")
[C#]
EventLog.Delete ("MyCustomLog", "myremotemachine");
    Note Because the Delete method is static, you do not need to create an instance of the EventLog component before you call the method — instead, you can call the method on the EventLog class itself.

See Also

Administering Event Logs


Determining if Specific Event Logs Exist

You can use the Exists method to determine whether a specific log exists. If the log exists, the method returns true. You might use this method if you are creating a custom log and want to make sure that a log by that name does not already exist.

Note Because the Exists method is static, you do not need to create an instance of the EventLog component before you call the method — instead, you can call the method on the EventLog class itself.

You must have registry rights in order to find out if a log exists. If you do not have the appropriate rights, the query will always return false. For information on setting registry rights, see your Windows documentation.

To determine if an event log exists

  • Call the Exists method, specifying the log on the local computer that you want to verify as a parameter. Your code might look like this:
    [Visual Basic]
System.Winforms.MessageBox.Show EventLog.Exists("Application")
[C#]
System.Winforms.MessageBox.Show(EventLog.Exists("Application").ToString());
    Tip You can specify a machine name as a second parameter to determine if an event log exists on a remote machine.

See Also

Administering Event Logs | Creating and Removing Custom Event Logs


Creating and Removing Custom Event Logs

You can use the EventLog class to create a custom event log on a local or remote computer. You might create a custom log if you wanted to organize your entries in a more granular way than is allowed when your components write entries to the default Application log. For example, suppose you have a component called OrderEntry that writes entry information to an event log. You are interested in backing up and saving these entries for a longer period of time than some other entries in the Application log. Rather than registering your component to write to the Application log, you can create a custom log called OrdersLog and register your component to write entries to that log instead. That way, all of your order information is stored in one place and will not be affected if the entries in the Application log are cleared.

You can use the CreateEventSource method to create a custom log. If you try to write to a log that does not already exist, the system automatically creates a custom log for you and registers your component as a source for that log.

Note You remove a custom log the same way you remove any log — by calling the Delete method. For details, see Deleting Event Logs.

It is important to note the difference between creating an event log and creating an instance of the EventLog component. When you use the CreateEventSource method, you are creating a new custom event log in Windows, rather than a component in your project or application. When you create an instance of the EventLog component, you create a component inside your Visual Studio project that references an external event log.

You can view custom logs in the Windows 2000 Computer Management window.

To create a custom event log on the local machine

  • Call the CreateEventSource method and specify the source string and the name of the log file you want to create. Your code should look like this:
    [Visual Basic]
EventLog.CreateEventSource("MyApp1", "MyNewLog")
[C#]
EventLog.CreateEventSource("MyApp1", "MyNewLog");
    Note If you do not specify a log name, it defaults to Application. This will not create a new log, but will register the specified source for the Application log.

To create a custom event log on a remote machine

  • Call the CreateEventSource method and specify the source string, the name of the log file you want to create, and machine name on which you want to create it as parameters. Your code should look like this:
    [Visual Basic]
EventLog.CreateEventSource("MyApp1", "MyNewLog", "myserver")
[C#]
EventLog.CreateEventSource("MyApp1", "MyNewLog", "myserver");

See Also

Administering Event Logs | Deleting Event Logs


Creating EventLog Components

You create EventLog components when you want to build event log monitoring functionality into your application. EventLog components allow you to connect to existing event logs, write entries to them, and react to entries the logs receive.

To create EventLog components programmatically

  1. Create an instance of the EventLog class in code. You code might look like this:
    [Visual Basic]
Dim myEL As New System.Diagnostics.EventLog
[C#]
System.Diagnostics.EventLog myEL = new EventLog();
  2. Configure any other necessary properties for your component. For details, see Configuring EventLog Components.
Note You must set the Monitoring property on your component instance to true before your component will work.

See Also

Introduction to the EventLog Component | Configuring EventLog Components


Configuring EventLog Components

There are several properties involved in creating an instance of the EventLog component:

  • You can set the MachineName property to indicate the machine on which the log you’re working with resides, or you can leave this property blank to indicate the local machine.
  • You can set the Log property to indicate the log with which you want to interact.
    Note You can set the Log property on your component directly, or you can set it in code that registers your component as an event source. After registering the component as a source, a connection is already set in the server's registry linking your component to a specific log.
  • You can set the Source property if you want to write entries to the log. Note that if the source is already registered with the event log, you do not need to set the Log property as it will be set automatically when the source is verified. For example, the following code will configure your component instance and allow you to write entries.

[Visual Basic]

EventLog.CreateEventSource("mysource","mylog","myserver")
Dim el As New EventLog
el.Source = "mysource"
el.MachineName = "myserver"
el.WriteEntry "Test"
[C#]
EventLog.CreateEventSource("mysource","mylog","myserver");
EventLog el = new EventLog();
el.Source = "mysource";
el.MachineName = "myserver";
el.WriteEntry("Test");
Note If you just want to read entries, you can set just the Log and MachineName properties.

For more information on these properties, see Event Log References.

Note Once you have set the Log property, you may have to reboot the server before the system will recognize a change to the selected log. For example, if you set the component to reference the Application log, then later change it to reference the System log, you may need to reboot before the new value will be recognized.

The entries you retrieve from an event log are categorized by multiple properties. For information on these properties, see EventLogEntry Members.

To configure an instance of the EventLog component

  1. Create an instance of the EventLog component. For details, see Creating EventLog Components .
  2. Set the following values:
    • Set the Log property to Application, Security, System, or the name of a custom log with which you want your component to interact.
      Note When you are referencing a log in an international application, you can query the DisplayName property to retrieve the localized name of the log. You must set the Log property to the non-localized (usually in English) version of the log name.
    • Set the MachineName property to the name of the server on which the event log that you want to write to exists, or use a period (.) to indicate the local machine.
  3. If you will be writing to the event log, set the Source property to the string of your choice. You do not have to set the Source property if you only want to read an existing log.
    Note The string you enter in the Source property is used to register your component as a valid source of entries to the event log with which your component interacts. If the source has already been registered, the Log property will automatically be set to the log in which the source is registered. If it has not, the Log property will default to Application.
    The actual registry occurs when you either call CreateEventSource in your code, or when the first WriteEntry procedure occurs. For more information on event sources, see Adding Your Application as a Source of Event Log Entries.
  4. Set the component instance’s Monitoring property to true.

See Also

Event Log Entry Properties | Event Log References | Creating EventLog Components | Adding Your Application as a Source of Event Log Entries | Writing Entries to Event Logs


Adding Your Application as a Source of Event Log Entries

Before your component can write an entry to an event log, you must register it with the event log as a valid source of events. You can do this by using the CreateEventSource method and specifying a string that uniquely identifies your component to the event log. When you write a log entry, the system uses the source you identified to find the appropriate log in which to place your entry. Your EventLog component can write to only a single log at a time.

Note By default, if you try to write an entry without first having registered your component as a valid source, the source is automatically registered with the event log, using the value of the Source property as the source string.

If you specify the name of a log that does not exist when you use the CreateEventSource method, the system creates a new, custom event log for you with that name. For more information, see Creating and Removing Custom Event Logs.

To establish your component as an event source on the local computer

  • To create entries on the local machine, call the CreateEventSource method and specify the source string and log name as parameters. Your code should look like this:
    [Visual Basic]
EventLog.CreateEventSource("MyApp1", "Application")
[C#]
EventLog.CreateEventSource("MyApp1", "Application");
    Note If you do not specify a log name, it defaults to Application.

To establish your component as an event source on a remote computer

  • To create entries on a remote machine, call the CreateEventSource method and specify the source string, log, and machine names as parameters. Your code should look like this:
    [Visual Basic]
EventLog.CreateEventSource("MyApp1", "Application", "myserver")
[C#]
EventLog.CreateEventSource("MyApp1", "Application", "myserver");

See Also

Determining Whether an Event Source Exists | Removing an Event Source | Introduction to the EventLog Component | Creating and Removing Custom Event Logs


Determining Whether an Event Source Exists

Once you identify a source for a particular event log, this information is stored in the server's registry files until you remove that source. If you try to re-register a source that has already been registered as a valid source for a given log, a run-time error is raised. You can use the SourceExists method to determine whether or not a particular source has already been registered.

To determine if a source has already been registered on the local computer

  • Call the SourceExists method, specifying the source name to query. Your code should look like this:
    [Visual Basic]
If Not EventLog.SourceExists("MyApp1") Then
    EventLog.CreateEventSource("MyApp1", "Application")
End If
[C#]
if (!EventLog.SourceExists("MyApp1"))
    EventLog.CreateEventSource("MyApp1", "Application");

To determine a source has already been registered on a remote computer

  • Call the SourceExists method, specifying the source name and machine name to query. Your code should look like this:
    [Visual Basic]
If Not EventLog.SourceExists("MyApp1", "myserver") Then
    EventLog.CreateEventSource("MyApp1", "Application")
End If
[C#]
if (!EventLog.SourceExists("MyApp1", "myserver"))
    EventLog.CreateEventSource("MyApp1", "Application");

See Also

Removing an Event Source


Removing an Event Source

You can remove your source if you no longer need to use it to write entries to that log. Doing so affects all components that used that source to write to the log. For example, if you have two components that write to a log using the source string “mysource,” removing “mysource” as a valid source of events affects both the components.

You might remove an event source if you were changing your source from one log to another. For example, suppose you want to stop writing entries to a OrdersProcessed log and start writing entries to a OrdersSent log. First, you would remove the association of the source with the first log, and then you would register the source with the new log to which you wanted to write events.

To remove an event source from the local computer

  • Call the DeleteEventSource method, specifying the source name to remove. Your code should look like this:
    [Visual Basic]
EventLog.DeleteEventSource("MyApp1")
[C#]
EventLog.DeleteEventSource("MyApp1");

To remove an event source from a remote computer

  • Call the DeleteEventSource method, specifying the source name to remove and the machine name from which to remove the source. Your code should look like this:
    [Visual Basic]
EventLog.DeleteEventSource("MyApp1", "myserver")
[C#]
EventLog.DeleteEventSource("MyApp1", "myserver");

See Also

Determining Whether an Event Source Exists


Writing Entries to Event Logs

When you write an entry to an event log, you specify the message you want to write to the log as a string. A message should contain all information needed to interpret what caused the problem and what to do to correct the problem.

The following conditions must be met in order to successfully write a log entry:

  • The source must be registered with the desired log.
    Note You must set the Source property on your EventLog component before you can write entries to a log. When your component writes an entry, the system automatically checks to see if the source you specified is registered with the event log to which the component is writing, and calls CreateEventSource if needed.
  • The message you specify cannot be more than 16,384 characters in length.
  • Your application must have write access to the log to which it is attempting to write. For more information, see Security Ramifications of Event Logs .

You can specify several parameters when you write an entry, including the type of entry you're making, an ID that identifies the message, a category, and any binary data you want to append to the entry. For more information on the properties associated with an entry, see EventLog Members.

To write an event log entry

  1. Create an instance of the EventLog component. For details, see Creating EventLog Components .
  2. Set the Log, MachineName, and Source properties for the component. For details, see Configuring EventLog Components.
    Note If you have already registered the source with the event log to which you want to write an entry, you need only set the Source property in this step.
  3. Use the WriteEntry method to specify the entry to be written to the log. Your code might look something like the following:
    [Visual Basic]
     EventLog1.WriteEntry("This is a simple event log entry")
[C#]
     EventLog1.WriteEntry ("This is a simple event log entry");

See Also

Introduction to the EventLog Component | Configuring EventLog Components | Creating EventLog Components | EventLog Members


Reading Event Log Entries

You use the Entries collection to look at the entries in a particular event log. You can use standard collection properties such as All, Count, and Item to work with the elements the collection contains. You might read event log entries to find out more information about a problem that occurred in your system, to identify usage patterns, or to identify problems (such as a failing hard drive) before they cause damage.

Note The Entries collection is read-only; to write a message to a log you must use the WriteEntry method. For information, see Writing Entries to Event Logs .

If you ask for the count of entries in a new custom log that has not yet been written to, the system will return the count of the entries in the Application log on that server. Make sure that logs you are counting have been created and written to in order to avoid this problem.

To read event log entries

  1. Create an instance of the EventLog component. For details, see Creating EventLog Components .
  2. Set the Log and MachineName properties for the component, if necessary. For details, see Configuring EventLog Components.
  3. Use the Entries collection to review the entries in the log.
The resulting code might look like the following:
[Visual Basic]
Dim en As EventLogEntry
For Each en In EventLog1.Entries
   Debug.Print en.Message
Next en
Dim enArr() As EventLogEntry
EnArr = EventLog1.Entries.All
[C#]
EventLogEntry en;
EventLogEntry[] enArr;
foreach (en in EventLog1.Entries) {
   Debug.Print en.Message;
}
EnArr = EventLog1.Entries.All;

See Also

Introduction to the EventLog Component | Handling the EntryWritten Event


Handling the EntryWritten Event

You can create event handlers for your EventLog components that automatically call the EntryWritten procedure when an entry is written to a log. An event handler is a method that is bound to an event for a component, form, or control. Each event handler provides two parameters that allow you to handle the event properly — the sender, which provides an object reference to the object responsible for the event, and the e parameter, which provides an object for representing the event and its information.

For more information on event handlers, see Event Handling.

To create your own handler

  1. Create an instance of the EventLog component. For details, see Creating EventLog Components .
  2. Set the necessary properties for the component. For details, see Configuring EventLog Components.
  3. Use the AddOnEntryWritten method to create an event handling delegate for your component that will call the EventLog1_EntryWritten procedure when an entry is written to the log. Your code should look like this:
    [Visual Basic]

[C#]
EventLog eventLog1 = new EventLog();
eventLog1.Log = “myLog”;
eventLog1.MachineName = “myMachine”;

eventLog1.AddOnEntryWritten(new EventLogEventHandler(OnEntryWritten));
eventLog1.Monitoring = true;

public static void OnEntryWritten(Object source, EventLogEvent e){
Console.WriteLine("Written: " + e.Entry.Message);
}
    Note For more information on this syntax, see Event Handling.
  4. Create the EntryWritten procedure and define the code you want to process the entries.

    5.

See Also

Event Handling


Clearing Event Logs

Event logs are set to a maximum size of 512 KB. This size determines how many entries each log can contain. When an event log is full, its default behavior is to start overwriting the oldest entries with new entries. You can clear the log of its existing entries to free the log and allow it to start recording events again. You must have Administrator rights to the computer on which the log resides in order to clear event log entries.

Note You can also use the Event Viewer to free up space on a log that has become full. You can set the log to overwrite existing events, or you can increase the maximum size of the log. For more information, see your Event Viewer documentation.

To clear an event log's contents programmatically

  1. Create an instance of the EventLog component and set the necessary properties to configure it. For details, see Creating EventLog Components and Configuring EventLog Components.
  2. Use the Clear method to clear the contents of the appropriate event log. Your code should look something like this:
    [Visual Basic]
EventLog1.Clear
[C#]
EventLog1.Clear();

See Also

Accessing and Initializing Server Explorer | Administering Event Logs


Security Ramifications of Event Logs

Access to the event logs is determined by the account under which the application is running. The LocalSystem account is a special account that service applications can use. The Administrator account consists of the administrators for the system. The Server Operator account (ServerOp) consists of the administrators of the domain server. The World account includes all users on all systems.

The following table shows which accounts are granted Read, Write, and Clear access to each log.

Log | Account | Access
Application | LocalSystem | Read Write Clear
 | Administrator | Read Write Clear
 | ServerOp | Read Write Clear
 | World | Read Write
Security | LocalSystem | Read Write Clear
 | Administrator | Read Write
 | World | None
System | LocalSystem | Read Write Clear
 | Administrator | Read Write Clear
 | World | Read Clear
 | ServerOp | Read

In addition, users can read and clear the Security log if they have been granted one of the following:

  • The "manage auditing and Security log" user right.
  • The SE_AUDIT_NAME privilege. For more information, see Windows NT® Privileges.

For more information, see your Windows documentation.

See Also

Introduction to the EventLog Component


Monitoring Performance Thresholds

Windows performance counters allow your applications and components to capture and analyze the performance data that applications, services, and drivers provide. You can use this information to determine system bottlenecks and fine-tune system and application performance. For example, you might use a performance counter to track the amount of time required to process an order or query a database, or you might monitor the size of a message queue and write code that performs a specific action whenever the queue reaches a preset limit.

Using the PerformanceCounter component, you can easily connect to performance counters on both local and remote computers and retrieve values from these counters. You can also write values to existing counters on the local computer and create your own custom counters on computers that have Windows 2000 installed.

For information on | See
Performance counter features | Introduction to Monitoring Performance Thresholds
The language elements used in PerformanceCounter component programming | PerformanceCounter Programming Architecture
The types of performance counters that you might work with | Performance Counter Types
Creating instances of and configuring PerformanceCounter components | Creating PerformanceCounter Components
Creating, deleting, and querying performance counters and the categories that classify them | Category and Counter Management
Incrementing or decrementing a performance counter’s value | Writing Values to Performance Counters
Reading values from and writing values to a counter | Performance Counter Value Retrieval
Managing performance counter state with a service | Setting up the Performance Counter Service


Introduction to Monitoring Performance Thresholds

The mechanism by which Windows collects performance data on various system resources is the performance counter. Windows contains a pre-defined set of performance counters with which you can interact; some of these are found on all Windows 2000 computers and some are custom to specific applications and are found only on certain computers. Each counter is related to a specific area of system functionality. Examples include counters that monitor a processor's busy time, memory usage, or the number of bytes received over a network connection. You use an instance of the PerformanceCounter component to connect directly to existing performance counters and interact with their data in dynamic ways.

A performance counter monitors the behavior of performance objects on a computer. These include physical components, such as processors, disks, and memory, and system objects, such as processes and threads. In the NGWS frameworks, these performance objects are shown as categories; system counters related to the same performance object are grouped into categories that indicate their common focus. When you create an instance of the PerformanceCounter component, you first indicate the category with which the component will interact and then choose a counter to interact with from within that category.

For example, a category of counters on Windows is the Memory category. System counters within this category track data such as bytes available and bytes cached. To deal with bytes cached in your application, you create an instance of the PerformanceCounter component and connect it to the Memory category, then pick the appropriate counter in that category (Cached Bytes, in this case).

In some situations, categories are further subdivided into instances. Instances track data about multiple occurrences of the object that a category relates to. It is important to note that instances apply to the category as whole, rather than to individual counters. All counters within a category have each instance defined for the category. For example, the Process category contains instances called "Idle" and "System." Each counter within the Process category specifies data in these two ways, showing information about either idle processes or system processes. The structure of the category and counters looks like this:

Category: Process

Instance1: Idle

Instance2: System

Counter1: Elapsed Time

Instance1: Idle

Instance2: System

Counter1: Thread Count

Instance1: Idle

Instance2: System

As you can see, both counters in the category contain instances of Idle and System.

Although your system makes available many more categories of counters, the default categories you'll probably interact with most frequently include the Cache, Memory, Objects, PhysicalDisk, Processor, Server, System, and Thread categories.

Performance Counter Data

Performance counters record values about various parts of the system. These values are not stored as entries, but are persisted for as long as the counter service remains in memory. The process of retrieving data from a performance counter is called sampling. When you sample, you either retrieve the immediate value of a counter or a calculated value.

Depending on how a counter is defined, its value might be the most recent aspect of resource utilization, also called the instantaneous value, or it might be the average of the last two measurements over the period of time between samples. For example, when you retrieve a value from the Process category's Thread Count counter, you retrieve the number of threads for a particular process as of the last time this was measured. This is an instantaneous value. However, if you retrieve the Memory category's Pages/Sec counter, you retrieve a rate per second based on the average number of memory pages retrieved during the last two samples.

Resource usage can vary dramatically based on the work being done at various times of day. Because of this, performance counters that show usage ratios over an interval are a more informative measurement than averages of instantaneous counter values. Averages can include data for service startup or other events that might cause the numbers to go far out of range for a brief period, thereby skewing results.

The PerformanceCounter component provides facilities for the most common Windows performance monitoring requirement, namely, connecting to an existing counter on the server and reading and writing values to it. Additional functionality, such as complex data modeling, is available directly through Windows Performance Monitor. For example, you can use Performance Monitor to chart the data a counter contains, run reports on the data, set alerts, and save data to a log.

Working with Performance Counters

Using the PerformanceCounter component, you can connect to the existing counters installed on the servers to which you have access, or you can create your own custom performance counters. You can read data from any counter, and you can write to your own custom counters.

When you connect to an existing performance counter, you do so by specifying the computer on which the counter exists, the category for the counter, and the name of the counter itself. Additionally, you have the option of specifying the instance of the counter you want to use, if the counter contains more than one instance. You can then read any and all data from the counter. You can also enumerate the existing categories, counters, and instances on the computer by using code.

When you create a new counter, you must specify a completely new category for your local computer. You cannot add a new counter to an existing category or create new counters on a remote computer. However, you can write data to and read data from any custom counters you create.

For more information, see Introduction to Server Resources.

Requirements

To use performance counters in your applications, keep the following in mind:

  • Performance counters are part of the Microsoft Windows operating system. To work with performance counters in your applications, you must have either Windows NT version 4.0 or Windows 2000 installed.
  • You can create PerformanceCounter components in Web applications and Windows applications built with the NGWS runtime.
  • In this release, you should make certain that the Performance Monitor (Perfmon.exe) that is installed with Microsoft Windows NT version 4.0 or Windows 2000 is not running when you create custom performance counters.

See Also

Performance Counter Data Analysis | PerformanceCounter Programming Architecture | Performance Counter Lifetime | Performance Counter Types


Performance Counter Data Analysis

Analyzing performance counter data consists of examining counter values that are reported while your system is performing various operations. During this process you should determine which processes are most active and which programs or threads, if any, are monopolizing a resource. Using this type of performance data analysis, you can understand how your system is responding to workload demands.

As a result of this analysis, you might find that your system performs satisfactorily at some times and unsatisfactorily at others. Depending on the causes of these variations and the degree of difference, you might choose to take corrective action or to accept these variations and delay tuning or upgrading resources to a later time.

The level of system performance that you consider acceptable when your system is handling a typical workload and running all required services is its baseline. The baseline performance is a subjective standard that the administrator determines based on the work environment. It might correspond to a range of counter values, including some that are temporarily unacceptable, but which generally indicate the best possible performance under the administrator's specific conditions.

Determining Acceptable Values for Counters

A large set of factors determines the normal operating range for each counter in a typical system. You must drive your system through a large number of conditions and develop a sense for normal operating ranges for your equipment. You should record these typical values in log files for future reference. Then, as you make changes to your workload or your hardware, you can refer to your earlier experience as a baseline.

In general, deciding whether or not performance values are acceptable is a subjective judgment that varies significantly with variations in user environments. The values you establish as the baseline for your organization are the best basis for comparison.

See Also

Introduction to Monitoring Performance Thresholds | Performance Counter Value Retrieval


Performance Counter Programming Architecture

The PerformanceCounter component uses these portions of the NGWS frameworks namespaces:

PerformanceCounter Namespace

If you are creating your PerformanceCounter components in code, you may need to add a reference to System.Diagnostics to your build command

Key Performance Counter Classes

The following are the major classes associated with PerformanceCounter components:

  • System.Diagnostics. PerformanceCounter — the base class used to create PerformanceCounter components, new counter categories, and custom counters.
  • System.Diagnostics. PerformanceCounterCategory — provides access to the categories for which performance counters exist on a server and all of their corresponding properties and methods.
  • System.Diagnostics. CounterCreationData — used to create multiple counters for a category; allows you to explicitly choose the counter type for each counter you create.
  • System.Diagnostics.InstanceData — used to retrieve multiple instances of a counter and all of their corresponding properties and methods.

Key Programming Elements in the PerformanceCounter Base Class

You can use the following methods to interact with an instance of the PerformanceCounter component:

  • Use the CreateCategory method to create a new counter by defining a category containing one or more counters. Similarly, the DeleteCategory method removes a user-defined category and the counters it contains.
  • Use the ReadCategory method to read all of the counters in a category.
  • Use the CategoryExists method to determine if a category of counters already exists.
  • Use the IncrementBy method to increment a counter value by the specified integer and to return the new value of the counter.
  • Use the NextValue method to return the next raw value for the sample, or the NextSample method to return the next sampling.
  • Use the GetCounters method to return an array of counters in a category.

See Also

Adding and Removing References | Introduction to Monitoring Performance Thresholds


Performance Counter Lifetime

It is important to understand that performance counter values are values rather than entries – when you write a value to a performance counter, you are not making a permanent entry in the counter as you do when you write an entry to an event log. Rather, performance counter values are transitory. When the last PerformanceCounter component to reference a particular system counter is disposed of, the counter resets itself to zero.

There are several ways you can manage the lifetime of a performance counter if you want to retain values for a longer period of time than the default counter behavior allows:

  • You can run the Performance Monitor application on the server where the counter lives. As long as Performance Monitor is open, the reference to the counters is maintained and counter values continue to accumulate.
  • You can install and run the Windows service PerformanceCounterService.exe. As long as the service is running, counter values continue to accumulate without being reset to zero, unless you manually reset the value. For instructions on setting up the service, see Setting Up the Performance Counter Service.
  • You can make sure that there is always an instance of the PerformanceCounter component connected to the particular counter for which you want to maintain values.

See Also

Setting Up the Performance Counter Service | Introduction to Monitoring Performance Thresholds


Performance Counter Types

Performance counters have a type definition that determines how they interact with monitoring applications and the counter values they contain. For example, suppose you are creating an instance of the PerformanceCounter component to count the number of orders processed per second in your e-commerce application. You create a performance counter that increments by one after processing each order. In this case, you would set the CounterType to RateOfChangePerSecond32. This type tells the counter to calculate the number of orders processed per second by taking two samplings and dividing by the timing of the samples. The more samplings taken, the more relevant the data becomes.

Choosing the Right Counter Type

There are many types of performance counters you can choose from when you create a counter. Some of the counters have specific calculations associated with them, and some contain uncalculated values only. In general, there are a few counter types that you will use most frequently. The following table lists these types by common usage patterns.

If You Need To | Use This Type of Counter | Example
Maintain a simple count of items, operations, and so on. | NumberofItems32 | You might use this counter type to track the number of orders received.
Maintain a simple count with a higher capacity | NumberofItems64 | You might use this counter type to track orders for a site that experiences very high volume.
Track the amount per second of an item or operation | CountPerTimeInterval32 | You might use this counter type to track the orders received per second on a retail site.
Track the amount per second with a higher capacity | CountPerTimeInterval64 | You might use this counter type to track the orders per second for a site that experiences very high volume.
Calculate average time to perform a process or to process an item | AverageTimePerSecond32 | You might use this counter type to calculate the average time an order takes to be processed.
Calculate average time to perform a process with a higher capacity | AverageTimePerSecond64 | You might use this counter type to calculate the average time an order takes to be processed on a system with very high volume.

A few caveats on counter type:

  • Counter type affects whether the Calculate method will work or throw an error. You cannot perform a calculation on counters of type NumberofItems32, NumberofItems64, NumberofItemsHex32, NumberofItemsHex64, NumberofSamplesTaken, or RawBase.
  • If you are comparing two samples, they must be of the same counter type. For more information on samples, see Performance Counter Value Retrieval.

For a full list of the types available, see CounterTypeEnum.

See Also

Introduction to Monitoring Performance Thresholds | Creating Custom Performance Counters | Creating Performance Counter Categories


Creating PerformanceCounter Components

You create an instance of the PerformanceCounter component when you want to build performance monitoring into your application. PerformanceCounter components allow you to connect to existing performance counters, write values to them, and react to values the counters receive.

To create an instance of the PerformanceCounter component programmatically

  1. Create an instance of the PerformanceCounter base class in code. You code might look like this:
    [Visual Basic]
Dim myPCtr as New System.Diagnostics.PerformanceCounter
Is this the way to do this in the new syntax?
[C#]
System.Diagnostics.PerformanceCounter myPCtr =
   new System.Diagnostics.PerformanceCounter();
  1. Configure any other necessary properties for your component. For details, see Configuring PerformanceCounter Components.

See Also

Configuring PerformanceCounter Components


Configuring PerformanceCounter Components

There are a variety of properties you must set on the PerformanceCounter components you create. These determine the counter with which the component interacts. The properties you must set include:

  • CategoryName, which determines the performance object with which the component will interact. All performance counters in Windows are grouped by categories, such as Memory, Processes, and Processor.
  • CounterName, which identifies the counter within the specified category with which the component will interact.
  • InstanceName, which identifies the instance of the category with which the component will interact. Note that many categories do not contain instances, so this property might be left blank, indicating a single instance counter.
    Note If the Category has been set to a category that was not defined by the user, you must choose from existing counters and instances. You can only create new counters and instances in custom categories. For example, you cannot create a new counter in the Memory category, but if you create a new category called Orders, you can define as many counters and instances as you need within that category.
  • MachineName, which identifies the server on which the performance counter with which you want your component to interact resides. You can set this property to "." to indicate the local machine.
    Note You can write counters only on the local machine. If you intend to read counter values, you can choose any computer to which you have access.

To configure an instance of the PerformanceCounter component

  1. Create an instance of the PerformanceCounter component. For details, see Creating PerformanceCounter Components .
  2. Indicate the counter with which your component should interact by setting the following properties.
    Property | Setting
    MachineName | Any server to which you have access.
    CategoryName | Any category that exists on the server.
    CounterName | Any counter within the selected category.
  3. If the counter with which you're interacting has several instances, choose the appropriate one in the InstanceName field.

See Also

Creating PerformanceCounter Components


Category and Counter Management

You create categories and custom performance counters if you want to track data that is not captured by the standard counters provided with Microsoft Windows. For example, you might create a custom counter to track the total number of orders processed by your Web site, or to track the orders processed per second by your site. When you create a counter, you add it to a performance counter category, and you assign it a type that governs how it will behave. For information on choosing the correct type, see Performance Counter Types.

There are several ways you can create custom counters:

  • You can create a single counter within a category using the CreateCategory method on the PerformanceCounter class. For instructions, see Creating Performance Counter Categories .
  • You can create multiple counters within a single category using the CounterCreationData class. For instructions, see Creating Custom Performance Counters .

You must create counters in a user-defined category rather than in the performance objects (called categories) defined by Windows. That is, you cannot create a new counter within the Processor category or any other non-user-defined categories. Additionally, you must create a counter in a new category; adding a counter to an existing user-defined category will overwrite all of the counters that category already contains.

The following restrictions apply to the process of creating new counters:

  • You can create custom counters on the local computer or on any remote machine to which you have administrator access.
  • If you do not specify a category type when creating the counter and its category, it will default to NumberofItems32.
  • You cannot create new counters within existing categories. To create a new counter, you must create a new category or add the counter to a category that was user-defined.
Caution This restriction includes counter categories you have created yourself. You can create a new counter in a user-defined category, but it will replace the existing counters within that category.

See Also

Creating Custom Performance Counters | Creating Performance Counter Categories | Deleting Performance Counter Categories | Determining if Counters and Categories Exist | Removing Performance Counter Instances | Performance Counter Types


Creating Custom Performance Counters

When you create a new counter, you first create a category, and then specify one or more counters to be placed in it. You can do this in the following way:

  • You can use the CounterCreationData class to create one or more counters programmatically, by defining an array of counters and passing it as a parameter of a special form of the CreateCategory method.
Note You can also use the CreateCategory method to create a single counter within a new category. For instructions, see Creating Performance Counter Categories.

It is important to note the difference between creating a counter and creating an instance of the PerformanceCounter component. When you create a counter, you are creating a new category and its associated counters in the Windows operating system, rather than a component in your project or application. When you create an instance of the PerformanceCounter class, you create a component that references an external counter.

To create a new category and set of performance counters programmatically

  1. Create an array of CounterCreationData objects, defining all of the counters you want to create within it.
  2. Call the CreateCategory method on the PerformanceCounter class and pass the array of CounterCreationData objects to it.

    The following example shows how to create a series of counters as an array and pass it to the category when you create it:

    [Visual Basic]
   dim cdCounter1 As New CounterCreationData
   dim cdCounter2 As New CounterCreationData
   dim CounterDatas(2) as New CounterCreationData
   cdCounter1.CounterName = strCountName
   cdCounter1.CounterHelp = strCountName & " help string"
   cdCounter1.CounterType = NumberOfItems64
   assign CounterDatas(0) = cdCounter1
   cdCounter2.CounterName = strCountName
   cdCounter2.CounterHelp = strCountName & " help string"
   cdCounter2.CounterType = NumberOfItems64
   assign CounterDatas(1) = cdCounter2
   dim pcTmp as PerformanceCounter
   pcTmp.CreateCategory "Multi Counter Category", "Category help", CounterDatas
[C#]
   CounterCreationData cdCounter1 = new CounterCreationData();
   CounterCreationData cdCounter2 = new CounterCreationData();
   CounterCreationData[] CounterDatas = new CounterCreationData[2];
   cdCounter1.CounterName = strCountName;
   cdCounter1.CounterHelp = strCountName + " help string";
   cdCounter1.CounterType = NumberOfItems64;
   CounterDatas[0] = cdCounter1;
   cdCounter2.CounterName = strCountName;
   cdCounter2.CounterHelp = strCountName + " help string";
   cdCounter2.CounterType = NumberOfItems64;
   CounterDatas(1) = cdCounter2;
   PerformanceCounter pcTmp = new PerformanceCounter();
   pcTmp.CreateCategory ("Multi Counter Category", "Category help", CounterDatas);

See Also

Creating Performance Counter Categories | Category and Counter Management


Creating Performance Counter Categories

You can create a new category to contain custom counters. For example, if you plan to create a series of counters to track various data about the orders processed on your Web site, you might create a category called OrderData on your server and then create the counters you need within it.

Creating a category is not a distinct process from creating the counters that are placed within it; counters can only be created at the point when you create the category itself. There are several ways you can create counters and categories:

  • You can use the CreateCategory method on the PerformanceCounter class, simultaneously creating a single performance counter within it. You cannot create more than one counter using this method.
  • You can use the Create method on the PerformanceCounterCategory class to create a new category and simultaneously create a single performance counter within it. This method functions identically to the previous bullet, so use whichever class is most convenient for you.
  • You can create an array of CounterCreationData objects and pass the array as a parameter of the Create or CreateCategory methods, creating a set of counters within the category. For instructions on this approach, see Creating Custom Performance Counters .
  • You can use the Performance Counter dialog box to create multiple counters while simultaneously creating a new category. For instructions on this approach, see Creating Custom Performance Counters .

To create a category and a single counter within it

  1. Call the CreateCategory method and specify the following parameters.
    Parameter | Value
    CategoryName | Any category name that is not already in use on this server.
    CategoryHelp | A description of the category.
    CounterName | A name for the counter.
    CounterHelp | A description of the counter. This text is displayed in Windows Performance Monitor when a user selects a counter and clicks the Explain button.

The following example shows how you might create a simple category with the CreateCategory method:

[Visual Basic]
Sub CreateCustomCounter()
      PerformanceCounter.CreateCategory "MyCategory", "Desc", "MyCounter", "Counterdesc"
End Sub
[C#]
void CreateCustomCounter() {
      PerformanceCounter.CreateCategory ("MyCategory", "Desc", "MyCounter", "Counterdesc");
}

See Also

Creating Custom Performance Counters | Category and Counter Management


Deleting Performance Counter Categories

You can delete performance counter categories when they are no longer needed. You can only delete categories that are not Windows-standard performance objects; that is, you can only delete categories that have been user-defined. You might delete a category if you have created a custom category associated with an application and you are removing that application from the system. For example, if you have an order entry application that creates counters in an OrderProcessing category, you would delete that category as part of uninstalling your application.

You use the DeleteCategory method to remove a category from the system. When you delete a category, all of the counters and instances associated with it are also removed.

Note You can also use the Delete method on the PerformanceCounterCategory class to remove a category. Both methods function identically, so use whichever class is most convenient for you.

It is important to note the difference between deleting a counter and deleting an instance of the PerformanceCounter component. When you use the Delete or DeleteCategory methods, you are deleting a category and its associated counters from the Windows operating system, rather than removing an instance of a counter from your project or application. When you delete an instance of the PerformanceCounter class, you remove a component that references an external counter.

To delete a category

  1. Call the DeleteCategory method, specifying the following parameters.
    Parameter | Value
    CategoryName | The name of the category you want to delete.
    MachineName | (Optional) The computer on which the category you want to remove resides. If left blank, this parameter defaults to the local machine.

See Also

Category and Counter Management


Determining if Counters and Categories Exist

You can use the Exists method on the PerformanceCounter class to determine if a given performance counter exists within a particular category on either the local or a remote computer. You might do this before creating a new counter to prevent an error from occurring if another counter with that name exists.

In addition to determining whether counters exist, you can determine whether a given category exists by using the CategoryExists method on the PerformanceCounter class. You might do this if you are creating a custom counter and want to determine if the category for it already exists. The CreateCategory method will raise an error if the category you specify has already been created.

Note You can also use the Exists method on the PerformanceCounterCategory class to determine whether a category exists. Both methods function identically, so use whichever object is most convenient for you.

Both the Exists and the CategoryExists methods return true if the item is found, and false if it is not.

To determine if a counter exists

  • Call the Exists method, specifying the following parameters.
    Parameter | Value
    CategoryName | Any category of performance objects on the server.
    CounterName | The name of the counter you want to query.
    MachineName | (Optional) The server on which to locate the category and counter. If blank, the local machine is assumed.

To determine if a category exists

  • Call the CategoryExists method, specifying the following parameters.
    Parameter | Value
    CategoryName | Any category you want to query.
    MachineName | (Optional) The server on which to locate the category. If blank, the local machine is assumed.

    The following example shows how to use an If statement to determine whether a category exists before creating a category and counter:

    [Visual Basic]
If Not (PerformanceCounter1.CategoryExists("MyCat")) Then
   PerformanceCounter1.CreateCategory "MyCat","Desc","MyCtr","Desc"
End If
[C#]
[C#]
if (!PerformanceCounter1.CategoryExists("MyCat")){
   PerformanceCounter1.CreateCategory ("MyCat","Desc","MyCtr","Desc");
}

See Also

Category and Counter Management


Removing Performance Counter Instances

You can use the RemoveInstance method to remove an instance of a custom performance counter. You might delete an instance if you have created a counter instance associated with an application and you are removing that application from the system. For example, if you have an order entry application that creates counters in an OrderProcessing category’s Total instance, you would delete that instance as part of uninstalling your application.

You cannot remove instances from the performance counters that are part of Windows. If your PerformanceCounter component does not refer to a valid instance, this method will throw an exception.

To remove a performance counter instance

  1. Create an instance of the PerformanceCounter component that is connected to the counter from which you want to remove an instance. For details, see Creating PerformanceCounter Components .
  2. Set the InstanceName property to the instance you want to delete.
  3. Call the RemoveInstance method on your component.
The following example shows how to remove an instance called Reference from a counter called BookOrders:
[Visual Basic]
PerformanceCounter1.InstanceName = "Total"
PerformanceCounter1.RemoveInstance
[C#]
PerformanceCounter1.InstanceName = "Reference";
PerformanceCounter1.RemoveInstance();

See Also

Creating PerformanceCounter Components | Category and Counter Management


Writing Values to Performance Counters

You write a value to a counter by incrementing the counter’s current raw value by a positive or negative number. You do this using the IncrementBy method on the PerformanceCounter class.

Note Incrementing by a negative number decrements the counter by the absolute value of the number. For example, incrementing with a value of 3 will increase the counter’s raw value by three. Incrementing with a value of –3 will decrease the counter’s raw value by three.

To write values to performance counters

  1. Create a PerformanceCounter instance and configure it to interact with the desired category and counter. For details, see Creating PerformanceCounter Components or Configuring PerformanceCounter Components .
  2. Call the IncrementBy method, specifying the amount to increment by:
    • To increase the counter’s raw value, specify a positive number.
– or –
  • To decrease the counter’s raw value, specify a negative number.

    The IncrementBy method returns the new value of the counter.

See Also

Introduction to Monitoring Performance Thresholds


Performance Counter Value Retrieval

There are several ways you can read performance counter values:

  • You can retrieve a raw value from a counter, using the RawValue property on the PerformanceCounter class.
  • You can retrieve the current calculated value for a counter, using the NextValue method on the PerformanceCounter class.
  • You can retrieve a set of samples using the NextSample method on the PerformanceCounter class, and compare their values using the Calculate method.

Raw, Calculated, and Sampled Data

The interaction between raw values, next (or calculated) values, and samples is fairly straightforward once you understand that raw and calculated values shift constantly, whereas samples allow you to retrieve a static snapshot of the counter at a particular point in time. The following diagram illustrates the relationship between raw value, next value, and samples.

Performance Counter Values: Raw, Calculated, and Sampled

This picture shows a representation of the data contained in a counter called Orders Per Second. The raw values for this counter are individual data points that vary by second, where the calculated average is represented by the line showing an increasing order receipt over time. In this chart, the following data points have been taken:

  • The user has used the NextValue method to retrieve the calculated value at three different times, represented by NV1, NV2, and NV3. Because the next value is constantly changing, a different value is retrieved each time without specifying any additional parameters.
  • The user has used the NextSample method to take two samples, indicated by S1 and S2. Samples freeze a value in time, so the user can then compare the two sample values and perform calculations on them.

How to Choose the Correct Method of Retrieving Counter Data

Retrieving a raw value with the RawValue property is very quick, because no calculations or comparisons are performed. For example, if you are using a counter simply to count the number of orders processed in a system, you can retrieve the counter’s raw value. For instructions on retrieving raw values, see Retrieving Raw Values from Performance Counters.

Retrieving a calculated value with the NextValue method is often more useful than retrieving the raw value, but this value may also give you an unrealistic view of the data because it can reflect unusual fluctuations in the data at the moment when the value is calculated. For example, if you have a counter that calculates the orders processed per second, an unusually high or low amount of orders processed at one particular moment will result in an average that is not realistic over time. This may provide a distorted view of the actual performance of your system. For instructions on retrieving a calculated value, see Retrieving Calculated Performance Counter Values.

Samples provide the most realistic views of the data in your system by allowing you to retrieve, retain, and compare various values over time. You would retrieve a sample, using the NextSample method, if you needed to compare values in different counters or calculate a value based on raw data. For instructions on retrieving a sample, see Retrieving a Sample of Performance Counter Values.

The NextSample method returns an object of type CounterSample. When you retrieve a sample, you have access to properties on the CounterSample class such as RawValue, BaseValue, TimeStamp, and SystemFrequency. These properties let you get a very detailed look at the data that makes up the sample data.

See Also

Retrieving Raw Performance Counter Values | Retrieving Calculated Performance Counter Values | Retrieving a Sample of Performance Counter Values | Introduction to Monitoring Performance Thresholds


Retrieving Raw Performance Counter Values

You retrieve raw performance counter values by retrieving the current value of the RawValue property on the PerformanceCounter class.

To retrieve a counter’s raw value

  1. Create a PerformanceCounter instance and configure it to interact with the desired category and counter. For details, see Creating PerformanceCounter Components or Configuring PerformanceCounter Components .
  2. Retrieve the current value of the RawValue property.

    The following example illustrates how to use the RawValue property to retrieve the current value of the BookOrders counter:

    [Visual Basic]
Dim MyCtr as New PerformanceCounter
MyCtr.Category= "Orders"
MyCtr.Counter= "Total"
Dim raw as int64
raw = MyCtr.RawValue
[C#]
PerformanceCounter MyCtr = new PerformanceCounter();
MyCtr.Category = "Orders";
MyCtr.Counter = "Total";
Int64 Raw = MyCtr.RawValue;

See Also

Performance Counter Value Retrieval | Retrieving Calculated Performance Counter Values | Retrieving Performance Counter Samples | Retrieving All Counters in a Category


Retrieving Calculated Performance Counter Values

You retrieve calculated performance counter values by calling the NextValue method on the current counter. The counter’s calculated value at the instant that the method executes is calculated and returned to you. Calling NextValue a second time will retrieve a different value, because the calculated value changes as the counter increments.

Note To retrieve more complex views of the data in the counter, you can use samples to retrieve static snapshots of counter data at two points in time, and perform comparisons on those returns. For instructions on retrieving a sample, see Retrieving a Sample of Performance Counter Values.

To retrieve a counter’s calculated value

  1. Create a PerformanceCounter instance and configure it to interact with the desired category and counter. For details, see Creating PerformanceCounter Components or Configuring PerformanceCounter Components .
  2. Call the NextValue method.

    The following example illustrates how to use the NextValue property to retrieve the current value of the BookOrders counter:

    [Visual Basic]
Dim MyCtr as New PerformanceCounter
MyCtr.Category= "Orders"
MyCtr.Counter= "Total"
Dim raw as int64
Raw = MyCtr.NextValue
[C#]
PerformanceCounter MyCtr = new PerformanceCounter();
MyCtr.Category= "Orders";
MyCtr.Counter= "Total";
Int64 Raw = MyCtr.NextValue;

See Also

Performance Counter Value Retrieval | Retrieving a Sample of Performance Counter Values | Creating PerformanceCounter Components | Configuring PerformanceCounter Components


Retrieving Performance Counter Samples

You use the CounterSample class to create samples and perform calculations on their contents. A sample class performs a "sampling" of a performance counter based on criteria you define. These criteria include the values from one or more counters and the frequency with which values should be provided. In addition, the class records the time at which samples were taken. You can collect all of this data in a single instantiated class and then use the Calculate method to perform a calculation. You can also use the Calculate method to perform a calculation that compares values in two different samples.

The calculation performed depends on the type of the counters; certain counter types have specific calculations associated with them. For example, counters of the type ElapsedTime compare the time stamp on two different samples and determine how much time has elapsed. Many counters perform an averaging calculation based on the data retrieved.

These are the steps you go through to define a sample:

  • Create one or more instances of the CounterSample class.
  • Retrieve a current sample for each instance.
  • Call the Calculate method, passing each sample to be included in the calculation as a parameter.

To retrieve performance counter samples

  1. Create a PerformanceCounter instance and configure it to interact with the desired category and counter. For details, see Creating PerformanceCounter Components or Configuring PerformanceCounter Components .
  2. Create an instance of the CounterSample class.
  3. Call the NextSample method on the class to retrieve the calculated value.

To perform calculations with retrieved samples

  1. For each sample that you need for your calculation, instantiate a CounterSample class and call the NextSample method on it.
  2. Dim a single to contain the results of the calculation.
  3. Call the Calculate method on the CounterSample class, specifying the retrieved samples as parameters, and set the result equal to the float.
    Note The two samples must be from counters of the same type or the method will throw an exception. The type of counter determines what kind of calculation performed. For more information, see Performance Counter Types.

    The following code illustrates how to retrieve two samples and compare them using the Calculate method:

    [Visual Basic]
Dim sample1 as CounterSample
Dim sample2 as CounterSample
Dim result as single
sample1 = PerformanceCounter1.NextSample()
'wait some interval of time here
sample2 = PerformanceCounter1.NextSample()
result = CounterSample.Calculate(sample1, sample2)
[C#]
CounterSample sample1 = new CounterSample();
CounterSample sample2 = new CounterSample();
single result;
sample1 = PerformanceCounter1.NextSample();
// wait some interval of time here
sample2 = PerformanceCounter1.NextSample();
result = CounterSample.Calculate(sample1, sample2);

Alternatively, you can call the NextSample method to provide the values for the second sample. The following example illustrates using this approach:

[Visual Basic]
Dim sample1 as CounterSample
Dim result as single
sample1 = PerformanceCounter1.NextSample()
result = CounterSample.Calculate(sample1, PerformanceCounter1.NextSample())

[C#]

CounterSample sample1 = new CounterSample();
single result;
sample1 = PerformanceCounter1.NextSample();
result = CounterSample.Calculate(sample1,
   PerformanceCounter1.NextSample());

See Also

Performance Counter Value Retrieval | Retrieving Raw Performance Counter Values | Retrieving Calculated Performance Counter Values | Retrieving All Counters in a Category


Retrieving All Counters in a Category

You can use the ReadCategory method to quickly retrieve a list of all of the counters or instances in a specific category. Each time you request the return of a counter from a category by creating an instance of a PerformanceCounter object, setting it to an existing counter, and retrieving a value, the entire category is read and the requested counter is retrieved. This means that if you request five different counters from a category that contains 20 counters, the system reads the whole category five times, searching for each counter separately. Using ReadCategory, you get the same data but the category is read only once.

Note The ReadCategory method does not take parameters – instead it returns the counters in the parent category for the current performance counter instance. That is, if you have an instance of the PerformanceCounter component configured to interact with the Processor category, calling ReadCategory on that component returns the counters in the Processor category.

The ReadCategory method returns a CountersData array that contains the counters in the category. You can use the Item property to work with the contents of the array.

To retrieve the counters in a category

  1. Create a PerformanceCounter object and configure it so that it points to the desired category. For details, see Creating PerformanceCounter Components or Configuring PerformanceCounter Components .
  2. Create an array of type CountersData to contain the resulting category list.
  3. Call the ReadCategory method and specify the desired category as a parameter.
  4. Set the results to the array.
The following example shows how to retrieve all of the counters in the Orders category:
[Visual Basic]
Dim myctr as New PerformanceCounter
myctr.Category = "Orders"
Dim processorObject as CountersData
processorObject = myctr.ReadCategory("Processor")
[C#]
PerformanceCounter myctr = new PerformanceCounter();
myctr.Category = "Orders";
CountersData processorObject;
processorObject = myctr.ReadCategory("Processor");

See Also

Performance Counter Value Retrieval | Creating PerformanceCounter Components | Configuring PerformanceCounter Components


Setting Up the Performance Counter Service

The PerformanceCounterService is a Windows service that maintains the current values of your counters in memory. You do not have to install and run PerformanceCounterService for your counters to run correctly, but unless you do (or run Performance Monitor) the values of your counters are reset to zero each time the last component to reference a counter is disposed of.

The PerformanceCounterService file is copied to your machine automatically during installation and can be found in your Bin\i386 directory.

To install PerformanceCounterService

  1. From the Windows command prompt, access the directory in which the performance counter service was copied.
  2. Type InstallUtil.exe PerformanceCounterService.exe.
  3. Open the Services dialog box by doing one of the following:
    • In Windows NT version 4.0, click Start, point to Settings, click Control Panel, and then click the Services icon.

      – or –

    • In Windows 2000 Server, click Start, point to Programs, click Administrative Tools, and then click Services.
  4. Right-click PerformanceCounterService in the list of services, and then click Properties.
  5. Right-click PerfCounterService in the list, and then click Start.

    You can now run your custom performance counter.

To uninstall PerformanceCounterService

  1. From the Windows command prompt, access the directory in which you installed the PerformanceCounterService executable file.
  2. Type InstallUtil.exe –u PerformanceCounterService.exe.

See Also

Performance Counter Lifetime


Optimizing ASP+ Applications

In a wide area network such as the Internet, Web application performance is of premium importance. ASP+ offers improved performance over other Web development platforms. Web developers can improve application performance simply by upgrading to ASP+. Also important is the ease with which system administrators can monitor and then tune application performance as necessary. For more information, see the following topics:

ASP+ Performance Architecture

Developing ASP+ Applications for Enhanced Performance

Monitoring ASP+ Applications


ASP+ Performance Architecture

ASP+ has been designed for enhanced performance out of the box. Without writing any new code, developers should see performance improvements simply by running their applications on the improved ASP+ architecture.

ASP+ Compiled Code

ASP+ Factorability


ASP+ Compiled Code

All code, whether Microsoft Visual Basic, C#, Managed Extension for C++, or Microsoft JScript, is compiled rather than interpreted. Even simple pages authored for ASP+ are compiled to a class, rather than being interpreted on the fly by a script engine.


ASP+ Factorability

ASP+ has been designed to be easily factorable. For example, if an application never uses session state, a developer can remove all session state handlers from application code. (


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Developing ASP+ Applications for Enhanced Performance

To be supplied.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

ASP+ Best Practices

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Myths About Web Application Performance

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Monitoring ASP+ Applications

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Using Performance Monitor with ASP+

[To be supplied.]

ASP+ Performance Counters


ASP+ Performance Counters

ASP+ supports the performance counters listed in this section. They are exposed under the ASP+ performance counter object within PerfMon, and can be used to count the performance of either a single instance of an ASP+ application or all ASP+ applications on a computer combined.

In the following list, all supported counters for the Personal Tier apply only at the per-application level. Counters followed by ** are not supported for Personal Tier applications.

Application Lifetime (Per Application Only)
Amount of time (in minutes) that the application has been running without restarts. Lifetime is measured as the Timeof(Application_OnEnd)–Timeof(Application_OnStart).
Application Restarts (Per Application and Aggregated Per Machine) **
Number of times the application has been restarted during the Web server’s lifetime. (Note that this value will be reset every time the Internet Information Server (IIS) host is restarted). Application restarts is incremented with each Application_OnEnd event.
Anonymous Requests (Per Application Only) **
Number of requests using anonymous authentication.
Anonymous Requests/Sec (Per Application Only) **
Number of requests per second using anonymous authentication.
Cache Entries (Per Application Only)
Total number of entries within the cache.
Cache Hits (Per Application Only)
Total number of hits from the cache.
Cache Misses (Per Application Only)
The number of failed cache requests per application.
Cache Hits % (Per Application Only)
Total cache hit/miss ratio.
Cache Page Hits (Per Application Only)
Total number of hits from the cache when requesting compiled pages.
Cache Page Misses (Per Application Only)
The number of failed cache requests when requesting compiled pages.
Cache Page Hits % (Per Application Only)
Percentage of requests for compiled pages serviced from the cache.
Debugging Requests (Per Application and Aggregated Per Machine)
Number of request that have occurred while debugging has been activated.
Errors During Preprocessing (Per Application and Aggregated Per Machine)
Number of errors that have occurred during parsing. Excludes compilation and runtime errors.
Errors During Compilation (Per Application and Aggregated Per Machine)
Number of errors that have occurred during dynamic compilation. Excludes parser and runtime errors.
Errors During Request Execution (Per Application and Aggregated Per Machine)
Total number of errors that have occurred during the execution of an HTTP Request. Excludes parser and compilation errors.
Errors Unhandled During Request Execution (Per Application and Aggregated Per Machine)
Total number of unhandled errors that have occurred during the execution of HTTP Requests. An unhandled error is any uncaught runtime exception where HandleError is not called.
Errors Unhandled During Request Execution/Sec (Per Application and Aggregated Per Machine) **
Number of unhandled exceptions per second that occur during the execution of HTTP Requests.
Errors/Sec (Per Application and Aggregated Per Machine) **
Number of errors per second that occur during the execution of HTTP Requests. Includes any parser, compilation, or runtime errors.
Output Cache Entries (Per Application Only)
Current number of entries in the output cache.
Output Cache Hits (Per Application Only)
Total number of requests serviced from the output cache.
Output Cache Misses (Per Application Only)
The number of failed output cache requests per application.
Output Cache Hits % (Per Application Only)
Percentage of total requests serviced from the output cache.
Output Cache Size (Per Application Only)
The size (in MBytes) of the output cache.
Pages Compiled (Per Application Only)
Total number of .aspx pages dynamically compiled.


Configuring and Deploying Your NGWS Application

This section discusses support for version control, packaging, and deployment, and explains how to configure NGWS applications. The following topics are provided:

Section | Description
Packaging and Deploying NGWS Runtime Applications | Explains the NGWS runtime's support for version control, packaging, and deployment.
ASP+ Configuration | Describes how developers can associate settings with an installable application and administrators can easily adjust or customize these values after deployment.


Packaging and Deploying NGWS Runtime Applications

The version control, packaging, and deployment story for application developers has vastly improved with the NGWS runtime. For NGWS runtime compliant libraries, you no longer have to rely on Registry entries to describe software components to the system. In addition, you can now specify version numbers for all components of an application. This not only simplifies installation but also makes uninstalling as simple as removing the application files. There are no more registry entries to worry about.

The NGWS runtime introduces the concept of assemblies. An assembly is not a packaging format, but is the fundamental unit of deployment, version control, reuse, activation scoping, and security permissions in the NGWS runtime. Assemblies are then packaged for distribution using any of several packaging and deployment scenarios.

This section presents an overview of the concepts you need to understand to build easily deployable NGWS runtime applications.


Working with Assemblies

A NGWS runtime assembly is the functional unit of sharing and reuse in the NGWS runtime. It is a collection of types and resources that are built to work together and form a logical unit of functionality, a “logical” dll. An assembly forms the fundamental unit of deployment, version control, reuse, activation scooping, and security permissions. Contained in an assembly is the assembly manifest, which contains all the metadata needed to specify the version requirements, security identity, and all information needed to define the scope of the assembly and resolve references to resources and classes.

It is important to understand what an assembly is not. An assembly is not a unit of application deployment. It is a unit of class deployment, much like a dll. An application is built from assemblies, and these assemblies can be packaged for deployment in several ways.

Versioning and DLL Conflicts

Currently there are two versioning problems that occur with Win32 applications. First, current applications cannot express versioning rules between pieces of an application and have these rules honored and enforced by the operating system. The current approach relies on “common-sense” coding practices that state that interface definitions are static once published and that a single piece of code must maintain backward compatibility with previous versions. Furthermore, code is typically designed so that only a single version of it can be present and executing on a machine at any given time.

The second versioning problem is the fact that there is no way to maintain consistency between the sets of components that were built together and the set that will be present at run time. These two versioning problems combine to create DLL conflicts, where installing one application can inadvertently break an existing application because a certain software component or DLL was installed that was not fully backward compatible with a previous version. Once this situation occurs, there is no support in the system for diagnosing and fixing the problem.

An End to DLL Conflicts

Windows 2000 introduced some progress toward fixing DLL conflicts. First, Windows 2000 enables you to create client applications where the dependent DLLs are located in the same directory as the application’s EXE. Windows 2000 first check for the component in the directory where the EXE is located before checking the fully qualified path or doing the normal path search. This allows components to be independent of components installed and used by other applications.

The second feature in Windows 2000 designed to partially fix DLL conflicts is System File Protection. The operating system locks the files in the System32 directory that are shipped with the OS so they cannot be inadvertently replaced when applications are installed.

The NGWS runtime continues this evolution toward a complete solution to DLL conflicts. To solve the remaining problems that lead to DLL conflicts, the NGWS runtime:

  • Allows developers to specify version rules between different software components
  • Provides the infrastructure to enforce versioning rules
  • Provides the infrastructure to allow multiple versions of a software component to be run simultaneously (called side-by-side execution)

The NGWS runtime solves versioning problems by using assemblies. An assembly is the fundamental unit of sharing and reuse in the NGWS runtime. For more information on versioning, see Assemblies and Versioning.

Assemblies and Packaging

An assembly is not a unit of packaging in and of itself. An assembly can be made up of PE files and resource files, and these files can be deployed in a number of ways. An assembly can contain one file or multiple files. How you choose to create assemblies will dictate much of your deployment story.

You can deploy assemblies using the Windows Installer, Internet Explorer, or by copying the assembly. Assemblies can be deployed in as .cab files or as .msi files. You can also simply download or xcopy an assembly to deploy. For more information on deployment, see Packaging Components for Distribution


Assembly Manifest

An assembly’s manifest contains information on all items considered part of an assembly; this information is known as the assembly's metadata. The manifest determines what items are exposed outside of the assembly and what items are accessible only within the current assembly’s scope. The assembly’s manifest also contains a collection of references to other assemblies. These references are resolved by the runtime based on information stored in the manifest. The assembly's manifest contains all information needed to use an assembly.

As you can see from the above illustration, the manifest can be stored in several ways. For an assembly with one associated file, the manifest is incorporated into the PE file to form a single-file assembly. A multi-file assembly can be created with either the manifest as a stand-alone file or incorporated into one of the PE files in the assembly.

From an external view (i.e. that of the assembly consumer), an assembly is a named and version-constrained collection of exported types and resources. From the internal view (i.e. that of the assembly developer), an assembly is a collection of one or more files that implement types and resources. Each assembly’s manifest enumerates the files that make up the assembly and governs how references to the assembly's types and resources are mapped to the files that contain their declarations and implementations. The manifest also enumerates other assemblies on which it depends. The existence of a manifest provides a level of indirection between consumers of the assembly and the implementation details of the assembly and renders assemblies self-describing.

The manifest contains the following information:

  • Assembly name - contains a friendly, textual string name.
  • Version information -consists of a major and minor version number, and a revision and build number. These numbers are used by the runtime when enforcing version policy. For more information on version policy, see Versioning Issues.
  • Shared name information - contains a hash of the file containing the manifest encrypted and a public key from the assembly's originator. For more information on shared names, see Shared Assemblies.
  • Culture, processor and OS supported - contains information on the cultures, processors, and operating systems the assembly supports.
  • List of all files in the assembly - consists of a hash of each file contained in the assembly and of each dependent assembly's manifest.
  • Type reference information - contains information used by the runtime to map a type reference to the file that contains its declaration and implementation.
  • Information on referenced assemblies - contains a list of other assemblies that are statically referenced by the assembly. Each reference includes the dependent assesmbly's name, metadata (version, culture, OS, etc.), and public key if the assembly is shared.

A developer can also set, in code, custom assembly attributes. These custom attributes include:

  • Title - Provides a friendly name, which can include spaces. For example, the assembly name of an assembly might be “comdlg,” while the assembly title would be “Microsoft Common Dialog Control.”
  • Description - a short description of the assembly.
  • Default Alias - Provides a friendly default alias in cases where the assembly name is not friendly or a GUID. For example, in COM interoperability, the generated assembly name will be the GUID of the type library, while the default alias will be the friendly type library name.
  • Configuration information - consists of a string that can be set to any value for configuration information such as Retail or Debug.
  • Product information such as Trademark, Copyright, Product, Company, and InformationalVersion.

For more information on setting an assembly's custom attributes, see Assembly Custom Attributes.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Assembly Custom Attributes

[To be supplied.]


Shared Assemblies

A shared assembly is an assembly that can be referenced by more than one application. In order to share an assembly, the assembly must be explicitly built for this purpose. To understand what this means, let's take a look at the problem of shared components.

An assembly with just a simple text name (e.g., MyApp) cannot be guaranteed to be unique – there is nothing in the tools or in the runtime to prevent name collisions. There is also nothing to prevent such an assembly from being modified in unintended or uncontrolled ways. This approach to assembly identity works fine for private assemblies (intended for use only by the application they are deployed with) but for code that is intended to be shared by multiple applications a stronger notion of identity is required.

This stronger notion of identity is addressed by giving the assembly what is known as a ‘strong’ or ‘shared’ name, using standard public-key cryptography. A shared name is a simple text name accompanied by a public key and a digital signature that was generated over the assembly file (the file that contains the assembly manifest, which in turn contains the names and hashes of all of the files that make up the assembly) using the corresponding private key.

Shared assemblies can be deployed in either the global assembly cache or the application directory. For more information on the global assembly cache, see xxxxxxx. For more information on shared names, see What is a shared name?.


What is a shared name?

A shared name is a simple text name accompanied by a public key and a digital signature that was generated over the assembly file (the file that contains the assembly manifest, which in turn contains the names and hashes of all of the files that make up the assembly) using the corresponding private key. Visual Studio 7.0 and other development tools can create shared names for each build, and signatures optionally can be added to deter tampering. Assemblies with the same shared name are expected to be identical.

A developer can ensure that a name is globally unique by assigning an assembly a shared name. In particular, shared names satisfy the following requirements:

  1. Shared names guarantee name uniqueness by relying on unique key-pairs
  2. Shared names prevent others from "taking over your namespace". Because only you have your private key; no one can generate the same name that you can. An assembly generated with one public key has a different name than an assembly generated with another private key.
  3. Shared names provide some notion of identity. If the runtime security checks pass, you can be guaranteed that the assembly comes from the person you thought it did (assuming his private key wasn't compromised, of course). Note however that shared names in and of themselves do not provide a level of trust. Trust is accomplished through full digital signatures complete with certificates.

Shared assemblies installed in the assembly cache must have shared names.


How to Assign and Reference a Shared Name

In general, the process of giving an assembly a shared name and later referencing it by that name works as follows:

  1. Assembly A is created with a shared name using either a development environment that supports shared names or by the sn.exe utility in the NGWS runtime SDK. The development environment or tool encrypts the hash of the file containing the assembly's manifest with the developer’s private key. This " shared name blob" is stored in the PE file containing the assembly manifest in Assembly A.
  2. Assembly B, a consumer of Assembly A, would include a "token" that represents Assembly A’s public key in the reference section of Assembly B's manifest. A "token" of the public key is stored rather than the key itself.
  3. The NGWS runtime verifies the shared name signature when the assembly is placed in the local assembly cache. When binding by shared name at runtime, the NGWS runtime compares the key stored in Assembly B’s manifest with the key used to generate the shared name for Assembly A. If the runtime security checks pass and the bind succeeds, Assembly B has some level of comfort that the bits it is getting haven't been tampered with and that they do in fact come from the developers of Assembly A.

The above scenario does not address trust issues. Assemblies can carry full PKCS standard compliant Digital Signatures. These signatures include a certificate that establishes trust. It is important to note that shared names do not require code to be signed in this way. In fact, the keys used to secure the assembly's name don't even have to be the same key used to sign the assembly itself.


Assemblies and the Cache

Each machine on which the NGWS runtime is installed has a machine-wide code cache. This cache is divided into two components, a download cache and a global assembly cache.

The download cache stores code downloaded on behalf of an application. Once downloaded, the assembly is "private" and is only visible to that application; it has no effect on other applications on the machine. The download cache gets scavenged based on LRU policies.

The global assembly cache is used to store assemblies meant to be used by several applications on the machine. The only way to deploy an assembly into the global assembly cache is by using an installer or a tool in the SDK such as alink.exe. The global assembly cache is never scavenged.

The Global Assembly Cache and Shared Named Assemblies

The global assembly cache is a repository for assemblies with shared names that strongly identify their contents. Assemblies with shared names avoid the compatibility risk of replacing a DLL shared by multiple apps or registering a new implementation for a COM class. With a DLL, there is also risk of name collision, either directly or through the search path. In a shared name, the version metadata can be overridden per application by bind policy specified in the admin.cfg file managed by the system administrator or in the application's configuration file. This factoring is more flexible than static linking or embedding the version number in the name reference, e.g. mfc42.dll. These latter approaches make it difficult to upgrade to newer components after they have been tested for compatibility. The robustness of the application and the managability of multiple versions should be the determining factors when deploying shared assemblies into the global assembly cache.

For more information on shared names, see What is a shared name?.

Cache Security

For downloading components that have not been trusted by the user, the assembly cache supplies a randomly generated subdirectory name to prevent a hacker from accessing the assembly via a file reference. When an assembly is added to the assembly cache, integrity checks are performed on the data streams. The assembly manifest specifies a hash value and hash algorithm for all of the other streams. The stream that is or contains the manifest is independently signed and verified.

Since the assemblies can be accessed directly from the file system, the assembly cache performs some level of integrity checking to ensure that an assembly has not been tampered with (e.g. a file changed but the manifest version stayed the same). Only users with "administrator" privileges can perform management tasks on the global assembly cache.


Type Assemblies and Type Identity

NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Assemblies and Type Identity

Assemblies are the construct used by the NGWS runtime to locate and load types. For all type references made in the scope of an assembly, the assembly manifest contains the information that the NGWS runtime uses to resolve the reference.

A type name in the NGWS runtime has logically two parts: the assembly name and the name of the type within the assembly. Two types with the same name but in different assemblies are treated as two distinct types.

While the assembly portion of the name clearly has meaning in the runtime, no particular semantics are applied to the type portion of the name---to the runtime it is just a string. The runtime does not associate any sort of hierarchy with names within an assembly.

Assemblies provide consistency between the scope of names seen by the developer and the scope of names seen by the runtime system. Developers author types in the context of an assembly. The contents of the assembly a developer is building establishes the scope of names that will be available at runtime.

Namespaces

Particular languages have constructs (and corresponding syntax) that help developers form logical groups of classes. Java has packages, VB has modules, and C++ has its own concept of a namespace. These constructs can be used to build hierarchical namespaces of classes, such as WinForms.ui.button, WinForms.ui.brush or WinForms.core.event.

These constructs are used within the particular languages to help developers build assemblies, but the constructs themselves are not understood by the runtime and do not play a part in binding or in the visibility of names. To the runtime, a namespace is just type names. You can build a single assembly that exposed types that look like they come from two different hierarchical namespaces, such as System.Collections and System.WinForms. Or, you could build two assemblies that both export types whose names contain " MyDll.MyClass."

If a tool wanted to present the above types as belonging to a hierarchical namespace, it would have to enumerate the types in any assembly or group of assemblies and parse the type names to derive a hierarchical relationship.


Locating Types

Development tools emit references to types as typerefs in the metadata for the module containing the call. Type reference information includes the name of the type, which can include the namespace, and the resolution scope which indicates the module or assembly from which the type can be resolved.

A type can be found in one of three locations relative to the caller:

  • The same module as the caller
  • A different module in the same assembly as the caller
  • A different assembly than the caller

The contents of these typeref fields varies depending on whether the target of the reference is contained in the same module, the same assembly, or a different assembly than the caller.

Reference within the Same Module

When a reference is made to a type in the same module as the caller, the NGWS runtime optimizes away the type reference and replaces it with the type definition of the referenced type. As a result, the method reference points directly to the definition of the type containing the method. All information needed to activate the type is contained in its type definition.

References inside a module cannot have version binding policy applied to them since versioning is done at the assembly level. They always run with the implementation of the class they were compiled with.

Reference between Modules within the Same Assembly

The runtime loader uses ModuleRefs to resolve type references between modules in the same assembly. This ModuleRef describes the module the requested type is in. When resolving the type reference, the loader uses this ModuleRef directly without going through the manifest.

References between types in the same assembly cannot have version binding policy applied to them. They always run with the implementation of the class pointed to by the typeref or manifest.

References between Assemblies

References to types in a different assembly are be emitted with AssemblyRef information. This AssemblyRef identifies the assembly the requested type is in. When resolving the type reference, the loader will use this AssemblyRef directly without going through the manifest.


Assemblies and Security Considerations

The runtime uses the set of permissions in an assembly to grant access privileges. When an assembly is built, the developer records the set of permissions that the assembly requires to run. For example, if the developer writes code that accesses the disk, the assembly must request “File IO Permission.” The set of requested permissions are stored in the assembly manifest.

At runtime, the assembly's security information is used as input to security policy. Security policy is typically established by the administrator and is used to determine the set of permissions that are granted to the code within the assembly. Security policy can be established for the publisher of the assembly if it is signed, the web site and zone (in Internet Explorer terms) the assembly was downloaded from, or the assembly’s name if that name has been secured with a key pair (termed a “shared name”).

For example, an administrator can establish security policy that says all code downloaded from the Internet and signed by “Joe’s Software” cannot access my disk drive, but may display user interface on my machine.

To allow policy to be based on either Authenticode signatures or shared names, the runtimer must be able to guarantee that the contents of the assembly have not been changed since it was built. That is, it must be able to guarantee the integrity of each assembly it runs. It does this through hash verification: the assembly manifest contains a list of all files that make up the assembly, including a hash each file as it existed when the manifest was built. As each file is loaded at runtime, its contents are hashed and compared with the hash value stored in the manifest. If the two hashes don’t match, the assembly fails to load. In addition to the hash checks described here, the runtime also validate the assembly's signature---either the shared name signature or the Authenticode signature.


Assemblies and Versioning

All versioning in the NGWS runtime is done at the assembly level. The specific version of an assembly and the versions of dependent assemblies are recorded in the assembly's manifest. Version policy, which version should be used, is specified by configuration files. These two files, the assembly manifest and an optional configuration file, provide the means for expressing version and version policy and the NGWS runtime then enforces this policy at runtime.

Note: Versioning is done only on assemblies with shared names. Private assemblies are never checked for version information.

Each assembly has its version expressed by two separate "numbers." The first "number" is called a compatibility version. This version number is part of the assembly’s identity and is a key part in the type resolution process at runtime. It is called a compatibility number because this number is used by the runtime when enforcing version policy to determine a particular version’s compatibility with previous versions.

The second version "number" is called an informational version. The informational version is a string and is used to represent additional version information that is included for documentation purposes only. This textual version information corresponds to the product’s marketing literature, packaging, or product name and is not used by the runtime. For example, an informational version could be “NGWS runtime Beta1" or “Acme Control SP 2”.

An Assembly's Compatibility Version Number

Each assembly has a specific compatibility version number as part of its identity. As such, two assemblies that differ by compatibility version are considered by the runtime to be completely different assemblies. This compatibility version number is physically represented as a four part number with the following format:

<major version>.<minor version>.<build number>.<revision>

For example, version 1.5.1254.0.

Each portion of this number has a specific meaning to the runtime as it determines which version of an assembly to load. It is important that you understand the significance of each number. The runtime views the four numbers as three logical parts:

  • The major and minor version numbers form the incompatible part of the version number. Any change in these two numbers indicates to the runtime that this version is incompatible with other assemblies with different major and minor version numbers. In other words, changes in the major and minor version numbers indicates a significant new release of an assembly.
  • The build number forms the maybe compatible part of the version number. A change in the build number indicates to the runtime that a change has been made to the assembly that may still be compatible and carries less risk than an incompatible change. Backwards compatibility is not assumed though. For example, a service pack or daily build would change this number.
  • The revision number forms the QFE part of the version number. A change in the revision number indicates to the runtime that this is an emergency fix and the runtime will assume this is a compatible assembly unless instructed to not use it by a configuration file override.

For example, an assembly with compatibility version number 5.4.0.0 is considered incompatibly with an assembly whose compatibility number is 4.1.11.00. An assembly with a version number of 3.5.1342.0 may be compatible with an assembly with version number 3.5.1340.0. Finally, an assembly with a revision number of 5.4.0.23 is considered a QFE for an assembly with a version number of 5.4.0.0.

The compatibility version number is stored in the assembly’s manifest along with other identity information including the assembly name, originator, and version number, as well as information on relationships and locations of other assemblies connected with the application. Because the version number is integral to the assembly, two assemblies with different version numbers are treated as different assemblies by the runtime.

When an assembly is built, the development tool records dependency information for each assembly that is referenced. This information is kept in the assembly manifest as AssemblyRefs. The runtime uses these version numbers, in conjunction with configuration information set by an administrator, to load the proper version of a referenced assembly.

The runtime distinguishes between private and shared assemblies for the purposes of versioning. No version checking is done when resolving references to private assemblies. Since a private assembly is deployed along with the application and is placed in the same directory or subdirectory as the application, the application author has control over the contents and distribution of the assembly.

For information on how to specify version binding policies, see Specifying Version Policies in Configuration Files. For information on how the runtime uses version information to find a particular assembly, see How the runtime Works with Assemblies.

An Assembly's Informational Version

As discussed above, the informational version is a string used to attach additional version information to an assembly for documentation purposes only; this information is not used at runtime. In this release the information is displayed by admin tools and the property pages visible in the shell.

The informational version is represented using the custom attribute System.Reflection.AssemblyInformationalVersionAttribute. The definition of that attribute is:


Specifying Version Policies in Configuration Files

The rules that specify the acceptable versions of an assembly are called version policies. Version policies are expressed using the <BindingPolicy> tag in configuration files. For more information on assembly versioning in the NGWS runtime, see Version Policy.

Configuration files can be used to override the manifest information. For example, the manifest of an assembly records the version number of each dependency at the time the assembly was compiled. In some cases, it may be acceptable to run with different versions of the dependent assemblies that those present at build time. For example, even if version 5.0.0.0 of a dependent assembly was present at build time, a more recent version, such as version 5.0.1.0 may be acceptable at runtime. Configuration files can also be used to change version policy if a version of a dependency causes an application to stop working. In this case, configuration information can be used to “roll back” to a specific version of a dependency that was present at build time.

The version policies supported in configuration files include:

Default Version Policy

The default version policy takes the version of the dependent assembly that is recorded in the manifest and matches the major and minor numbers exactly, then takes the latest build and revision numbers. In other words, of the four numbers that make up the version number, the first two must match exactly and the highest of the build and revision numbers are used. For example, if the runtime is given a reference to version 5.0.11.0, and a version 5.0.11.12 is present in the global assembly cache, version 5.0.11.12 will be used.

There are a few points to consider about the default version policy. First, the “use latest” behavior described above only applies to the build and revision portions of the version number. The policy of taking the latest build and revision is often referred to as Automatic QFE Policy. This QFE policy allows software vendors to release critical bug fixes without requiring dependent applications to be rebuilt or reconfigured. It is worth noting that this policy is implicit in that there is nothing that the runtime does to enforce that a QFE is in fact backward compatible with the version it is replacing. The runtime never implicitly takes the latest major and minor numbers because they are, by definition, incompatible with what is being requested.

Second, the runtime only looks in the global assembly cache for QFEs. This is important. If a search path is specified in a configuration file, the runtime does not look at all assemblies on the path, searching for the one with the highest build and revision number. The performance cost of doing this would be prohibitive.

Bind to a specific version

The Bind to a specific version policy allows the developer to specify a particular assembly version using the BindingPolicy tag in a configuration file. This policy allows you to bind to a different version of an assembly than the version recorded in the manifest. The policy can be used to map either references to a specific version of a dependency or references to all versions of a dependency.

For example, the following configuration file entry maps references to all versions of an assembly called calcr to version 6.0.0.0:

 <BindingPolicy>
      <BindingRedir Name="Calcr"
                    Originator="32ab4ba45e0a69a1"
                    Version="*" VersionNew="6.0.0.0"
                    UseLatestBuildRevision="yes"/>
   </BindingPolicy>

Don't Accept Implicit QFEs

The two previously described polices employ automatic QFE policy, that is they will use the latest build and revision number available. There are times when this is not desirable, such as when a QFE is applied and it works for some applications but breaks others. In this scenario, the administrator or developer could disable the automatic QFE policy that have broken because of the update.

This policy also uses the BindingPolicy tag in a configuration file. The attribute UseLatestBuildRevision is set to no to disable automatic QFE policy for references to a particular assembly. For example, this excerpt from a configuration file binds all references to an assembly called calcr to exactly version 6.1.1212.14; no QFEs to that version will be picked up:

   <BindingPolicy>
      <BindingRedir Name="Calcr"
                    Originator="32ab4ba45e0a69a1"
                    Version="*" VersionNew="6.1.1212.14"
                    UseLatestBuildRevision="no"/>
   </BindingPolicy>

Safe Mode

The Safe Mode policy is used to ensure that the versions of the assemblies built and tested together are the same versions that are run together after the application is deployed. The safety mode policy can also be used to revert to a known state in the presence of failures caused by running with incompatible or buggy versions of dependnet assemblies.

Safe mode always applies to an entire application, not to individual assembly references. The runtime ignores any other version policies and binds to exactly the versions of the dependent assemblies specified in the manifest. All four parts of the version number are checked; no QFEs are applied.

The safe mode policy uses the BindingMode tag in the configuration files. The following example enables the safe mode policy:

   <BindingMode>
      <AppBindingMode Mode="safe"/>
   </BindingMode>

For more information on configuration files, see Creating Configuration Files.


Side by Side Execution

Side-by-side execution is the ability to run multiple versions of the same assembly simultaneously. The NGWS runtime provides the infrastructure to allow multiple versions of the same assembly to be run on the same machine, or even in the same process.

Code that is side-by-side compatible has more flexibility in terms of providing compatibility with previous versions. Components that can run side by side do not have to maintain strict backward compatibility. For example, consider a class MyClassX that supports side-by-side execution and an incompatibility is introduced between versions 1 and 2. Callers of MyClassX that express a dependency on version 1 will always get version 1 regardless of how many subsequent versions of MyClassX are installed on the machine. A caller would only get version 2 if it specifically “upgraded” its version policy.

Support for side-by-side storage and execution of different versions of the same assembly is clearly an integral part of the versioning story and is built into the infrastructure of the NGWS runtime. As has already been discussed, the assembly's version number is part of an assembly’s identity. As a result, the NGWS runtime can store multiple versions of the same assembly in the global assembly cache and load those assemblies at runtime.

There are two distinctions of side-by-side execution: running on the same machine and running in the same process. Side-by-side execution on the same machine involves multiple version of the same application running on the same machine at the same time without interfering with each other. An application that supports side-by-side execution on the same machine demands careful coding. For example, consider an application that uses a file at a specific location for a cache. The application must either handle multiple versions of the application accessing the file at the same time or the application must remove the dependency on the specific location and allow each version to have its own cache.

Side-by-side execution in the same process places even more requirements on the developer of an application. To successfully run side by side in the same process, multiple versions cannot have any strict dependencies on process-wide resources. The advantages to running side by side in the same process include single applications with multiple dependencies running multiple versions of the same component as well as web servers being capable of running applications with possible conflicting dependencies in the same process.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Versioning Issues

[To be supplied.]


Packaging Components for Distribution

Because the NGWS runtime components are self-describing and the assembly itself describes the relationships of its components, application installation and deployment is greatly simplified. The NGWS runtime applications do not require any registry entries; deploying a NGWS runtime application can be as simple as copying the assemblies that constitute an application to a directory structure on disk. The runtime can then launch the application and resolve all references directly from the file system. Because each assembly describes where to locate the classes it contains, there is no need to make registry entries of any kind.

To deploy NGWS runtime files, you can create either .msi files for use with the Windows Installer or.cab files for distribution by downloading or copying. Obviously, if you intend to deploy files from a website, you can simply have an application download assembly files directly into the download cache or the assembly cache.

For more information on packaging and deployment, see Application Distribution Overview.


Application Distribution Overview

A common misperception is that assemblies are a packaging format. Assemblies are not a packaging or distribution technology in and of themselves. Instead, assemblies can be packaged for deployment in several ways.

An assembly's files are represented as PE files and resource files. These files can be distributed as is, or they can be packaged into some other distribution unit. For the first release of the NGWS runtime the supported distribution formats are .cab and .msi.

Assemblies that make up web-based applications can be downloaded and installed via the NGWS runtime MIME filter. In all web-based applications, the top-level HTML document defines the assemblies used by the application. The HTML document contains OBJECT tags referencing assemblies needed by the application. These assemblies may reference additional dependent assemblies as needed.

The following picture shows the "lifecycle" of an assembly's files as they relate to standard packaging and distribution:

  1. Source files are compiled and linked into .exe's, .dll's and .resources files (other file types, like .gif can be included in an assembly as well).
  2. The assemblies created by the build step can be deployed as is simply by xcopy'ing them to a directory. Also, .dll's can be referenced by codebases and downloaded on demand.
  3. Alternatively, a developer may choose to package the assembly files into some other distribution unit like compressed .cab files or .msi files that can be installed by the Windows Installer. CAB files can also be distributed through code download.

Configuration, Grouping Classes into Assemblies, Managing Resource Files


Standard Deployment Techniques

For many applications, assembly files can be deployed by either using the Windows Installer or by deploying or downloading .cab files. This section provides information on each of these scenarios:

Deploying a NGWS Runtime + 2.0 Application Using the Windows Installer

Deploying a NGWS Runtime Application Using CAB Files


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Deploying a NGWS Runtime Application Using the Windows Installer

[To be supplied.]


Deploying a NGWS Runtime Application Using CAB Files

Creating a CAB file for deployment of a NGWS Runtime application is very similar to creating such files for traditional Windows applications. For this release, there are a few restrictions that apply. First, only one assembly can be put in a single CAB file. Second, the CAB file must have the same name, minus the extension, as the file in the assembly that contains the manifest. For example, if the file containing the manifest is called MyClasses.dll, then the CAB file must be named MyClasses.cab.


Considerations When Creating Assemblies and Packaging NGWS Runtime Applications

The developer has several options when it comes to packaging modules into assemblies. How you divide up the application into modules that go into assemblies depends on several factors. These factors include:

Versioning - The assembly is the smallest unit of versioning in the NGWS runtime. Group modules in an assembly that you want to have the same version information.

Reuse - If you intend to reuse the modules of an assembly, such as in a shared assembly, group these modules into their own assembly.

Security - Group modules into assemblies that contain types that require the same security permissions.

Scoping - Group modules containing types that you want to restrict their visibility into the same assembly.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Deploying Applications Using IE5.5

[To be supplied.]


Managing Resource Files

Resources are retrieved using the resource's logical name. The logical name maps to a physical location for the resource. The physical location can be one of the following:

  • In separate resource files in the same assembly. Each resource file can contain one or more resources.
  • Embedded in a PE in the same assembly
  • In another assembly

The APIs hide the physical location from a user, i.e., a user retrieves a resource by specifying its logical name. This allows a developer to change how the resources are packaged. For example, the runtime resources in mscorlib were in a separate mscorlib.resources file at one time but are today packaged in mscorlib.dll.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Managing Unmanaged APIs and Resources

[To be supplied.]


Creating Configuration Files

The NGWS runtime configuration system allows administrators and developers to configure those aspects of an application that are either specific to the deployment environment, or can logically be changed after the application was built. By default, all information that the runtime needs to locate, load, and execute an application is available either implicitly (e.g., the location where you started the application establishes the default search path) or within the manifest for each assembly that's loaded as a part of that application. For example, by default, each assembly binds to the versions of its dependencies it was compiled with as specified in its manifest. A configuration file can be used to change these default settings.

It is important that you understand that configuration only applies at the application level. For example, it is not possible to configure a specific assembly to use a specific version of a dependent assembly on an assembly-by-assembly basis The configuration files apply to all assembly binding done in the scope of a single application.

A typical example of configuration data is security policy. When building an assembly, a developer specifies the set of permissions that are required for the code to run. However, the decision of whether to grant the assembly the requested permissions is up to the administrator who creates a configuration file, and is made independent of when the assembly was built.

In this release, an administrator will be able to configure a) security policy, at both the machine and user level; b) application behavior; and c) runtime behavior. In addition, a developer or support engineer will also be able to configure various runtime settings in order to debug problems.

When the runtime is installed, there are defaults for all of these settings so that an administrator does not have to take any specific action to configure the system to run applications. The following sections provide the details on how to configure the various aspects of the runtime and applications that target the runtime.

Note that some of the configuration information can be provided programmatically using the System.Configuration.ConfigurationManager namespace. For more information, see System.Configuration in the reference section.


Benefits of NGWS Runtime Configuration

The benefits to the developer of the NGWS runtime configuration system include:

  • Storage of rich data types - The settings used to configure the NGWS runtime do not all have the same size and shape. Some of the data is static and can readily be represented as a relational schema; some of the data is stored as serialized objects. In addition, configuration data in the NGWS runtime is user extensible, and therefore its contents are not natively known to the NGWS runtime. The configuration system supports the storage of these various types of data.
  • Flexibility - The NGWS runtime developer can add configuration data to the system without going through a central authority. Additionally, end user customers can leverage the configuration systems for customer-specific settings.
  • Hierarchical Configuration - It is often useful for administrators to specify configuration information at various levels of a hierarchy. These levels include machine, user, application and assembly. The configuration system supports the ability to specify configuration at multiple, custom-defined levels, and provides for custom merge rules to be supplied.
  • Configuration data security - The configuration system protects all configuration data from unwarranted access and tampering.
  • Integration with Admin Tools - The interfaces defined by the configuration system support both read and write access and are efficiently usable by administrator tools.


Configuration Security Policy

Security policy allows the administrator to restrict the permissions granted to code running on the machine. The policy system looks at evidence about all managed code---where it comes from, name or publisher identity---as the basis of a trust decision about the code This results in a set of permissions the system is willing to grant the code. Security policy may be configured at the machine and user levels in this release.


Application Behavior

There are several application settings that can be configured either on an application-by-application basis, or at the machine level, for all applications launched on that machine. There are rules for how settings at each of these levels are merged to obtain a single set of configuration settings for a given application. The rules for each setting are given in the description of the settings that follow.

The configuration settings available in this release are:

  • Application specific search path - the path that the runtime uses to locate assemblies that are referenced during the execution of an application. The application specific search path is relative to the application path.
  • Version policy -- for a given application, it is possible to specify as policy what version of a particular assembly should be used, or specify that the application should run in "safe mode," using exactly those versions that it was compiled with.
  • Codebase - explicit information about where to find a referenced assembly.
  • NGWS Runtime Remoting – the information necessary for the remoting infrastructure to properly expose and consume remote types and assemblies.

Excluding the NGWS runtime remoting, these configuration settings are located in one of two places:

  • The machine wide configuration file located at %windir%\admin.cfg where %windir% is the Windows directory.
  • The application specific configuration file located in the same directory as the application that is executing. The filename is the same name as the application with a .cfg extension instead of an .exe extension. For example, the configuration file for xyz.exe is xyz.cfg.

In this release, NGWS runtime remoting currently uses its own configuration file. In future releases, remoting configuration information will be included in the configuration files mentioned above.


Application Specific Search Path

A common deployment paradigm used in the NGWS runtime is to simply xcopy all the assemblies that make up an application into a particular directory structure and run the application. In this scenario, the NGWS runtime needs to know where to look to resolve references to private assemblies. Private assemblies are those assemblies that typically have simple names and are deployed in the directory structure of the application in which they are used. We do have a default search path that is sufficient for most cases, but this can be customized through configuration if desired. The application specific search path is typically used to find private, simply named assemblies. However, shared named assemblies can also be found on the application specific search path.

To change the application specific search path, <AppDomain> tags are added to the configuration file. Each <AppDomain> tag has a PrivatePath property that contains semicolon-delimited paths relative to the application path.


Version Policy

Sometimes, it is useful or necessary to tell an application to use a specific version of an assembly. These policies are established by entries in the configuration files.

A policy can state that:

“References to assembly XZY, version 1.0 should be bound to version 1.2”

When the runtime needs an assembly, these policies are reviewed to determine if the runtime should use the originally targeted version or a different version of the assembly.


Codebases

A configuration can supply Codebases, providing locations for particular assemblies. If a codebase is specified for an assembly, the NGWS runtime will get the assembly directly from that codebase and not search for it on disk.

Unlike version policy, codebases are specified using only the application specific configuration files and do not included in the machine wide configuration file.

To specify a codebase for an assembly, <CodeBaseHint> tags are added to the <Assemblies> collection in the application configuration file. Each <CodeBaseHint> tag has several properties that describe that the assembly name, version, originator, and the URL where the assembly can be found.


NGWS Runtime Remoting

NGWS runtime remoting allows an application to be configured so that the assemblies used by the application can be part of the local application or part of a remote application. If an assembly is configured to be part of the remote application, the types in that assembly will be instantiated in the remote application and a “proxy” will be returned to the local application.

The NGWS runtime remoting configuration is used to specify where a type or assembly lives, what kind of communication is used to talk to the remote type, and the lifetime of the remote type.


Writing Administrator Configuration files

An administrator can create a configuration file that overrides any other configuration file on the machine it resides on. This XML-based configuration file can set policy for access to data, as well as assembly binding policies. For more information on the role the admin.cfg file plays in determining which assembly version is chosen for binding, see How the runtime Works with Assemblies.

Sample admin.cfg

The following is a sample madmin.cfg file from %windir%\complus\config. In addition to version policy settings, this file also contains configuration settings for the web server. These settings are documented separately.

<?xml version="1.0"?>
<configuration xmlns="x-schema:catalog.xms">
  <AppPoolList>
    <AppPool AppPoolID="AppPool32"
             PeriodicRestartTime="211" PeriodicRestartRequests="212" MaxProcesses="213"
             PingingEnabled="214" IdleTimeout="115" RapidFailProtection="216"/>
  </AppPoolList>
  <SiteList>
    <Site SiteID="304" DefaultAppPoolID="AppPool32"
          HomeDirectory="%systemroot%\xspdt_invalid">
      <BindingList>
        <Binding Name="URLPre34"/>
      </BindingList>
    </Site>
  </SiteList>

  <webconfig xmlns=””>
    <!-- xmlns=”” only needed when xmlns is specified in <configuration> root element -->
   <!-- ASP+ configuration -->
  </webconfig>

  <BindingPolicy>
    <BindingRedir Name="wininet" PK="" Version="0.5.0.10" VersionNew="0.7.0.1"
                  NoPublisherPolicy="no"/>
  </BindingPolicy>

</configuration>


Writing Application Configuration Files

The application configuration file, sometimes referred to as app.cfg, is an XML file that contains a several attributes that can control the binding behavior of the application. This section describes each of the attributes in the application configuration file.

The terminology used in the application configuration file is consistent with the other configuration files. A collection defines the beginning of a set of rows, and is delineated by start and end tags, such as: <tag> and </tag>. A row is an entry inside a collection which contain specific settings in the form of name-value pairs. A property is a name-value pair inside a row that contains the setting information.

The outer-most collection that defines the application configuration file is the Configuration collection. Each of the sub-collections and rows described below appears between the start and end tags of the Configuration collection.

AppDomain Row

Under the Configuration collection, an AppDomain row can provide information on different locations of assemblies. Specifically, the AppDomain row contains properties that can be used to specify a shared and private binpath that is appended to the binpath provided during a bind operation. There is an additional property, called ShadowCopy, that is used by the runtime to determine whether or not to copy the assembly into the download cache even if the assembly can be run from the source location.

For example:

<AppDomain PrivatePath="[semicolon delimited private binpath]"
           ShadowCopy="[True|False]"/>

BindingMode Collection

The BindingMode collection contains a single row that describes the “binding mode” for the application. This row, named AppBindingMode has a single property called Mode that can be set to one of two values: “safe” and “normal”. (so what do they mean??) If a value for the Mode property is not “safe” or “normal”, the binding mode is assumed to be “normal”.

For example:

<BindingMode>
   <AppBindingMode Mode="[safe|normal]"/>
</BindingMode>

BindingPolicy Collection

The BindingPolicy collection contains BindingRedir rows that redirect one version of an assembly to another. This version redirection, or policy only applies to shared assemblies (ie. assemblies which contain a public key). The BindingRedir row contains a number of properties, which are listed below:

Property Name | Supported Values | Description
Name | Any string | Name of the assembly
Originator | String form of originator, an eight-byte has of the public key | Originator of the assembly
Version | String form of version: w.x.y.z, or “*” for all versions | Version which should be redirected
VersionNew | String form of version: w.x.y.z | Version which the assembly should be redirected to
UseLatestBuildVersion | “yes” or “no” | Indicates whether or not this assembly should be subject to automatic QFE updates from the global cache

For example:

<BindingPolicy>
   <BindingRedir Name="[name]" Originator="[orig]" Version="[ver|*]"
                 VersionNew="[ver]" UseLatestBuildRevision="[yes|no]"/>
</BindingPolicy>

Assemblies Collection

The Assemblies collection contains one or more CodeBaseHint rows, which can tell the runtime where to find a particular assembly. These codebase “hints” are always attempted before the standard runtime probing occurs. The CodeBaseHint row contains a number of properties which describe the assembly, as well as the location where the assembly can be found:

Property Name | Supported Values | Description
Name | Any string | Name of the assembly
Originator | String form of the originator, the eight-byte hash of the public key | Originator of the assembly
Version | String form of version: w.x.y.z | Version of assembly
CodeBase | String containing a valid URL | Codebase URL where the assembly can be found

For example:

<Assemblies>
   <CodeBaseHint Name="[name]" Originator="[orig]" Version="[ver]"
                 CodeBase="[URL]"/>
</Assemblies>

Sample app.cfg

The following is an example of an application config file. This file contains settings for the web server in addition to version policy and codebase settings.

<?xml version ="1.0"?>
<Configuration>
   <AppDomain
       PrivatePath="bin;bin2;foo"
       ShadowCopy="True"
    />

   <BindingMode>
      <AppBindingMode Mode="normal"/>
   </BindingMode>

   <BindingPolicy>
      <BindingRedir Name="g_SglAsm_SglMod1.dll"
                    Originator="8e47bf1a5ed0ec84"
                    Version="*" VersionNew="3.3.3.3"
                    UseLatestBuildRevision="no"/>
   </BindingPolicy>

   <Assemblies>
      <CodeBaseHint Name="g_SglAsm_SglMod1.dll"
                    Originator="8e47bf1a5ed0ec84"
                    Version="3.3.3.3"
                    CodeBase="http://codebasehint/Mydll.dll"/>
   </Assemblies>

</Configuration>


Writing Security Configuration Files

For this release, a security configuration file can be created that can set security policy machine wide. This file is an XML file that contains several attributes that can control the security settings of the application. This section describes each of the attributes in the security configuration file.

Sample security.cfg

The following is a portion of the default security.cfg file that ships with the NGWS runtime. This file can be found in %windir%\system32.

<Configuration>
   <Security>
      <Policy>
         <PolicyLevel class="System.Security.Policy.PolicyLevel">
            <NamedPermissionSets>
               <PermissionSet class="System.Security.NamedPermissionSet">
                  <Permission class="System.Security.Permissions.ClientStoragePermission">
                     <Unrestricted/>
                     <VersionTag>XMLNotationV1</VersionTag>
                  </Permission>
                  <Permission class="System.Security.Permissions.EnvironmentPermission">
                     <Read>USERNAME TEMP</Read>
                     <VersionTag>XMLNotationV1</VersionTag>
                  </Permission>
                  <Permission class="System.Security.Permissions.ReflectionPermission">
                     <Unrestricted/>
                     <VersionTag>XMLNotationV1</VersionTag>
                  </Permission>
                  <Permission class="System.Security.Permissions.SecurityPermission">
                     <Assertion/>
                     <Execution/>
                     <VersionTag>XMLNotationV1</VersionTag>
                  </Permission>
                  <Permission class="System.Security.Permissions.UIPermission">
                     <Unrestricted/>
                     <VersionTag>XMLNotationV1</VersionTag>
                  </Permission>
                  <Name>LocalIntranet</Name>
               </PermissionSet>


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Special Deployment Considerations

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Tasks Related to Assemblies, Packaging, and Deployment

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Creating a Single-File Assembly

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Creating a Multi-File Assembly

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Creating a Multi-File Assembly With a Separate Manifest File

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Creating an Assembly With a Shared Name

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Specifying an Assembly's Dependencies

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Referencing Assemblies in your Code

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Deploying your Application

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Specifying Version Numbers in a Config File

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Deploying an Assembly into the Assembly Cache

[To be supplied.]


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

ASP+ Configuration

[To be supplied.]


ASP+ Configuration Concepts

A central requirement of any web application server is a rich and flexible configuration system – one that enables developers to easily associate settings with an installable application without having to embed values into code, and enables administrators to easily adjust or customize these values post-deployment.

The ASP+ configuration system addresses both of these requirements. It does this through a hierarchical configuration infrastructure that enables extensible configuration data to be defined and used throughout ASP+ applications. At the same time, ASP+ provides a rich set of initial configuration settings.

The ASP+ configuration system provides the following benefits:

  • Configuration information is stored in XML-based configuration files that are both human-readable and human-writeable. Administrators and developers can use any standard text editor, XML parser, or PERL script to interpret and update configuration settings.
  • Configuration information is stored in files that are kept within the same directory tree as the rest of the application files. This enables easy installation of ASP+ applications, complete with configuration information, using xcopy or ftp.
  • The configuration system is easily extensible. You can store your own configuration criteria and settings within the configuration system.
  • Changes to ASP+'s configuration files are automatically detected by the system, and are applied without requiring any user intervention. The administrator does not need to "kick" the system to get the changes to take effect.
  • The configuration information contained is applied hierarchically. Child directories inherit or override configuration settings from parent directories. This allows different settings for different applications or different parts of a single application


Config.web Configuration Files

All configuration information for ASP+ is contained in configuration files named Config.web. The following example illustrates the structure of an ASP+ configuration file:

<!-- CONFIG.WEB FILE -->
<configuration>
   <configsections>
      <add names="httpmodules" type="System.Web.Config.HttpModulesConfigHandler"/>
      <add names="httphandlers" type="System.Web.Config.HttpHandlerConfigHandler"/>
      <add names="sessionstate" type="System.Web.Config.SessionStateConfigHandler"/>
      <add names="globalization" type="System.Web.Config.GlobalizationConfigHandler"/>
      <!-- ADDITIONAL CONFIGSECTION DECLARATIONS GO HERE -->
     </configsections>

   <httpmodules>
      <!-- http module subelements go here -->
   </httpmodules>

   <httphandlers>
      <!-- http handlers subelements go here -->
   </httphandlers>

   <sessionstate>
      <!-- session state subelements go here -->
   </sessionstate>

   <globalization>
      <!-- session state subelements go here -->
   </globalization>

   <!-- ADDITIONAL CONFIG SECTIONS GO HERE -->

</configuration>

All configuration information must reside between the <configuration> and </configuration> tags. There are two main parts to the file. At the top are the configuration section handler declarations (contained within the <configsection> <\configsection> tags). The rest of the file contains the actual configuration sections (for clarity, their subelements have been removed). Notice that there must be one <configsection> declaration for each configuration section that follows it. Each declaration gives the configuration section name and indicates the NGWS frameworks assembly and class that will handle that configuration information. Each configuration section contains subelements that contain the configuration settings for that particular area of ASP+.

The following code illustrates these concepts:

<configuration>

   <configsections>
      <add name="debugmode" type="System.Web.Config.SingleTagSectionHandler" />
      <add name="globalization" type="System.Web.Config.SingleTagSectionHandler" />
      <add name="assemblies" type="System.Web.UI.AssembliesSectionHandler" />
      <add name="security" type="System.Web.Config.SecurityConfigHandler" />
   </configsections>

   <debugmode enable="true" />

   <globalization
      requestencoding="us-ascii"
      responseencoding="iso-8859-1"
   />

   <assemblies>
      <add assembly="System.Data.dll"/>
      <add assembly="System.dll"/>
      <add assembly="System.Drawing.dll"/>
      <add assembly="*"/>
   </assemblies>

   <security>
      <authorization>
         <allow users="*" /> <!-- Allow all users -->
      </authorization>
   </security>

</configuration>

This example illustrates a configuration file containing four configuration sections - debugmode, globalization, assemblies, and security. The following settings are established:

  • In the debug section, the debug mode is enabled (set to true).
  • In the globalization section, the request and response encoding types are set.
  • In the assemblies section, four assemblies are added.
  • In the security section, all users are given access.


Hierarchical Configuration Architecture

As mentioned earlier, there can be multiple configuration files located in different directories on the server. When a request arrives for a particular URL, ASP+ computes the settings for that URL in a hierarchical fashion, using the configuration files located in the path for the requested URL.

For example, there might be a web site with the following file structure:

Where the administrator wants to configure the application settings to allow all users to have access to the Application Root directory, but allow only selected users to have access to the two subdirectories:

Assume that there is a Config.web file in the SubDir1 directory and none in the Application Root or SubDir2 directories. In this case there are actually two Config.web files that are being used. The highest-level file is the one located in the %windir%\Complus\Version directory. This file is said to be at the machine level, and all ASP+ directories and subdirectories inherit its settings. There is a standard ASP+ configuration file shipped with the NGWS frameworks and runtime that contains default settings, and it is located in this directory. The default setting for the security configuration section of this file is to allow all users access. Since there is no configuration file in the example Application Root directory that modifies this behavior, all users have access to it, because that directory inherits from the machine-level configuration file. If the Config.web file in the SubDir1 directory contains a security configuration section that allows access only to certain users, then SubDir2 inherits that setting but the Application Root directory will not. So, all users have access to the Application Root directory, but only certain users have access to both of the subdirectories.


Standard Configuration Settings

ASP+ is shipped with a standard Config.web file that contains a rich set of configuration settings. This file is located in the %windir%\ComPlus\Version directory. The standard configuration sections included in the machine level configuration file can be found under ASP+ Standard Section Handlers.


Custom Configuration Settings

It is possible for you to extend the standard set of settings with some of your own. To do this you will need to create your own Configuration Section Handler. This handler must be a NGWS class that implements the System.Web.Config.IConfigSectionHandler interface. It is responsible for interpreting and processing the XML settings defined within a specific portion of a Config.web file -- and then returning an appropriate configuration object based upon them. This returned configuration object can be any data-structure – it is not limited to any base configuration class or configuration format.

The IConfigSectionHandler interface is defined as:

namespace System.Web.Config
{
   public interface IConfigSectionHandler
   {
      public Object Create(Object parent,
                           ConfigInput[] input,
                           String path);
   }
}

IConfigSectionHandler exposes a single method Create that accepts three arguments – parent, input, and path. The parent parameter enables configuration factories to inherit settings calculated by the configuration section handler within a parent directory. The input parameter provides access to the raw XML contents of the configuration file’s setting. The path argument indicates the virtual path to the configuration factory that is responsible for calculating values.

The Create method is passed an array of ConfigInput objects that contain the XML relevant to the requested configuration file section (for example, the standard sections: <htttphandlers>, <sessionstate>, and so forth). An array is passed because it is possible for configuration file authors to partition their configuration data across multiple sections within the file. For example, there could be two <httphandlers> tag sections.

ConfigInput passes in an XML tree that is rooted at the base of the relevant configuration section. For example, a <httphandlers> configuration section handler would be provided a reference to the <httphandlers> element.

Configuration Inheritance

The object-based value returned from a call to Create represents the computed configuration data object for the particular node in the URL hierarchy. It will be passed as the “parent” argument to the configuration section handler handling subnodes deeper within the hierarchy. For instance, the configuration data returned for the URL "/MyApp/Sub1" would be passed as the parent value to the IConfigSectionHandler of "/MyApp/Sub1/Sub2". The only exception is if there is no parent configuration data (for example: when the data is computed by the root handler), in which case the “parent” argument is null.

You can use this value to help calculate subvalues (in which case the data is said to "inherit") or alternatively, ignore these values completely and simply use the current node’s values when calculating the appropriate configuration data.

For example, suppose a developer wanted to compute the configuration data of the <sessionstate> settings for the URL – “/401k/Order/”:

SessionStateConfig config = (SessionStateConfig)
                   Context.GetConfig(“sessionstate”, “/401k/Order/”);

This would result in the below psuedocode being executed:

// <sessionstate> section data handled by
// System.Web.Config.SessionStateConfigHandler
SessionStateConfigHandler sessionHandler = new
                                         SessionStateConfigHandler();

// Default settings for machine (declared in: c:\winnt\complus\Config.web)
inputFirst = InputFrom(“sessionstate”, “c:\winnt\complus\Config.web”);
outputFirst = sessionHandler.Create(null, input, “”);

// Overridden by settings at application level (declared in: c:\401k)
inputSecond = InputFrom(“sessionstate”, “c:\401k\Config.web”);
outputSecond = sessionHandler.Create(outputFirst, inputSecond, “/401k”);

// Overridden by settings at directory level (declared in:
// c:\401k\Order\Config.web)
inputThird = InputFrom(“sessionstate”, “c:\401k\Order\Config.web”);
outputThird = sessionHandler.Create(outputSecond, inputThird,
                                                      “/401k/Order”);

return outputThird;

Configuration Is Threadsafe and Immutable

It is important that you understand that since the configuration system caches the configuration objects returned by configuration section handlers, the configuration objects they generate should be both threadsafe and immutable - so that multiple callers who request the same configuration object get exactly the same results.

In particular, it is very important that a configuration section handler not modify the "parent" argument passed into the Create method. Even if the return value is conceptually just a small modification of the parent, actual modifications must be made on a clone of the parent instead of on the original.


Accesssing Configuration Settings

At runtime ASP+ uses Config.web configuration files to hierarchically compute a unique collection of configuration settings for each incoming URL request. Web developers – whether they are writing HttpModules classes, IHttpHandler instances, or ASP+ pages – can access these configuration settings using the GetConfig() methods exposed on the HttpContext object. The settings returned are not computed by ASP+, but rather by the IConfigSectionHandler class registered to handle a specific section of the configuration file.

The following example code demonstrates how you can access the <sessionstate> configuration data exposed for the current URL:

SessionStateConfig config = (SessionStateConfig)
                             Context.GetConfig("sessionstate");

if (config.CookieLess == true)  {
   // Cookieless sessions are activated…
}

if (config.InProc == true) {
   // InProc session state is being used….
}

The following example code demonstrates how you can access the <sessionstate> configuration data exposed for a specific URL:

SessionStateConfig config = (SessionStateConfig)
                   Context.GetConfig("sessionstate", "/MyDir/MyApp.aspx");

if (config.CookieLess == true)  {
   // Cookieless sessions are activated…
}

if (config.InProc == true) {
   // InProc session state is being used….
}

Important: Since the configuration settings might change at any time (for example, if someone updates a Config.web file), a developer should not attempt to cache any configuration settings themselves – and should instead rely solely on the configuration system to do this.


ASP+ Standard Section Handlers

ASP+ ships with a several standard configuration section handlers that can be used to process configuration settings within Config.web files.

The following sections describe each of the standard configuration section handlers in detail, and demonstrate the full syntax they support.

Standard Configuration Sections | Description
<browsercaps> | Configures the settings for the browser capabilities component. For information on the browser capabilities component, see the HttpBrowserCapabilities Class in the Reference section of the NGWS runtime documentation.
<compilation> | Contains all the compilation settings used by ASP+. Supports the <compilers> and <assemblies> subtags.
<configuration> | Root configuration section for all ASP+ configuration files.
<configsections> | Contains a list that indicates the configuration section handlers associated with each configuration section.
<globalization> | Configures the globalization settings of the application. The settable attributes are: requestencoding, responseencoding, fileencoding, and culture.
<httphandlerfactories> | Maps incoming URL requests to IHttpHandlerFactories.
<httphandlers> | Maps incoming URL requests to IHttpHandler classes. This config section is the exception to the inheritance rule. Subdirectories do not inherit these settings.
<httpmodules> | Adds, removes or clears Http Modules within an application
<processmodel> | Configures the ASP+ process model settings on IIS Web Server systems. The settable attributes are: enable, timeout, idletimeout, shutdowntimeout, requestlimit, requestqueuelimit, memorylimit, cpumask, and usecpuaffinity.
<security> | Configures all security settings used by ASP+. Supports the <authentication>, <authorization>, and <identity> subtags.
<sessionstate> | Configures the session state HttpModule. The settable attributes are: inproc, cookieless, timeout, server, and port.
<tracing> | Configures the ASP+ trace service. The settable attributes are: enabled, requestlimit, and pageoutput.


<browsercaps> Section Handler

Controls the settings of the browser capabilities component.

To see a list of browser properties that can be retrieved and set, see the HttpBrowserCapabilities Class.

Example

<configsections>
   <add name="browsercaps"
        type="System.Web.Config.BrowserCapsConfigHandler"/>
</configsections>

<browsercaps>
   <result type="System.Web.HttpBrowserCapabilities" />
   <use var="HTTP_USER_AGENT" />
            browser="Unknown"
            version=0.0
            majorver=0
            minorver=0
            frames=false
            tables=false
   <filter>
      <case match="Windows 95|Win95">
            platform=Win95
      </case>
      <case match="Windows NT|WinNT">
            platform=WinNT
      </case>
   </filter>
</browsercaps>

Remarks

[To be supplied.]

Requirements

Contained Within: <configuration>

Web Platform: IIS 5.0

Configuration File: config.web

Configuration Section Handler:
System.Web.Config.BrowserCapabilitiesConfigHandler

See Also

ASP+ Configuration Concepts


<compilation> Section Handler

Configures all compilation settings used by ASP+.

<compilation debugmode="true/false">

   <compilers defaultlanguage="[Lang]">
      <compiler
         language="[Lang]"
         extension="[ Ext]"
         type="[Assembly]# [NGWS Class] "/>
   </compilers>

   <assemblies>
      <add assembly="[Assembly] " />
      <remove assembly="[Assembly]"  />
      <clear />
   </assemblies>

</compilation>

The <compilation> tag supports one attribute – debugmode – that indicates whether compilation should compile retail (False) or debug (True) binaries. The default is False.

The <compilers> subtag supports one attribute – defaultlanguage – that controls the default language used within .aspx, .asmx and .asax files when no languge is specified. It also supports one subelement, <compiler>, which creates a new compiler definition. The <compiler> element supports three attributes:

Subelement | Attribute | Description
<compiler> |  | Creates a new compiler definition.
 | Language | Indicates the language name used in dynamic compilation files. For example: “C#”, “PERL”, “Jscript”, “VB”, etc.
 | Extension | Filename extension used for dynamic code-behind files. Examples: .pl, .cls, .js
 | Type | Indicates the NGWS class (which implements Icompiler) that should be used to compile all resources that use either the above language or file extension.

The <assemblies> subtag supports three types of subelements that act as processing directives:

Subtag | Directive | Description
<assemblies> |  | Adds or removes assembly references that are to be used during the compilation of a dynamic resource.
 | add | Adds an assembly reference to be used during the compilation of a dynamic resource. ASP+ will automatically link this assembly when compiling each code module The value specified is an assembly name – not a dll path. ASP+ will first perform a lookup on the supplied assembly name in order to find the physical dll location. A developer can optionally specify "*" to add every assembly within an application's private assembly cache – located within the "bin" sub-directory of an application.
 | remove | Removes assembly reference from compile settings. The value must explicitly match a previous "add" directive. Wildcard selections are not supported.
 | clear | Removes all assembly references currently contained in or inherited by a particular Config.web file.

Remarks

Several compiler names/extensions can be configured to work against the same ICompiler type. Simply add multiple <compiler> tags to the <compilation> section. This is shown in the following example.

Example:

<configsections>
   <add name="compilation"
         type=”System.Web.Config.CompilationConfigHandler”/>
</configsections>

<compilation debugmode="true">
   <compilers defaultlanguage=”VB”>
      <compiler language=”VB”
                extension=”.cls”
                type=”MSVSA.dll#Microsoft.VB.Compiler” />
      <compiler language=”C#”
                extension=”.cs”
                type=”C#.dll#Microsoft.C#.Compiler” />
      <compiler language=”C#”
                extension=”.cs” type=”
                C#.dll#Microsoft.C#.Compiler” />
   </compilers>

   <assemblies>
      <add assembly=”MyDll.dll” />
      <add assembly=”ADODB.dll” />
      <add assembly="*" />
   </assemblies>

</compilation>

Requirements

Contained Within: <configuration>

Web Platform: IIS 5.0

Configuration File: config.web

Configuration Section Handler:
System.Web.Config.CompilationConfigHandler

See Also

ASP+ Configuration Concepts


<configsections> Configuration Section

Contains a list that indicates the configuration section handlers associated with each configuration section.

<configsections>
   <add name="config section element" type="config section handler" />
   …
</configsections>

The <add> sub-element contains the following attributes:

Directive | Description
name | Specifies the name of the element that will contain configuration data.
type | Specifies the configuration section handler class to be associated with the element specified in the name attribute.

Remarks

There must be a configuration section handler declared for each configuration section in the file.

Configuration section handler settings are inherited throughout the application's scope. However, once an association is made between a configuration section and its handler, that association cannot be overridden by configuration file in a subdirectory.

All configuration section handlers must implement the IConfigSectionHandler interface. For more information about the IconfigSectionHandler interface, see that topic in the NGWS runtime Reference.

Example

<configsections>
   <add name="httpmodules"
        type="System.Web.Config.HttpModuleConfigHandler"/>
</configsections>

<httpmodules>
   <add type="System.Web.Cache.OutputCacheModule" />
   <add type="System.Web.SessionState.SessionStateModule" />
</httpmodules>

Requirements

Contained Within: <configuration>

Web Platform: IIS 5.0

Configuration File: config.web

See Also

ASP+ Configuration Concepts


<configuration> Configuration Section

Root configuration section for all ASP+ configuration files.

<configuration>
   ...
</configuration>

Remarks

All other configuration sections must be contained within the <configuration> section. This section must appear within all config.cfg configuration files.

Requirements

Contained Within: No containing section, <configuration> is the root.

Web Platform: IIS 5.0

Configuration File: config.web

See Also

ASP+ Configuration Concepts


<globalization> Section Handler

Configures the globalization settings of an application.

<globalization
      requestencoding="[any valid encoding string]"
      resonseencoding="[any valid encoding string]"
      fileencoding="[any valid encoding string]"
      culture="[any valid culture string]" />

The <globalization> tag supports the following attributes:

Attribute | Description
requestencoding | Assumed encoding of each incoming request (default is: "us-ascii").
responseencoding | Content encoding of responses (default is: "iso-8859-1").
fileencoding | Default encoding for .aspx, .asmx and .asax file parsing.
culture | Default culture used to process incoming web requests.

Example:

<configsections>
   <add name="globalization"
         type="System.Web.Config.GlobalizationConfigHandler">
</configsections>

<globalization
   requestencoding=”us-ascii”
   responseencoding=”iso-8859-1” />

Requirements

Contained Within: <configuration>

Web Platform: IIS 5.0

Configuration File: config.web

Configuration Section Handler:

System.Web.Config.GlobalizationConfigHandler

See Also

ASP+ Configuration Concepts


<httphandlerfactories> Section Handler

Maps incoming requests to the appropriate IHttpHandlerFactory class, based on the URL and HTTP verb specified in the request. Unlike the mapping in the <httphandler> element, the HTTP handler factory mappings are inherited.

Administrators can configure the <httphandlerfactories> section using three sub-tag directives: “add”, “remove” and “clear” that are interpreted/processed in top-down sequential order.

<httphandlerfactories>
   <add verb="verb list" path="path" type="assembly#NGWS class" />
   <remove verb="verb list" path="path" />
   <clear />
</httphandlerfactories>

The <add> and <remove> sub-elements contain three attributes.

Directive | Attribute | Description
add |  | Adds a verb/path mapping to an IHttpHandlerFactory class. Note that if an identical verb/path has been specified earlier (for example – in a Config.web file in a parent directory), the second call to add will override the previous setting.
 | verb | The verb-list can be either a comma-separated list of HTTP verbs (for example: “GET, PUT, POST”) or an '*', which indicates all HTTP verbs.
 | path | Specifies the URL path or wildcard that maps to the HTTP handler factory class. The value can be either a single URL path, or a simple wildcard (for example, "*.aspx"). Full wildcard regular expressions are not supported in PDC Tech Preview.
 | type | The class that implements IHttpHandlerFactory. The value is composed of an assembly and class name combination, separated by a "#" character. The assembly DLL is always resolved first against an application’s private “bin” directory, and then against the system assembly cache.
remove |  | Removes a verb/path mapping to an IHttpHandlerFactory class. Note that the remove directive must exactly match the verb/path combination of a previous add directive in order to remove an entry (wildcards are not supported).
clear |  | Removes all IHttpHandlerFactory mappings currently configured or inherited by a particular Config.web file.

Example

<configsections>
   <add name="httphandlerfactories"
        type="System.Web.Config.HttpHandllerFactoriesConfigHandler"/>
</configsections>

<httphandlerfactories>
   <add verb=”GET,POST,PUT”
        path="*.aspx"
        type="System.Web.Pages.PageFactory" />
   <add verb=”*”
        path="*.scottgu"
        type="Scottgu.dll#ScottGuFactory" />
   <add verb=”*”
        path="*.asax"
        type="System.Web.RestrictedResourceHandler" />
</httphandlerfactories>

Requirements

Contained Within: <configuration>

Web Platform: IIS 5.0

Configuration File: config.web

Configuration Section Handler:

System.Web.Config.HttpHandlerFactoriesConfigHandler

See Also

ASP+ Configuration Concepts


<httphandlers> Section Handler

Maps incoming requests to the appropriate IHttpHandler class, based on the URL and HTTP verb specified in the request. Unlike the mapping in the <httphandlerfactories> element, the HTTP handler mappings are not inherited. Thus, setting values for this element at the machine level are meaningless.

<httphandlers>
   <add verb="[verb list]"
        path="[path/wildcard]"
        type="[Assembly]#[NGWS Class] " />
   <remove verb="[verb list]"
           path="[path/wildcard]" />
   <clear />
</httphandlers>

The directives and attributes are shown in the following table.

Directive | Attribute | Description
add |  | Enables administrator to add a verb/path mapping to an IHttpHandler or IHttpHandlerFactory class. Note that if an identical verb/path has been specified earlier (for example – in a Config.web file in a parent directory), the second call to add will override the previous setting.
 | verb | The verb-list can either be a comma-separated list of HTTP verbs (for example: “GET,PUT,POST”) or a start-script mapping (for example: "*")
 | path | The path attribute can either contain a single URL path or a simple wildcard (for example: *.aspx). Full wildcard regular expressions are not supported in PDC Tech Preview.
 | type | The type attribute value is composed of an assembly plus a class combination (where the two values are separated by a '#' character). The assembly DLL is always resolved first against an application’s private "bin" directory, and then against the system assembly cache.
remove |  | Removes a verb/path mapping to an IHttpHandler or an IhttpHandlerFactory class. The remove directive must exactly match the verb/path combination of a previous add directive in order to remove an entry (wildcards are not supported).
clear |  | Removes all IHttpHandlerFactory mappings currently configured or inherited by a particular Config.web file.

Remarks

If an identical verb/path combination is specified by two or more <add> sub-elements, the last <add> will override all others.

Example

<configsections>
   <add name="httphandlers"
        type="System.Web.Config.HttpHandlersConfigHandler"/>
</configsections>

<httphandlers>
   <add verb="*" path="MyApp.Scott" type="MyApp.dll#MyApp.Scott" />
   <add verb="*" path="MyApp.Baz" type="MyApp.dll#MyApp.Baz" />
</httphandlers>

Requirements

Contained Within: <configuration>

Web Platform: IIS 5.0

Configuration File: config.web

Configuration Section Handler: System.Web.Config.HttpHandlersConfigHandler

See Also

ASP+ Configuration Concepts


<httpmodules> Section Handler

Configures the Http Modules within an application.

<httpmodules>
     <add type="[Assembly]# [NGWS Class] " />
     <remove type="[Assembly]# [NGWS Class] " />
     <clear />
</httpmodules>

You can configure the <httpmodules> section using three sub-tag directives:

Directive | Attribute | Description
add |  | Adds HttpModule classes to an application. Note that if an identical verb/path has been specified earlier (for example – in a Config.web file in a parent directory), the second call to add will override the previous setting.
 | type | Composed of an assembly+class combination (where the two values are separated by a '#' character). The assembly DLL is always resolved first against an application's private 'bin' directory, and then against the system assembly cache.
remove |  | Removes an HttpModule class from an application.
clear |  | Removes all HttpModules mappings currently configured or inherited by a particular Config.web file from the application.

Example:

<configsections>
   <add name="httpmodules"
        type="System.Web.Config.HttpModuleConfigHandler”/>
</configsections>

<httpmodules>
   <add type="System.Web.State.CookielessSessionModule" />
   <add type="System.Web.OutputCacheModule" />
   <add type=”System.Web.State.SessionStateModule” />
   <add type=”ScottGu.dll#MyClass” />
</httpmodules>

Requirements

Contained Within: <configuration>

Web Platform: IIS 5.0

Configuration File: config.web

Configuration Section Handler:

System.Web.Config.HttpModulesConfigHandler

See Also

ASP+ Configuration Concepts


<processmodel> Section Handler

Configures the ASP+ process model settings on IIS Web Server Systems.

<processmodel
         enable="[true/false]"
         timeout=”[mins]”
         idletimeout=”[mins]”
         shutdowntimeout=”[mins]”
         requestlimit=”[num]”
         requestqueuelimit=”[num]”
         memorylimit=”[MBs]”
         cpumask=”[num]”
         usecpuaffinity=”[true/false]” />

The <processmodel> tag supports the following attributes:

Attribute | Description
enable | BOOL value indicates whether the process model is enabled
timeout | DWORD value set to the number of minutes after which a new worker process will be launched to take the place of the current one. Default: infinite.
idletimeout | DWORD value set to the number of minutes of inactivity after which the worker process gets automatically shut down. Default: infinite.
shutdowntimeout | DWORD value set to the number of minutes the worker process is given to shut itself down gracefully. When the timeout expires, XSP ISAPI will kill the worker process. Default: 5.
requestlimit | DWORD value set to the number of requests after which new worker process will be launched to take place of the current one. Default: infinite.
requestqueuelimit | DWORD value set to the maximum allowed number of requests in the queue after which the worker process is considered misbehaving. A new process will be launched and the requests will get reassigned. Default: infinite.
memorylimit | DWORD value set to the maximum allowed memory size, in MB, that the worker process consumes before it is considered misbehaving. A new process will be launched and the requests will get reassigned. Default: infinite.
cpumask | DWORD value controls the number of processes and the way the web garden works. One process is launched for each CPU where the corresponding bit in the mask set to 1. When UseCPUAffinity (see below) is set to 0, the CPUMask setting only controls the number of worker processes (number of bits set to 1), the maximum allowed number of worker processes is the number of CPUs. By default all CPUs are enabled - as many worker processes as there are CPUs are launched.
usecpuaffinity | Controls CPU affinity. "True" indicates that the processes should be affinitized to the corresponding CPU. Default is "True".

Remarks

The <processmodel> configuration settings are actually ignored by the managed code configuration system. Instead, they are directly read by the XSPISAPI unmanaged dll.

Example:

<configsections>
   <add name="processmodel"
        type="System.Web.Config.IgnoreConfigHandler">
</configsections>

<processmodel
   enable=”true”
   timeout=”15”
   idletimeout=”25”
   shutdowntimeout=”5”
   requestlimit=”1000”
   requestqueuelimit=”500”
   memorylimit=”200”
   usecpuaffinity=”true” />

Requirements

Contained Within: <configuration>

Web Platform: IIS 5.0

Configuration File: config.web

Configuration Section Handler:

System.Web.Config.IgnoreConfigHandler

See Also

ASP+ Configuration Concepts


<security> Section Handler

Establishes all security settings used by the XSP security HttpModule.

<security>
   <authentication mode="[Windows/Cookie/Passport]">
      <cookie cookie="[name]" loginurl="[url]" decryptionkey="[key]">
         <credentials passwordformat="[Clear, SHA1, MD5]">
            <user name="[UserName]" password="[password]"/>
         </credentials>
      </cookie>
   </authentication>

   <authorization>
      <allow
         users="[comma separated list of users]"
         roles="[comma separated list of roles]"/>
      <deny
         users="[comma separated list of users]"
         roles="[comma separated list of roles]"/>
   </authorization>

   <identity>
      <codeaccess permissionset="[permissionset filename]">
      <impersonation enable="[true/false]"/>
   </identity>
</security>

The <authentication> Subtag

The <authentication> subtag supports one attribute – mode – that controls the default authentication mode for an application, and can be set to one of the following string values: "windows", "cookies", or "passport". It also supports one subelement - <cookie> - that defines cookie authentication settings.

The <cookie> Subtag

Contained within the <security> subtag is the <cookie> subtag, which supports three attributes.

Attribute | Description
cookie | Name of the HTTP Cookie to use for the authentication ticket. By default this is value is .ASPXAUTH.
loginurl | The URL to which the request is redirected if it doesn’t contain a valid authentication ticket.
decryptionkey | A key used to decrypt authentication tickets. If login and authentication are distributed across multiple machines, they all need to share the same key. The key is stored in cleartext.

The <cookie> subtag optionally allows users to define name/password credentials within the <credentials> subtag. Alternatively, developers can implement their own custom password scheme where validation occurs from external stores like databases, etc.

The <credentials> Subtag

Credentials are captured within a <credentials> subtag, which is contained within the <cookie> subtag. This subtag contains one attribute – passwordformat – that defines the encryption format used to store passwords (clear, MD5, or SHA1). The <credentials> sub-tag in turn supports multiple <user> sub-tags, each of which records the user login name and password.

Attribute | Description
name | User login name.
password | User password

The <authorization> Subtag

This subtag is contained within the <security> subtag, and controls client access to URL resources. Sub-directories hierarchically inherit settings specified within an <authorization> section.

The <authorization> section is configured using two subtag directives, “allow” and “deny”, which are interpreted and processed in top-down sequential order. <allow> tags enable administrators to explicitly identify a class of users (either via usernames, roles, anonymous or all access) to grant access to. <deny> tags explicitly identify a class of users to which access is denied.

At runtime, the authorization module iterates through the <allow> and <deny> tags looking for the first access rule that fits a particular user. It decides to grant or reject access to a URL resource depending upon the whether the first access rule satisfied is either an <allow> or <deny> rule. By default it rejects access if no rule (either an allow or deny one) is found.

Directive | Description
allow | Allows access to a URL resource. Access is granted based on the following filtering constraints: Users: A comma separated list of “user names” that should be granted access. The "?" character allows anonymous users and the "*" character is used to indicate that all users are accepted. Roles: A comma separated list of “roles” that should be granted access
deny | Denies access for a URL resource. Access is denied based on the following filtering constraints: Users: A comma separated list of “user names” that should be denied access. The "?" character denies anonymous users and the "*" indicates that all users are denied. Roles: A comma separated list of “roles” that should be denied access.

The <identity> Subtag

The <identity> subtag is contained within the <security> subtag, and defines the application-level identity of code running on the server. It supports two subtags:

Attribute | Description
impersonation | Controls whether client NT impersonation is used on each request. For legacy ASP/IIS purposes, this value by default is “true”
codeaccess | Controls whether NGWS runtime code-access security is enabled, and if so what permissionset is used to guard the code running on the server.

Example:

<configsections>
   <add name="security" type="System.Web.Config.SecurityConfigHandler">
</configsections>

<security>
   <authentication mode="Cookie">
      <cookie cookie="401kApp" loginurl="/login.aspx"
                      decryptionkey="1!#$$">
         <credentials passwordformat="SHA1">
            <user name="Mary" password="GASDFSA9823423BSD"/>
            <user name="John" password="ZA#$34343443BSD44"/>
            <user name="Fred" password="IFSKDSJFSLKFJKDLS##"/>
         </credentials>
      </cookie>
   </authentication>

   <authorization>
      <allow roles="Admins", "Managers" />
      <allow users="Fred, "John" />
      <deny users="Jack", "Joe" />
   </authorization>

   <identity>
      <impersonation enable="false"/>
      <codeaccess permissionset="foo.xml"/>
   </identity>
</security>

Requirements

Contained Within: <configuration>

Web Platform: IIS 5.0

Configuration File: config.web

Configuration Section Handler:

System.Web.Config.SecurityConfigHandler

See Also

ASP+ Configuration Concepts; ASP+ Web Application Security


<sessionstate> Section Handler

Configures the session state HttpModule.

<sessionstate
   inproc="true/false"
   cookieless="true/false"
   timeout="minutes"
   server="server name"
   port="port number" />

You can configure the <sessionstate> section using the following five tag attributes:

Attribute | Description | Default
inproc | Indicates whether session state should be stored in-process with ASP+ (TRUE), or in an external state store process (FALSE). | TRUE
cookieless | Indicates whether cookieless sessions should be used to identify client sessions (TRUE), or if cookie enabled sessions should be used. (FALSE) | FALSE
timeout | Number of minutes a session can be idle. Once this limit has passed, the session will be abandoned. | 20
server | Server name of remote session state store. | localhost
port | Port number of remote session state store. | 42424

Remarks

If you wish to have a remote session state store, you must set the inproc attribute to FALSE, and specify the server name and port number (using the server and port atttributes) of the machine on which state services are running.

Example:

<configsections>
   <add name="sessionstate"
        type="System.Web.Config.SessionStateConfigHandler">
</configsections>

<sessionstate
   inproc=”true”
   cookieless="true"
   timeout="20"
   server="localhost"
   port="42424"/>

Requirements

Contained Within: <configuration>

Web Platform: IIS 5.0

Configuration File: config.web

Configuration Section Handler:

System.Web.Config.SessionStateConfigHandler

See Also

ASP+ Configuration Concepts


<tracing> Section Handler

Configures the ASP+ trace service.

<tracing
   enabled="[true/false]"
   requestlimit="[Integer]"
   pageoutput="[true/false]" />

You can configure the <tracing> section using the following attributes:

Attribute | Description
enabled | Indicates whether tracing is enabled for an application (default is false).
requestlimit | Number of trace requests to store on the server (default is 10).
pageoutput | Indicates whether trace information should be rendered at the end of each page – or only be accessible via the trace utility.

Example:

<configsections>
   <add name="tracing" type="System.Web.Config.TraceConfigHandler">
</configsections>

<tracing enabled="false" requestlimit="15" pageoutput="true" />

Requirements

Contained Within: <configuration>

Web Platform: IIS 5.0

Configuration File: config.web

Configuration Section Handler:

System.Web.Config.TraceConfigHandler

See Also

ASP+ Configuration Concepts


Topic Not Found

No matching topic was found. The link may be out of date, incorrectly authored, or the topic may not be available in this release.

You may be able to find the topic by using the index or search tab instead.


Copyright

NGWS SDK Documentation

This is a preliminary document and may be changed substantially prior to final commercial release. This document is provided for informational purposes only and Microsoft makes no warranties, either express or implied, in this document. Information in this document, including URL and other Internet Web site references, is subject to change without notice. The entire risk of the use or the results of the use of this document remains with the user. Unless otherwise noted, the example companies, organizations, products, people and events depicted herein are fictitious and no association with any real company, organization, product, person or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation.

Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.

Unpublished work. © 1999 - 2000 Microsoft Corporation. All rights reserved.

Microsoft, MS-DOS, Windows, Windows NT, Active Directory, ActiveX, Authenticode, FrontPage, IntelliSense, JScript , Outlook, Visual Basic, Visual Basic Scripting Edition (VBScript), Visual C++, Visual Studio, and Win32 are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.

The names of actual companies and products mentioned herein may be the trademarks of their respective owners.


NGWS SDK Documentation

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Selecting Text Programmatically in the TextBox Control

You can select text programmatically in the Win Forms TextBox control. For example, if you create a function that searches text for a particular string, you can select the text to visually alert the reader of the found string's position.

To select text programmatically

  1. Set the SelectionStart property to the beginning of the text you want to select.

    The SelectionStart property is a number that indicates the insertion point within the string of text, with 0 being the left-most position. If the SelectionStart property is set to a value equal to or greater than the number of characters in the text box, the insertion point is placed after the last character.

  2. Set the SelectionLength property to the length of the text you want to select.

    The SelectionLength property is a numeric value that sets the width of the insertion point. Setting the SelectionLength to a number greater than 0 causes that number of characters to be selected, starting from the current insertion point.

  3. (Optional) Access the selected text through the SelectedText property.

The code below selects the contents of a text box when the control's GotFocus event occurs. The TextBox1_GotFocus event handler must be bound to the control; see Creating Event Handlers at Run Time for Win Forms for more information.

[Visual Basic]
Private Sub TextBox1_GotFocus(ByVal sender As Object, ByVal e As EventArgs)
   TextBox1.SelectionStart = 0
   TextBox1.SelectionLength = Len(TextBox1.Text)
End Sub

[C#]

See Also

Controlling the Insertion Point in a TextBox Control | Creating a Password Text Box with the TextBox Control | Creating a Read-Only Text Box | Putting Quotation Marks in a String Programmatically | Viewing Multiple Lines in the TextBox Control| TextBox Control