Monday, June 23, 2014

Creating real world WPF applications with Prism 5 and F# (part 2 - MEF)

In the previous post I explained how you could port the Hello World example from the Quick Starts of Prism 5. There was one issue. The Shell had knowledge op the module. This could be solved in different ways, for instance with configuration files.

I prefer a pluggable solution. Just by dropping dlls into a directory and everything should work fine. Microsoft has a framework that supports this way of working: Managed Extensibility Framework (MEF).

In this post I will show you how you can move the Hello World example from Unity to MEF.

I start from scratch and highlight the MEF changes You could also rework the Unity solution.

Create a empty solution, and add a project called Shell.

Next add:

  • FsXaml in case you don’t want to use the Xaml Type provider
  • Prism
  • the Prism.MEFExtensions.

image

If needed add the required references

image

In this case we added also System.ComponentModel.Components to get access to MEF.

image

Make sure it is a Windows application:

image

Next remove, add or rename the files

image

and be sure the xaml file has Resource as build action and contains the following content.

<Window 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.codeplex.com/prism"
Title="Hello World" Height="300" Width="300">
<
ItemsControl Name="MainRegion" cal:RegionManager.RegionName="MainRegion" />
</
Window>

We have done all the preparation. So we add the app class and the boodstrapper to the App.fs file.

module MainApp

open System
open System.IO
open System.Windows
open System.ComponentModel.Composition
open System.ComponentModel.Composition.Hosting
open System.Windows.Controls
open Microsoft.Practices.Prism.Modularity
open Microsoft.Practices.Prism.MefExtensions
open FsXaml

type Shell = XAML<"Shell.xaml">

type App() =
inherit Application()
override x.OnStartup(e) =
base.OnStartup(e)
let bootstrapper = new Bootstrapper()
bootstrapper.Run()

and Bootstrapper() =
inherit MefBootstrapper()
override x.CreateShell() =
let window = Shell()
window.CreateRoot() :> DependencyObject
override x.InitializeShell() =
base.InitializeShell()
App.Current.MainWindow <- x.Shell :?> Window
App.Current.MainWindow.Show()
override x.ConfigureAggregateCatalog() =
let path = @"..\..\..\DirectoryModules"
let dir = new DirectoryInfo(path);
if not dir.Exists then dir.Create()
let catalog = new DirectoryCatalog(path)
x.AggregateCatalog.Catalogs.Add(catalog)

[<STAThread>]
(
new App()).Run()|> ignore

Let’s compare this with Unity file from the previous post. First I have added or renamed some namespaces.


The app class did not change.


The bootstrapper now derives from MefBootstrapper. In this case we override the ConfigureAggregegateCatalog. We add a DirectoryModules directory in case it did not exists. This will prevent the creation of an exception by the DirectoryCatalog in case the directory id not exist. Next we add the catalog to the AggegateCatalog. Mef and Prism are now able to handle dll’s that are put into DirectoryModules directory.


F5 and everything works fine


image


and we have created the directory.


image


Next we create the Hello World Module again by taking the same steps as we did with the Shell. Except this time we create a dll.


image


The xaml of the HelloWorldView is the same as in the Unity example:

<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<
Grid>
<
TextBlock Text="Hello World" Foreground="Green" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Calibri" FontSize="24" FontWeight="Bold"></TextBlock>
</
Grid>
</
UserControl>

Next step is to create the module:

namespace HelloWorldModule

open System
open System.Windows
open System.Windows.Controls
open System.ComponentModel.Composition

open Microsoft.Practices.Prism.Modularity
open Microsoft.Practices.Prism.Regions
open Microsoft.Practices.Prism.Mvvm
open Microsoft.Practices.Prism.MefExtensions.Modularity

open FsXaml

type HelloWorldXaml = XAML<"HelloWorldView.xaml">

[<ModuleExport(typeof<HelloWorldModule>)>]
type HelloWorldModule =
val regionViewRegistry: IRegionViewRegistry
interface IModule with
member
x.Initialize() = x.regionViewRegistry.RegisterViewWithRegion("MainRegion", fun _ -> HelloWorldXaml():> obj)
[<ImportingConstructor>]
new (regionViewRegistry) = {regionViewRegistry = regionViewRegistry}

Some of the namespaces were renamed or added.


This time we need to handle the MEF requirements. We need to add attributes. The class has the ModuleExport attribute. This attribute is provided by Prism:


image


and take care of the registration of the module.


This time we can’t use the default constructor because we need to add the ImportingConstructor attribute to the constructor.


There is no way in F# to add an attribute to the primary constructor.


image


So we need to transfer the constructor and add the attribute to this constructor with the keyword new and use an explicit field regionViewRegistry with the val keyword. In this way MEF is able handle the construction of the class by providing a value for regionViewRegistry.


image


Now we are ready to test the program. If we run it we don’t get the green Hello World. We need to copy


image


to the DirectoryModules.


image


The dll was dynamically loaded and added to the Shell.


If you don’t like to copy the dll by hand every time you change it, you could add  a post-build event


copy /y "$(TargetPath)" "..\..\..\DirectoryModules"


image


and forcing to build the module.


image


When we change the color of Hello World to red, recompilation will show us the result in the correct color.


image


Next time we will have a look at MVVM by adding functionality to the view and the new Prism 5 ViewModelLocationProvider.

4 comments:

Tobias Burger said...
This comment has been removed by the author.
Tobias Burger said...

Actually there is a way to add an attribute to the default constructor:

type HelloWorldModule [<ImportingConstructor>] (registry: IRegionViewRegistry) =

Gene said...
This comment has been removed by the author.
Gene said...

Piet,

Really appreciate this series. There's not much out there on programming Gui's in F#. This is a great example with good clear instructions.

Gene

Total Pageviews