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.
If needed add the required references
In this case we added also System.ComponentModel.Components to get access to MEF.
Make sure it is a Windows application:
Next remove, add or rename the files
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
and we have created the directory.
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.
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:
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.
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.
Now we are ready to test the program. If we run it we don’t get the green Hello World. We need to copy
to the DirectoryModules.
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"
and forcing to build the module.
When we change the color of Hello World to red, recompilation will show us the result in the correct color.
Next time we will have a look at MVVM by adding functionality to the view and the new Prism 5 ViewModelLocationProvider.
4 comments:
Actually there is a way to add an attribute to the default constructor:
type HelloWorldModule [<ImportingConstructor>] (registry: IRegionViewRegistry) =
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
Post a Comment