Building Re-Usable ASP.NET User Control and Page Libraries with Visual Studio 2005
Product Unit Manager, Web.NET
Microsoft Visual Studio 2005
Microsoft ASP.NET 2.0
Summary: Scott Guthrie shows how you can build Web projects that encapsulate common libraries of .ascx Web user controls or .aspx pages with Microsoft Visual Studio 2005 that can then be easily re-used across multiple other Web projects. (17 printed pages)
Note This article originally appeared on Scott's blog. Join in the discussion here.
Quick Scenario Overview
Step 1: Define the Re-Usable Page/Control Web Solution and Directory Structure
Step 2: Develop the Re-Usable Page/Control Web Solution
Step 3: Building and Deploying the Re-Usable Page/Control Web Solution
Step 4: Using the Page/Control Web Library Within a Web Application
The scenario I'm going to use to illustrate building re-usable page/control libraries in this article is one in which a development team wants to build a re-usable e-commerce Web UI library that they can then re-use across multiple Web projects and applications. (Note: If they are an ISV, they will also want to be able to package it up and sell it.)
The developers building this e-commerce Web UI library want to provide two things:
- Pre-built storefront UI pages (.aspx files) that provide catalog listings, shopping cart management, customer billing, and more. They want developers using the library to be able to add a storefront directory into their Web project, copy this library of pages into them, and have e-commerce functionality incorporated within their application.
- Pre-built strong-front UI user controls (.ascx files) that enable scenarios such as current sales, current shopping cart items, and best-selling product lists to be easily added to pages outside of the storefront directory. By encapsulating this functionality as user-controls, they want developers using this user control library to be able to easily add this functionality to any page on their site.
One condition the developers have for this e-commerce UI library is that no code-behind or C#/VB source code is shipped with the library. Instead, they want to be able to easily package it up and deploy all code as compiled .dll assemblies.
For the purposes of the following walkthrough, I am going to assume that the development team building this Web UI library wants to build it as an isolated Microsoft Visual Studio solution, separate from any Web projects that might be consuming it (this seems to be the most common scenario I see when I talk with customers). Note that you could also incorporate this library as part of a bigger solution that also contains Web projects consuming it.
We want to cleanly encapsulate and maintain our e-commerce library solution. To help with this, I can use a straightforward directory structure and Visual Studio solution architecture.
Specifically, I'm going to define an EcommerceUI directory underneath a libraries subdirectory in my source tree. Contained within this will then be four subdirectories: a myclasslibraryproject directory to encapsulate the non-code-behind pieces of my library within a class library project; a myWebproject directory to encapsulate the Web UI pieces of my library using a Web project; a mytestproject directory to encapsulate the functionality testing of my library using a new Visual Studio Team System test project; and a buildoutput directory that I'll use to build and ultimately publish this library into (more on this later). In addition to these subdirectories, I will define and store a Visual Studio solution file called EcommerceUI.sln underneath the root EccomerceUI library directory that defines the solution structure and cross-project relationships.
My resulting directory structure will look like as follows.
C:\sources\libraries\EcommerceUI\ C:\sources\libraries\EcommerceUI\EcommerceUI.sln C:\sources\libraries\EcommerceUI\myclasslibraryproject\ C:\sources\libraries\EcommerceUI\myWebproject\ C:\sources\libraries\EcommerceUI\mytestproject\ C:\sources\libraries\EcommerceUI\buildoutput\
Note that Visual Studio 2005 makes it much easier to manage and store Web projects outside of your inetpub and wwwroot directories. This makes it possible to easily store Web projects side-by-side with companion projects as part of a solution (like above). I can configure the above Web project directory to run either using the built-in Visual Studio file-system-based Web server, or using IIS directly. For this particular solution, because I am not using any subapplications or vdirs, I am going to be using the built-in Visual Studio 2005 Web server, which will help me avoid having to register anything with IIS.
When I open up the solution defining the above projects using Visual Studio 2005, I will see a corresponding solution explorer view such as the one shown in Figure 1.
Figure 1. Solution explorer in Visual Studio 2005
One last note: I obviously don't recommend calling subdirectories myWebproject or myclasslibraryproject—I'm using those names here only to add clarity about what each directory does.
Once the above library structure is in place, obviously the next thing to do is write the code and build it. For the Web project, this typically involves building one or two top-level directories to encapsulate the pages and user controls of the solution.
For our e-commerce solution, I'm going to take the approach of defining a top-level directory called storefront that will encapsulate all of the content for our re-usable Web project library. Contained within the storefront directory will be the .aspx pages that make up our pre-built page functionality, as well as a controls subdirectory that will contain all of the .ascx user controls for the library. Figure 2 shows the directory structure for this solution.
Figure 2. Breaking out the user controls
Note the following things in the above solution:
- In addition to the storefront subdirectory above, I've added three test pages (testpage1.aspx, testpage2.aspx, testpage3.aspx) in the root of the Web project. I can use these for easily testing my library, without having to first deploy it inside another Web application. I can also use these pages with the Visual Studio Team System test project that I have in my solution, to host and then perform Web UI testing of the user controls in the library. (Note: The Visual Studio Team System edition of Visual Studio now includes a built-in Web UI recorder that makes building Microsoft ASP.NET UI unit testing easy.)
- I have a site.master master page file defined in the root directory. Master pages are one of the big new features of ASP.NET, and allow developers to define a common layout across pages within their application. I can structure my EcommerceUI library pages (underneath the storefront directory) to use a master page defined outside of storefront—which would allow the Web application that is taking advantage of the EcommerceUI library to integrate it within the overall Web application look and feel, without having to modify the .aspx pages contained within storefront. If I wanted to, I could even compile away the HTML within these storefront pages, so that the application couldn't modify these and was limited to only changing the masterfile or using the new ASP.NET 2.0 themes feature to integrate it within the site. (This can make versioning dramatically easier, and help avoid messy merge-hell scenarios when upgrading or deploying a new version of the EccomerceUI library.) Because master pages can be specified both declaratively and programmatically, the EcommerceUI library could optionally allow developers using the library to configure the exact master page they should use, and then dynamically select this one at runtime.
- I have a Web.sitemap file defined underneath the storefront directory. Web.sitemap files are used by the new ASP.NET 2.0 Site Navigation system to define the logical scope and structure of a site layout. Developers can then write code against the SiteMap API to get access to this structure and figure out where a visiting browser is within the site hierarchy at runtime, and/or they can use some of the new built-in ASP.NET 2.0 UI controls, such as the Menu, Treeview and Breadcrumb Navigation controls, in order to easily visualize it. One of the cool things about the built-in XML Site Map Providers in ASP.NET 2.0 is that they allow you to partition the site definition across multiple files, which can then be automatically merged into a single site map at runtime. The Web.sitemap file defined underneath the storefront directory above would then contain only the site structure for the EcommerceUI library we are defining. When added to a Web application that had its own site map defined, the site structure we defined would be merged into that, and can show up in a menu that is defined on the master page of the entire site, without our having to do a lot of extra work.
- One of the goals we tried to accomplish in ASP.NET V2.0 was to provide a much richer framework for building Web applications, and to build in "building-block application service APIs" that provide a common model and framework for accomplishing core things such as Membership, Role Management, Profiles, Personalization, Health Monitoring, and so on. One of the nice things this provides is a consistent way for components, controls, and libraries to integrate and work together better. For example, in our EcommerceUI example we could have our library use the new ASP.NET 2.0 Membership, Role Management, and Profile APIs. As a result our storefront section of the Web app would integrate nicely with the rest of the application if they are using the same APIs. Because these APIs are pluggable by means of providers, it also means that we don't lose flexibility as a result (read http://Weblogs.asp.net/scottgu/archive/2005/08/25/423703.aspx for more information about how providers work and how they can be configured). The end result should be much richer code re-use and flexibility of libraries with ASP.NET 2.0.
So What Can't I Do Within This Web Project Library?
You can pretty much use all of the same designers, code editors, and features when building a re-usable Web project library that you can with a normal stand-alone Web application. Because you'll be shipping and re-using the library within other Web applications, though, there are a couple of things you'll need to avoid:
- Don't define a global.asax file. You are only allowed one of these for each application in ASP.NET, and therefore you don't want to define one in your re-usable library.
- Don't define classes under app_code, or service proxies under app_Webservices. Like global.asax, there is one of each of these for each application. Code-behind class files are obviously fine inside the Web project library, and these typically live next to their .aspx/.ascx equivalents. Non-UI and business classes for the Web project library should be defined within a companion class library project in the solution (for example, the myclasslibraryproject above).
- Be careful about what you require in your root Web.config file. If possible, define application settings and other configuration within the Web.config file that lives underneath subdirectories (for example, storefront above). This will make re-using these Web project libraries much easier and help avoid having to write a setup program that does custom merge semantics.
Once you've built and tested your Web project library, it is time to build and deploy it for re-use in other Web projects. There are a lot of new compilation and deployment options introduced by ASP.NET 2.0 and Visual Studio 2005. In particular, there are two big new decisions that developers can now make:
- Decision #1. Whether the HTML and server control markup should be preserved when deploying a Web project (which is what Visual Studio 2003 does today), or whether this should be removed as part of the compilation process, and compiled directly into the generated bin assemblies. The benefit of the first approach (preserving the HTML) is that it allows later modification of the markup, without having to re-build the project (therefore, we call this the "updatable" build option in dialog boxes you'll see below). The benefit of the second approach (compiling the markup out) is that it allows ASP.NET to avoid ever having to parse and compile the .aspx file at runtime, which can dramatically improve the first-load/first-request performance of the application. It also allows ISVs to better protect their intellectual property, and to hide their HTML and server control definitions.
- Decision #2: How granular the deployed assemblies should be. Specifically, Visual Studio 2005 with ASP.NET 2.0 now, by default, compiles your Web project so that each separate directory of .aspx/.ascx content compiles into a separate assembly. For even more flexibility, you can also optionally choose to compile each .aspx or .ascx file into its own separate assembly (this option is called the "fixed name" option, because it also results in assemblies whose names are fixed across multiple compilations). The benefit of this second approach is that you can now deploy individual updates on your system without having to re-build and update your entire site. It can also sometimes make deploying Web project libraries much easier, because it allows you to copy and deploy just those .ascx user controls and associated assemblies that you want out of the Web project, without having to grab everything.
Note One request that we've received from several people since Beta 2 has been to provide a new third compilation granularity option, which would allow you to merge the assembly output from multiple directories into a single assembly that has a well-known name that you define (and that does not change across re-builds—which is one unfortunate side-effect of the per-directory build option today). We are working on a tool right now that does this, and have a prototype up and running that seems to work great.
There are two ways I can easily build and deploy my solution:
- From within the Visual Studio IDE.
- From the command-line using MSBuild.
Building and Deploying the Web Project Library from the Visual Studio IDE
To build and publish/deploy my Web library solution within the Visual Studio IDE, I can click the Build menu and then click Publish Web Site. This will bring up the Publish Web Site dialog box (Figure 3), which provides me with the various deployment/compilation options for building the site.
Figure 3. Deploying a Web project from Visual Studio (click the image for a larger picture)
For this walkthrough I am going to use the default "updatable" option (meaning that the HTML and Server control markup is preserved), and select to have individual assemblies created for my .ascx and .aspx files. The reason for selecting the individual assembly option is that I want to be able to remove the compiled code for the root test pages and master templates that I am currently using to test my storefront library, and I also want to have the flexibility to update these assemblies on a more granular level in the future.
Note: For simplicity's sake, in this walkthrough I'm going to only describe one deployment combination (specifically I'll use updatable HTML + individual compiled assemblies). I could have just as easily picked another (for example, fully compiled HTML + per-directory assemblies). As a developer, you can to pick whatever you feel is most appropriate for your particular Web library deployment scenario.
When I click the OK button, Visual Studio 2005 will compile all three projects (myclasslibrary, myWebproject, mytestproject), and then deploy the classlibrary and Webproject into the BuildOutput directory we defined earlier, under c:\sources\libraries\ecommerceUI\directory. Figure 4 shows the deployed project.
Figure 4. Deployed project (click the image for a larger picture)
Note that the .aspx/.ascx files will remain (because they still have HTML/server control content defined within them), but all code-behind files are now gone, because they have been compiled into assemblies underneath the bin directory.
Building and Deploying the Web Project Library from the Command-Line
To build and publish/deploy my Web library solution from the command-line (without having the Visual Studio IDE loaded or, potentially, even installed on my machine), I can take advantage of the new MSBuild support that ships with Visual Studio 2005 and ASP.NET 2.0. There is a lot of richness in MSBuild—easily 20 or more blog entries' worth. I'm going to only show a super-basic (but still pretty useful) way to use it with Web projects.
To configure basic MSBuild options with my Web project library solution, I can right-click my Web project in the solution explorer, and then click Properties on the shortcut menu. This will bring up a configuration dialog box for the Web project, and allows me to configure lots of different things (start pages, references, accessibility compliance checker, and so on). If I click MSBuild Options I will see the options shown in Figure 5.
Figure 5. Configuring MSBuild Options (click the image for a larger picture)
Once I have configured the MSBuild options this way, and clicked Save All to make sure the solution is up to date, I can perform command-line builds on my solution.
MSBuild.exe is installed underneath the framework redist directory (c:\windows\microsoft.net\framework\v2.0.xyz). If this was on my command-line path, I could simply type the command shown in Figure 6 to initiate a command-line build.
Figure 6. Using MSBuild on a Visual Studio solution (click the image for a larger picture)
This would then generate the console output shown in Figure 7, and produce the exact same bits we did before, using the IDE.
Figure 7. Output of MSBuild (click the image for a larger picture)
Obviously, this is a very simple command-line build scenario, but I could compose additional MSBuild rules to make this much richer (for example, by adding additional steps to copy the appropriate files from the produced library into multiple projects). MSBuild has a custom task called AspNetCompiler that can also be used to declaratively author custom MSBuild scripts from scratch.
To use the EcomerceUI library that we just created inside a new Web application in Visual Studio 2005, I need to do two things:
- Copy the appropriate .aspx/.ascx files from the built EcommerceUI solution into my new Web application project directory.
- Copy the appropriate .dll assemblies from the built EcommerceUI solution into my new Web application project's bin directory. (Note: I could either copy these manually or as part of an automated build process, or I could have a CopyLocal reference set up to auto-refresh these assemblies when new versions get built and deployed under the EcommerceUI solution.)
Figure 8 shows what my blank Web application will look like when I copy these items into it.
Figure 8. New Web project
Note that, because I've built using the option to generate separate assemblies for each control/page, I end up having several granular assemblies in my bin directory. Note also that this allowed me to remove all of the testpage assemblies that I was using within the Web project to verify things.
I can now go ahead and build my Web project, taking advantage of the Ecommerce UI library. I can add whatever pages I want into the project, and integrate them around the storefront subdirectory. I can also use the user controls from /storefront/controls on any page within the site.
Figure 9 is a screenshot of the page library in action with Visual Studio 2005.
Figure 9. Using the page library in Visual Studio (click the image for a larger picture)
In Figure 9, note how the catalog page that we defined within the EcommerceUI library picks up and integrates within this new Web project the custom Site.Master master page file that we defined at the root level. It shows up both in WYSIWYG in Visual Studio 2005, and (obviously) also at runtime.
Also note the TreeView control on the left that is defined within the master page in our new Web project, as well as the breadcrumb control that is defined within the catalog.aspx page of the EcommerceUI library. Both of these new ASP.NET 2.0 controls are binding against the new ASP.NET Site Navigation system and showing a consistent site hierarchy view of the overall Web solution, regardless of whether they were used inside the storefront or outside of it. This view shows up consistently both at runtime and also at design-time in Visual Studio 2005.
Figure 10. Using the control library in Visual Studio (click the image for a larger picture)
In Figure 10, note how the wishlist.ascx user control from the /storefront/controls directory shows up in WYSIWYG on the default.aspx homepage defined at the Web project root. In previous versions of Visual Studio, user controls were rendered as grey boxes, but now developers using the ECommerceUI library will be able to see the actual representation of the user control content that they'll see at runtime. Visual Studio 2005 also now provides strong-typing and intellisense against the user controls when writing code-behind code for default.aspx. (By default, previous versions of Visual Studio declared these controls as type-less user controls.)
Note that, as I'm building my new Web project, I can regularly run it (using F5 or CTRL+F5). When I'm finished, I can also then do a "Publish Web" or command-line build operation on this new Web project and solution, and generate a completed Web application with the EcommerceUI library included and ready to deploy.
Building re-usable sets of controls and pages allows you to share functionality across multiple Web projects. The techniques shown in this article are one way of creating these sets of re-usable functionality.