Web Custom Control Behavior and Authoring
Some Best Practices and Guidance for Web Control Vendors Targeting Visual Studio
The goal of this post is to provide guidance for control vendors on best practices for writing custom controls with regard to their behavior in Visual Studio. It is designed to give vendors insight into how Visual Studio behaves when performing common actions related to custom controls, as well as suggestions to optimize the experience for their consumers from within VS—largely concentrating on VS versions 2008 and 2010. This post does not cover runtime or control programming concepts such as usage of any control or design-time specific APIs. For more information on these other concepts, please see the “links and other resources” section at the bottom.
The post is split into a few parts:
- Review of WSP and WAP compilation models
- The toolbox and how VS handles references to your control assemblies
- Basics techniques for installing controls to the toolbox
- Which controls the toolbox shows
- Dragging/dropping of controls and how references get added
- Other tips for installing your controls
- Having them show in the add reference dialog
- Adding them to AssemblyFoldersEx
- Overall Summary of Basic Best Practices
- External links and resources
- This section should cover links to any gaps in this post such as information on designer services
In order to understand some of the intricacies of how references affect the usage of custom controls in the web application project and web site project models, it’s important to outline the differences between the two. Web Sites are folder based projects without project files. References in a web site are entirely determined by the content of the config hierarchy and assemblies in the Bin folder. When building a web site, VS actually runs the asp.net compiler internally in a separate app domain in the VS process and reports errors returned by the compiler. Likewise VS will automatically run the ASP.NET compiler in the background to report errors on any open page. Web sites can therefore use the “CodeFile” attribute in its page directives, which means that code behind files are actually compiled as partial classes with the page, so no explicit references to controls are needed inside of the code behind files.
WAP projects work differently. They behave as class libraries with the web site project system layered on top. References are kept in your reference list stored in your project file and are used by the IDE compilers to compile your code behind pages. Non-GAC’ed such references are copied into your bin folder along with the assembly built by your web application. Dependencies of these assemblies will also be copied to bin assuming the reference system can find them (for more info on how VS finds references, please see Chris Mann’s Blog Post series on this). Those assemblies can subsequently be picked up as bin references by ASP.NET which allows the page to reference the code in them and work at runtime. This reference system also allows inline code intellisense in WAPs, as VS will run the internal ASP.NET compiler (the same one used for web sites) for any open page and create an internal reference to code in your code behind. VS however will not run the internal ASP.NET compiler on a build command nor will it block a build on errors in inline code; only the IDE compilers are run on build, which means while some inline errors might go unnoticed at compile-time, WAPs inherit the scalability benefits of class libraries.
Installing your Controls to the Toolbox
There are a variety of ways to install your controls to the toolbox. Some common ones are:
- Use the ToolboxControlInstaller
- Use the Visual Studio Content Installer
- Have users manually add the controls via the Choose Toolbox Items dialog
For more information on these, please see their respective MSDN articles.
Which Controls the Toolbox Shows:
By default, VS will show whatever controls apply to the active editor and framework version. So if you’re in an ASPX page, you will only see web forms and HTML controls, and in a windows forms application you’ll only see windows forms controls. You can see all controls installed to the toolbox at any time by right clicking and selecting “Show All”. Only controls that apply to the active editor and framework version will be enabled.
Showing Controls Targeting Different Frameworks:
In both VS2008 and VS2010, the toolbox only shows controls that can possibly apply to your project’s target framework. VS walks the dependencies of the control assembly and finds out if it applies to the 2.0, 3.0, 3.5, or 4.0 frameworks and depending on the type of web site you’re in, you’ll only see controls that apply to your framework or lower.
In VS2008, if you ship and install multiple versions of controls that target different framework types, the end developer may therefore see multiple versions of the controls in the toolbox. For instance, if the customer is targeting Fx3.5, and you install Fx2.0 and Fx3.5 versions, they will see both since Fx2.0 controls can be used in an Fx3.5 project. However in VS2010, there is an added feature such that if you have multiple versions of your control assemblies that target different frameworks, and they are named the same, only the highest particular version that applies to that framework will be shown in the toolbox. So if you shipped and installed Fx2.0, Fx3.5, and Fx4.0 versions of your controls, and you were in a 4.0 web, you would only see 4.0 controls. If your assembly names are different for different framework versions, such as MyControls.Fx20.dll, MyControls.Fx35.dll, and MyControls.Fx40.dll, all tabs that could apply to that framework would show because VS will treat them as different sets of controls. So in this example, in a 4.0 web, all controls MyControls.Fx20.dll, MyControls.Fx35.dll and MyControls.Fx40.dll would show; in a 3.5 web all controls in MyControls.Fx20.dll , and MyControls.Fx35.dll would show.
There is one peculiarity in behavior in VS2010 that control vendors who ship multiple versions of their controls may have to work around. If your control assemblies are named the same and targeting different frameworks, when you have your control assemblies added to the Framework’s AssemblyFoldersEx registry locations (see “Adding Your Controls to the .NET Framework AssemblyFoldersEx location” section for details on what this means), the highest version of your controls may be incorrectly enabled for projects targeting lower versions. To work around this behavior, you can add a registry value that tells which target framework each assembly is targeting:
[HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio10.0ToolboxControlsInstaller<strong name of your controls assembly>]
… where 2.0, 3.5, or 4.0 can be substituted for the X’s above
For example, if your controls are installed using the Toolbox Control Installer method, that registry key may look like the following:
[HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio10.0ToolboxControlsInstallerMyCompanysControls.Web.UI, Version=126.96.36.199, Culture=neutral, PublicKeyToken=148af42dc4482dc1]
@=”MyCompanysControls for ASP.NET Targeting NET 20″
“CodeBase”=”C:\Program Files\MyCompanysControls\MyCompanysControls for ASP.NET Targeting NET 20\MyCompanysControls.Web.UI.dll”
Of course in these examples for x64 OSes use the the Wow6432Node between the SOFTWARE and Microsoft nodes.
Dragging a Control – Which References Get Added
When dragging a control from the toolbox, VS makes efforts to make sure the control is immediately usable. This includes adding appropriate references to your project and register directives to your page. There are some important things to consider however with regard to how VS handles this which affects ways you may decide to factor and install your assemblies.
Web Site Projects
In web sites, when dragging a control, Visual Studio adds required references for your controls. If your assemblies are in the GAC, VS will add the references to the assemblies in your web.config. If they are on disk, VS will copy the references to the Bin folder. This includes the assembly from which the control comes, as well as dependent assemblies. The web project system finds and adds these assemblies by searching the directory from which the control assembly was added to the toolbox, as well as locations “known” by the MSBuild reference assembly resolution system. For more information on how these references are resolved, please see Chris Mann’s blog post on Assembly Resolution in MSBuild. Everything should subsequently work at compile-time and runtime.
Web Application Projects
In a WAP project, the behavior is slightly different and more complex. VS will copy the same set of references to your bin folder including first level and dependents, searching the same way as in web sites; if your assembly is GACed, the reference is added to web.config as in web sites. However only first level references are added to your project’s reference list. This means that if your control derives from a type or implements an interface that is defined in the dependent assembly, your web site will not compile since the project system needs knowledge of your dependent assembly. The error the user will get here is very straightforward and should guide him or her on steps to fix the problem:
Error 1 The type ‘ReferencedAssemly.IExternalInterface’ is defined in an assembly that is not referenced. You must add a reference to assembly ‘ReferencedAssemly, Version=188.8.131.52, Culture=neutral, PublicKeyToken=null’. C:ProjectsMyProjectDefault.aspx.designer.cs 32 56 MyProject
If your control does not have such an inheritance model, you will not get a compilation error. You likewise should not get an error at runtime since ASP.NET will look in the bin folder to find any type from the dependent assembly that the parent assembly loads. However, the user may get a compilation error in the process of coding against the parent assembly if they attempt to use a type defined in the dependent assembly returned from somewhere in the parent assembly.
If you have a model such that some of your controls are in one assembly and some are in another, and there is a dependency between the two, the situation gets even trickier. In this case, when you drag and drop a control from one assembly the same behavior as described above will happen; the first level reference is added to the reference list and both assemblies are copied into the bin. However if you subsequently drag and drop a control that comes from the dependent assembly, VS will not add a reference to the assembly. This behavior is due to the design of the web site reference system, as VS already thinks this assembly is referenced, so doesn’t add it to the reference list.
As mentioned above, when you drag and drop a control for the first time, the first level reference is added to the project file. In VS2008, this value receives a “Hint Path” which means that on build, any updated assembly from the hint path location and all its dependents will be copied into the bin folder. In VS2010, the hint path is not generated automatically on first drag-drop. This means that on rebuild (or “Clean” and then “Build”), all dependent assemblies will be deleted and not necessarily re-copied to bin. To work around this behavior, the MSBuild reference assembly resolution system needs to be able to find them. To add these to the searchable path, you can add the path to the assemblies into the .NET Framework’s AssemblyFoldersEx registry location. For more info on how to add these assemblies to AssemblyFoldersEx, please read the section titled “Adding Your Controls to the .NET Framework AssemblyFoldersEx location”.
Showing Your Control Assemblies in the Add Reference Dialog Box
There are multiple ways to add your controls to the Add Reference dialog box. The recommended way is to add your control assemblies to the appropriate AssemblyFoldersEx location under the .NET framework registry hive.
…where <AssemblyLocation> is the install location of your assemblies, such as “C:Program FilesMy Controls”, and <version> is the version of the .NET framework (v2.0.20727, or v3.5, or v4.0.xxxxx), and <version> is either “v2.0.50727”, “v3.5”, or “v4.0.30319”. If the “v3.5” registry key doesn’t exist, you can create it.
Note that you can choose to do this for only the current user by substituting HKEY_LOCAL_MACHINE with HKEY_CURRENT_USER. And of course on an x64 environment, use the Wow6432Node node under SOFTWARE.
It is important to use the appropriate framework version and not the deprecated AssemblyFolders key found in HKLMSOFTWAREMicrosoft.NETFramework. This will ensure that the right version of your control is referenced in the event you have multiple versions of your controls built against different frameworks listed.
- The simplest way to factor your control assemblies to work with VS out of the box is to put everything in one assembly
- In a WAP project, VS will only add a reference to the main assembly and not dependent assemblies
- You can factor your custom control assemblies into separate assemblies, though it does present some complications to be cognizant of:
- It is recommended not to put controls into both primary and dependent assemblies, as dependents may not be added as references if dragged/dropped from the toolbox
- If your control inherits from a class or implements an interface defined in a dependent assembly, in a WAP the user will get a compilation error on the generated field on first drag-drop, as dependent assemblies are not automatically referenced in the project file
- Especially in the case of using dependent assemblies, it is recommended to add your assembly locations to the AssemblyFoldersEx registry node under the .NET framework. This allows the MSBuild assembly resolution system to know about these assemblies and for dependencies to be copied back into your bin folder in the event they get deleted via a clean or rebuild
- When shipping multiple versions of your controls that target different frameworks:
- To ensure the right version of your assemblies are added on drag/drop, make sure to add the paths to your control assemblies to the correct AssemblyFoldersEx registry node under the .NET framework
- To ensure the right versions of your controls are enabled in the toolbox for webs targeting various frameworks, add the “TargetFramework” registry value under the ToolboxControlInstaller key for each assembly
- For general support of multi-targeting in Visual Studio, see Binu’s blog on Managed Multi-Targeting.
- For programming concepts related to multi-targeting, including designer services, enabling for control designers, information on the multi-targeting designer services, see Christy’s blog post on VS2010 designer multi-targeting.
- For assembly resolution in Visual Studio 2010, see Chris Mann’s blog post series on assembly resolution in VS2010
Hope this helps.
John Dundon | Visual Web Developer Team