Multi-Targeting

This topic helps you understand how multi-targeting works and how you should create a multi-targeting solution. It also includes some considerations you should think about when developing multi-targeting applications for Windows Presentation Foundation (WPF) and Silverlight.

A Solution to Multi-Targeting: Multiple Linked Projects

Silverlight and WPF are not binary compatible, so code and components have to be recompiled for each target environment. The approach the Composite Application Guidance for WPF and Silverlight is taking is to provide guidance on structuring application and module code into multiple linked projects. Each project manages all the references, resources, and code specific to the WPF or Silverlight target environment. Code that is shared is linked between two projects so that it is automatically compiled into each target. Unit tests can similarly be shared between the two environments, so that they can be written once and run against the target code in either of the two target environments. The Composite Application Guidance for WPF and Silverlight includes tooling to help create and maintain these links.

Non-UI code and components are probably going to be the easiest to share, so adhering to separated UI patterns is essential in making sure that the UI and non-UI pieces of the application or modules are cleanly separated.

Core Application

The overall pattern is based on defining the core application in shared code and then augmenting that with extensions that implement WPF (desktop) or Silverlight (browser) specific functionality. The core application defines the overall structure of the application and contains the application code and components that are common to the two environments. Silverlight is largely a subset of WPF, so developing the core application in Silverlight reduces the risk of relying on an API or feature that is available in WPF but not in Silverlight.

Figure 1 shows the Solution Explorer for the Multi-Targeted QuickStart. Most files in the WPF version of the QuickStart are linked files because the core application was developed in Silverlight. The shared files found in the QuickStart are images, models, services, interfaces, and resources, specifically localization strings. The shared (linked) files are highlighted.

Ff921092.b62a803c-3d38-40ad-85f2-9103a1365a5b(en-us,PandP.20).png

Figure 1

Shared files in the Multi-Targeting QuickStart

Multi-Targeting Guidance

This section describes three areas to consider when developing a multi-targeted application:

  • Design and Code Guidelines. This section describes considerations for sharing code between Silverlight and WPF.
  • Process Guidelines. This section describes approaches for sharing code between Silverlight and WPF.
  • Team Build Guidelines. This section describes specific Microsoft Team Build issues when building multi-targeted applications.

Design and Code Guidelines

Design and code guidelines include the following:

  • Use Separated Presentation patterns to maximize the amount of shared code.
  • Where possible, write code so it compiles on both platforms. When this is not possible, do the following:
    • Use #if statements if you have simple or single line constructs.
    • Use partial classes when most of the class is similar but some methods have platform-specific implementations.
    • Use partial methods only if you need to call an extra method on one platform but not the other.
    • Build platform-specific classes with a single responsibility.
  • Create a solution folder for Silverlight and another for WPF.
  • Check Silverlight and WPF references when refactoring code.

The next sections describe each of these guidelines in more detail.

Use Separated Presentation Patterns to Maximize the Amount of Shared Code

Sharing view-code across platforms can be difficult. Sharing presentation and business logic is easier if you separate this logic from the user interface logic. Additionally, this makes your code easier to understand and maintain. For more information, see the Separated Presentation pattern.

Write Code So It Compiles on Both Platforms

Where possible, write your application code so that it compiles on both platforms to enable reuse. When this approach becomes too complicated, you have to make the tradeoff to see if the cost of having two codebases is less than having a less elegant solution. For example, in Silverlight you can execute the following LINQ against the Item property of an ItemsControl.

ItemsControl someItemsControl = new ItemsControl();
someItemsControl.Items.Add(new TextBox());
bool hasDependencyObjects = someItemsControl.Items.Any(item => item is DependencyObject);

However, because the Items property is not IEnumerable<T> in WPF, this preferred approach does not work. Instead of creating a different version for WPF and Silverlight, opt for a less-preferred but single source solution, as shown in the following code.

ItemsControl someItemsControl = new ItemsControl();
someItemsControl.Items.Add(new TextBox());
bool hasDependencyObjects = false;
foreach (var item in someItemsControl.Items)
{
    if (item is DependencyObject)
    {
        hasDependencyObjects = true;
        break;
    }
}

The easiest approach is to begin writing your code in Silverlight, because it is a more constrained version of the .NET Framework.

Use #if Statements If You Have Simple or Single Line Constructs

Sometimes it is not possible to create a single code base because of incompatibility between WPF and Silverlight. In this case, you can use #if SILVERLIGHT constructs to create conditional compiled sections.

The following code example shows a #if SILVERLIGHT statement.

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

However, #if statements have several drawbacks:

  • The code is less readable and maintainable. If code is scattered with #if constructs, it becomes hard to read and a lot harder to maintain.
  • Debugging becomes harder. If there is a compiler error within a construct, when you try to open the file, Visual Studio selects the solution that has the physical file instead of the solution that has the error. This means you either have to manually close the file and open the correct solution or edit the code without IntelliSense.

Use Partial Classes When Most of the Class Is Similar but Some Methods Have Platform-Specific Implementations

When the changes between Silverlight and WPF become more complex, you can create partial classes. Mark the shared class files as partial and then create a partial class for the specific platforms. This also applies to unit tests. Below are some additional recommendations:

  • Try to keep the platform-specific methods private. This way, the unit tests will not need to contain specific logic for specific platforms.
  • Make sure your class has a single, clear responsibility. Any partial methods for platform-specific code should only change the implementation details.

Note

If the differences between the two platforms become very extensive, and the classes for both platforms become very different, consider creating platform-specific classes instead of partial class.

The following code example shows a shared partial class, named PropertyService, that is shared between the Silverlight and WPF projects in the Multi-Targeting QuickStart.

namespace RealEstateListingViewer.Services
{
    public partial class PropertyService : IPropertyService
    {
        public Property GetProperty()
        {
            var property = new Property
                               {
                                   Address = "132 Main Street",
                                   County = "Redmond",
                                   State = "WA",
                                   …
                                   CriteriaMatching = new List<PropertyFeatureMatching>(),
                                   Image = GetImage()
                               };

            property.CriteriaMatching.Add(new PropertyFeatureMatching
            {
                FeatureDescription = "Bedrooms",
                CriteriaMatchingPercentage = 100d,
                CriteriaWeight = 1d
            });
…
            return property;
        }

    }

The following code example shows the Silverlight-specific partial class for retrieving images for the PropertyService class in the Multi-Targeting QuickStart.

namespace RealEstateListingViewer.Services
{
    public partial class PropertyService
    {
        /// <summary>
        /// Return the images. In Silverlight, you want to download the image
        /// from a Web server.
        /// You can either store the images on the server or build an
        /// HTTP handler to retrieve the images. 
        /// </summary>
        /// <returns></returns>
        private BitmapImage GetImage()
        {
            Uri imageUri;
            Uri source = App.Current.Host.Source;

            if (source.ToString().StartsWith("file://"))
            {
                imageUri = new Uri("../Images/House.jpg", UriKind.Relative);
            }
            else
            {
                source = new Uri(string.Format("{0}://{1}:{2}/", source.Scheme, source.Host, source.Port));
                imageUri = new Uri(source, "Images/house.jpg");
            }
            return new BitmapImage(imageUri);
        }
    }
}

The following code example shows the WPF-specific partial class for retrieving images for the PropertyService class in the Multi-Targeting QuickStart.

namespace RealEstateListingViewer.Services
{
    public partial class PropertyService
    {
        /// <summary>
        /// Return the images. In a Windows Forms application, you might
        /// retrieve the image from a database.
        /// But for simplicity, it is just being retrieved from the file system.  
        /// </summary>
        /// <returns></returns>
        private BitmapImage GetImage()
        {
            return new BitmapImage(new Uri("../Images/house.jpg", UriKind.Relative));
        }
    }
}

Use Partial Methods Only if You Need to Call an Extra Method on One Platform but Not the Other

If some work needs to be performed only in either Silverlight or WPF, you could also use partial methods. This means you can put an interface for the method in the parent class and put an implementation of that interface in only one of the platform-specific classes. For the other platform, the compiler will remove the method call. There are several limitations to partial methods:

  • Partial method declarations must begin with the contextual keyword partial and the method must return void.
  • Partial methods can have ref parameters but not out parameters.
  • Partial methods are implicitly private; therefore, they cannot be virtual.
  • Partial methods cannot be extern, because the presence of the body determines whether they are defining or implementing.
  • Partial methods can have static and unsafe modifiers.
  • Partial methods can be generic. Constraints are put on the defining partial method declaration and may optionally be repeated on the implementing one. Parameter and type parameter names do not have to be the same in the implementing declaration as in the defining one.
  • You cannot make a delegate to a partial method.

Build Platform-Specific Classes with a Single Responsibility

Frequently, it makes more sense to factor all platform-specific code into a separate class (for example, services or service agents). This can happen if most of the logic differs between the platforms. This way, you can create platform-specific implementations for services, such as caching, data access, or authentication. This approach also works for providing functionality that is only present in one platform. The following are some additional recommendations:

  • Use a common interface to share code between the different platforms. For example, in the Composite Application Library, there are several platform specific classes for loading module types, such as the XapModuleTypeLoader for Silverlight and the FileModuleTypeLoader for desktop applications. They both share the IModuleTypeLoader interface.
  • When there is some shared functionality between the different platforms, favor composition over inheritance (for example, using the strategy pattern). In other words, see if it makes sense to factor out the shared code in a shared class with a specific responsibility. In some scenarios, inheritance makes sense.

Create a Solution Folder for Silverlight and Another for WPF

Use solution folders to keep your solution organized. Typically, you do this by using two solution folders, one for the Silverlight code and the other for WPF code. For an example of how to structure your solution, see Multi-Targeting QuickStart.

Check Silverlight and WPF References When Refactoring Code

Sometimes a Silverlight reference might slip into a WPF project or vice versa. This is caused by using refactoring tools. If you get unexpected compiler errors about Silverlight assemblies not being referenced in your WPF project, check the references.

Process Guidelines

Process guidelines include the following:

  • Develop the core application in Silverlight.
  • Link the shared code between the source project and the target project.
  • Use the same namespace for Silverlight and WPF projects.

The next sections describe each of these guidelines in more detail.

Develop the Core Application in Silverlight

Because Silverlight is a subset of WPF, the same code will work on WPF without major modifications, so you should develop your core application in Silverlight. For information about the differences between Silverlight and WPF as they relate to composite applications, see Contrasting Silverlight and WPF.

For files that are common to both Silverlight and WPF but need different references, you should link the files. One of the ways to do this is to use the Project Linker tool to link the shared code between the source project and the target project. For more information, see Project Linker: Synchronization Tool.

Use the Same Namespace for Silverlight and WPF Projects

Keep the namespaces the same between projects because the shared code requires the same namespace.

Team Build Guidelines

This section describes Team Build guidelines.

Configure Team Build to Build in Place

If you use Team Build to build a solution that holds both WPF and Silverlight projects, you might run into file collision problems. By default, Team Build will copy the output from the projects to a single output folder. Because the output of the WPF and Silverlight projects should have the same name, there is a file name collision problem that will prevent you from compiling the projects or running unit tests.

By setting the property CustomizableOutDir to true, you are telling Team Build to build in place instead of copying the output to a single location. This prevents the collision name problem.

Additionally, if you set this property and want to run unit tests in your build, you also need to specify where the TestContainers are located.

<PropertyGroup>
<!--Build in place-->
    <CustomizableOutDir>true</CustomizableOutDir>
</PropertyGroup>

  <!—Override the BeforeTestConfiguration target to specify where the testcontainers live. -->
  <Target Name="BeforeTestConfiguration">

    <!--
      Change the outdir, because the testtoolstask needs this to execute all the tests
    -->
    <PropertyGroup>
      <OutDir>$(SolutionRoot)\Source\</OutDir>
    </PropertyGroup>

    <!--Create a list of all tests dll's to run (test only the desktop versions) -->
    <CreateItem Include="$(SolutionRoot)\**\Desktop\**\Bin\Debug\%2a.Tests.dll">
      <Output ItemName="TestContainer" TaskParameter="Include" />
    </CreateItem>

  </Target>

Note

This example assumes that your desktop projects are located in a folder named Desktop. MSTest.exe is only able to run unit tests that are targeting the desktop version of the .NET Framework, so Silverlight test assemblies are excluded.

More Information

For information about creating multi-targeted applications in WPF and Silverlight, see the following topics:

For more information about other Composite Application Guidance technical concepts, see the following topics:

Home page on MSDN | Community site