Part 1: Creating the Portable Library
Part 2: Portability in Silverlight and WPF: a Tale of Type Forwarders
Part 3: Portability in Metro: A CLR and WinRT Love Affair (this post)
Portability in Metro: A CLR and WinRT Love Affair
In this series we’ve covered the portable library and reviewed how it allows you to create assemblies that can be shared without recompilation across multiple platforms. You created a portable assembly with a view model and a command in it, then successfully integrated it in a WPF and a Silverlight project. Now it’s time to code for the future and go Metro.
Create a new C# Metro application using the blank page template. Reference the PortableCommandLibrary project. Open the BlankPage.xaml file and drop in the same XAML you used for WPF and Silverlight. First, fix up a reference:
xmlns:portable="using:PortableCommandLibrary"
Next, add the XAML inside of the main grid:
<Grid.DataContext> <portable:ViewModel/> </Grid.DataContext> <Button Content="{Binding Text}" Command="{Binding ClickCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10"/>
Now compile, deploy, and run the application. It will work just as it did for WPF and Silverlight.
First the button:
Then the disabled button:
What’s interesting for this example is that you know when you want to wire a command, you have to use a completely separate namespace from Silverlight. In fact, the namespace implies that you are accessing a WinRT component that is part of the operating system and not even a managed object. How do we pull that off with an assembly that isn’t modified?
To begin the journey, start with the assembly that is referenced directly by the portable library. This is System.Windows.dll only this time you’ll inspect it in the .NETCore folder, which is the smaller profile allowed for Metro applications. Once again, the assembly contains no implementation. Opening the manifest, you will find a series of type forwarders. This time the ICommand interface is redirected to System.ObjectModel.dll.
What’s next? You guessed it. Pop open the System.ObjectModel.dll assembly and you’ll find:
So there it is … but there’s a problem. When you specify your own command implementation, you have to reference the Windows.UI.Xaml.Input namespace. So how will this reference work? This is where Metro works a little bit of magic.
It turns out the platform maintains an internal table that maps CLR namespaces to the WinRT equivalents. This allows seamless integration between the types. For example, the CLR may be exposed to the type Windows.Foundation.Uri when dealing with a WinRT component. When this happens, it automatically maps this to the .NET System.Uri. When the CLR passes System.Uri to a WinRT component, it is converted to a Windows.Foundation.Uri reference.
In our case, the code references:
System.Windows.Input.ICommand
The platform will automatically map this to the WinRT counterpart,
Windows.UI.Xaml.Input.ICommand
This is a very powerful feature because it enables compatibility between legacy code and the new runtime with minimal effort on the part of the developer. If your type maps to an actual object that can have an activated instance, rather than just an interface, the CLR will automatically instantiate a Runtime Callable Wrapper (RCW) to proxy calls to the underlying WinRT (essentially COM) component.
The whole portable path looks like this in the end:
If you want to see the “projected” types you can use ildasm.exe with the /project switch and in theory, if you run this against one of the .WinMD files (such as Windows.UI.Xaml.Input.WinMD) located in %windir%\winmetdata you should see .NET projected types instead of Windows Runtime types … I have yet to get this to work but if you have, please post the details here.
And that’s it – you’ve learned how to create an assembly that is completely portable between .NET Framework 4.5 (WPF), Silverlight 4.0 and 5.0, and Windows 8 Metro, and learned a bit about how it works by chasing down ICommand under the covers. Hopefully this helps with understanding the library and also with planning out how to map future projects that need to share code between existing implementations and future Metro targets.