Click here to Skip to main content
16,016,669 members
Articles / Web Development / ASP.NET

Straight way to create ASP.NET user controls library

Rate me:
Please Sign up or sign in to vote.
4.71/5 (5 votes)
20 Mar 2009Ms-PL7 min read 125.4K   3.5K   45   34
My post-build tool transforming any WebApplication into a library containing .ascx controls.

Straight way to create ASP.NET user controls library

My post-build tool transforming any WebApplication into a library containing .ascx controls.

Home page of the tool

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.

I researched many articles offering several workarounds like

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!

In action

How to create new library

  • Extract WebLibraryMaker to somewhere on your PC

  • Create new WebApplication project

  • Select project root, press F4 and select Always Start When Debugging = false

  • Edit post-build step on property pages: Input something like "$(MSBuildProjectDirectory)\..\WebLibraryMaker\WebLibraryMaker.exe" /net " $(Framework20Dir) " /name " $(MSBuildProjectName) " /prj " $(MSBuildProjectDirectory) " /obj " $(IntermediateOutputPath) " /out " $(OutDir) " /debug $(DebugSymbols) /key " $(AssemblyOriginatorKeyFile) " where $(MSBuildProjectDirectory)\..\WebLibraryMaker\WebLibraryMaker.exe is path to your WebLibraryMaker directory

  • Make sure you have directory mentioned in WebLibraryMaker.exe.config -> TemporaryPath setting (C:\temp\ASP.NET.Tmp by default)

  • Build the project

I’ve created two libraries in my example: LibraryA and LibraryB:

Image 1

LibraryA and LibraryB have 'Always Start When Debugging' = false:

Image 2

ControlA and ControlB are very similar and simple. Each of them contains one textbox:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ControlA.ascx.cs" Inherits="LibraryA.ControlA" %>

<asp:TextBox ID="TextBox1" runat="server">ControlA</asp:TextBox>

 

ControlC is more complex. I'll describe it later.

I also added post-build step for LibraryA and LibraryB:

"$(MSBuildProjectDirectory)\..\WebLibraryMaker\WebLibraryMaker.exe" /net " $(Framework20Dir) " /name " $(MSBuildProjectName) " /prj " $(MSBuildProjectDirectory) " /obj " $(IntermediateOutputPath) " /out " $(OutDir) " /debug $(DebugSymbols) /key " $(AssemblyOriginatorKeyFile) "

Image 3

It is content of solution directory (note, there is WebLibraryMaker directory):

Image 4

I also have 'C:\temp\ASP.NET.Tmp' directory for temporary files.

Now you can press Build and viola! Two libraries are ready.

How to use the library

  • Link the library to your target project

  • Add reference to the library project

  • OR add reference to the library dll

  • Use library controls like Custom Controls (control type names are equal to codebehind type names):

  • Use <% register assembly="yourassembly" namespace="yournamespace" tagPrefix="your_prefix" %> within .ascx/.aspx files

  • Use new yourassembly.yourcontrol() within *.vb files.

In my example LibraryB uses LibraryA. MainApplication uses LibraryB. All references are created as project links.

LibraryB.ControlC uses LibraryA.ControlA. Both static and dynamic control creation methods are used.

Static control creation (ControlC.ascx):

<%@ Register Assembly="LibraryA" Namespace="LibraryA" TagPrefix="LibraryA" %>

...

<LibraryA:ControlA runat="server" ID="ControlA"/>

Dynamic control creation (ControlC.ascx.vb):

C#
new LibraryA.ControlA();

Default.aspx page of MainApplication uses ControlC:

<%@ Register Assembly="LibraryB" Namespace="LibraryB" TagPrefix="LibraryB" %>

...

<LibraryB:ControlC runat="server" ID="ControlC" />

How to reuse controls within the same library

  • Use library controls like User Controls within .ascx/.aspx files

    • Use <% register src="~/yourpath/yourcontrol.ascx" tagPrefix="yourprefix" tagName="yourcontrol"%>

  • 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.

Static control creation (ControlC.ascx):

<%@ Register Src="~/ControlB.ascx" TagName="ControlB" TagPrefix="LibraryB" %>

...

<LibraryB:ControlB ID="ControlB" runat="server" />

Dynamic control creation (ControlC.ascx.vb):

C#
(ControlB)Activator.CreateInstance(Type.GetType("LibraryB.ControlB"))

Signing (v0.6 only)

New version of the tool allowes to create signing assemblies. It supports two additional parameters: /key and /ds. There are two signing modes: regular and delayed.

  • Regular mode. Just create signed assembly with standard VS functionality. Make sure that /key " $(AssemblyOriginatorKeyFile) " agrument is used

  • Delayed signing mode. Don't create signed assembly with standard VS functionality. Only create public key. Change /key argument to use real key filename like /key " publickey.snk ". Also specify /ds true. Resulting command example: "$(MSBuildProjectDirectory)\..\WebLibraryMaker\WebLibraryMaker.exe" /net " $(Framework20Dir) " /name " $(MSBuildProjectName) " /prj " $(MSBuildProjectDirectory) " /obj " $(IntermediateOutputPath) " /out " $(OutDir) " /debug $(DebugSymbols) /key " publickey.snk " /ds true

How does it work?

  1. depending of hash of the Dll: backup unchanged Dll OR restore it before processing (in order to prevent double processing of the same file)

  2. call aspnet_compiler

  3. gather names of newly created Dlls (Asp_web_[control name].ascx.73dba69a.dll, etc )

  4. create "interface" Dll containing classes inherited from ASP.XX ones. These classes have names similiar to grandparents' ones.

  5. load special unmanaged resources from Dlls created by aspnet_compiler

  6. merge all these Dlls into one file (interface Dll + Dlls created by aspnet_compiler + initial Dll) using ILMerge tool

  7. write special unmanaged resource into resulting Dll

  8. overwrite Dll files within output and intermediate folders

  9. calculate hash code of the Dll

I.e. the tool merges following DLLs:

  • initial Dll containing codebehind classes

  • output of aspnet_compiler

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

ILMerge renames codebehind classes with random names and referencing assemblies start use 'interface' classes instead of codebehind ones.

ILMerge also copies managed resources from initial Dll into output Dll

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.

  • License

    This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


    Written By
    Software Developer (Senior) HRsoft
    Russian Federation Russian Federation
    This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

    Comments and Discussions

     
    GeneralRe: Strong Name required Pin
    Alexey Z121-Jan-09 9:11
    Alexey Z121-Jan-09 9:11 
    GeneralRe: Strong Name required Pin
    Alexey Z116-Feb-09 8:41
    Alexey Z116-Feb-09 8:41 
    QuestionRe: Strong Name required Pin
    Zauberer20-Mar-09 14:15
    Zauberer20-Mar-09 14:15 
    AnswerRe: Strong Name required Pin
    Alexey Z122-Mar-09 20:26
    Alexey Z122-Mar-09 20:26 
    GeneralSpecific Error Details Pin
    Tim Elvidge21-Dec-08 6:54
    Tim Elvidge21-Dec-08 6:54 
    GeneralRe: Specific Error Details Pin
    vlater13-Feb-09 2:34
    vlater13-Feb-09 2:34 
    GeneralRe: Specific Error Details Pin
    Alexey Z116-Feb-09 8:43
    Alexey Z116-Feb-09 8:43 
    GeneralRe: Specific Error Details Pin
    vlater17-Feb-09 0:53
    vlater17-Feb-09 0:53 
    Thanks for the fast response. I checked the new version and it is compiling. But there is a strange problem with namespaces. If the control has a public property and it is set up from the page in the host website, an error occures - "Could not load type <controltype>". The same case appears when the control is using gridview with Linqdatasource and there is "linq to sql" - dbml file in controllibrary's app_code folder.

    Best regards,
    Night_dog
    GeneralRe: Specific Error Details Pin
    Alexey Z117-Feb-09 10:31
    Alexey Z117-Feb-09 10:31 
    GeneralError in build Pin
    Tim Elvidge20-Dec-08 1:57
    Tim Elvidge20-Dec-08 1:57 
    GeneralRe: Error in build Pin
    Alexey Z121-Jan-09 9:09
    Alexey Z121-Jan-09 9:09 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.