Extreme ASP.NET

Web Client Software Factory

Fritz Onion

Contents

What Is a Software Factory?
Composite Web Application Block
Building Multi-Module Sites
Views, Presenters, and Controllers
Modular Site Maps
Conclusion

One of the first steps in building a long-lived ASP.NET project is to define standards for how to use the many features of the platform. Without such standards, it is easy for the project to degenerate into a jumble of different development techniques and styles. One developer may use Master Pages extensively in her part of the project, for example, while another favors nested user controls. The end result is a tangle of different techniques applied across the application, making it difficult to make high-level architectural decisions or changes.

Enforcing these standards for developing applications can be difficult, however, due to the lack of tools. The wizards and designers in Visual Studio® are typically focused at a smaller level of granularity, with convenient tools for creating Web pages with codebehind, Web sites with code directories, and so on. If you decide on a set of standards that are not directly supported by the normal tools within your development environment, you have to either build your own tools or create very specific instructions for developers to follow when performing certain tasks.

This is precisely the problem that the patterns & practices team at Microsoft set out to solve with the Web Client Software Factory. It created a toolset designed specifically for the task of building large Web sites with ASP.NET that are worked on by many developers concurrently.

What Is a Software Factory?

Before I delve into the details of what the Web Client Software Factory provides, it makes sense to describe what a software factory is. The patterns & practices group at Microsoft builds a different kind of product than many other Microsoft product groups, such as the ASP.NET, CLR, and SQL Server teams. It creates packages of tools, documentation, and reference implementations for product groups that are intended as guidance for how to best use a particular technology in an enterprise environment. For example, the Web Service Software Factory is designed to provide guidance and enhanced tools for building Web services using either ASMX or Windows Communication Foundation (WCF). Instead of starting with the somewhat low-level Web service features exposed by the WCF libraries, the Web Service Software Factory provides guidance and tool support for creating message-based systems that follow commonly used patterns of design and are built for longevity, interoperability, and maintainability. Similarly, there are software factories for smart clients, mobile clients, and Web clients—the focus of this column. (For more information on the Web Service Software Factory, see the December 2006 Service Station column.

While a software factory may sound like a single component you could incorporate into a project, it actually describes an entire collection of assets you can use in developing applications with a specific technology. Each of the software factories available contains:

  • Application blocks and libraries—actual pieces of software that build on top of the target technology, with the intent of providing a prebuilt infrastructure that follows recommended best practices.
  • Guidance package recipes and templates—typically plug-ins for Visual Studio that generate initial solution templates and/or augment an existing project with features built using recommended best practices. These plug-ins rely upon the application blocks and libraries for much of their implementation.
  • Designers—additional designer support for Visual Studio to work with pieces of the application.
  • Reference implementation—a complete execution of a somewhat real-world application using all of the other assets of the software factory.
  • Architecture guidance and patterns—comprehensive documentation on the patterns and best practice choices used by the software factory.
  • How-to topics—detailed step-by-step directions for using parts of the software factory (or even just directions for how to use a particular technology in the way recommended by the software factory).

The Web Client Software Factory in particular introduces two application blocks: the Composite Web Application Block and the Page Flow Application Block. The goal behind the Composite Web Application Block is to create a framework for building modular Web applications with ASP.NET, and it addresses many of the architectural concerns outlined in the introduction. The Page Flow Application Block provides a framework for incorporating Windows® Workflow Foundation into your Web applications to manage the page navigation in a reliable and enforceable way. Each of these application blocks comes with a collection of recipes and templates that extend Visual Studio, a full set of how-to documents describing specific tasks along with descriptions of the patterns involved with each, and a complete reference implementation of an e-banking site that uses both application blocks to their full extent.

For the remainder of this column, I will focus on the composite application block, both to narrow the scope of the topic and also because I think developers will find it the most immediately compelling.

Composite Web Application Block

Once you have installed the Web Client Software Factory (installation binaries and complete instructions for setup can be found at codeplex.com/websf, you will find a new project template in Visual Studio 2005 called Web Client Solution, listed under the Guidance Packages project types as shown in Figure 1.

Figure 1 Web Client Solution Project Template

Figure 1** Web Client Solution Project Template **(Click the image for a larger view)

The first thing you will notice about the solution that is created with the Web Client Development Guidance Package project, shown in Figure 2, is that it is split into two distinct projects: a library project called Shell listed under a directory called Modules, and a Web site called DevelopmentWebsite listed under a directory called WebSites. These are actually just the first two of several projects you will add to your application with the goal of creating multiple independent modules that all work together to define a single Web site. The general layout of this solution is to create a separate module (class library project) for each section of the site. Each module will be independently compilable and will have a separate directory of pages and codebehind files in the main Web site directory. The goals of this project layout are to make it feasible for multiple developers to work independently on different portions of the site without interfering with each other and to be able to deploy modules independently by pushing out a new binary and a fresh directory of pages to the deployment server without redeploying all modules together.

Figure 2 Project Layout

Figure 2** Project Layout **

In order to prepare for multiple modules to be incorporated into a single site, the Web Client Solution project template implicitly makes several decisions about the structure of the Web site. First, it defines a single top-level Master Page under the /Shared subdirectory called Default.master. As you’ll see later, every page created on behalf of a module will automatically be linked to this shared Master Page using the standard MasterPageFile property of the @Page directive. This Master Page by default contains a tree view control tied to a SiteMapDataSource to display the navigable pages on the site as well as a SiteMapPath control to display the current location with breadcrumb links back up the tree. This SiteMapDataSource is not tied to the standard Web.sitemap file, but rather to a custom SiteMapProvider that collects navigation information from each of the modules independently.

The initial application template also creates a default theme and implicitly associates each page in the site with that theme using the global styleSheetTheme attribute of the <pages> element in the top-level web.config file.

There are several other pieces of infrastructure put in place by the project template, which you will notice as you poke around the generated files. Most of these are in anticipation of managing multiple modules or have to do with the authorization features of this application block. For example, you will notice in web.config there is an HttpModule registered called WebClientAuthorizationModule to perform custom client authorization. There are also several custom configuration section handlers registered including compositeWeb/authorization and compositeWeb/modules for specifying settings for module authorization and managing multiple modules, which I’ll turn to next.

Building Multi-Module Sites

One of the core benefits of working with the Composite Web Application block is the ability to split a site into multiple independent modules. As mentioned before, each module is responsible for a subdirectory in the main site and all of its content. Typically this is combined with the View-Presenter pattern that I’ll discuss next, to isolate all of the page logic in a separate module assembly and remove it completely from the page or even its codebehind class.

There is a recipe for adding a module to a Web Client Solution that you can access by right-clicking on the Modules directory within the Solution Explorer and selecting Web Client Factory | Add Business Module. Figure 3 shows the elements that will be added using this template with a new business module named Customers.

Figure 3 Add a Module

Figure 3** Add a Module **

This recipe creates a new top-level subdirectory in your Web site with the same name as the business module, as well as a new class library project under the Modules directory. The class library project is intended to contain any business logic associated with the set of pages in the /Customers directory. Initially it contains a definition for a presenter class (DefaultViewPresenter) and an interface (IDefaultView), defining methods for the View to be implemented by the Default.aspx page in the /Customers directory. A top-level Controller class is also defined in the project that combined with the view and presenter classes will complete the page logic for this module.

The last piece of interest is the CustomerModuleInitializer.cs file, which contains a CustomersModuleInitializer class that inherits from ModuleInitializer. This class defines initialization methods that will be called as the Web site first loads, and it gives the module an opportunity to initialize any data, register site map information, and so on. The module is hooked into the Web site’s loading through the web.config file that was added to the /Customers subdirectory, which contains an entry under compositeWeb/modules describing the name, assembly, and URL of this module:

<compositeWeb>
  <modules>
    <module name=”Customers” assemblyName=”Customers” 
            virtualPath=”~/Customers”>
      <dependencies>
        <dependency module=”Shell” />
      </dependencies>
    </module>
  </modules>
</compositeWeb>

There is also a recipe for adding a new foundational module to your application (rather than a business module), which creates a new class library project complete with a ModuleInitializer class but without any explicit correspondence to a directory or page within the Web site. This can be useful for supplemental business logic that does not tie directly to any particular set of pages in the site.

Views, Presenters, and Controllers

One of the difficulties in trying to separate an ASP.NET Web site into multiple modules is that, by default, most of the programmatic logic associated with a page resides in the page’s codebehind file. It is generally a futile effort to try and separate codebehind pages into a separate assembly as the tools don’t support it and it makes working with the site rather cumbersome.

In order to truly create independent modules of functionality associated with pages in a site, all of the page logic, event-handling logic, and navigation logic needs to be somehow extracted from the page and hosted in a separate assembly. The default solution in the Composite Application Block is to use a View-Presenter pattern to separate the page logic into a distinct class (the Presenter) that responds to any events forwarded by the View (the Web page). The Presenter class is implemented entirely in the business module, keeping the application logic out of the Web site, and an interface is defined in the business module that defines the methods implemented by the View. This way, the Web page ends up forwarding all events to the Presenter and acts as more of a shell without any real application-specific responsibilities. It also makes it much easier to design tests for the Presenter without actually involving the front-end Web pages. (Using View-Presenter does add the complexity of having extra classes and files, so if you are not looking for this separation, you might choose to forgo the pattern altogether and write ASPX classes the usual way).

As mentioned earlier, the initial Default.aspx page generated by the Add Business Module recipe contained a corresponding DefaultViewPresenter class and an IDefaultView interface for implementing the View-Presenter pattern. To add a new .aspx page to your module with a corresponding View and Presenter, there is another recipe you can access by right-clicking on the subdirectory created for your module and selecting Web Client Factory | Add View (with presenter). If you were to create a new form for inserting customers, for example, you might use this recipe to create a new AddCustomer.aspx page as shown in Figure 4.

Figure 4 Adding a View with Presenter

Figure 4** Adding a View with Presenter **(Click the image for a larger view)

The recipe will add a new Web page to the /Customers directory called AddCustomer.aspx, with a corresponding codebehind file. To the Customers business module project, it adds a new class called AddCustomerPresenter and a corresponding IAddCustomer interface, which is already implemented by the codebehind class in your AddCustomer.aspx.cs file. The codebehind class also contains a property declaration for the corresponding AddCustomerPresenter class to which the page should forward events. For example, if your new AddCustomer.aspx page had a button for inserting a new customer, the handler of that button might look something like:

protected void _insertCustomerButton_Click(
      object sender, EventArgs e)
{
  Presenter.InsertCustomer(
    new Customer(_firstName.Text, _lastName.Text));
}

The logic for inserting the customer, and what to do next, would be defined in the InsertCustomer method of your AddCustomerPresenter class, leaving the page itself devoid of any real business logic. The interface implemented by the page (IAddCustomer in this case) will typically contain properties defining data sources used by the page and other attributes that might influence the appearance of the page itself. The presenter is defined with a property bound to the page’s implementation of the interface so that it can set the data properties as appropriate when the page is navigated to.

The final piece in the picture is the controller class that implements the Application Controller pattern. When you first create your business module, the recipe will generate a controller class (CustomersController in this case) to which you add methods for the presenter classes to call in response to events of pages in the site. The controller is then responsible for controlling navigation among the pages. For example, once a new customer is inserted through the AddCustomer.aspx page, you may want to navigate back to the Default.aspx page in the /Customers directory. The controller would define a method to be called once a customer was inserted, which would then redirect to the Default.aspx page. Each presenter class that is added to your site has a local reference to the controller class of the module so that it can invoke navigation methods when needed.

The recipes and classes provided by the software factory take care of many of the details of implementing these two patterns for you. You don’t need to worry about creating the corresponding Presenter class from your View or allocating a controller when needed in your Presenter. Your work is entirely devoted to building the application logic and interface for your business module. The samples, how-tos, and reference implementation included with the Web Client Software Factory are replete with examples of site building that use View-Presenter and Application Controller patterns, which I encourage you to inspect in detail if this model sounds useful in your application development.

Modular Site Maps

Another detail that is taken care of nicely when you use this model of Web site development is the aggregation of site map information across all modules in your site. If you have used the SiteMapDataSource before to populate a menu or tree view for your site, you’re aware that by default it relies only on the web.sitemap file located at the root of your application directory. Trying to manually keep a single XML file up to date with all of the page navigation information of your site is just asking for trouble for large multi-module sites. To deal with this, the Web Client Software Factory includes a custom SiteMapProvider class called ModuleSiteMapProvider that will explicitly ask each module for its site map information as the application starts. By default, this SiteMapProvider is registered as the default provider and will be used by the SiteMapDataSource for all navigation controls.

To populate the ModuleSiteMapProvider with your particular module’s site map information, you override the RegisterSiteMapInformation method in your ModuleInitializer-derived class. The recipe you used to create the business module will already have overridden this method and inserted the Default.aspx page into the collection of SiteMapNodes, but it is up to you to add any other pages to the collection as you add them to your site. For example, in your Customers module, you might create one top-level node for the default page in our module, and one nested page for the Add Customer page, as shown in Figure 5.

Figure 5 Adding Pages in the Customer Module

protected virtual void RegisterSiteMapInformation(
    ISiteMapBuilderService siteMapBuilderService)
{
  SiteMapNodeInfo moduleNode = new SiteMapNodeInfo(“Customers”, 
    “~/Customers/Default.aspx”, “Customers”);
  siteMapBuilderService.AddNode(moduleNode);

  SiteMapNodeInfo addCustomerNode = new SiteMapNodeInfo(“Add customer”, 
    “~/Customers/AddCustomer.aspx”, “Add customer”, 
    “Add customer page”);

  siteMapBuilderService.AddNode(addCustomerNode, moduleNode, 0);
}

Conclusion

The Web Client Software Factory offers a compelling solution for developers trying to manage large Web sites built with ASP.NET. With support for the creation of modules associated with individual subdirectories in the site, and for patterns such as View-Presenter and Application Controller, this software factory provides a framework that enables independent development, testing, and deployment of a Web site in a modular fashion. I have barely scratched the surface of features offered by this software factory, but I hope it’s been enough to give you a taste of the possibilities and compel you to explore the remainder of the factory. Finally, the patterns & practices team is starting work on the release of the second version of the Web Client Software Factory; you can get involved at codeplex.com/websf.

Send your questions and comments for Fritz to  xtrmasp@microsoft.com.

Fritz Onion is a cofounder of Pluralsight, a Microsoft .NET training provider, where he heads the Web development curriculum. Fritz is the author of Essential ASP.NET (Addison Wesley, 2003) and Essential ASP.NET 2.0 (Addison Wesley, 2006). You can reach him at pluralsight.com/fritz.