Building Re-Usable ASP.NET User Control and Page Libraries with VS 2005
http://weblogs.asp.net/scottgu/archive/2005/08/28/423888.aspx
Turning an .ascx User Control into a Redistributable Custom Control
http://msdn.microsoft.com/en-us/library/aa479318.aspx
Background
Since its early days, ASP.NET has always supported two different ways of authoring server controls:
- Custom controls: These are controls written from scratch exclusively using code. Essentially, you write a class that extends Control
(directly or indirectly), and you take care of everything. You need to
write logic to create child controls that you want to use, and you
override the Render method to perform rendering.
Typically, you build this control into a redistributable assembly (that
is, a DLL), which can then be used by any ASP.NET applications, simply
by placing the assembly in the "bin" directory of the application (or in
the Global Assembly Cache).
- User controls: These controls are written using an
.ascx file, which looks much like an aspx page. That is, you can simply
drag and drop the UI elements that you want to use into the design
surface. You don't need to worry about control creation, nor about
overriding the Render method.
These two methods are very different, and each has a number of
advantages and disadvantages. I won't discuss them all, but will focus
on those that are relevant to this article:
- Custom controls require a lot of development expertise to be
written, while user controls are authored using an advanced designer,
and are much more approachable. For this reason, user controls typically
take a lot less time to write.
- Custom controls can easily be redistributed without having to give
away their sources. On the other hand, user controls are always used
based on an ascx text file, making it less ideal for reuse across
applications.
The goal of this article is to show how you can have the best of both
worlds by turning an ascx user control into a redistributable custom
control, by making use of the new ASP.NET precompilation features.
Brief Outline of the Steps
The basic steps to make this happen are as follows:
- Write your user control as you normally would, typically using the Visual Studio designer.
- Test it using a simple page before trying to deploy it.
- Deploy the application to precompile it.
- Grab the user control's assembly produced by the deployment step, and you're essentially done: You have your custom control.
- Finally, use your custom control in other apps the same way as you always use custom controls.
We will look at those steps in more detail in the rest of the article.
Step 1: Authoring the User Control
To author the user control, it is best to start with an empty app
that contains nothing other than the ascx. While the authoring of the
user control uses "standard" techniques, there are some restrictions
that you need to be aware of in order for it to be successfully turned
into a standalone custom control.
The main restriction is that the user control needs to be
self-contained. That is, it cannot be dependent on app global things
like App_Code or global.asax. The reason for this is that since the goal
is to turn the UserControl into a standalone DLL, it would break in
other apps if it relied on code that is not part of that DLL. One
exception to this rule is that the UserControl can be dependent on
assemblies that live in the bin directory (or in the GAC). You just have
to make sure that the other assemblies are always available when you
use your custom control in other apps.
Another tricky thing is the use of static resources, such as images.
After you turn it into a custom control in a standalone assembly, it
becomes hard for it to keep such references, and avoiding them
simplifies deployment. If you really must have such references, one
option is to use absolute URLs if you can guarantee that the resource
will always be available on a certain site. Or you can look into machine
wide resources (e.g., src="/MyImages/welcome.jpg"), though you will
then need to make sure the resources are installed on the server machine
(e.g. as part of some setup).
So let's start and actually author the user control. Visual Studio
2005 gives you the choice to place the code in a separate file, or use
inline code. This is a matter of personal preference, and either one
will work to create a custom control. For this article, we will use
inline code. When you create the user control (say MyTestUC.ascx),
Visual Studio creates it with the a @control directive that looks like
this:
<%@ControlLanguage="C#"ClassName="MyTestUC"%>
This is fine, except for one thing: we want the class to live within a
namespace of our choice (instead of "ASP", which is used by default).
To do this, we simply modify the
ClassName attribute to include the namespace (this is a new feature in 2.0). Here's an example:
<%@ControlLanguage="C#"ClassName="Acme.MyTestUC"%>
That's really the only "special" thing you need to do. Now you can go
ahead and implement your user control as you always would: add some
server control, some client HTML, server script, client script, and so
on.
Step 2: Testing Your User Control
Before trying to turn the user control into a custom control, it is a
good idea to test it in the source app using a simple page. To do this,
simply create a new Page in Visual Studio, go to design View, and drag
and drop your user control into it.
The two notable pieces of your page are the Register directive:
<%@RegisterSrc="MyTestUC.ascx"TagName="MyTestUC"TagPrefix="uc1"%>
and the user control declaration:
Note that at this point, the Register directive uses the user control
syntax (Src/TagName/TagPrefix) and not the custom control syntax
(TagPrefix/Namespace/Assembly). This will change after we turn the user
control into a custom control.
Run your page (press
CTRL-F5) and make sure the user control works the way you want before moving to the next step.
Step 3: Use the Publish Command to Precompile the Site
The next step is to use the new
Publish command to precompile your site and turn your user control into a potential custom control. You'll find the command under
Build /
Publish Web Site. In the
Publish dialog, do the following:
- Pick a Target Location. This is the location on your hard drive that your site will be precompiled to.
- Deselect "Allow this precompiled site to be updatable". In
updatable mode, only the code behind file (if any) would get compiled,
and the ascx would be left unprocessed. This is useful in some
scenarios, but is not what you want here since you want the resulting
DLL to be self-contained.
- Select "Use fixed naming and single page assemblies". This will
guarantee that your user control will be compiled into a single assembly
that will have a name based on the ascx file. If you don't check this
option, your user control could be compiled together with other pages
and user controls (if you had some), and the assembly would receive a
random name that would be more difficult to work with.
Though it is entirely optional, note that the
Publish Web Site
dialog lets you strongly name the generated assemblies. This allows you
to sign the assembly so that it cannot be tampered with. Additionally,
it allows you to place the assemblies in the Global Assembly Cache
(GAC), which makes it easier to use machine-wide. I will provide more
information on this in Step 5.
Go ahead and complete the dialog, which will perform the precompilation.
Note This same step can also be accomplished without
using Visual Studio by using the new aspnet_compiler.exe command-line
tool. The options it supports are basically the same as what you see in
the Publish dialog. So if you are more command-line
inclined, you might prefer that route. For example, you would invoke it
using the command:
aspnet_compiler -p c:\SourceApp -v myapp -fixednames c:\PrecompiledApp
.
Step 4: Finding the Resulting Custom Control
Now, using the Windows Explorer or a command-line window, let's go to
the directory you specified as the target so we can see what was
generated. You will see a number of files there, but let's focus on the
one that is relevant to our goal of turning the user control into a
custom control.
In the "bin" directory, you will find a file named something like
App_Web_MyTestUC.ascx.cdcab7d2.dll. You are basically done, as this file
is your user control transformed into a custom control! The only thing
that's left to do is to actually use it.
Note In case you're curious, the hex number within the file name (here
cdcab7d2
)
is a hash code that represents the directory that the original file was
in. So all files at the root of your source app will use
cdcab7d2
,
while files in other folders will use different numbers. This is used
to avoid naming conflicts in case you have files with the same name in
different directories (which is very common for default.aspx!).
Step 5: Using Your New Custom Control
Now that we have created our custom control, let's go ahead and use
it in an app. To do this, create a new Web application in Visual Studio.
We then need to make our custom control available to this application:
- In the solution explorer, right-click on your application, and choose Add Reference.
- In the Add Reference dialog, choose the Browse tab.
- Navigate to the location of your custom control (App_Web_MyTestUC.ascx.cdcab7d2.dll) and select it. It will be copied to the bin directory of your new app.
Note as an alternative, you can choose to place the
assembly in the GAC instead of the "bin" directory. In order to do this,
you need to choose the Strong Name option in Step 3. You then need to
add your assembly in the
section of web.config in the
Web application that requires it (or in machine.config to make to
globally usable).
Create a test page that uses the custom control. This is similar to
Step 2, except that you are now dealing with a custom control instead of
a user control.
First, add a Register directive to your page. It should look something like this:
<%@RegisterTagPrefix="Acme"Namespace="Acme"Assembly="App_Web_mytestuc.ascx.cdcab7d2"%>
Note how we are using a different set of attributes compared to Step 2. Recall that in Step 1, we made sure that the
ClassName
attribute included a namespace. This is where it becomes useful, as
custom control registration is namespace-based. Also, you need to
specify the assembly name, which is why having a name that is easily
recognizable is useful, as discussed in Step 3.
Declare a tag for the custom control, for example:
That's it, you can now run your app (
CTRL-F5), and you are using your new custom control!
This shows how to use your custom control declaratively, but note
that it can also be used dynamically, just like any other control. To do
this, just create the control using "new". Here is what the previous
sample would look like using dynamic instantiation:
Note Instantiating your custom control dynamically as
described above is basically the equivalent of instantiating your
original user control using the LoadControl API. Note that you can no longer use the standard LoadControl API after converting it to a custom control, since custom controls don't have a virtual path. However, ASP.NET 2.0 has a new LoadControl override that takes a Type that you could use in this case. The one reason I can think of that you might choose to call LoadControl
instead of just using "new" is to take advantage of fragment caching
(also called partial caching). If you use "new", any OutputCache
directive in your ascx will be ignored.