How to Make Portable Class Libraries Work for You

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 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

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

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/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

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

A simple example of a cross platform app that uses portable libraries and MVVM can be found in thisPortableNotepad 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.  Autofacis 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 BetaRC, 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.

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.

from: http://blogs.msdn.com/b/dsplaisted/archive/2012/08/27/how-to-make-portable-class-libraries-work-for-you.aspx

时间: 2024-11-06 03:29:14

How to Make Portable Class Libraries Work for You的相关文章

Inside Portable Class Libraries

Portable Class Libraries were introduced with Visual Studio 2010 SP1 to aid writing libraries that could be used on many different platforms – the full .NET 4/4.5 framework, Windows Phone, Silverlight, Xbox, and Windows Store apps. You simply select

问题:IIS部署 MVC项目 (autofac) 错误解决

http://www.cnblogs.com/yelaiju/p/3375168.html Could not load file or assembly 'System.Core, Version=2.0.5.0 和autofac冲突的问题 在部署到iis的时候会出现这个状况. 解决:下载安装这个补丁 http://support.microsoft.com/kb/2468871 http://www.microsoft.com/zh-cn/download/confirmation.aspx

centos7下源码编译方式安装httpd

语法: chkconfig --list [name] chkconfig --add name chkconfig --del name chkconfig [--level levels] name <on|off|reset> chkconfig [--level levels] name 参考文章http://www.cnblogs.com/jipeng87/p/6308725.html 前言 Apache至少需要apr.apr-util.pcre组件的支持. APR(Apache p

centos 7中编译安装httpd-2.4.25.tar.gz

检查是否已经安装了下载工具wget和编译环境gcc.make: [[email protected] ~]# rpm -qa|grep -e wget -e ^gcc -e makegcc-4.8.3-9.el7.x86_64 make-3.82-21.el7.x86_64 wget-1.14-10.el7_0.1.x86_64 如果没有安装,则使用下面的命令安装: [[email protected] ~]# yum -y install wget gcc makeLoaded plugins

Could not load file or assembly &#39;System.Core, Version=2.0.5.0 和autofac冲突的问题

在部署到iis的时候会出现这个状况.   这个软件(NDP40-KB2468871-v2-x64) 解决:下载安装这个补丁 http://support.microsoft.com/kb/2468871 http://www.microsoft.com/zh-cn/download/confirmation.aspx?id=3556 http://download.microsoft.com/download/2/B/F/2BF4D7D1-E781-4EE0-9E4F-FDD44A2F8934/

一篇很好的解释了.Net Core, .Net Framework, .Net standard library, Xamarin 之间关系的文章 (转载)

Introducing .NET Standard In my last post, I talked about how we want to make porting to .NET Core easier. In this post, I’ll focus on how we’re making this plan a reality with .NET Standard. We’ll cover which APIs we plan to include, how cross-frame

启动tomcat时遇到的问题

1.当启动Tomcat时,出现了如下信息: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: D:\tools\myeclipse\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\bin;D:\Progr

[译]Introducing ASP.NET vNext and MVC 6

原文:http://www.infoq.com/news/2014/05/ASP.NET-vNext?utm_source=tuicool Part of the ASP.NET vNext initiative, ASP.NET MVC 6 represents a fundamental change to how Microsoft constructs and deploys web frameworks. The goal is to create a host agnostic fr

Windows10(UWP)下的MEF

前言 最近在帮一家知名外企开发Universal Windows Platform的相关应用,开发过程中不由感慨:项目分为两种,一种叫做前人栽树后人乘凉,一种叫做前人挖坑后人遭殃.不多说了,多说又要变成月经贴了. 讲讲MEF. MEF全称Managed Extensibility Framework.我们做.Net的碰到依赖注入(DI:Dependency Injection)这一块的内容,一般会选择使用Unity或者MEF,这也是Prism主要使用的两种方式.在.Net 4.0之前,MEF一直