Поделиться через


How to Make Portable Class Libraries Work for You

A Portable Class Library is a .NET library that can be used (in binary form, without recompiling) on multiple .NET platforms.  When you create a Portable Class Library in Visual Studio, you can choose which platforms to target.  Portable libraries support targeting the .NET Framework, Silverlight, Windows Phone, Windows Store apps, and XBox 360 XNA games.

Portable Class Library dialog to choose target frameworks 
Portable Class Library creation dialog in Visual Studio 2012

This can be a big improvement over creating a separate project file for each platform you want to target, and keeping them all in sync.  For example, shown below is the project structure for the MVVM Light Toolkit.

MVVM Light Toolkit Project Structure
MVVM Light Toolkit Project Structure

I bet it’s not fun to keep all those projects in sync each time a file needs to be added, removed, or renamed.  There are only three logical libraries here (the core library, extras, and the tests).  Wouldn’t it be great to just have one portable version of each of those libraries instead of seven different ones?

Trouble in Paradise

Unfortunately, it’s not that simple.  The fact that Portable Class Libraries need to run on multiple platforms imposes some constraints on what you can do in them.  Many .NET Framework APIs (such as File I/O or anything to do with the user interface) aren’t available.  Portable libraries can’t reference non-portable libraries.  You can’t use P/Invoke from most portable libraries.  This is all because portable libraries are supposed to actually run on different platforms, but it means it can be more complicated to write them, and can in some cases be very difficult (or not possible) to convert existing .NET libraries to portable libraries.

We’ve seen lots of people excited about portable libraries, but we’ve also seen some people who have run into these limitations questioning whether portable libraries are useful for them.  In this post, I’ll cover how you can use Portable Class Libraries even when you run into these limitations.  First, let’s look at the reasons some APIs aren’t supported, and what functionality actually is supported.

Why APIs Aren’t Portable

API Y U NO PORTABLE

Here are some of the reasons APIs aren’t available in portable libraries (taken from David Kean’s MSDN article on Creating a Continuous Client Using Portable Class Libraries):

The API Isn’t Implemented by All Platforms
Traditional .NET Framework file IOs, such as System.IO.File and System.IO.Directory, fall into this bucket. Silverlight and Windows Phone use the System.IO.IsolatedStorage APIs (though different from the .NET Framework version), whereas Windows Store apps use Windows.Storage.

The API Isn’t Compatible Across All Platforms
Some APIs look and feel the same, but it’s hard or impossible to write code against them in a portable and consistent way. ThreadStaticAttribute, which enables static fields to have a unique value for each thread, is an example. Though it’s present on both the Windows Phone and Xbox platforms, neither of their runtimes supports it.

The API Is Considered Obsolete or Legacy
These APIs either contain behavior that’s unlikely to be present on future platforms, or they’ve been replaced by newer technologies. BackgroundWorker is an example of this; it was replaced by Task and the new asynchronous program features in Visual Studio 2012.

We Ran out of Time
Most APIs weren’t written with portability in mind. We spend a significant amount of time going through each API to make sure it can be programmed against in a portable manner. This might involve tweaking or adding to the API to make it portable. Because of the time and effort involved, in the first version of PCLs we made available on Visual Studio Gallery, we prioritized the high-value, highly used APIs. System.Xml.Linq.dll and System.ComponentModel.DataAnnotations.dll are examples of APIs that weren’t available in that first version but are now available in the Visual Studio 2012 release.

What Is Supported in Portable Libraries?

For a .NET Framework API to be available in a portable library, the API needs to be supported on all the platforms the portable library is targeting.  This means that the platforms you choose to target in your portable library affect the APIs that are available to you.  In general, the more platforms you choose to support, the fewer APIs will be available to you.  For example, the Task type from the TPL is available in .NET 4 and up, Silverlight 5, and Windows Store apps.  So if you target only those platforms you will be able to use the Task type in your portable library.  If you target Silverlight 4, XBox, or Windows Phone 7, you won’t be able to use it.

The following chart gives a summary of what API areas are available in portable libraries and on what platforms.

Portable Class Library Platform and Feature Matrix
Portable Class Library Platform/Feature Support Matrix

The full list of APIs available in portable libraries as of Visual Studio 2012 RTM is available in this spreadsheet: Portable Class Library APIs

Solving Problems With Indirection

Any problem in computer science can be solved with another layer of indirection. – David Wheeler (probably)

What should you do when you’re trying to write a portable library but you need some functionality that isn’t supported?  You can’t call the API directly, and you can’t reference a library that does, because portable libraries can’t reference non-portable libraries.  The solution is to create an abstraction in your portable library that provides the functionality you need, and to implement that abstraction for each platform your portable library targets.  For example, if you need to save and load text files, you might use an interface like this:

 public interface IFileStorage
{
    Task SaveFileAsync(string filename, string contents);
    Task<String> LoadFileAsync(string filename);
}

It’s a good idea to include only the functionality you need in the abstraction.  In this example, the interface doesn’t abstract general file system concepts such as streams, folders, or enumerating files.  This makes the abstraction more portable and easier to implement.  The methods return Tasks so that the implementation for Windows Store apps can call the WinRT file IO APIs, which are async.

Creating an abstraction allows portable libraries to call into non-portable code, and this pattern is applicable almost any time you need to access non-portable functionality from a portable library.  Of course, you need some way for the portable code to get a reference to an implementation of the abstraction.  How you do that can depend on whether you are writing a cross platform app or a general purpose reusable library.

Cross Platform Apps

When writing a cross platform app in .NET, you should create the user interface separately for each platform to take advantage of the screen sizes and UI metaphors of each platform.  To avoid duplicating work, you’d like most of the rest of your code to be shared across platforms.  A clean separation between your user interface and the rest of your code will make this easier, and a great pattern for this is the Model-View-View Model (MVVM) pattern.

Diagram of the MVVM Pattern
Diagram of the MVVM Pattern

Using the MVVM pattern, you can share models and view models across platforms using Portable Class Libraries.  When a model or view model needs to access non-portable functionality, you can create an abstraction for that functionality and implement it in platform-specific code.  The app for each platform will contain the views and reference the portable library, as shown in the following diagram.

Cross Platform App Project Structure with Portable Class Libraries and MVVM
Cross Platform App Project Structure with Portable Class Libraries and MVVM

A simple example of a cross platform app that uses portable libraries and MVVM can be found in this PortableNotepad sample.  In that app, the view model needs access to an instance of IFileStorage, so it takes it as a constructor parameter.  The view’s constructor creates the view model, passing in the platform-specific implementation of IFileStorage, and sets the view’s DataContext to the view model it created.

For more complicated apps, it can get messy passing the implementations for the platform specific functionality down into portable code.  One solution to this is a simple “service locator” in the portable library with static properties corresponding to the portable abstractions, which get set during app startup.  Portable code can then access the non-portable functionality through the service locator class.

 public class ServiceLocator
{
    public static IFileStorage FileStorage { get; set; }
    public static IPhotoChooser PhotoChooser { get; set; }
}

Of course, using static properties like this may raise “singleton pattern” alarm bells, and will make it harder to write unit tests for your code.  So you may want to use an IoC container, as hooking up dependencies while maintaining testability and separation of concerns is what IoC containers are designed to do.  Autofac is one IoC container which has a portable version, and there’s also a portable fork of Ninject.

David Kean’s MSDN article on Creating a Continuous Client Using Portable Class Libraries includes a sample which demonstrates a more complex cross platform app which uses portable libraries and MVVM.  The sample is a shopping list app called OnYourWayHome and has a Windows Phone 7 and a Windows Store app version.  It shows how you can handle navigation between pages, use an IoC container, and use Azure Service bus to sync state between multiple clients.  The version from the MSDN article is based on the VS 2012 Beta and Windows 8 Consumer Preview, so it will need some updates to run on Windows 8 RTM.  An updated version of the app which uses MEF Lightweight composition (instead of Autofac) is included as a sample in the MEF source code.

Another sample app is Contoso Helpdesk, which I’ve been using in presentations about Portable Class Libraries.  This app uses the same architecture (and much of the same code) as the OnYourWayHome sample.  Like OnYourWayHome, it has a Windows Phone 7 and Windows Store app version, but instead of a shopping list, it keeps track of helpdesk tickets.  In Contoso Helpdesk, I’ve factored out the MVVM infrastructure pieces which could be reused in other apps into a library called MvvmPortable.  You can use this library as a starting point to write your own cross platform apps.

Reusable Portable Libraries

If you are creating a library for others to use (such as an open source library), you probably want to make it as easy as possible to reference and use your library.  That means consumers of the library should be able to add a reference to it as a single unit.  Some libraries won’t need any non-portable functionality, so you can create a single portable DLL.  Autofac and Json.NET are two libraries which have done this.  For libraries that need to be factored into multiple DLLs (for example a portable and a platform specific part), publishing them as NuGet packages allows consumers to reference the different components of the library as one unit.  (NuGet also makes it a lot easier to consume a library that consists of a single assembly, so either way I recommend publishing your libraries on NuGet.)

The code to use your library should also be simple.  That means the project referencing your library shouldn’t have to manage hooking up the platform specific part of your library with the portable part.  One way to do this is to have an initialize method in your platform specific part of your library which passes references to platform specific implementations down to the portable part of your library.  Here’s an example of what this could look like:

 public class MyLibraryPhoneUtil
{
    public static void Initialize()
    {
        MyLibraryServiceLocator.FileStorage = new PhoneFileStorage();
        MyLibraryServiceLocator.PhotoChooser = new PhonePhotoChooser();
    }
}

This is simple for you as a library author to write, but it means that consumers of your library need to call the initialize method on startup.

For consumers of your library, it will be easier if the portable part of your library connects to the platform specific part with no action required on their part.  You can do this by having the portable part load the platform-specific part by calling Assembly.Load(), and using reflection to get the functionality it needs.  For an example of how to do this, see  the Portable Class Libraries Contrib project.  The code which handles this functionality is in the Source\Portable.Runtime\Adaptation folder.  The Reactive Extensions (Rx) also use this method to connect their portable System.Reactive.Core assembly with the platform-specific System.Reactive.PlatformServices assembly.  Speaking of Reactive Extensions, the team’s blog posts announcing Rx 2.0 Beta, RC, and RTM cover how they converted Rx to support Portable Class Libraries, and are a great case study for how to do so with a large existing library.

Another option is to use reflection to call platform-specific APIs from a portable library directly.  This can be a good choice for a library which can be almost entirely portable, but which has a small, isolated set of platform-specific functionality it needs to access.  This portable fork of MVVM Light uses this method in the ViewModelBase class to determine whether it is running in the designer.

Doing the Impossible

Portable libraries can’t reference non-portable libraries, because anything a portable library references needs to run on the same platforms the portable library supports.  But what if there is a library that is supported on all the platforms a portable library needs, it just has a separate DLL for each platform instead of being implemented as a Portable Class Library?  Wouldn’t it be nice if you could reference that library from a portable library?  With a bit of work, you can.

To use a concrete example, let’s imagine there’s a .NET library for sqlite that you would like to use from a portable library.  The trick is to reference one assembly when compiling, and deploy a different one to be used at runtime.  You can create a portable library with the same API surface and assembly identity as the sqlite library.  This is your “reference assembly.”  This assembly should never be used at runtime, so you don’t actually have to implement anything—just throw NotImplementedExceptions in all methods.  Then a portable library can reference and compile against the reference assembly version of the sqlite library.  Apps that use the portable library can reference the normal implementation of the sqlite library for the platform they target, which will cause that version of the sqlite library to be packaged with the app and used at runtime.

I don’t know of any projects that are currently using this strategy, and I’m not sure if overall it’s likely to be a better solution than creating a portable abstraction like I described previously.  Still, it’s an interesting option.  I’d love to hear how it works for you if you do try using it.  If it turns out to be a useful pattern we can consider providing more tooling to support it.

Conclusion

Using the patterns I’ve described, Portable Class Libraries can be used for a wide variety of projects, both for cross platform apps and general purpose reusable libraries.  Of course, the fact that these patterns are needed in the first place means that using portable libraries can be more complicated than writing code for a single platform.  If you want to target multiple platforms, however, you will have to deal with additional complexity somehow.  You can choose to keep entirely separate code bases; share your code with linked source files, separate projects for each platform, and conditional compilation; or use portable libraries to share code between platforms.  There are advantages and disadvantages to each of these options.  I think in many cases, portable libraries are a great choice—they avoid the project proliferation problem and make it easier to share code in a way that will be maintainable in the long run.

Portable Class Library solution structure sample from MvvmCross
Sample of how Portable Class Libraries can simplify the solution structure for a cross platform app – MvvmCross

The functionality supported in portable libraries will continue to grow over time.  At the beginning of 2011, we released the first version of Portable Class Library support as an add-in for Visual Studio 2010 with support for a fairly limited set of APIs.  With the Visual Studio 2012 release, we now support much more functionality (which is also now available in the add-in for Visual Studio 2010).  We will continue to work on making more code portable across more platforms using portable libraries.  We also hope to see third-party libraries which provide portable abstractions and implementations for you so you have less work to do when writing a cross-platform app (Sqlite and Xamarin.Mobile would be great candidates for this).

We’d also love to hear your feedback.  We hope you will try using Portable Class Libraries and let us know what problems you run into and what seems to be more difficult than it should be.  This will help us know what to focus on for future improvements.  You can leave a comment on this blog post or contact David Kean and me on Twitter.

For more information on Portable Class Libraries, see this Overview of Portable Class Libraries on the .NET Framework Blog, and the links from this Channel 9 Visual Studio Toolbox show on Portable Class Libraries.

Comments

  • Anonymous
    August 27, 2012
    OData-generated proxies derive from System.Data.Services.Client.DataServiceContext, which is not portable. Than means, neither ViewModel nor business logic can be Portable. How come?

  • Anonymous
    August 27, 2012
    XLinq not available in 4.0??  What is this XLinq you speak of?  The XLinq I know has been available since Linq was introduced.  XDocument, XElement, Xetera.

  • Anonymous
    August 28, 2012
    Maybe I'm missing something, but I don't see how portable class libraries work for XNA. I have tried to add references to the XNA DLL's without success. If I tweak the options to target only, say, .NET 4 and WP7 (which both support XNA) the settings page informs me that .NET for Windows 8 and Silverlight will automatically be supported. It's obviously useful to be able to define a set of models in a single project, but with games this likely means recreating core types such as Point, Vector2, and Rectangle.

  • Anonymous
    August 28, 2012
    @Lex Lavnikov, That sounds like a great candidate for the abstraction pattern!  Have you tried creating a portable abstraction for the OData context class and then making the data source class portable and calling into the context via the abstraction? @Derp, Sorry for the confusion.  The chart is showing what features you can use from Portable Class Libraries that target the various platforms.  So XLinq is certainly available in .NET 4, but it is not available in a Portable Class Library that targets .NET 4. The reasons for this have to do with the implementation details of how portable libraries work and the limited time we had to make changes to .NET 4 to support portable libraries.  As you can see in the chart, we were able to add support for XLinq from portable libraries in .NET 4.0.3. @Alex Schearer, I have a simple example of a puzzle solver that uses XNA and portable libraries here: github.com/.../Disentanglement You can see a demo of the program in this video: channel9.msdn.com/.../Visual-Studio-ToolboxPortable-Class-Libraries That demo is a very simple XNA app with a pretty simple division between the portable code which holds the puzzle state and algorithms to solve it and the "game" code which handles the rendering and response to input. For a real game it will be harder to take advantage of portable libraries, since as you mentioned you can't share core types like points and vectors.  You'd have to create your own portable versions of the ones you need and handle converting to and from the XNA types as necessary.  If you do try to do this I'd be interested in hearing about the strategies you used and whether you felt it was worth it in the end (compared to just using linked files). In the future, maybe something like MonoGame (which is an open source implementation of the XNA APIs) will support portable libraries and this will be a lot easier. Daniel Plaisted, MSFT

  • Anonymous
    August 28, 2012
    @Daniel, I will take a look. I've completed two games which run on Windows 8 and Windows 7 at the moment and am using MonoGame for Windows 8. (<a href="spottedzebrasoftware.com/.../a> and <a href="spottedzebrasoftware.com/.../a>) I tried to get portable assemblies to work but in the end found they didn't work. In the end I wound up using linked files. This works but is pretty cumbersome. As I was doing some more research I came across this and wanted to follow up.

  • Anonymous
    January 08, 2013
    I like Portable Class Libraries a lot, but there seems to be a small bug in the 'Add Service Reference' dialog on PCL projects. The dialog does not show options based on the PCL project's target platforms. Targeting only 4.5 and Windows Store does not show the option to generate 'Task-based' async operations even though it is supported on both platforms. The code that is generated is pretty much unusable. The workaround is to create a temporary Windows Store class library. Add the service reference. Then copy the generated code and paste it in the Portable class library. Delete the temporary project. Not very ideal. I logged a bug last year: connect.microsoft.com/.../portable-class-library-add-service-reference-dialog Am I doing something wrong or not understanding something? Please advise. Only really a problem with non-Microsoft web service references. I usually code the proxies manually.

  • Anonymous
    February 28, 2013
    Can you help me with this problem? social.msdn.microsoft.com/.../2745a77e-dfaf-4308-a3e9-7bba4bafbd44 .

  • Anonymous
    March 26, 2013
    Hello, Great post, although I partially disagree on sharing ViewModels. IMO, the complete navigation and information flow is different on Phone and Windows 8 apps. I wrote an article with a sample on how to project some API's onto an interface which maybe interesting if you'd like to implement this: www.kenneth-truyers.net/.../patterns-for-sharing-code-in-windows-phone-and-windows-8-applications

  • Anonymous
    April 02, 2013
    Hi, I had added a service reference in my Windows Phone Project, Which is working fine. Now i had tried to add that service reference in a portable class library. But when i am using that service reference in PCL, it is giving me some errors. So i had compared "Reference.cs" file of both project (Windows Phone Project & Portable Class Library Project) and i found that "IsWrapped" property is missing in Portable class library, due to that the format of request envelope is different in both project & that causes an exception from web service. Please refer this example :   social.msdn.microsoft.com/.../e381f322-cc78-43b5-86b8-05c8fd9279d9 Please help me to solve this problem of "Add service reference" in "Portable Class Library"

  • Anonymous
    June 29, 2013
    The spreadsheet you linked to is very nice and detailed.  I was able to track down the exact method that was not supported in Silverlight 4 and adjusted my target platfoms accordingly.  Thank you so much for the hard work, and preciseness and granularity you used when indicating what was supported.  This blew away half the errors in my first and brand new portable class library that I brought my existing classes into.  

  • Anonymous
    April 10, 2014
    Can you implement code for Windows Console using IFileStorage? I try it, but VS return me an error. //using (var reader = File.OpenText(fileName)) //{ //    return reader.ReadLineAsync(); //}

  • Anonymous
    May 28, 2014
    Can I use Windows.system namespace in PCL project.  I have tried by creating new portable class library project but this assembly reference it not present .

  • Anonymous
    February 13, 2015
    The comment has been removed