Straight way to create ASP.NET user controls library
My post-build tool transforming any WebApplication into a library containing .ascx controls.
- Download source code - 310.31 KB
- Download example solution - 333.91 KB
- Download executable only - 293.51 KB
Straight way to create ASP.NET user controls library
My post-build tool transforming any WebApplication into a library containing .ascx controls.
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
- Copy wry DLLs (Asp_web_[control
name].ascx.73dba69a.dll, etc ) + controls’ source into target project.
- Use wry IIS configuration to substitute foreign
virtual directory.
- Create single DLL with ILMerge and use wry
control names like
mycontrols_goodcontrol_ascx
.
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
:
LibraryA
and LibraryB
have 'Always Start When Debugging' = false:
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) "
It is content of solution directory (note, there is WebLibraryMaker directory):
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):
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 <% register
src="~/yourpath/yourcontrol.ascx" tagPrefix="yourprefix"
tagName="yourcontrol"%>
- Use
Activator.CreateInstance()
within *.vb files. It is not possible to useControl.LoadControl
function. You can also use something likeControlLoader
class from the source example to speed-up this operation.- Use
(Control)Activator.CreateInstance(Type.GetType("your_control"))
- OR Use
ControlLoader.LoadControl<your_control>
- Use
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):
(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?
- depending of hash of the Dll: backup unchanged
Dll OR restore it before processing (in order to prevent double processing of
the same file)
- call aspnet_compiler
- gather names of newly created Dlls (Asp_web_[control
name].ascx.73dba69a.dll, etc )
- create "interface" Dll containing
classes inherited from ASP.XX ones. These classes have names similiar to
grandparents' ones.
- load special unmanaged resources from Dlls
created by aspnet_compiler
- merge all these Dlls into one file (interface
Dll + Dlls created by aspnet_compiler + initial Dll) using ILMerge tool
- write special unmanaged resource into resulting
Dll
- overwrite Dll files within output and
intermediate folders
- 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.
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.