Straight way to create ASP.NET user controls library

Preamble

I feel necessity to compile my ASP.NET user
controls into a library. It is good for modularity and reusing. However, there
is no official way to do it. We can create custom controls library only.

Note the difference between custom
controls and user controls. Custom control is ordinary .NET class inherited
from System.Web.WebControls.WebControl. It overrides method RenderContents and
renders itself with output.Write(...).

User control consists of *.ascx file,
codebehind * .vb file and *.designer.cs file. It allows to simple write HTML
code and uses all benefits of codebehind model. ASP.NET compiles it at runtime.

All these ways have seriously weakness. I
have several requirements for straight way:

Preserve designers, code generators, etc working
within the library project

It should be easy to use controls of the library
in any other web application

It should be easy to use controls of the library
in other such libraries

It should be easy to reuse controls of the
library within itself

It should be possible to reference the library
as project

It should be possible to reference the library
as DLL

The library should consist of one file.

It should be possible to create new library in
30 seconds

The solution should work for .NET v2.0, v3.0,
v3.5

Solution

I base my solution on the idea of K. Scott
Allen: compile the project with AspNetCompiler and then use ILMerge. In
addition, my tool creates additional DLL containing classes inherited from
mycontrols_goodcontrol_ascx, coolcontrols_thebestcontrol_ascx, etc. Moreover,
these inherited controls have the SAME names as codebehind classes!

Use Activator.CreateInstance() within *.vb files. It is not possible to use Control.LoadControl function. You can also use something like ControlLoader class from the source example to speed-up this operation.

Use (Control)Activator.CreateInstance(Type.GetType("your_control"))

OR Use ControlLoader.LoadControl<your_control>

LibraryB.ControlC uses LibraryB.ControlB
also. Both static and dynamic control creation methods are used again.

Additional problem occurs because VS copies
compiled Dll into intermediate folder (obj/Debug or something like it). Then
“smart” compiler uses this copy if no changes were done in *.vb files. The tool
calculates hash code to prevent double processing of the same file. Also the
tool makes its own backup copy of the initial Dll. This backup is used instead
of copy from obj/Debug to rebuild. (The tool rebuilds library each time, even
there was not changes in .vb files. It is necessary in cases when *.ascx files
were changed only.)

Unmanaged resources processing is necessary
because aspnet_compiler puts large texts from *.ascx files into special
unmanaged resources.

output of aspnet_compiler

"interface" Dll containing classes
inherited from ASP.XX ones

Known problems

You can see "Could not load type [strange type name] from assembly [assembly name]" error in runtime.
It occures if you try to use members of an user control outside of the control. I.e. you can't create public properties/methods within
your user controls directly. It is problem of the IlMerge tool. I can't fix it myself but i will try to notify ILMerge author.

You can use following workaround: Create a base class for your user control and place all public members into the base class.
See LibraryB.ControlC for the example.

It is impossible to create project references to signed libraries. DLL references are available only.
It occures because the tool replaces output library and new library has different public key.

You can see "An assembly with the same identity 'System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
has already been imported. Try removing one of the duplicate references.". This problem was solved in v0.6. Just download it.

Say me in case of any other problems. I will try to help you.

Conclusion

The tool fills a small gap in perfect
ASP.NET platform. I hope, this thing will be useful for your projects.

I have created a Web User Control library using your tool. Here's the issue that I'm having: I have 4 user controls - Head, Header, Footer, and Foot. I use these to build Master Pages in multiple projects which have near-identical HTML. However, here's what I'm getting when I include these user controls in the order Head - Header - Footer - Foot

All of Head
(where Header should be) A portion of Head mashed together with a portion of Header
All of Footer
All of Foot

When I remove Head from the page, I get the following:
All of Header
(where Footer should be) A portion of Head mashed together with a portion of Footer
(where Foot should be) A portion of Foot

All of the UCs except Header are straight HTML and simple .NET controls with no code-behind (Header has two OnClick events). Something strange is going on with the OnRender methods of these UCs but I'm not sure what. Please let me know if you have any insight.

Hi Alexey
I downloaded the source example and I succeed doing the process with your example.
Unfortunately, when it comes to do it from scracth I have a problem.
Here it is : I can't see my aspComponent that I put in aspx files, like if it doesn't initiate correctly, But when I add a control in code behind in the page_load, the control appears.
I really spend all my day to fix this trying to compare my project and yours and I can't reach the solution. I noticed that when I add a control from your project it succeed but not in my project. Can you tell more about the class ControlsLoader and FastActivator, I am sure this is related to my problem.
Thanxs for your reply.

"•You can see "Could not load type [strange type name] from assembly [assembly name]" error in runtime. It occures if you try to use members of an user control outside of the control. I.e. you can't create public properties/methods within your user controls directly. It is problem of the IlMerge tool. I can't fix it myself but i will try to notify ILMerge author."

. Basically it seems to be grabbing the markup from one ascx and inserting it into the middle of another ascx's markup.

It appears that what is happening is related to the large text problem with the aspnet_compiler creating unmanaged resources that your code tries to handle by doing the whole FindResource/UpdateResource.

The following code that is generated by the compiler shows it grabbing the resource using an offset of 0. This is the same in all the ascx's in that library. I am not sure how this would work if the offset is always 0 for all ascxs but the central dll now has a resource made up of multiple ascxs merged together. Wouldn't the offset have to be modified 0, 50, 100 etc? Maybe I do not understand exactly what is going on with your step 5 and 7 (from the list above).

Ok I have this fixed. Not easy to do. Basically I had to modify the final dll by updating the offset for CreateResourceBasedLiteralControl based on how many resources got added and in the correct order. This worked. I will post the code on Monday.

I've tried to do merge unmabnaged resourced with the same way as aspnet_merge utility does it. I didn't notice that aspnet_merge changes the offset, but, seems, it was my error. It would be great to see the correct code.

Been working on this for awhile and this is the best I could come up with as of yet. Originally tried to do this using mono.cecil but they have some issues with generating a valid pdb in 3.5 right now. Basically I stored the offsets as we grab them from the original dlls and modified the offsets in the final dll then resaved. This might be the long term solution but like I said cecil has some issues right now. So here is my 2nd attempt.

As we load resources calculate the offsets. Stored them off into a second byte array list. Pass that into the interfacebuilder and gen up a special class that looks like the code below marked #1 Example. Then have a base class that you inherit all your usercontrols from that call this method inside of AddParsedSubObject if it is either a ResourceBasedLiteralControl or a HtmlGenericControl. Using reflection we call into the method created in example #1 and viola the method updates the ResourceBasedLiteralControls private var _offset with the correct calculated value see #2 Example below. Again this would be simpler if we could modify the dll directly but no luck on that front yet. Example #3 contains the code in interfacebuilder to gen the method in #1. If u want I can give u the actual modified files. Just let me know where to put em. Also if you can think of a better way to do this let me know.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Diagnostics;
using System.Reflection;
using WebLibraryMaker.Properties;

I created a solution with an empty class library project, then added a UserControl with codebehind.
I added the postbuild commandline and everything worked as expected.
I then tried an existing solution which also contains a class library project with some usercontrols, but with a dozen or so references to custom framework dlls.
This time, WebLibraryMaker gave me the following exception:

Hi Zuber. It’s me, Zauberer. I was asking you some time ago for a trouble that I see now is solved with the workaround of a base class for the public properties and methods. Now I am having the same problem that this post refers. I don’t understand what you mean with VB to C # because my project is fully written in C# as the WebLibraryMaker so that where the VB part fits? Thank you in advance.

I've fixed this error in the last release. Just download new version.
This project was written in VB initially. Then I converted it to C#, and the problem occured because of different arrays definition syntax in VB and C#.

You can access anything that is NOT custom with no problems ie Controls.ID. You can even set it but nothing that has been defined in the code behind of the ascx. This happens regardless of if the ascx is loaded dynamically or via markup.

I've found that the problem occures because of a bug of ILMerge tool. I can't fix it myself. But you can use following workaround: Create a base class for your user control and place all public members into the base class. See LibraryB.ControlC for the example.

You can use regular functionality of VS to sign your library (project properties -> signing) in version 0.6. Don't forget to include /key " $(AssemblyOriginatorKeyFile) " parameter in the post-build command. See Signed project of the code example also.