In this section, we'll look at how to create a custom application wizard that generates a Web application using ASP.NET and managed C++. We'll look at the details involved in writing a Web Forms applications using ASP.NET and managed C++ in the second half of the book. For now, we'll create an application wizard that can generate Web Forms applications. A Web Forms application involves several different kinds of files to be generated. In addition, we can add several options such as tracing/debugging options and include several kinds of controls to see how the application wizard works. The files included in the Web Forms application include source code for a Managed C++ DLL, an ASP.NET (ASPX) file, a
We'll create our wizard using the Custom Wizard. As mentioned, the Custom Wizard is a canned Visual Studio .NET wizard that creates a custom wizard. The name of the sample wizard for this chapter will be ManagedCWebFormWizard. The wizard will have a user interface consisting of one page. You can have as many pages as you want in your wizard. We're keeping it to one page in this example to make the sample more digestible.
The user interface itself will include check boxes for adding controls to the page and for turning on debugging and tracing options. Solution Explorer lets you get to the HTML page representing the wizard user interface. Editing this page is much like editing normal dialog boxes. You can select a control from the Toolbox on the left side of the Visual Studio .NET's IDE, place the control on the page, and set its properties using the Properties window. The wizard has six check boxes on the interface page. Three of the check boxes will manage the controls on the Web Form—one each for adding a CheckBox control to the Web Form, for adding a Label control, and for adding a TextBox control. You can use the Properties window to provide IDs for each of the controls. The TextBox check box has an ID of UseTextBox, the Label check box has an ID of UseLabel, and the CheckBox check box an ID of UseCheckBox. When the wizard generates the code, it looks for these symbols to add code to the ASPX page and the code page.
The three other check boxes are for managing debug options: one for page tracing, one for request tracing, and one to turn on debugging. The check boxes have IDs of UsePageTracing, UseRequestTracing, and UsePageDebugging. As with the user interface page, the wizard will look for these symbols to add the right code to the generated project.
Figure 4-1 shows
Figure 4-1: Default of the ManagedCWebFormWizard application in the finished wizard.
Once the controls are on the page, they need to be associated with symbols that the wizard can use to make substitutions. The wizard's default user interface page (
<SYMBOL NAME="UseCheckBox" TYPE="checkbox" VALUE="false"></SYMBOL> <SYMBOL NAME="UseTextBox" TYPE="checkbox" VALUE="false"></SYMBOL> <SYMBOL NAME="UseLabel" TYPE="checkbox" VALUE="false"></SYMBOL> <SYMBOL NAME="UsePageTracing" TYPE="checkbox" VALUE="false"></SYMBOL> <SYMBOL NAME="UseRequestTracing" TYPE="checkbox" VALUE="false"></SYMBOL> <SYMBOL NAME="UsePageDebugging" TYPE="checkbox" VALUE="false"></SYMBOL>
Notice that each of these symbols is associated with a check box on the wizard user interface page.
The next step is to take the original source code and insert annotations where you want the wizard to add replacement code. Once we have the original boilerplate code, all the original boilerplate source code for the wizard will live under the Templates directory for that wizard. The final ManagedCWebForm will need to include three files: the header file containing the C++ class, the ASPX file containing the Web page layout information, and the
// ManagedCWebForm.h #pragma once using namespace System; #using <System.Dll> #using <System.Web.dll> using namespace System; using namespace System::Web; using namespace System::Web::UI; using namespace System::Web::UI::WebControls; using namespace System::Collections; using namespace System::ComponentModel; namespace ProgVSNET_ManagedCWebForm { public __gc class ManagedCWebPage : public Page { public: Button* m_button; [!if UseLabel] Label* m_label; [!endif] [!if UseTextBox] TextBox* m_text; [!endif] [!if UseCheckBox] CheckBox* m_check; [!endif] ManagedCWebPage() { // To do: Construction code here... } void SubmitEntry(Object* o, EventArgs* e) { // Called when Submit button pressed // To do: insert Page Loading code here... String* str; str = new String("Hello "); str = str->Concat(str, m_text->get_Text()); str = str->Concat(str, new String(" you pushed Submit")); [!if UseLabel] m_label->set_Text(str); [!if UseLabel] } void Page_Load(Object* o, EventArgs* e) { // To do: insert Page Loading code here... [!if UsePageTracing] Trace->Write("Custom", "Inside Page_Load"); [!endif] if(!IsPostBack) { } } }; }
When the wizard generates the final code, it looks for the key symbol contained in the square brace to see whether it is in the symbol table. In our example, the expressions are simply Boolean tests. If the check boxes are selected, the controls or debugging features are turned on. Otherwise, they're turned off, and the specific code will be omitted from the generated source code. The same principle applies to every file that needs to be generated. For example, the wizard will take the following boilerplate code for the ASP.NET page and examine the UseRequestTracing, UseTextBox, UseLabel, and UseCheckBox symbols to figure out what code to include:
<%@ Page Language="C#" [!if UseRequestTracing] Trace=true [!endif] Inherits="ProgVSNET_ManagedCWebForm.ManagedCWebPage" %> <l> <body> <form runat=server> <h2>ASP.NET Web Form</h2> <br><br><br> <asp:Button Text="Sumit Entry" id="m_button" OnClick="SubmitEntry" runat=server /><br/> <asp:Label Text="Type your name here" runat=server /> [!if UseTextBox] <asp:TextBox id="m_text" runat=server /><br/> [!endif] [!if UseCheckBox] <asp:CheckBox id="m_check" runat=server /> <br/> [!end] [!if UseLabel] <asp:Label id="m_label" runat=server /> [!endif] </form> </body> <l>
The last file that needs to be created is the
<configuration> <system.web> [!if UsePageDebugging] <compilation debug='true'></compilation> [!endif] [!if UsePageTracing] <trace enabled='true'></trace> [!endif] </system.web> </configuration>
In addition to the code boilerplate, the wizard also needs to know which files to include when it generates the application. The Templates directory for the wizards includes a file named
function GetTargetName(strName, strProjectName) { try { var strTarget = strName; if (strName.substr(0, 15) == "ManagedCWebForm") { var strlen = strName.length; strTarget = strProjectName + strName.substr(15, strlen - 15); } return strTarget; } catch(e) { throw e; } }
After the wizard generates the files, it creates a project out of those files. The scripts for creating the project are found in the scripts subdirectory for the wizard project. The default scripts generated by the Custom Wizard include a method named AddConfig. Visual Studio .NET includes a project object model that lets you change the project configuration of the generated project. Following is the source code that flips the DLL switch on and generates a managed assembly. (We'll cover managed code in the last part of the book.)
function AddConfig(proj, strProjectName) { try { var config = proj.Object.Configurations('Debug'); config.IntermediateDirectory = 'Debug'; config.OutputDirectory = 'Debug'; config.ConfigurationType = typeDynamicLibrary; var CLTool = config.Tools('VCCLCompilerTool'); // TODO: Add compiler settings CLTool.CompileAsManaged = managedAssembly; var LinkTool = config.Tools('VCLinkerTool'); // TODO: Add linker settings config = proj.Object.Configurations('Release'); config.IntermediateDirectory = 'Release'; config.OutputDirectory = 'Release'; var CLTool = config.Tools('VCCLCompilerTool'); // TODO: Add compiler settings CLTool.CompileAsManaged = managedAssembly; var LinkTool = config.Tools('VCLinkerTool'); // TODO: Add linker settings } catch(e) { throw e; } }
Once the wizard has been created, you need to let Visual Studio .NET know of its existence. In order for Visual Studio .NET to pick up on the wizard, the wizard needs its own directory under \Program Files\Microsoft Visual Studio .NET\VC7\VCWizards. The user interface files go into the HTML directory underneath the wizard directory, the template files (boilerplate code) go into the Templates directory underneath the wizard directory, the images go in the Images directory under the wizard directory, and the scripts go under the Scripts directory underneath the wizard directory. Both the user interface files and the template files can be localized. The VSDIR file, the VSZ file, and the icon file go under \Program Files\Microsoft Visual Studio .NET\VC7\VCProjects. As mentioned earlier, the VSDIR and VSZ files are generated by the Custom Wizard.
The application wizard model within Visual Studio .NET is rich and flexible. We only looked at substitutions that use the state of a check box to determine whether to include code. There are many other ways to set up the application wizard to generate any kind of application. In fact, this wizard architecture is also how Visual Studio .NET implements its other wizards— including the ATL Simple Object Wizard, the Generic C++ Class Wizard, and the Add Member Variable Wizard.
Each of these wizards can reach into Visual Studio .NET and access the entire Visual Studio object model, which is how the environment seems to understand the classes and other code within your application.
Be sure to check out \Program Files\Microsoft Visual Studio .NET\VC7\VCWizards for more examples—you'll find all of Visual Studio .NET's wizards there.