August 2009

Volume 24 Number 08

Inside Microsoft patterns & practices - Building WPF and Silverlight Applications with a Single Code Base Using Prism

By Erwin van der Valk | August 2009

Contents

Introduction
Creating a Silverlight Version of Prism
Creating Project Linker
Test-Driven Development on Silverlight
Architecting Your Application for Multi-Targeting
Identifying What Can Easily be Multi-Targeted
Separated Presentation
Building Platform-Specific Services
Avoid Inconsistencies and Try to Keep a Single Code Base
Accommodate for Different Platform Capabilities
Conditional Compilation
Partial Classes
Caveats
Summary

Composite Application Guidance for WPF and Silverlight, also affectionately known as Prism v2, has been out for several months now. One of the areas Prism provides guidance on is the ability to target your application to both Windows Presentation Foundation (WPF) and Silverlight. It's interesting that, initially, this part of our guidance met quite a bit of resistance. Why were we focusing on multi-targeting for the first couple of iterations when we could be spending our time giving guidance on composition? But since the release of the Prism v2 project, we've found that a lot of our customers really love this part of the guidance. They especially like the Project Linker tool we built to help multi-targeting; as basic as it is, the tool has been well received and is being used in ways we couldn't have imagined.

So let's look at the approach we took for writing multi-targeted applications and how Prism can help you do the same.

Introduction

When we started work on Prism v2, in August 2008, we knew we wanted to support both WPF and Silverlight. Silverlight makes big strides toward closing the gap between the power of a rich-client application and the reach and ease of deployment of a Web application. The first version of Prism targets only WPF, but since WPF and Silverlight are very similar, we knew it wouldn't be too hard to create a Silverlight version. And since Prism helps in writing loosely coupled, modular applications, we felt that Prism could also help in allowing you to write applications that target both WPF and Silverlight from a single code base. The challenge is, of course, that although WPF and Silverlight are similar, they are not binary-compatible. The API and XAML language itself also has minor differences that make it harder to multi-target.

What Is Multi-Targeting and Why Should You Care?

Multi-targeting is the ability to target multiple platforms from a single code base. In this case, we are talking about targeting both the normal desktop version of the Microsoft .NET Framework version 3.5, which contains WPF and the Silverlight version of the .NET Framework.

So why should you care about this capability? The most obvious reason would be that you would like to take advantage of the strengths of WPF and Silverlight. In WPF, you can build applications that make full use of the client platform and interact with existing applications, such as Microsoft Office. It's also a lot easier to reuse existing assets, such as through COM interop or Windows Forms interop.

Whereas WPF gives you more capabilities, Silverlight gives you a much broader reach, because it runs on multiple platforms. It also runs in a protected sandbox, so end users can safely install Silverlight without needing administrative privileges. You don't need to completely multi-target your application to make it worthwhile. For example, you might expose small parts of internal applications over the Internet to give customers the ability to view and change some of their own information.

Creating a Silverlight Version of Prism

Even though we felt that multi-targeting would be a valuable capability for our customers, we also had a somewhat selfish reason for creating it. We wanted to build a Silverlight version of Prism, but we didn't want the overhead of maintaining two code bases. Because we didn't know how much code we would be able to reuse, we did a couple of spikes. A spike (in "agile" terminology) is a time-boxed exploration that allows you to learn more about a problem domain so that you can make a more valid estimation. So we did a couple of spikes to see how much of the Prism v1 code base we could migrate to Silverlight and how hard it would be to port our code to Silverlight. The conclusion was fascinating. We estimated that we could reuse around 80 percent of the code in the Prism library without modifying it.

Ideally, we would have liked to be able to create a single project and compile it to both WPF and Silverlight. The problem with that approach is that the project system in Visual Studio assumes that a project has one set of references, one compiler, and one type of output. We tried several ways of getting a single project to emit both Silverlight and WPF outputs, but none of them worked to our satisfaction.

One approach that worked really well was to create two projects and link files from one project to another. The nice thing about this approach is that you have fine-grained control over what each output should look like. For example, you can control which files should be shared or which assemblies should be referenced for each project. And any change to a linked file is immediately reflected in both projects.

Creating Project Linker

After our spiking phase, we agreed that the approach of linking files was working, although it was quite tedious and error-prone. We then had a lot of discussions: Should we invest a considerable amount of time in building a tool to help with multi-targeting, rather than spend that time creating guidance? It might not be obvious, but building and shipping a tool that integrates into Visual Studio is quite costly for us. Integrating with Visual Studio takes a bit of time in and of itself. Add to that the time it takes to build an installer that is signed according to the stringent Microsoft internal signing guidelines and to test the tool on many different environments. All of these factors reduce the amount of time we can spend on creating actual guidance. But because we felt quite strongly that this tool would help us in our efforts and be very helpful to our customers, we decided to go ahead and build it.

For the first couple of two-week iterations, we worked almost exclusively on Project Linker. As mentioned before, we received quite a bit of feedback from the community. Why were we spending time on multi-targeting? Wasn't Prism a project to help build composite applications? But while we were building Project Linker, we were also using it to create a Silverlight version of Prism. And because we were using Project Linker (internally, this is called dog-fooding, or eating your own dog food), we were confident our tool would be very usable.

We intentionally kept Project Linker very simple. It isn't magical in what it does, and anything you do using Project Linker can also easily be done manually. As the name implies, you can link two projects together as shown in Figure 1. Any file you add to the first project will be added as a linked file to the second project. Changes such as moving, renaming, or deleting would also be reflected.

fig01.gif

Figure 1 Using the Project Linker to Link Files Between Projects

Because you might want to have control over which files to link, we implemented a simple naming convention. By default, all files except for those with a .xaml file name extension are linked. However, if you want to create files that are specific for a single platform, you can append the suffix .Desktop or .Silverlight to it. If you want, you can change this naming convention. In the project file, you'll find a regular expression that determines whether a file needs to be linked. The following is an example:

ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?Silverlight (\\.*)?$;\.desktop;\.Silverlight;\.xaml;^service references(\\.*)?$;\. clientconfig;^web references(\\.*)?$"

Project Linker proved to be invaluable in creating Prism v2. After getting used to the minor quirks of this approach, which we'll go into later, it was such a pleasant experience to be able to write code once and have it compile to both a Silverlight and a WPF version. After awhile, project members had lists of extra features they would have liked Project Linker to have, but even without those features, the tool was very usable. The simplicity of the tool made it a lot more versatile than we had imagined. After it had been on CodePlex for a while, we heard from people that they weren't using it just to create Silverlight and WPF applications. Some people were also using it to link different Visual C++ projects together. Other people used it to have the same set of business validation rules running in a Silverlight application and in the Web services that the Silverlight application uses.

Test-Driven Development on Silverlight

Another area in which I've personally found multi-targeting to be handy is for doing test-driven development in Silverlight. I'm a big fan of test-driven development. Although there is an excellent unit test framework available for Silverlight, it has some shortcomings. It's not integrated with Visual Studio, and you can't select which test to run. It will run all the tests in your test assembly.

So now, even if I'm writing an application that will run only on Silverlight, I'll still create a linked WPF version of that project, just for writing and running the unit tests. I love having the ability to write a test and to then run it directly from Visual Studio. Within seconds, I'll have the result of my test, so the "code -> test -> fix -> test" cycles are very short. This really boosts my productivity. Just don't forget to run your Silverlight unit tests before you check in your code, because occasionally, a test will pass on one platform and fail on the other.

Architecting Your Application for Multi-Targeting

While building the Silverlight version of the Prism library, the Stock Trader Reference Implementation, and QuickStarts, we learned some valuable best practices regarding multi-targeting. The most important best practice is that a loosely coupled and modular architecture can really help your multi-targeting efforts. Loosely coupled architectures allow you to pick and choose which pieces of your application you would like to multi-target.

Identifying What Can Easily be Multi-Targeted

Before you can architect your solution to support multi-targeting, it's important to know what can and cannot easily be multi-targeted. In general, you can say that most business-logic-related code can very easily be multi-targeted. For example:

  • Presentation logic.This is the logic that responds to user actions and controls which data to pass to the visual elements.
  • Business logic and business rules.The business logic drives the business processes, and the business rules can perform validation on the business entities.
  • Business entities.These are classes that represent the data of your application.

Code that is more infrastructure-related is usually very hard to multi-target. The following are examples:

  • Visual elements (views).The way that you specify visual elements, such as controls, differs enough between WPF and Silverlight to make them hard to multi-target. Not only are different controls available for each platform, but the XAML that's used to specify the layout also has different capabilities. Although it's not impossible to multi-target very simple views or some simple styling, you'll quickly run into limitations.
  • Configuration settings.Silverlight does not include the System.Configuration namespace and has no support for configuration files. If you want to make your Silverlight application configurable, you'll need to build a custom solution.
  • Data access.The only way a Silverlight application can have access through data is through Web services. Unlike WPF, a Silverlight application cannot directly access databases.
  • Interop (with other applications, COM, or Windows Forms).A WPF application in a full-trust environment can interact with other applications on your computer or use existing assets such as COM or Windows Forms objects. This is not possible in Silverlight, because it runs in a protected sandbox.
  • Logging and tracing.Because of the protected sandbox, a Silverlight application cannot write log information to the EventLog or trace information to a file (other than in isolated storage).

In order to design an application that allows you to easily reuse your business logic, you should try to separate things that are easy to multi-target from things that are hard to multi-target. Interestingly enough, this is exactly the architecture of a typical Prism application. Figure 2shows the typical architecture of a Prism application.

fig02.gif

Figure 2 Typical Prism Application Architecture

In this diagram, the views are classes that perform the visualization aspect of your application. Typically, these are controls and pages, and in the case of WPF or Silverlight applications, they often define the layout in XAML. The logic of your application is factored out into separate classes. I'll dive a bit more into the design patterns behind this when I talk about separated presentation patterns.

The application services in this diagram can provide a wide variety of functionality. For example, a Logger or a Data Access component can be considered an application service. Prism also offers a couple of these services, such as the RegionManager or the XapModuleTypeLoader. I'll discuss these services more when I talk about building platform-specific services.

Separated Presentation

As part of the guidance that we provide with Prism, we recommend that you separate the visualization aspect of your application from the presentation logic. A lot of design patterns, such as Model-View-ViewModel or Model-View-Presenter, can help you with this. What most of these patterns have in common is that they describe how to split up your user-interface-related code (and markup) into separate classes, each with distinct responsibilities. Figure 3shows an example of the Model-View-ViewModel pattern.

fig03.gif

Figure 3 Example of Model-View-ViewModel Pattern

The Model class has the code to contain and access data. The View is usually a control that has code (preferably in the form of XAML markup) that visualizes some of the data in your model and ViewModel. And then there is a class named either ViewModel, PresentationModel, or Presenter that will hold as much of the UI logic as possible. Typically, a separated presentation pattern is implemented to make as much of your UI-related code unit testable as possible. Because the code in your views is notoriously hard to unit test, these separated presentation patterns help you place as much of the code as possible in a testable ViewModel class. Ideally, you would not have any code in your views, just some XAML markup that defines the visual aspects of your application and some binding expressions to display data from your ViewModel and Model.

When it comes to multi-targeting, a separated presentation pattern has another significant advantage. It allows you to reuse all of your UI logic, because you have factored out that logic into separate classes. Although it's not impossible to multi-target some of the code in your views (the XAML, controls, and code-behind), we've found that the differences between WPF and Silverlight are big enough that multi-targeting your XAML is not practical. XAML has different abilities, and the controls that are available for WPF and Silverlight are not the same. This not only affects the XAML, but it also affects the code-behind.

Although it's not likely that you are able to reuse all of your UI-related code, a separated presentation pattern helps you reuse as much of the presentation logic as possible.

Building Platform-Specific Services

While building the Prism libraries and the Stock Trader Reference Implementation, we strictly followed the single-responsibility principle. This principle describes that each class should have only one reason to change. If a class addresses multiple concerns or has more than one responsibility, it has multiple reasons to change. For example, a class that can load a report from a database and print that report can change if the database changes or if the layout of the report changes. An interesting indication if your class does too much: if you find that you have difficulty determining a name for your class that describes its responsibility, it has too many responsibilities.

If you follow the single-responsibility principle, you'll often end up with a lot of smaller classes, each with its own discrete responsibility and a descriptive name. We often consider many of these classes to be application services, because they provide a service to your application.

This single-responsibility principle really helps when it comes to multi-targeting. Take, for example, the module loading process in Prism. A lot of aspects of this process are similar for both WPF and Silverlight. Some similarities include how the ModuleCatalog keeps track of which modules are present in the system and how the ModuleInitializer creates the module instances and calls the IModule.Initialize() method on them. But then again, how we are loading the assembly files that contain the modules differs quite a bit between WPF and Silverlight. Figure 4illustrates this.

fig04.gif

Figure 4 Module Loading in Prism

It's perfectly reasonable for a WPF application to load its modules from disk. So this is what the FileModuleTypeLoader does. However, this doesn't make sense for a Silverlight application, because its protected sandbox doesn't give access to the file system. But for Silverlight, you'll need a XapModuleTypeLoader to load modules from a .xap file.

Because we created smaller classes, each with a distinct responsibility, it was a lot easier to reuse most of these classes and create only platform-specific services to encapsulate the behavior that differs between the platforms.

Avoid Inconsistencies and Try to Keep a Single Code Base

Even though most functionality in Prism was easily ported to Silverlight, we inevitably ran into situations where we would rely on a feature in WPF that didn't exist in Silverlight. Dependency property inheritance was one of them. In WPF, you could set a dependency property on a control and it would automatically be inherited by any of its children. We were using this capability to associate a region with a region manager. Unfortunately, automatic property inheritance is not available in Silverlight.

For Silverlight, we had to create a solution that delayed the creation of regions until the region manager could be located through some other mechanism. With a couple of tweaks, we could reuse this code for WPF. We could have kept the original, much simpler solution for WPF and used only the new solution for Silverlight, but then we would have had to maintain two code bases and offer a different public API.

When trying to build a functionality for use in both WPF and Silverlight, you'll inevitably run into situations where one of the platforms doesn't support a feature that you want to use. Your best defense against these situations is to try to work around these "incompatibilities" and create a solution that works in both environments. Maintaining a single code base is a lot easier than maintaining two code bases!

Accommodate for Different Platform Capabilities

There are cases where it doesn't make sense or isn't possible to work around platform differences, such as when there is no common solution that would work in both WPF and Silverlight. When this happens, there are a couple of strategies to consider. For anything but small and isolated platform differences, I would recommend building platform-specific services. But for small platform differences, you could consider either conditional compilation or partial classes.

Conditional Compilation

The simplest thing to do to accommodate for different platform capabilities is to use conditional compilation. By using the #if SILVERLIGHT pre-compiler statement, shown in Figure 5, you can create sections of code that are compiled only into Silverlight or only into WPF. This seems very handy, but a method or class can quickly become unreadable with this approach.

Figure 5 Conditional Compilation

#if SILVERLIGHT Application.Current.RootVisual = shell; #else shell.Show(); #endif

We've found that you should use the #if pre-compiler directive only to sporadically change a single line of code. Because readability really suffers, I don't recommend using this technique for anything but very simple cases like this.

Partial Classes

Another technique you could employ is the usage of partial classes. Using this technique, you can create classes that are mostly shared, but differ in one or two small methods. This technique can be very useful for adjusting small implementation changes between WPF and Silverlight.

I find partial classes to be particularly useful in the area of exceptions. In the .NET Framework, it's often recommended to make your exceptions serializable. However, Silverlight doesn't support the [Serializable] attribute. Using partial classes, you can reuse most of the exception code, but you can apply the [Serializable] attribute only on the .NET Framework variant of the code as shown in Figure 6.

Figure 6 Partial Classes for Exceptions

// MyException.cs Public partial class MyException : Exception { . . . } // Desktop only additions to this class. // MyException.Desktop.cs [Serializable] Public partial class MyException : Exception { protected MyException (SerializationInfo info, StreamingContext context) : base(info, context) { } }

Unfortunately, partial classes do suffer from discoverability and readability issues. It's not immediately clear in which file your functionality resides. A class should have a single responsibility and be named to reflect that single responsibility. If you find that a class does something in one way in Silverlight and another in WPF, it's not following the single-responsibility pattern. Extracting the platform-specific code into services with a descriptive name is usually a better solution.

Because Visual Studio was never designed to accommodate multi-targeting, you will likely run into some quirks that are caused by the approach. None of them are very serious, but it helps to know what they are.

Visual Studio knows that a linked file can be placed in different types of projects. So, depending on whether you open the linked file from WPF or Silverlight, it will adapt the IntelliSense accordingly. It's often possible that you use a language construct that is available in only one of the platforms. You will encounter build errors when compiling your solution because your code is invalid for one of the platforms. Depending on whether you opened the file from WPF or Silverlight will determine whether you will see the red squiggly. But once you grow accustomed to the fact that a change in one project can break its linked project, you'll quickly know where to look.

Project references are also interesting. Project Linker doesn't automatically add references. We looked into this while building the tool, but there were too many edge cases to make this reliable. For example, the binaries for Silverlight have different names than the binaries for WPF.

Lastly, you can link almost any type of file, such as resource files. However, you should make sure that the properties, such as the compilation options, are the same between both projects.

Many of our customers absolutely love Prism. Even if your application doesn't need the composite capabilities that Prism offers, it can still benefit tremendously from the flexible architecture that Prism promotes. The ability to easily multi-target your application is just one of those benefits—and it's a really nice one! You can learn more about Prism online.

Erwin Van der Valk is a developer on Microsoft ’s patterns and practices team. He is also a scuba diver, a kung fu master, and a metal guitarist. Give him some code—he’ll dive right in, kicking and screaming, and will make a lot of noise while doing it! Check out the noise he makes at erwinvandervalk.net.