ASP.NET 2.0 Unleashed
Creating a Custom BuildProvider
When you write an ASP.NET page and save the page to your computer's file system, the ASP.NET page gets compiled dynamically into a .NET class in the background. The page is compiled dynamically by a BuildProvider. The ASP.NET Framework includes a number of BuildProviders. Each BuildProvider is responsible for compiling a file with a particular extension that is located in a particular type of folder. For example, there are BuildProviders for Themes, Master Pages, User Controls, and Web Services. When a BuildProvider builds, it builds a new class in the Temporary ASP.NET Files folder. Any class added to the folder becomes available to your application automatically. When you use Visual Web Developer, any public properties and methods of the class appear in Intellisense. You can create your own BuildProviders. This can be useful in a variety of different scenarios. For example, imagine that you find yourself building a lot of ASP.NET pages that display forms. You can tediously build each ASP.NET page by hand by adding all the necessary form and validation controls. Alternatively, you can create a new BuildProvider that takes an XML file and generates the form pages for you automatically. Or, imagine that you are spending a lot of time building data access components. For example, every time you need to access a database table, you create a new component that exposes properties that correspond to each of the columns in the database table. In this case, it would make sense to create a custom BuildProvider that generates the data access component automatically. Creating a Simple BuildProvider
Let's start by creating a really simple BuildProvider. The new BuildProvider will be named the SimpleBuildProvider. Whenever you create a file that has the extension .simple, the SimpleBuilderProvider builds a new class with the same name as the file in the background. The dynamically compiled class also includes a single method named DoSomething() that doesn't actually do anything. The SimpleBuildProvider is contained in Listing 25.1. Listing 25.1. App_Code\CustomBuildProviders\SimpleBuildProvider.vb
All BuildProviders must inherit from the base BuildProvider class. Typically, you override the BuildProvider class GenerateCode() method. This method is responsible for generating the class that gets added to the Temporary ASP.NET Files folder. An instance of the AssemblyBuilder class is passed to the GenerateCode() method. You add the class that you want to create to this AssemblyBuilder by calling the AssemblyBuilder.AddCodeCompileUnit() method. In Listing 25.1, a CodeSnippetCompileUnit is used to represent the source code for the class. Any code that you represent with the CodeSnippetCompileUnit is added, verbatim, to the dynamically generated class. This approach is problematic. Unfortunately, you can use the SimpleBuildProvider in Listing 25.1 only when building a Visual Basic .NET application. It doesn't work with a C# application. Because the code represented by the CodeSnippetCompileUnit is Visual Basic .NET code, using the SimpleBuildProvider with a C# application would result in compilation errors. The SimpleBuildProvider would inject Visual Basic .NET code into a C# assembly. The proper way to write the SimpleBuildProvider class would be to use the CodeDom. The CodeDom enables you to represent .NET code in a language neutral manner. When you represent a block of code with the CodeDom, the code can be converted to either C# or Visual Basic .NET code automatically. You'll learn how to use the CodeDom when we build a more complicated BuildProvider in the next section. For now, just realize that we are taking a shortcut to keep things simple. When you add the SimpleBuildProvider to your project, it is important that you add the file to a separate subfolder in your App_Code folder and you mark the folder as a separate code folder in the web configuration file. For example, in the sample code on the CD that accompanies this book, the SimpleBuildProvider is located in the App_Code\CustomBuildProviders folder. You must add a BuildProvider to a separate subfolder because a BuildProvider must be compiled into a different assembly than the other code in the App_Code folder. This makes sense because a BuildProvider is actually responsible for compiling the other code in the App_Code folder. The web configuration file in Listing 25.2 defines the CustomBuildProviders folder and registers the SimpleBuildProvider. Listing 25.2. Web.Config
The web configuration file in Listing 25.2 associates the SimpleBuildProvider with the file extension .simple. Whenever you add a file with the .simple extension to the App_Code folder, the SimpleBuildProvider automatically compiles a new class based on the file. For example, adding the file in Listing 25.3 to your App_Code folder causes the SimpleBuildProvider to create a new class named Mike. Listing 25.3. App_Code\Mike.simple
The actual content of the file that you create doesn't matter. The SimpleBuildProvider ignores everything about the file except for the name of the file. You can see the new file created by the SimpleBuildProvider by navigating to the Sources_App_Code folder contained in the folder that corresponds to your application in the Temporary ASP.NET Files folder. The contents of the auto-generated file are contained in Listing 25.4. Listing 25.4. mike.simple.72cecc2a.vb
Any class added to the Temporary ASP.NET Files folder is available in your application automatically. For example, the page in Listing 25.5 uses the Mike class. Listing 25.5. ShowSimpleBuildProvider.aspx
The Mike class appears in Intellisense. For example, if you type Mike followed by a period, the DoSomething() method appears (see Figure 25.1). Figure 25.1. Using a BuildProvider to generate a class dynamically.
Creating a Data Access Component BuildProvider
In the previous section, we created a simple but useless BuildProvider. In this section, we create a complicated but useful BuildProvider. In this section, we create a DataBuildProvider. The DataBuildProvider generates a data access component automatically from an XML file. For example, if you add the XML file in Listing 25.6 to your project, then the DataBuildProvider generates the class in Listing 25.7 automatically. Listing 25.6. App_Code\Movie.data
Listing 25.7. movie.data.72cecc2a.vb
The XML file in Listing 25.6 contains the name of a database table (Movies) and it contains a list of columns from the database table. When you add the file in Listing 25.6 to your project, the class in Listing 25.7 is generated automatically. The data access component in Listing 25.7 contains a property that corresponds to each of the columns listed in the Movie.data file. Furthermore, each property has the data type specified in the Movie.data file. Notice, furthermore, that the Movie data access component includes two Select() methods. You can retrieve all the records from the Movies database table in two ways: by passing an open SqlConnection object to the Select() method or by passing the name of a connection string defined in the web configuration file to the Select() method. The page in Listing 25.8 illustrates how you can use the Movie data access component within an ASP.NET page (see Figure 25.2). Figure 25.2. Displaying data returned by a dynamically generated data access component.
Listing 25.8. ShowDataBuildProvider.aspx
Unlike the SimpleBuildProvider created in the previous section, the DataBuildProvider uses the CodeDom to represent code. This means that you can use the DataBuildProvider in both Visual Basic .NET and C# applications. The DataBuildProvider generates the data access component in different languages automatically. For example, if you use the DataBuildProvider in a C# application, the BuildProvider generates the code in Listing 25.6 in C#. Unfortunately, the code for the DataBuildProvider is much too long to include here. The entire code is included on the CD that accompanies the book. The file in Listing 25.9 contains part of the DataBuildProvider code. Listing 25.9. DataBuildProvider.vb (Partial)
The DataBuildProvider's GenerateCode() method loads a .data file into an XmlDocument. Notice that the VirtualPath property represents the path of the file that is being built. For example, if you add a file named Products.data to your project, then the VirtualPath property would represent the path to the Products.data file. Next, the code for the data access component is created from the XML file by the GeTDataCode() method. The GeTDataCode() method makes heavy use of the CodeDom to generate the code in a language-neutral manner. Working with the CodeDom is a strange and tedious experience. You must build up a block of code by building a code tree. In Listing 25.9, a CodeCompileUnit named dataCode is created. A CodeNamespace named dataNS that represents a namespace is created and added to the CodeCompileUnit. And, a CodeTypeDeclaration named datatype that represents a class is added to the namespace. After the class is created, the methods and properties are added to the class block by block. |
Категории