IntroWe have so many platforms to code for these days: windows desktop, tablets, phones, the web, even xbox. One of the issues stemming from this abundance is that there are a ton of SDKs that have quite similar APIs, but also some subtle and not-so subtle differences that make cross-platform development a real pain. Almost all the platforms feature and gravitate around XAML, including WPF, Silverlight, WinRT and the new "Universal Apps" as well. Having said that, it is worth noting that the new "Universal Apps" are a huge step in the right direction. They manage to simplify develpment and code sharing for Windows Phone and Tablets/PCs. The tooling that comes with Visual Studio 2013 really helps you share quite a lot of code when targeting the windows runtime. If you are developing for the windows store life just got a lot more easier for you in the last year. However, (and also unfortunately for many of us) there is a big elephant in the room: WPF. With Silverlight going away focus is coming back to WPF with great force. Universal Apps handle the windows store quite well, but when things come to developing a modern application for the traditional windows desktop there is not much else you can use than WPF. So what's the story with code sharing between apps for the windows store and WPF? I'm afraid it's not as rosy. Traditional desktop apps are substantially more different than apps for phones/tablets but there is nothing wrong with trying to share code, especially if you are targeting both platforms. Another big consideration here is supporting Windows 7 and earlier versions. While Universal Apps are great for Win8+ they cannot save you if you need to support older versions of Windows (at the time of this writing, Windows 7 is still by far the most popular windows OS yet at 56% market share). It just so happens that over the last couple of weeks I have been working with a customer on a project that needs to ship for both the desktop and the windows store. Having established that there are solid reasons to desire a common codebase, let's take a look at how we can do it. Before I began work on this project I tried to see if Microsoft offers any official guidance on how to approach this. I was able to locate this resource here: Developing for Multiple Platforms with the .NET Framework. As you can probably see there's not much there that we are looking for. There is also not much information about this on the web as well. This pushed me to try and share some of my experiences with you in the hope that you will find them useful.
The BasicsSince our application needs to target three platforms (Desktop/Windows Phone/Tablets) I have decided to go with the blank universal app template in Visual Studio 2013. It creates a nice starting structure including a Windows Phone specific project, a Windows specific project and the new Shared project for common stuff. My next step was to add a new WPF project to the mix. Here comes the first obstacle - There is no way to add a reference to the shared project from the WPF one. Not with vanilla studio at least. Therefore, one of the first things I had to do was download the Shared Project Reference Manager extension for Visual Studio which will allow me to reference the shared code. After you add your reference, and you try to build it you will get some errors. That's because the shared project starts off by hosting the App.xaml and App.xaml.cs files by default. This implementation is fine for our WP/Windows apps, but is very different for WPF. What we want to do is to have that code shared between the universal apps only, and for WPF to use the one that comes with the default template. To do this, we remove the App.xaml files from the shared project and place them in either one of our universal apps and add them as links in the other project (don't forget to set the appropriate build action too). In this way, those files continue to be shared between the Windows and Windows Phone projects, but our WPF/Desktop app can use it's own. We now have a solution, that has the most basic infrastructure in place: three different projects: one for phones, one for tablets and one for the desktop (WPF). We have also a shared project that allows us to easily share code between the three projects. Finally we have also managed to preserve the initial state of the project templates and keep the App.xaml shared between the universal apps. With the basics done, it is time to think about the scope of the application.
PRISMSince our customer requested an application whose behavior and UI resembles a line of business style app, it makes a great candidate for using the MVVM design pattern. All of our three platforms are MVVM friendly, and this is a great fit for our particular case. Having chosen MVVM for our app we need to look for a library which facilitates this pattern for the platforms of our choice. The thinking here is the following - since we try to share as much code as possible and also write it in the most platform-agnostic way as we can, it makes good sense to use a library/framework which supports all three platforms and takes care of as much boiler-plate code as possible. Experience has thought us that platform differences are usually most prominent when dealing with such basic plumbing code. Having a framework to abstract as much boring code as possible will allow us to focus on the business problems rather than platform issues. One such framework is PRISM. It comes in two flavors: A version for store apps and one for WPF. There are many features of PRISM (especially in its WPF flavor), some of which are only available to one of the two platforms. We are not going to use everything. In this series of articles we are going to use Prism.Mvvm, a subset of PRISM which is available for both windows store apps and the desktop. It provides the basic services needed for a decent implementation of the MVVM pattern. You can check the bootstrapping code in the downloadable project attached at the end of this blog post.
Implementing our first cross-platform View/ViewModel pairFirst things first, let's talk a bit about project organization. Prism.Mvvm is a convention-over-configuration style framework. Basically, this means that things will "just work" if you follow some simple conventions. Another benefit to using the conventions is that your project will end up with a nice organization that will be familiar to other developers as well. Here is how we are going to do this:
The reasoning behind this organization is solid. The MVVM pattern insists on the separation of Views from ViewModels. Views are concerned with the particular way our screen or page will look on each platform. They are extremely likely to be different for each platform, therefore we are going to have Views folders for each project. ViewModels on the other hand, should be platform-agnostic and should be placed in the Shared project. Another thing worth noticing is the naming. We are using exactly "Views" and "ViewModels" since this is what the default PRISM convention is. This can be customized to your liking if you wish, but we are not going to do this because we want to reap the benefits explained in the previous paragraph. Last, but not least, notice the naming on the ViewModel: It is the same as the View with the suffix "ViewModel". This is also part of the PRISM conventions.
ViewModel ImplementationSo here we are implementing our first view model. When working with PRISM you should inherit your view models from the BindableBase (in WPF) or ViewModel (in WindowsStore apps). Actually, BindableBase is available in PRISM for store apps as well, but it is a good idea to consider inheriting from the ViewModel class (which descends from BindableBase anyway), because it provides additional functionality for universal apps that you may want to consider using in certain cases. In order to reduce the amount of conditional compilation directives such as #if #elif and #else we are going to create a new base class for our view models called ViewModelBase:
We are now ready to code our first cross-platform bindable view model class. Here is a simple implementation:
Shameless plug: You will most likely be implementing many properties such as Title for your ViewModels. Why not try my code snippet for it?
Views implementationWe can also add in another utility class at this point. Since all Views in PRISM should implement the IView marker interface we can introduce a new PageBase class as well:
This one is totally up to your choice as well. The benefit here is that apart from the automatic implementation of IView you also get the possibility to add some additional shared code for all views. Ideally this shouldn't happen, but in reality such hacks tend to be needed sometimes *sigh*. We can now go ahead and implement the MainPage.xaml/MainPage.xaml.cs views. To do that we simply create a new blank page with the chosen name. Next we need to change some things up in our XAML to designate the page as a PRISM view model:
There are two things to notice here. First we have changed the type of the view to PageBase and secondly we have set the AutoWireViewModel="true" attached property. What it does is to instruct PRISM to automatically load the MainPageViewModel and set it as the data context for the view. Finally we need a super-simple implementation of the code-behind file for the view:
The only thing we do here is swap the base class. We are ready! We now have our ViewModel and View running on all three platforms: