
| ASP.NET Control Reference |
|
Extending ASP.NET
What's New in 2.0
ASP.NET 2.0 was designed with pluggability and extensibility in mind. You can replace many
of the built-in modules and behaviors in ASP.NET with your own custom implementations. Among the
improvements to extensibility in this release are:
- The Page framework lifecycle - has additional steps and processes;
- Server controls runtime - Custom controls can be written to take advantage of
new features such as:
- State management such as view-state and control-state, or requesting encryption
of the page-state;
- You can write control and page adapters where you require your control or page to
handle behaviors that are dependant on the browser, device or device class or the markup. Adapters plug into
the runtime lifecycle;
- The new data-source and data-bound controls mean you can add declarative
data binding support for new data storage providers, by writing custom data-source controls that
represent those storage providers;
- Web resources, to embed a resource like client-side script files;
- Use the enhanced client script management functionality;
- Make use of the theming or device filtering features in ASP.NET, or;
- Call-backs - Where a page developer or control developer wishes to perform actions
that require communication with the server, without an entire post-back, they can make use of call=backs;
- Page-state - In addition to the handling of view-state and control-state in the control
you can also extend the page-state persister used to handle the collective page state;
- Server controls design-time - Designer classes associated to server controls provide
the means to edit the control in a visual designer. You can write control designers to provide rich
editing features like region-editing, task-based editing, painting and template-editing;
- Application service providers - All of the built-in services in ASP.NET 2.0 are
completely extensible through a provider-based registration model. This includes Membership,
Roles, Personalization, Site Counters, Profile, Session State, Site Navigation, and Web Events.
- Expression builders - You can add custom expression builders to add declarative support
for substituting values into a page prior to the parsing and compilation phases. Expression builders
can provide both runtime and design-time support;
- Localization - Rather than using ResX files to support localization, you can implement your
own resource providers to support the runtime and design-time localization model that obtains data
from another source such as a backend data store;
- Compilation and pre-compilation - You can add new file format extensions into the ASP.NET compilation system
using build providers. For example, ASP.NET 2.0 uses build providers to process
WSDL files for compiling Web service proxies and XSD files for
compiling strongly-typed data sets in the App_Code directory. If you want to 'virtualize' your web content from a source other
than traditional files, then you can implement a virtual path provider.
This section describes these and other general extensibility features in ASP.NET 2.0.
ASP.NET 2.0 was designed with extensibility in mind. While ASP.NET 2.0 strives to meet the demands
of most applications with out-of-the-box features, we understand that it is very important to be
able to extend those features with your own implementations when your application calls for a
behavior outside the scope of the built-in features. Fortunately, nearly every part of the
ASP.NET 2.0 system is replaceable or extendable.
Page Framework Lifecycle
The page framework lifecycle is a process that begins with the page and recursively drills into the
controls and child controls within that page. At each point in the lifecycle there are specific
operations that constitute the behavior of the page and the control and understanding this is
important for control extensibility, page extensibility and specific page development features.
For example:
Page.InitializeCulture where you can explicitly handle the UICulture and
Culture for localization, e.g. from Profile;
Page.OnPreInit, Page_PreInit, where you can dynamically set the Page.Theme or
Page.MasterPageFile;
Control.LoadControlState, Control.SaveControlState
to explicitly handle new state management functionality;
- Other new page events, like
OnInitComplete, OnPreLoad, OnLoadComplete, OnPreRenderComplete,
OnSaveStateComplete etc.
Server Controls Runtime
User Controls, (.ASCX) are 'compositional controls' that are written declaratively
using the same techniques used to build a declarative page. Visual Studio now provides a
'preview' of user-controls in the design-view when these controls are displayed in the consuming page.
Custom controls, (code files, assemblies) are programmatically created and provide
extensibility in many forms, (composition, inheritance etc.). When writing a custom control, it is
important to understand a control's lifecycle stages, including the handling of state information
and any post-back handling.
When creating custom controls you can compile your controls into assemblies that are added to the
application's Bin directory or in ASP.NET 2.0, you can add code file(s) to the
application's App_Code directory. In this case ASP.NET 2.0 will dynamically compile the
control. You can also in ASP.NET 2.0 reuse tag prefixes when registering controls from multiple namespaces. So
it is possible to create several controls in several namespaces that all contribute to the same tag prefix.
<%-- Register the controls defined in an assembly that resides in the Bin directory --%>
<%@ Register TagPrefix="Demos" NameSpace="DemoControls" Assembly="MyDemoControls" %>
<%-- Register the controls defined in code that resides in the App_Code directory --%>
<%@ Register TagPrefix="Demos" NameSpace="DemoControls" %>
<Demos:ControlFromBinAssembly runat="server" ID="CustomControl1" />
<Demos:ControlFromCodeFile runat="server" ID="CustomControl2" />
Control Classes
ASP.NET introduced the Control and WebControl base classes, and ASP.NET 2.0
adds more features to these classes along with new classes for example the CompositeControl.
This simple base class overrides the Controls collection and provides a mechanism for
an associated CompositeControlDesigner to ensure the child controls are always created.
public class MyCompositionControl : CompositeControl {
protected override void CreateChildControls() {
Controls.Clear();
// Create control collection ..
}
..
}
C#
The CompositeControl is a WebControl and implements INamingContainer. The
Controls collection calls EnsureChildControls. You would create all
child controls in CreateChildControls. The base class provides design-time support,
again ensuring the Controls collection is created.
You can enable a page developer to style your control by exposing top-level properties
that delegate to the child control style properties. If you have many properties and controls,
then consider exposing properties of type Style and applying these during
render methods so that child controls do not persist these as view-state, but rather the
parent handles state management of these complex properties.
Control Class and Property Meta-data
There are a number of interesting class-level and property-level attributes used for features in ASP.NET
2.0 some of which are:
LocalizableAttribute: Localization is enabled for all controls and objects,
including static markup in a web application through the use of expressions, (both implicit and
explicit). These features are 'layered' on a control at parse-time and you do not
need to add any special handling to your control. During design-time, Visual Studio will 'push' values
to a neutral resource for properties that are marked with the LocalizableAttribute.
ThemeableAttribute: Page developers and developers performing customization can take advantage of
themes to affect the stylistic appearance and to some extent the content of a page or site.
All properties are by default theme-able, so if there are properties that you decide should not be
theme-able, such as sensitive data properties or properties that are not style-based,
mark them as Themeable(false).
UrlPropertyAttribute: Where a control could be used in master-page for example, or other such
types then Url type properties will potentially require re-basing (an image or a style sheet). In order to
base the Url property value to the consuming page, e.g. a content-page in a master-page, content-page
relationship the page framework will rebase that Url when marked with this attribute;
WebResourceAttribute: Used to mark the assembly that contains an embedded resource, so
that resource can be served in a request;
FilterableAttribute: Used to mark control properties, or controls that wish to
support the device filtering, declarative syntax in the page.
Note: that when creating custom controls, Visual Studio intellisense and validation will interpret types and
class and property meta-data attributes to create the schema.
State Management
In ASP.NET 1.x, it was common for controls to store data in the view-state dictionary in order
to round-trip data across post-backs. Page developers could disable view-state however,
and consequently lose some of the core control behavior as that data was also
round-tripped in view-state. In ASP.NET 2.0 you can take advantage of control-state if you
have a requirement to keep some state regardless of the setting the page developer
makes. Items stored in control-state should be limited:
a current page index or a data key value. Be prudent using this form of state, it is not
intended to be a repository for large amounts of data.
Custom controls need to register for control-state (ideally OnInit but
before OnLoad) and provide explicit save and load handling. You'll need to register on
every post-back.
protected override void OnInit(EventArgs e) {
base.OnInit(e);
Page.RegisterRequiresControlState(this);
}
protected override object SaveControlState() {
object o = base.SaveControlState();
..
return new Pair(o, ViewIndex);
}
protected override void LoadControlState(object state) {
Pair p = state As Pair;
if (p != null) {
base.LoadControlState(p.First);
ViewIndex = (int)p.Second;
}
..
}
C#
In ASP.NET 2.0, the control-state and view-state are combined in the hidden field in the page rendering. You can make a request
to the page to encrypt the field, (Page.RegisterRequiresViewStateEncryption) if you need to round-trip potentially sensitive data.
Control and Page Adapters: Handling Behavior for Devices, Browsers or Markups
The 'behavior' of a control, (e.g. the rendering) is normally handled within the class for the
control (using the associated text writer and HttpBrowserCapabilitites).
You can, however, create configurable page and control adapters that handle the behavior on behalf of the
control. Such configuration is made within the browser capabilities files, (.browser).
The HttpBrowserCapabilities creation process uses
declarative browser files that reside in the install directory under Browsers and/or the
application's
local App_Browsers directory. The following snippet
shows a custom browser file that maps a custom adapter for a custom control;
the assembly containing the adapter is created in the App_Code directory of the application.
<!-- New browser node links into parent -->
<browser id="My_PIE_PPC" parentID="PIEnoDeviceID">
<identification>
<capability name="browser" match="Pocket IE" />
<capability name="majorversion" match="4" />
</identification>
<!-- Define new text-writer for this browser and define control-adapter mapping -->
<controlAdapters markupTextWriterType="MyNamespace.MyCustomTextWriter" >
<adapter controlType="MyNamespace.MyControl" adapterType="MyNamespace.MyControlAdapter" />
</controlAdapters>
</browser>
During the page framework lifecycle, ASP.NET 2.0 attempts to look for an
adapter on the control using the Control.ResolveAdapter method. If an adapter is returned, the page
framework will call on the adapter methods instead of the control's lifecycle methods.
As an extensibility developer you can extend the adapter base classes, e.g. ControlAdapter,
WebControlAdapter and PageAdapter and define overrides for the
lifecycle stages and other methods such as child control creation and post-back handling.
- The base adapter classes call back on the control's equivalent method. It is typical therefore,
to call on the base method in your adapter. This is not necessarily true of the
Render
method(s), as you could end-up with unwarranted markup behavior;
- Adapters can implement
IPostBackEventHandler and IPostBackDataHandler
to handle post-back data and raise events;
- Adapters can implement view-state and control-state methods,
LoadAdapterViewState, SaveAdapterViewState
and LoadAdapterControlState, SaveAdapterControlState. These methods
contribute to the control's behavior however;
- Adapters can implement
CreateChildControls and handle data-binding calls;
- Alternatively you can prevent 'adaptation' for your custom control by returning null from
Control.ResolveAdapter.
// Control
public class MyControl : Control {
// Example showing override of Render to control entire rendering
protected override void Render(HtmlTextWriter writer) {
Style s = new Style();
s.ForeColor = System.Drawing.Color.Green;
writer.EnterStyle(s);
writer.Write("Welcome custom control.");
writer.ExitStyle(s);
}
..
}
// Adapter
public class MyControlAdapter : ControlAdapter {
// Example showing override of Render to control entire rendering
protected override void Render(HtmlTextWriter writer) {
Style s = new Style();
s.ForeColor = System.Drawing.Color.Red;
writer.EnterStyle(s);
writer.Write("Welcome custom control adapter.");
writer.ExitStyle(s);
}
..
}
C#
Data Source and Data Bound Controls
A data source control represents a backend data storage provider that can expose data to data-bound
UI controls. ASP.NET ships with several data source control, for example data sources representing
SQL databases, middle-tier objects, or XML files. All data source controls have the following in common:
- Represent one or more named views of data
- Each view provides an enumeration of objects
- Essentially SELECT in SQL terms
- A view may also be able to perform editing operations on its collection of objects
- Essentially UPDATE, INSERT, DELETE in SQL terms.
- Capabilities model exposed as boolean properties
- Ability to trigger change events
- Ability to load data on-demand
You can easily add support for additional data sources by creating a custom data source control, which is a server controls that implements either the IDataSource or IHierarchicalDataSource interface, or both. ASP.NET 2.0 also includes the DataSourceControl and HierarchicalDataSourceControl abstract base classes that encapsulate the common behavior that most data source controls need.
public interface IDataSource {
event EventHandler DataSourceChanged { add; remove; }
DataSourceView GetView(string viewName);
ICollection GetViewNames();
}
public abstract class DataSourceControl :
Control, IDataSource, IListSource {
...
}
A DataSourceView represents the various operations that can be performed over a view of the data that the data source control exposes. The following is a simplification of the actual DataSourceView base class (in actuality, the methods follow an asynchronous design pattern).
public abstract class DataSourceView {
public virtual bool CanDelete { get; }
// CanInsert, CanPage, CanSort, CanUpdate, ...
public event EventHandler DataSourceViewChanged;
public virtual int Delete (IDictionary keys, IDictionary oldValues, ...);
public virtual bool Insert (IDictionary values, ...);
public virtual int Update (IDictionary keys, IDictionary values, IDictionary oldValues, ...);
public abstract IEnumerable Select(DataSourceSelectArguments arguments);
}
The .NET Framework reference documentation for these classes includes examples for how to write a
custom tabular or hierarchical data source control.
Web Resources
In ASP.NET 1.x developers writing custom controls that required custom resources such as images
or client script would need to install the resources in the aspnet_client virtual folder.
In ASP.NET 2.0 you can take advantage of web resources to
simplify the process. Web resources allow resources to be embedded in an assembly and are
retrieved through the web resources handler.
// Mark the assembly with the resource
[assembly: WebResource("MyClientScript.js", "text/javascript")]
public class MyControl : WebControl {
protected override void OnInit(EventArgs e) {
// Register the script to be rendered as a link
String sScript = Page.ClientScript.GetWebResourceUrl(typeof(MyControl),
"MyClientScript.js");
Page.ClientScript.RegisterClientScriptInclude("MyInclude", sScript);
base.OnInit(e);
}
..
protected override void OnPreRender(EventArgs e) {
this.Attributes["onmouseover"] = "MouseOverScript()";
base.OnPreRender(e);
}
}
C#
Client Script Management
The Control.Page property exposes a ClientScript property that
encapsulates features for handling, registering and referencing client-script.
When combined with web resources, it is possible for you to also embed those scripts within the
assembly of the control, see the sample in web resources.
public class MyButton : Button {
protected override void OnPreRender(EventArgs e) {
String sScript = "function DoAlert(){alert('Hello World');}";
this.Page.ClientScript.RegisterClientScriptBlock(typeof(MyButton),
"ScriptFunction", sScript, true);
OnClientClick = "javascript:DoAlert();";
base.OnPreRender(e);
}
..
}
C#
Device, (device markup or browser) Filtering
A page developer can use a new declarative syntax to qualify control properties so that they are
only set when a device filter definition evaluates to true. For example, a device filter ID may define the
Internet Explorer class of browsers, as defined in browser files in the application. The device-filter
can be used to declaratively qualify properties of a control. You do not have to add anything to your custom
control to support this. However, in cases where you do not wish device filters to apply simply mark your
control or property with the Filterable(false) attribute. Note that localization also takes device filtering
into account too.
<!-- Text property is defaulted to a value, reset given IE and also if the custom filter applies. -->
<!-- The more specific the device-filter wins.. -->
<asp:Label runat="server" Id="WelcomeLabel" Text="Welcome to ASP.NET's Quickstarts"
IE:Text="Quickstarts" MyFilter:Text="Welcome" />
Call-backs
Call-backs allow a control or page to perform post-back to the server in a manner that does not
require the page to be posted back entirely. You can easily combine for example,
web resources with client script management to also make use of the call back infrastructure.
To enable a call back you will
- Create a call back event with a given signature in your class. (
ICallbackEventHandler);
- Create client-side script that will manage the return call or error;
- Use the call back event reference hooked up to a client-side event in your custom control.
public class MyControl : CompositeControl, ICallbackEventHandler {
public string ICallbackEventHandler.RaiseCallbackEvent(string eventArgument) {
if (eventArgument == "Dogs")
return "Labrador";
else
throw new ApplicationException("Only Dogs allowed!");
}
..
// Client script functions that handle the callback
private String sButtonCallBack =
"function ButtonCallBack(result, context){ alert(result);}";
private String sButtonCallBackError =
"function ButtonErrorCallBack(result, context){ alert(result);}";
protected override void OnInit(EventArgs e) {
base.OnInit(e);
..
Page.ClientScript.RegisterClientScriptBlock(typeof(MyControl),
"ButtonCallBack", sButtonCallBack, true);
Page.ClientScript.RegisterClientScriptBlock(typeof(MyControl),
"ButtonCallBackError", sButtonCallBackError, true);
}
..
protected override void OnPreRender(EventArgs e) {
// Set up the OnClick event to fire an out-of band call to the handler
Attributes["OnClick"] = Page.ClientScript.GetCallbackEventReference(this,
"'Dogs'", "ButtonCallback", "null", "ButtonErrorCallback", true);
base.OnPreRender(e);
}
}
C#
Page-state
Page-state, (the cumulative view-state and control-state for the controls and the page) is
typically persisted to the page's response, using the HiddenFieldStatePersister (it
is the VIEWSTATE hidden field). You can
override the Persister on the Page for custom persistence however.
Given the adaptive nature of controls to the requesting device, some devices may not handle
significant amounts of data that are typically 'round-tripped'. Through the
PageAdapter configured for a device, the state can be persisted to alternative
sources. ASP.NET 2.0 defines two state persisters, HiddenFieldPageStatePersister
and SessionPageStatePersister.
ASP.NET 2.0 also enables the splitting up the single hidden field into several
using the maxViewStateFieldLength attribute in configuration.
Server Controls Design-time
There are many improvements, simplifications and advances in control designers.
You write a ControlDesigner to provide design-time behavior for your custom
control in a development environment like Visual Studio. The following features are available in ASP.NET 2.0:
- Region-based editing. In ASP.NET 1.x you would use a
ReadWriteControlDesigner to
provide a single read/write area for your control on the design-surface. In ASP.NET 2.0 you can
create editable, template-able and read-only regions (DesignerRegion types) for richer editing experiences.
Regions can also provide tool-tips and highlighting. You can also handle click events on the control
and interact with the regions;
- Painting on the design-surface. You can provide graphics on
the design-surface for your control by indicating that the control supports painting and overriding
OnPaint;
- Control-designer types can interact with the control more closely with accessors but also can
take advantage of control features such as embedded resources like images;
- Task-based editing. Using
DesignerActionLists you can provide a very
visual, contextual task list for page developers to interact with. This advances the previous
concept of DesignerVerbs;
- Improved and much simplified template-editing. Your control-designer can defer simply to the tool's
template editing UI or you can create your own UI using regions. Adding template editing is now as simple as
defining the
TemplateGroups collection on the ControlDesigner;
- Several new convenient control-designer base classes,
CompositeControlDesigner and
ContainerControlDesigner for common scenarios.
The following snippet shows a very simple WebControl and associates a
ContainerControlDesigner. In this form, the designer tool creates a single, editable
region for the control, which allows drag-and-drop. The control defines children as child controls,
see ParseChildren(false) and PersistChildren(true).
The ControlDesigner creates a caption bar and sets styles accordingly. The pseudo code also
shows steps to implement a simple DesignerActionList.
[Designer(typeof(MyDesigner)), ParseChildren(false), PersistChildren(true)]
public class MyControl : WebControl { }
public class MyDesigner : ContainerControlDesigner {
private Style _style = null;
public override string FrameCaption { get { /* Return a caption */"; } }
public override Style FrameStyle { get { /* Return a Style for the frame*/ } }
public override DesignerActionListCollection ActionLists {
get {
// Get the base collection and add my own list
..
al.Add(new MyList(this));
return al;
}
}
private sealed class MyList : DesignerActionList {
// Members to handle the smart-tag
..
public override DesignerActionItemCollection GetSortedActionItems() {
// Create a collection and add DesignerActionTextItem,
// DesignerActionPropertyItem and DesignerActionMethodItem types
}
}
}
C#
Application Service Providers
Many ASP.NET application services have been designed with a provider-based model. Providers abstract the physical data storage for
a feature from the classes and business logic exposed by a feature. Features that are built on top of providers allow you to create
your own custom providers and configure them to work with the feature. Custom providers allow developers to implement specialized
business logic and to operate against alternate data stores. Pages that use a provider-based feature continue to work normally with
custom providers, and without any changes in page code. The following application services support the provider model and allow you
to author and configure custom providers:
- Membership
- Role Manager
- Session State
- Profile
- Site Navigation
- Site Counters
- Web Parts Personalization
- Web Events
Expression Builders
ExpressionBuilders in ASP.NET 2.0 are a parse-time feature that allows
the page developer to add declarative syntax to assign to a control's properties. ASP.NET 2.0 ships with expression builders for:
- Connection strings. Used to access connection-strings in configuration.
<%$ connectionstrings: MyConnStr %>;
- Application settings. Used to access the application setting in configuration.
<%$ appsettings: MyAppValue %>, and;
- Resources. Used as the foundation for building a multi-lingual
site.
<%$ resources: mykey %>.
Expression builders can offer design-time handling through the Expressions dialog in Visual Studio which is
accessed through the property grid. You can add your own expression builders and expression prefix and
not only handle the expression during runtime but you can also support the design-time too.
Expression builders can also be constructed to support the no compile feature of ASP.NET 2.0.
Instead of ASP.NET generating code through the expression builder during the generation of the page-class, the
runtime instead instantiates the expression builder and evaluates the expression.
The following example shows a pseudo ExpressionBuilder and
ExpressionBuilderEditor.
.
// In configuration, add your expression builder. Here the type is defined in App_code
<expressionBuilders>
<add expressionPrefix="MyExpr" type="MyExpressionBuilder" />
</expressionBuilders>
// In code, create Expression builder and define the prefix, the editor etc.
[ExpressionPrefix("MyExpr"), ExpressionEditor("MyExpressionEditorDesigner"]
public sealed class MyExpressionBuilder : ExpressionBuilder {
public override bool SupportsEvaluate {
get {
return true; // Supports the evaluation for no compile scenarios
}
}
public override CodeExpression GetCodeExpression(..) {
// Return a code expression, given the expression
}
public override object EvaluateExpression(..) {
// Return an evaluation of the expression for no compile scenarios
}
..
}
// The editor is used at design-time.
public sealed class MyExpressionEditor : ExpressionEditor {
public override ExpressionEditorSheet GetExpressionEditorSheet(..) {
// Create a sheet
return new MyExpressionEditorSheet(..);
}
public override object EvaluateExpression(..) {
// Return an evaluation during design-time
}
private sealed class MyExpressionEditorSheet : ExpressionEditorSheet {
// Expose any properties that represent the expression's value, you can define
// default values, type converters and descriptions
}
}
Localization
The localization model in ASP.NET 2.0 allows the page developer to set implicit or explicit,
declarative expressions. You can extend the resource expressions as described above and still use the
underlying resource providers. More likely however, is extending localization to provide an
alternative source for resource data rather than use ResX and it's resultant satellite assemblies.
You can simply swap out the existing ResourceProviderFactory that is defined in configuration
and supply a new one. The factory creates IResourceProviders for both global and local
resources. The following pseudo code demonstrates a simple case (the custom factory
must be defined in configuration).
// In configuration. Here the type is defined in App_Code
<globalization resourceProviderFactoryType="MyResourceProviderFactory" ../>
// In code. Note that to support design-time too, add the following attribute and
// define the provider
[DesignTimeResourceProviderFactoryAttribute(typeof(MyDesignTimeResourceProviderFactory))]
public sealed class MyResourceProviderFactory: ResourceProviderFactory {
public override IResourceProvider CreateGlobalResourceProvider(string classKey) {
return new MyGlobalResourceProvider(classKey);
}
public override IResourceProvider CreateLocalResourceProvider(string virtualPath) {
return new MyLocalResourceProvider(virtualPath);
}
private sealed class MyGlobalResourceProvider: IResourceProvider {
object IResourceProvider.GetObject(string resourceKey, CultureInfo culture) {
// Get a resource object for the resource key and culture
}
IResourceReader ResourceReader {
get { /* return a reader that enumerates the neutral resource */ }
}
}
..
}
Compilation and Pre-compilation
ASP.NET 2.0 defines several ways to compile a web application and several extensibility points.
Dynamic compilation includes for example, code separation files
associated with web pages and user controls, code in the App_Code directory and abstract files
such as ASPX, ASCX, RESX, and also, WSDL, XSD.
There are two forms of pre-compilation of sites for deployment as well as a pre-compilation mode that
'primes' the site. Pre-compiling for deployment can be performed in two ways
that either allows specific updates to the compiled site or not.
In both cases code is removed from the compiled site (target). Where
no updates are selected, the declarative files like ASPX are removed too. Pre-compiling to prime a site means that ASP.NET
will perform regular compilation of the entire site to avoid the first-time request penalty.
To precompile a site, you can use the aspnet_compiler.exe tool. This tool is located in the .NET Framework
installation directory, for example %WINDIR%\Microsoft.NET\Framework\<version>. You can
run the tool with a /? switch to see the list of available options. For example, to pre-compile
a site in-place by specifying the virtual path to the application, use the following command line:
> aspnet_compiler.exe -v /MyApp c:\MyTarget
You can also extend the compilation processes in a number of ways; by contributing new file
types or extensions to the compilation using BuildProviders, or by making
specific content virtual so that the source is for example not on disk.
The VirtualPathProvider provides this extensibility point.
Build Providers
Build providers are associated to a file extension through configuration. The
build provider produces code for a virtual path that is compiled by ASP.NET. With a
build provider you can add source to the compilation processes of ASP.NET or define new abstract
content that you handle the parsing of to supply code to ASP.NET.
// In configuration
<compilation ..>
<buildProviders>
<add extension=".myext" type="MyBuildProvider, .." appliesTo="All"/>
</buildProviders>
</compilation>
public class MyBuildProvider : BuildProvider {
..
public override void GenerateCode(AssemblyBuilder assemBuilder) {
// Return code, e.g. a CodeCompileUnit that is added to the
// assembly builder. This will involve custom parsing and code generation
}
public override System.Type GetGeneratedType(CompilerResults results) {
// Return the type generated (e.g. MyNamespace.MyClass)
}
}
If you need to obtain a type in application code you can use the
BuildManager.GetType method as assembly names are not predictable.
Virtual Path Provider
ASP.NET allows you to provide content for web 'content', such as an ASPX through a
mechanism called a VirtualPathProvider. The compilation
process will call on registered VirtualPathProviders to supply the content. You need to
register your virtual path provider at an early point in the application's lifecycle, for example
in a special method you can define in code in the App_Code directory, AppInitialize. Only
specific types of web content can be virtualized in this manner. For example, code in the App_Code directory
cannot.
Your virtual path provider can return content for a virtual path, or simply hand off to the
previous provider in the chain.
// In code in the App_Code directory
public sealed class MyVirtualPathProvider : VirtualPathProvider {
public static void AppInitialize() {
MyVirtualPathProvider myVpp = new MyVirtualPathProvider();
HostingEnvironment.RegisterVirtualPathProvider(myVpp);
}
// Implement other methods that define file hashing and dependencies ..
public override bool FileExists(string virtualPath) {
// Return whether this virtualPath exists, or defer to the Previous.
}
public override bool DirectoryExists(string virtualDir) {
// Return whether this virtualDir exists, or defer to the Previous.
}
public override VirtualFile GetFile(string virtualPath) {
// Handle the virtualPath and return a
// VirtualFile or defer to Previous.
// VirtualFile implements an Open method to return a Stream
}
public override VirtualDirectory GetDirectory(string virtualDir) {
// Handle the virtual directory and return a
// VirtualDirectory or defer to Previous.
// VirtualDirectory implements enumerations of sub-dirs and files
}
}
|