Cross-Platform Development with Portable Class Libraries
In a Bring Your Own Device (BOYD) world, .NET Framework support for Portable Class Libraries (PCLs) provides a base for writing code that will run on any platform -- provided you understand the limitations of PCLs and how to structure your applications to exploit them.
- By Muhammad Siddiqi
The Microsoft .NET Framework Common Language Specification (CLS) ensures code written in one language can generate Microsoft intermediate language (MSIL) that can be used from other compliant languages. But the various .NET target frameworks (Silverlight, Windows Phone, Xbox 360 and .NET for Windows Store apps) all have different sets of supported features and encompass different namespaces and libraries. So, "cross-platform development" really means "cross-platform development for a targeted framework."
When you need to share code between these frameworks -- if you really want to build something that runs on different platforms -- you have to create separate libraries for each framework, often by recompiling an existing library for each framework. Using XAML to create views for different frameworks reduces the divide between the frameworks but, as a result, increases the need to share business code across frameworks in order to have truly sharable libraries that don't need to be recompiled.
Portable Class Libraries (PCLs) allow you to write code that can be targeted at a combination of .NET frameworks. You select the frameworks you want to target when creating a PCL project to gain assembly portability across those frameworks. You do lose some functionality, however -- the feature set available to your library is the intersection set of the features in the selected frameworks (see Figure 1). Nor are all classes in that intersection area necessarily available in a PCL.
In order for a class to be a candidate for portability, it not only has to be available in all the target frameworks, but it also has to have the same behavior across those frameworks. Even then, there are some classes that meet both conditions that aren't yet "portable" (but, if you wait, you should see them become portable). However, there are some strategies for reducing the impact of the "intersection set," which will be discussed later.
You can use the MSDN Library to determine if a particular class is available for a PCL project: Just look for Portable Class Library in the Version Information section of a class's documentation. For any class, not all members may be available in a PCL.
Creating and Referencing PCLs
Earlier versions of Visual Studio provided some support for targeting multiple platforms through file and project linking (available as an extension to Visual Studio 2010), but both processes had limitations. Portable Library Tools, which adds the project templates to develop PCL projects for Visual Studio, is the latest iteration to address cross-framework support. Portable Library Tools is available out-of-the-box in Visual Studio 2012 Professional, and as an extension for Visual Studio 2010 SP1.
When you select the PCL project from the Visual Studio Add New Project dialog, you're presented with a dialog listing the frameworks you can target (Figure 2). These include:
- .NET Framework (version 4 and later)
- Silverlight (version 4 and later)
- Windows Phone (version 7 and later)
- .NET for Windows Store apps
- Xbox 360
After you've created your project, you can change your selections from your project's Property Pages. Also, by clicking the Install Additional Frameworks link at the bottom of the Add Portable Class Library dialog, you can add additional targeting packs such as Windows Azure, for instance. In Visual Studio 2012, you'll find your project's References node in Solution Explorer contains a single library called .NET Portable Subset.
The .NET Framework 4 uses special re-targetable assemblies created specifically for PCLs, which choose the correct assembly at runtime depending on the host framework. The .NET Framework 4.5 was built with a portable mindset, and its assemblies have been designed from the ground up with portability in mind through type forwarding.
While it's tempting to select all the available frameworks (the "you never know" attitude), it's better to think in terms of "you aren't going to need it." Because the feature set available to your code is the intersection of the frameworks selected, each additional framework you select reduces the set of features available to you. The best advice is to choose only the target frameworks where you expect your code to be hosted. But, thanks to the way PCLs can be referenced, there are some strategies that allow you to extend the functionality in a portable application.
Referencing Portable Applications
While PCLs can't reference non-portable class libraries, other kinds of projects (those targeting a single framework) can reference PCLs, provided the PCL includes the framework that the original project is targeting. PCLs can also reference each other: Your PCL project can reference another PCL, provided that the target frameworks in the referenced library are a subset of your project's frameworks. If a PCL you want to reference targets a set of frameworks different from your project, you won't be allowed to add a reference to the PCL.
PCL projects also support adding Service References, with some restrictions. All the service operations in the client proxy are generated as asynchronous. Any non-portable classes in the service are ignored (for instance, any constructor involving a non-portable class will not be included in the client proxy). Within the service contract, ProtectionLevel, SessionMode, IsInitiating and IsTerminating are removed and only a couple of bindings are supported.
As of NuGet 2.1, PCLs can be part of NuGet packages. If your NuGet package contains a framework-specific library and a portable version, the more specific-versioned libraries get preference when the package is installed (but this raises the question of why your package has a portable framework-specific version of the same library). Installing a NuGet package that doesn't target a framework within the project will fail.
Strategies for Portable Applications
In a Model-View-ViewModel (MVVM)-based application, views are platform-dependent, while view models and models can be kept in separate class libraries, which can be PCLs. You could, for instance, mix combinations of "portable model" libraries with "non-portable view and view model" or "portable model and view model" with "non-portable views." There are two factors that will influence your decision in selecting PCLs to hold your models and view models: which framework you're targeting, and whether the frameworks and libraries your model or view model need are available to a PCL.
When writing code for a PCL project, the Template Method and Non-Virtual Interface design patterns provide a way of dealing with the subset of framework types available. In your PCL project, you can provide the basic definition of your process in a base class, defining any framework-dependent members (members that require functionality not available in the intersection subset) as abstract/MustOverride or virtual/Overrideable methods.
The implementation for these methods can be provided in classes that inherit from your PCL classes; those classes can be in framework-specific libraries where the full functionality for the framework is available. With virtual/Overrideable methods, you might be able to provide a generic implementation that wouldn't always need to be overridden by framework-specific code (or would extend the framework-specific code) by using the Protected Variations General Responsibility Assignment Software Patterns (GRASP) principle.
You can also intelligently divide your code across PCLs by looking at the functionality required in each framework. While your application might target several frameworks, you rarely want all of the application's features to be available in all of the frameworks. For example, the supported features in a Windows Phone version of your application will probably be more limited than the Windows Presentation Foundation (WPF)- and Silverlight-based versions. With that in mind, you can develop "universal" PCLs (those with features shared across all the required platforms) and reference them from other "local" PCLs (those with features targeting a smaller set of frameworks). Assembly reference rules should allow the assemblies targeting the larger set of frameworks to be referenced from an assembly targeting a subset.
Figure 3 shows an application targeting five frameworks that's distributed over three PCLs. PCL1 is the universal PCL that provides the functionality available in all frameworks, while PCL2 and PCL3 extend that functionality in selected frameworks.
Software Development Tools and Frameworks
PCL support continues to increase. JetBrains dotPeek 1.0 supports decompiling PCLs, for example. The Microsoft.Bcl.Async-beta NuGet package supports the async keyword for the .NET Framework 4, Silverlight 4 (and higher), and Windows Phone 7.5, and their combinations in a PCL. There's also a fork of the MVVM Light toolkit on CodePlex for PCL.
Before deciding on adopting -- or rejecting -- PCLs, check the tools in your development toolkit. As more and more tools and frameworks add support for PCLs, the absence of that support will become critical. In a Bring Your Own Device (BYOD) world, PCLs aren't just desirable features -- they're essential.
About the Author
Muhammad Siddiqi is technology enthusiast and passionate blogger and speaker. He is a co-author of "MVVM Survival Guide for Enterprise Architectures in Silverlight and WPF". He blogs at http://shujaat.net, and you can find him on Twitter @SiddiqiMuhammad.