Building Re-Usable ASP.NET User Control and Page Libraries with VS 2005
One of the questions I’ve been asked a few times recently is whether it is still possible to build web projects that encapsulate common libraries of .ascx web user controls or .aspx pages with VS 2005 that can then be easily re-used across multiple other web projects (note: the quick answer is “yes").
This blog post walks-through how to easily accomplish this with VS 2005, as well as some of the new features you can take advantage of to make this scenario even better than before.
Quick Scenario Overview
The scenario I’m going to use to illustrate building re-usable page/control libraries in the below post is one where a development team wants to build a re-usable “ecommerce” 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 ecommerce web UI library want to provide two things:
1) Pre-built store-front 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 ecommerce functionality incorporated within their application.
2) Pre-built strong-front UI user controls (.ascx files) that enable scenarios like 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 ecommerce 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 below walkthrough, I am going to assume that the development team building this web UI library wants to build-it as an isolated VS 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.
Step 1: Define the Re-Usable Page/Control Web Solution and Directory Structure
We want to cleanly encapsulate and maintain our ecommerce library solution. To help with this I can use a straight-forward directory structure and VS solution architecture.
Specifically, I’m going to define an “EcommerceUI” directory underneath a “libraries” sub-directory in my source tree. Contained within this will then be 4 sub-directories: 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 VSTS test project, and a “buildoutput” directory which I’ll use to build and ultimately publish this library into (more on this later). In addition to these sub-directories, I will define and store a VS 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 this:
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 VS 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 using either the built-in VS file-system based web-server or using IIS directly. For this particular solution, because I am not using any sub-applications or vdirs, I am going to be using the built-in VS 2005 web-server – which will avoid me having to register anything with IIS.
When I open up the solution defining the above projects using VS 2005, I will see a corresponding solution explorer view that looks like this:
One last note: I obviously don’t recommend calling sub-directories “mywebproject” or “myclasslibraryproject” – I’m using those names here only to add clarity about what each directory does.
Step 2: Develop the Re-Usable Page/Control Web Solution
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 ecommerce 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” sub-directory that will contain all of the .ascx user controls for the library.
A couple of things to note in the above solution:
1) In addition to the /storefront sub-directory 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 VSTS test project I have in my solution to host and then perform Web UI testing of the user-controls in the library (note: the VSTS edition of Visual Studio now includes a built-in web UI recorder that makes building ASP.NET UI unit testing easy).
2) 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 their 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 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.
3) 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 can use some of the new built-in ASP.NET 2.0 UI controls like the Menu, Treeview and Breadcrumb Navigation controls to easily visualize it. One of the cool things about the built-in XML Site Map Providers in ASP.NET 2.0 is that it allows you to partition the site definition across multiple files which can then be automatically merged into a single sitemap 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 sitemap defined, the site structure we defined would be merged into that, and can show up in a menu defined on the Master Page of the entire site – without us having to-do a lot of extra work.
4) 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 like Membership, Role Management, Profiles, Personalization, Health Monitoring, etc. One of the nice things this provides is a consistent way for components, controls and libraries to integrate and work better together. For example: in our EcommerceUI example we could have our library use the new ASP.NET 2.0 Membership, Role Management, and Profile APIs – and as a result have our /storefront section of the web app integrate nicely with the rest of the application if they are using the same APIs. Because these APIs are pluggable via providers, it also means that we don’t loose flexibility as a result (read http://weblogs.asp.net/scottgu/archive/2005/08/25/423703.aspx for more on how providers work and how they can be configured). The end-result should be much richer code-reuse 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 the same designers, code-editors and features when building a re-usable web project library that you can with a normal standalone 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 per-application in ASP.NET, and so 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 are one of these each per application. Code-behind class files are obviously fine inside the web project library and 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 applicationsettings and other configuration within the web.config file that lives underneath sub-directories (like /storefront above). This will make re-using these web project libraries much easier and avoid having to write a setup program that does custom merge semantics.
Step 3: Building and Deploying the Re-Usable Page/Control Web Solution
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 VS 2005. In particular, there are two big new decisions that developers can now make:
Decision #1) Whether to preserve the HTML + Server Control markup when deploying a web project (which is what VS 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 with the first approach (preserving the html) is that it allows later modification of the markup without having to re-build the project (hence the reason we call this the “updatable” build option in dialogs you’ll see below). The benefit with the later approach (compiling the mark-up out) is that it allows ASP.NET to avoid having to ever 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 hide their HTML and server control definitions.
Decision #2) How granular the deployed assemblies should be. Specifically, VS 2005 + 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 with this later 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 we’ve heard from several people since Beta2 has been to provide a new third compilation granularity option above 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 which does not change across re-builds – which is one unfortunate side-affect 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. I’ll provide more details on this over the next week or two once we confirm that it fully works for all scenarios.
There are two ways I can easily build and deploy my solution: a) from within the VS IDE, or b) from the command-line using MSBuild.
Building and Deploying the Web Project Library from the VS IDE
To build and publish/deploy my web library solution within the Visual Studio IDE, I can go to the “Build” menu and select the “Publish Web Site” option. This will bring up the “Publish Web Site” build dialog which provides me with the various deployment/compilation options on how to build the site:
For this walkthrough I am going to use the default “updatable” option (meaning the HTML + 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 because 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 store-front library, and want to have the flexibility to update these assemblies on a more granular level in the future.
Note: For simplicity 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). You as a developer are allowed to pick whatever you feel is most appropriate for your particular web library deployment scenario.
When I click the “ok” button, VS 2005 will compile all three projects (myclasslibrary, mywebproject, mytestproject) and then deploy the classlibrary and webproject into the “BuildOutput” directory we defined earlier under our c:\sources\libraries\ecommerceUI\ directory.
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 – since they have been compiled into assemblies underneath the \bin directory.
Building and Deploying the Web Project Library from the VS IDE
To build and publish/deploy my web library solution from the command-line (without having the VS IDE loaded or potentially even installed on my machine), I can take advantage of the new MSBuild support that ships with VS 2005 and .NET 2.0. There is *a lot* of richness in MSBuild – easily 20+ blog entries worth. I’m going to only show a super basic (but still pretty useful) way to use it below with web projects. I’ll blog some more about it in the future to go into some of the more advanced ways you can use it with web projects.
To configure basic MSBuild options with my web project library solution, I can right-click on my web project in the solution explorer and select the “Properties” menu item on the context menu. This will bring up a configuration dialog for the web project, and allows me to configure lots of different things (start pages, references, accessibility compliance checker, etc). If I click on the MSBuild tab I will see the below options:
Once I have configured the MSBuild options this way, and hit “save all” to make sure the solution is up-to-date (note: I always forget to-do this), I can now 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, then I could simply type the below command to kick off a command-line build:
This would then generate the below console output and produce the exact same bits we did before using the IDE:
Obviously this is a very simple command line build scenario – but I could compose additional MSBuild rules to make this much richer (including 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.
Step 4: Using the Page/Control Web Library within a Web Application
To use the EcomerceUI library we just created inside a new web application in VS 2005, I need to-do two things:
1) Copy the appropriate .aspx/.ascx files from the built EcommerceUI solution into my new web application project directory.
2) 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 have a CopyLocal reference setup to auto-refresh these assemblies when new versions get built and deployed under the EcommerceUI solution).
Here is what my blank web-application will look like when I copy the above items into it:
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 I was using within the web project to verify things.
I can then go ahead and build my web project and take 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.
Here are two screen-shots of this in action with VS 2005:
Note in the above screen-shot how the catalog page we defined within the EcommerceUI library picks up and integrates within the custom Site.Master masterpage file we defined at the root level within this new web project. It shows up both in WYSIWYG in VS 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 bread-crumb 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 are 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 VS 2005.
Note in the above screen-shot 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. With previous versions of VS user-controls were rendered as grey-boxes – now developers using the ECommerceUI library will be able to see the actual representation of the user control content they’ll see at runtime. VS 2005 also now provides strong-typing and intellisense against the usercontrols when writing code-behind code for default.aspx (previous versions of VS declared these controls by default as type-less usercontrols).
Note that as I’m building my new web project I can regularly run (just do a standard F5 or Ctrl-F5 to run the web project). 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 EcommerceUI library included that is ready to deploy.
Hope this helps,
Scott