Using MSBuild and ILMerge to Package User Controls For Reuse

One of the advantages to ASP.NET server controls is the ability to package them into an assembly and reference them from other web applications. Server controls are relatively difficult to write but easy to reuse. User controls (ascx files), on the other hand, are relatively easy to develop, but don't like to swing with other projects. A common solution in 1.x involves setting up virtual directories. Yuck.

The ASP.NET 2.0 environment is different. We have MSBuild. We have an ASP.NET compiler. Perhaps you've also noticed we have a tool by the name of ILMerge.

Here is a proof of concept.

Step 1: I created a new solution in Visual Studio 2005 and added a plain class library project. I then added two user controls to the project, which Visual Studio doesn’t like initially, but it does all work, even the intellisense. The first user control is all inline code:

<%@ Control Language="C#" ClassName="SayHello" >

<script runat="server">
  <protected void Page_Load(object sender, EventArgs e)
  {
    label.Text = "Hello at " DateTime.Now.ToShortTimeString();
  }       
</script>

<asp:Label runat="server" ID="label"/>

The second control, SayGoodbye.ascx, is the same, except it puts the Page_Load logic into a separate CodeFile by the name of SayGoodbye.cs.

Step 2: One of the cool features of Visual Studio 2005 is that the project files are MSBuild files. The default .csproj file for the class library I created does not know what to do with .ascx user control files, but I help it. I can right-click the project and select unload, then right-click the now disabled project and select Edit. Behold - the project is exposed naked before me. Pure angle-brackety goodness.

What I want to do at this point is modify the project to run the ASP.NET precompilation task – easy enough since there is an MSBuild task available by the name of AspNetCompiler. What is tricky is that AspNetCompiler will likely produce multiple assemblies. The compilation tool will batch compile by default, which means one assembly per directory of user control files. Of course we don’t want to keep all the ascx files in the root of the project, and we don't want to reference an entire directory of .dll files, so this is where ILMerge comes in.

ILMerge is a utility to merge multiple .NET assemblies into a single assembly. Combining AspNetCompiler and ILMerge together gives us something like the following:

<Project DefaultTargets="CompileUserControls" 
         xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
           ...
  <Target Name="CompileUserControls">
    <!-- TargetPath cannot be underneath PhysicalPath :( -->
    <AspNetCompiler 
      Debug="false"
      PhysicalPath="$(MSBuildProjectDirectory)" 
      TargetPath="$(TempDirectory)" 
      Updateable="false" 
      Force="true" 
      VirtualPath="$(MSBuildProjectName)" />

    <CreateItem Include="$(TempDirectory)\bin\*.dll">
      <Output ItemName="PrecompiledAssemblies" TaskParameter="Include" />
    </CreateItem>
    
    <Exec Command="$(ILMergeEXE) /out:$(MSBuildProjectName).dll 
                    /targetplatform:v2 @(PrecompiledAssemblies, ' ')" />
  </Target>
    ...

Inside the target I first run the AspNetCompiler and produce all the user control assemblies. Next, I need a list of all the assemblies the compiler just spit out. I do this with , which produces the Item PrecompiledAssemblies (thanks to Chris Tavares and the Chris Sells' Win-OT list for figuring this out). The last piece is to execute the ILMerge tool, passing all the assembly names as command line arguments.

Plop! Out comes ReusableControls.dll.

For the last step, I created a new web project and referenced ReusableControls.dll, then created a little test aspx web form.

<%@ Page Language="C#" %>
<%@ Register TagPrefix="rc" Namespace="ASP" Assembly="ReusableControls" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<form id="form1" runat="server">
   <rc:SayHello runat="server" ID="hello" />
  
   <br />
  
   <rc:saygoodbye_ascx runat="server" ID="goodbye" />
  <br />
</form>

Notice in the @ Register directive we need to specify a namespace of ASP, as this is the default given by the AspNetCompiler. The compiler also munges the user control name when a CodeFile is used – we are using the class that was code-generated from the SayGoodbye.ascx file and inherits from the SayGoodbye class in the CodeFile. With inline code there is no adjustment.

It might be possible to also re-use master pages and webforms with a little bit of VirtualPathProvider trickery, but I’m not going there as yet. I want to see how this will shake out at RTM time when combined with the Build Project that should arrive, as announced by others.

Print | posted @ Thursday, October 06, 2005 9:46 PM

Comments on this entry:

Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by Stan at 10/6/2005 10:09 PM

This rocks! but how did you get the MSBuild file into the solution explorer?
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by scott at 10/6/2005 10:15 PM

The beauty of the system is MSBuild file == .csproj file, so VS2005 groks it. Once you edit the file by hand, just right-click and "reload project". VS2005 will run MSBuild against the project.
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by Raghu at 10/7/2005 4:08 PM

In 1.x, I had used virtual directories approach. This is really very cool way to reuse user control cross project.
Good digging scoot!!!
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by scott at 10/8/2005 1:44 AM

The MSBuild team has the annoucement, too:

blogs.msdn.com/.../478425.aspx
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by scott at 10/20/2005 4:27 PM

I'm having an interesting discussion with Guenter in the newsgroups, who is refining the proj file:

groups.google.com/.../c7b8dfc1c1adda17
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by Robert W. Anderson at 10/20/2005 11:49 PM

Good article -- it gave me validation to try generally merging ASP.NET 2.0 assemblies for release. That is the article mentioned in the trackbacks.
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by Robert W. Anderson at 10/20/2005 11:50 PM

P.S. Sorry about all of the trackbacks -- editing my blog caused this to happen automatically. Feel free to delete them, and this one for that matter. Cheers.
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by Scott Allen at 10/21/2005 12:58 PM

No problem, Robert! Glad you found it useful.
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by Keith J. Farmer at 10/26/2005 8:43 PM

Very cool.

Now we just need to work on that class naming problem...
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by Scott at 10/31/2005 8:58 PM

David Ebbo is also having a crack at this: blogs.msdn.com/davidebb/comments/487160.aspx

  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by Paul V. at 11/11/2005 5:00 PM

Very interesting article. It would do just what I need. I just can't get it to work for user controls that rely on code in the same assembly or a remote one. What am i missing? :(
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by scott at 11/11/2005 6:37 PM

Paul: I haven't tried either of those scenarios yet. I'd think if the code the controls needed was in App_Code you'd be ok, as it would all get merged togeter.

I'm going to be taking a second look at this now that the web deployment project released.
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by Nicklas Johansson at 12/5/2005 3:29 PM

This is great, this helped me alot. The only problem I have right now is that the project is creating two .dll with the same classes in them. I'm kind of new to modding the Build-file, so I guess it's something very basic.

Any thoughts?
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by Michael at 12/13/2005 2:05 PM

Great article, it has set me on the right track! However I have a problem building custom controls with a codefile, as every server control that I use in the ascx file then generates a "does not exist in current context" error when I refer to it in the ascx.cs file.
So apparently the "link" between the .ascx file and the .ascx.cs file is lost. Any idea? Could you maybe post the code of your SayGoodbyeClass? Although I set the CodeFile, Inherits and ClassName attributes in the control definition, I probably forgot to set one or another attribute correclty... Thanks!
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by paul at 5/18/2006 6:52 PM

Was the code for this ever submitted?
  
Gravatar # A Newer Solution for ASP.net 2
by Graham at 7/26/2006 11:22 AM

I've just posted a new solution for .Net 2 that allows you to include UserControls, Pages and MasterPages in a single Web Application Project library that can be distrubed easily.

Its quick and easy to set up, there a description of how it works and a solution download that demonstrates everything working.

http://www.nearasmakesnomatter.co.uk/wapul.htm
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by whatispunk at 2/22/2010 2:06 PM

I'm missing something. Where do you define the $(ILMergeEXE) variable?
  
Gravatar # re: Using MSBuild and ILMerge to Package User Controls For Reuse
by Aaron Prohaska at 2/23/2010 6:01 PM

This seems so simple yet I get a red squiggly under the class name where I have (from your example) <rc:SayHello. It acts like the compiler does not see SayHello. How can I make this work?
  

Your comment:

Title:
Name:
Email:
Website:
 
Italic Underline Blockquote Hyperlink
 
 
Please add 4 and 5 and type the answer here: