If you write Silverlight controls, you should consider writing design time assemblies for your controls too, for two simple reasons:
- developer productivity: try to imagine Silverlight development without tools like Visual Studio or Blend! For custom controls, you may need to provide much of the design time experience for your controls in Visual Studio or Blend yourself.
- designers: XAML and tools like Blend enable developers and designers work together. A key design criteria for Silverlight controls is to make sure designers can use them without writing a single line of code.
Design time experience usually includes (but not limited to) the following:
- Metadata for property window, like category, infotip, custom property/binding/collection editors etc.
- Metadata for design surface, like initializer, adorner, context menu, adapters etc.
- Toolbox integration, like icons, control registration
- intellisense for code editor
Except intellisense (please see Add Rich Intellisense for Your Silverlight Controls) and control registration (please see Register Silverlight Controls with visual Studio and Blend), above design time experience are usually delivered through design time assemblies. Below I will discuss various approaches of delivering design time experiences, in increasing complexity and flexibility, and gradually introduce pieces of the naming convention for design time assemblies.
Runtime Assembly Only
The simplest way to deliver design time experience is to package design time code into the runtime assemblies, especially when design time metadata are meaningful at runtime too, like TypeConverterAttribute.
The pros and cons of this approach:
- Pro: simple
- no separate design time assemblies, simpler setup
- design time attributes are specified directly on the runtime code, easier to maintain
- Con: tight coupling of runtime and design time code
- perf degradation because of useless design time code at runtime
- design time dependencies (like MWDs and other VS/Blend assemblies) get dragged into runtime unnecessarily
- can’t service runtime or design time independently
- can’t support multiple designers (like both VS2008/Blend2 and VS2010/Blend3)
The cons are particularly bad for Silverlight, because design time assemblies are actually .NET assemblies, with references to Silverlight types. Mixing Silverlight and .NET assemblies can be challenging and confusing, and may lead to subtle bugs. Also, Silverlight applications are mostly web applications, so download size and performance are particularly important. Dragging in .NET assemblies in Silverlight applications certainly doesn’t help. So this approach is highly discouraged, unless there is strong justification for it.
Shared Design Time Assembly
So the revolutionary step forward is to decouple design time and runtime code, release and service them with separate assemblies. This opens up all kinds of possibilities. Of course, we need a way to link design time assemblies to their corresponding runtime assemblies, without introducing any unnecessary dependency or perf degradation to runtime assemblies. Hence the naming convention: if a runtime assembly Foo.dll is referenced in a Silverlight project, the designer (like Visual Studio and Blend) will first try to load design time information like icons (via another naming convention, see How to Add an Toolbox Icon for Your Silverlight Control) and design time metadata (via interface, like IRegisterMetadata for VS2008 and Blend2, or IProvideAttributeTable for VS2010 and Blend3. Please see How to Write Silverlight Design Time for All Designers: Visual Studio 2008, Blend 2; Blend 3, and Visual Studio 2010) from the runtime assembly; it will then look for a design time assembly by the name Foo.Design.dll in the same directory as Foo.dll; if found, the designer will try to load design time information from Foo.Design.dll as well.
Designer Specific Design Time Assembly
Visual Studio is mostly for developers, while Blend is mostly for designers, so they have different requirements for design time experiences. Putting all design time code in one shared design time assembly introduces tight coupling among designers. So the naming convention is enhanced: for runtime assembly Foo.dll, there is a shared design time assembly Foo.Design.dll that’s loaded by all designers; each designer will also try to load its own design time assemblies, like Foo.VisualStudio.Design.dll for Visual Studio, and Foo.Expression.Design.dll for Blend. The designer specific design time assembly is loaded after the shared design time assembly. Third party designers can define their own designer specific design time assembly. Silverlight Toolkit December 2008 Release is using this naming convention. Please see Design Time Features in Silverlight Toolkit and Design Time Feature Implementation in Silverlight Toolkit for more information.
Design Sub Folder
So to support both Visual Studio and Blend, each runtime assembly has three design time assemblies, in the same directory, and a package (like Silverlight SDK or Silverlight Toolkit) usually contains several runtime assemblies. So the directory gets a bit crowded. A minor improvement is to put the design time assemblies under a Design subfolder. So the naming convention is further enhanced: if a designer (like Visual Studio or Blend) can’t find corresponding design time assemblies in the same directory as a runtime assembly, it will look for them in the Design subfolder, if it exists.
Support Multiple Versions of MWDs
Design time are built on top of designer extensibility framework, which consists of several dlls like Microsoft.Windows.Design.Extensibility.dll and Microsoft.Windows.Design.Interaction.dll. We usually call them collectively as MWDs. To make life more interesting, sometimes we have to introduce breaking changes to the extensibility framework, like VS2008 and Blend2 use MWD version 3.5, VS2010 and Blend3 use MWD version 4.0, and they are incompatible. So if you have a runtime assembly Foo.dll, and you want your users to be able to develop against it with both VS2008 and VS2010, you have to provide two sets of design time assemblies: one set built against v3.5 MWDs and used by VS2008, and another set built against v4.0 MWD and used by VS2010. The two sets of design time assemblies probably have a lot of code in common, while the one for VS2010 may have some new code to leverage the new functionalities exposed by VS2010. Since design time assemblies are loaded by name and you can’t have two files with the same name, so the naming convention is enhanced yet again:
for a runtime assembly Foo.dll, the shared design time assembly is named Foo.Design*.dll, the Visual Studio specific design time assembly is named Foo.VisualStudio.Design*.dll, and the Blend specific design time assembly is named Foo.Expression.Design*.dll, where * can be zero or more valid characters for file names. When a designer (like Visual Studio or Blend) tries to load a design time assembly and several fit the naming convention, zero or one will be loaded:
- If the MWD version referenced by the design-time assembly has a different major version number than the designer’s MWD version, then the design-time assembly will not load and is bypassed.
- If more than one design-time assembly is compatible with the designer’s MWD version, the Designer loads the one compiled against the highest MWD version that is less than or equal to the designer’s MWD version.
Silverlight 3 Toolkit October 2009 Release uses this naming convention to support both VS2008 and Blend3/VS2010. Please see Silverlight Design Time: Toolkit October 2009 Release Update, How to Write Silverlight Design Time for All Designers: Visual Studio 2008, Blend 2; Blend 3, and Visual Studio 2010, and Extensibility Series – WPF & Silverlight Design-Time Code Sharing – Part I more information.
Support Both WPF and Silverlight
To complicate life further, since WPF and Silverlight are so awfully similar, you may be tempted to try to write it once and run with both WPF and Silverlight. You are not alone. There are many articles on how to share source code and/or binaries across Silverlight and WPF. We’d like to do that for design time assemblies too, i.e. have the same design time assemblies for both WPF and Silverlight controls. One approach is to make most of the design time assemblies platform agnostic, and limit platform specific (WPF or Silverlight) code and references to a small platform specific assembly. Silverlight 3 SDK GDR 2 (installed by VS2010 automatically too) uses this approach for DataGrid designer (there is a System.Windows.Controls.Data.VisualStudio.Design.4.0.Silverlight.dll in the SDK directory). Please see Extensibility Series – WPF & Silverlight Design-Time Code Sharing – Part I for more information.
Last One Wins
The same design time metadata for a class or its member can be specified multiple times in multiple design time assemblies, which allows the shared design time assembly to specify default/common behavior and then designer specific design time assemblies to override it if necessary. The same design time metadata can also be specified multiple times within a single design time assembly (please see Design Time Feature Implementation in Silverlight Toolkit as an example, where DescriptionAttribute for a class or its property can be added by AddDescriptions, AddAttributes and AddTables methods). So we need to know which metadata wins. The simplest and most logic design is last one wins. This is mostly true but not always. Sometimes the result can be un-deterministic when the same design time metadata is specified several times but with different values.
Design time sounds easy, but devil is in the details! Feedbacks are always appreciated. Please let me know what issues you run into, what requests/improvements you’d like to make, for both design times experience and designer extensibility framework. Thanks!