Pokazywanie postów oznaczonych etykietą Prism. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą Prism. Pokaż wszystkie posty

niedziela, 11 marca 2012

Prism na WP7 - Silverlight Unit Test Framework / Mocking

W poprzednim poście przedstawiłem migrację Prisma na WP7. Dziś chciałbym przetestować zmigrowany kod za pomocą testów jednostkowych dołączonych do Prisma. Bardzo fajną sesje Jeffa Wicox'a nt testów jednostkowych na WP7 możemy znaleźć pod tym linkiem. Będziemy używać Silverlight Unit Test Framework i binarek dostępnych tutaj. Przede wszystkim musimy zmigrować projekty testów do projektów Windows Phone Application. Następnie dołączamy wspomniane binarki a code behind MainPage'a powinniśmy zmienić w ten sposób:
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;

}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
SystemTray.IsVisible = false;

var testPage = UnitTestSystem.CreateTestPage() as IMobileTestPage;
BackKeyPress += (x, xe) => xe.Cancel = testPage.NavigateBack();
(Application.Current.RootVisual as PhoneApplicationFrame).Content = testPage;
}

W powyższym kodzie nie dzieje się nic specjalnego. Po prostu ustawiamy stronę z poziomu której wykonujemy testy. Silverlightowa wersja testów Prisma używa Silverlight.Moq.dll. A więc, aby projekt się skompilował musimy dołączyć bibliotekę Silverlight.Moq.dll. Teraz możemy odpalić testy. Ekran powitalny wygląda w ten sposób:

Możemy odpalić wszystkie testy lub wpisać tag, który przefiltruje testy. Radzę nie uruchamiać testów w trybie debug, ponieważ jest to wyjątkowo irytujące jeżeli korzystamy z bibliotek silverlightowych, które nie wiadomo jak się zachowają (np. Silverlight.Moq.dll). Ani razu nie udało mi się dojść do końca testów, gdyż Visual wywalał się mniej więcej w połowie testowania (za chwilę poznamy tego przyczynę). Poradziłem sobie z tym uruchamiając testy bez debugowania CTRL+F5. No i wówczas otrzymałem następujący wynik:

84% testów przeszło. Wszystkie testy, które nie przeszły spowodowała biblioteka Moq, która też była odpowiedzialna za wywalanie się mojego visuala w trybie debug. Za pomocą Silverlight Unit Test Framework możemy normalnie podejrzeć szczegóły testu:

Generalnie na WP7 do tej pory nie można było korzystać z biliotek mockujących. Nie skorzystamy więc z RhinoMock lub Moq gdyż korzystają one z Reflection.Emit, który jest mocno okrojony w Mango (polecam ten topic). Jedynym rozwiązaniem jakie znalazłem jest biblioteka MoqaLate. Jest to "niby"-biblioteka pozwalająca tworzyć mocki pod Mango. Przede wszystkim MoqaLate nie jest żadnym frameworkiem tylko generatorem Mocków. Generuje klasy mocków na podstawie interfejsów projektu. Pakiet należy dodać do normalnego projektu, który będzie testowany czyli jest to w naszym przypadku Prism.Phone. Potem w zdarzeniach post build'a dodajemy linijke która odpala generator. Mocki wygenerują się w wyznaczonym folderze. Jednak jest to na tyle słabe rozwiązanie, że wywala się podczas generacji mocków prisma. Mówiąc szczerze to się zawiodłem :( Jedyne co mi zostało to stworzyć wszystko ręcznie (ale nie w tym odcinku;P). Projekt z testami dostępny jest tutaj - PRISM 4.1 / WP7.

wtorek, 29 listopada 2011

Prism cz. 6 - Interactivity oraz Interaction

Interactions oraz Interactivity są to dwie dll-ki, które poszerzają sposób komunikacji pomiędzy widokiem oraz ViewModelem. Wprowadzają one nowy typ EventTriggerów(w Silverlighcie nie ma triggerów, natomiast w WPF-ie event triggery można praktycznie używać jedynie do animacji) oraz dodatkowo wprowadzają ciekawe sposoby(funkcje) na interakcję widoku z ViewModelem. Pierwszym z tych sposobów jest użycie właściwości InvokeCommandAction.Jak sama nazwa wskazuje można wywołać komendę z ViewModelu. Jaka jest różnica między wywołaniem komendy poprzez InvokeCommandAction, a Command="{Binding CommandName}" ? InvokeCommandAction ma tą przewagę nad normalnm bindowaniem do property Command,że pozwala wywołać daną komendę w odpowiedzi na zajście jakiegoś zdarzenia na widoku. Przykładowe użycie może wyglądać następująco:

<Button Content="Submit" IsEnabled="{Binding CanSubmit}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseMove">
<i:InvokeCommandAction Command="{Binding SubmitCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>

Powyższy przykład działa w następujący sposób. Za każdym razem gdy zostanie wywołane zdarzenie MouseMove na przycisku, odpalona zostanie komenda SubmitCommand. Wadą używania InvokeCommandAction jest to, że sami musimy się martwić o wygaszenie kontrolki w przypadku gdy dana komenda nie może być wykonana. Dlatego też w pierwszej linii mamy bindowanie właściwości IsEnabled przycisku do właściwości CanSubmit z ViewModelu. Alias i: jest aliasen na namespace interactivity z dll-ki

System.Windows.Interactivity.xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity".


Drugim sposobem powiadomiania ViewModelu o jakimś zdarzeniu w widoku jest użycie InvokeMethodAction. Dzięki tej właściwości możemy bezpośrednio z widoku (z XAML-a) wywołać funkcję w ViewModelu. Składnia przedstawia się w następujący sposób:

<Button Content="Submit" IsEnabled="{Binding CanSubmit}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction TargetObject="{Binding}" Method="Submit"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>

TargetObject jest zbindowany do DataContextu, to właśnie tam będzie szukana funkcja Submit. Należy pamiętać, że właściwość CallMethodAction nie wspiera parametrów funkcji. Zatem jeżeli musimy wywołać jakąś funkcję z parametrami, należy opakować ją w funkcję bezparametrową, natomiast wszystkie parametry przekazać z właściwości ViewModelu.

Kolejnym ciekawym featurem Interactivity jest klasa Behavior<T>. Jest niezastąpiona jeżeli potrzebujemy ingerować w działanie widoku po jakimś zdarzeniu. Nie musimy wówczas tworzyć obsługi eventów w code behind. Możemy wyłuskać daną funkcjonalność i zamknąć ją w klasie Behavior a następnie doczepić do wszystkich widoków danego typu. Załóżmy, że sterujemy ListBoxem z poziomu ViewModelu (przełączamy SelectedItem). Możemy zaznaczyć jakiś Item, co jednak jeżeli nasza lista jest bardzo długa,a zaznaczamy ostatni element. Na widoku element rzeczywiście się zaznaczył jednak lista się nie przewinęła. Przez co cały czas wiszą elementy z początku i nie widać czy któryś element jest zaznaczony. Dopiero gdy przeskrolujemy ręcznie ListBoxa widzimy, że rzeczywiście prawidłowo zareagował na zmianę property w ViewModelu. Jest to idealna sytuacja, aby stworzyć własny Behavior, który będziemy mogli podczepić do wszystkich listboxów w każdym widoku.

public class Behavior<ListBox>
{
protected override void OnAttached()
{
AssociatedObject.SelectionChanged += ListBoxSelectionChanged;
}

protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= ListBoxSelectionChanged;
}

private void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is ListBox && ((ListBox)sender).SelectedItem != null)
{
var listBox = sender as ListBox;
listBox.ScrollIntoView(listBox.SelectedItem);
}
}
}

Metoda OnAttached odpala się w momencie, gdy behavior jest podpinany do kontrolki. W metodzie tej powinniśmy podpiąć się pod wszystkie eventy, które są dla nas istotne. W momencie, gdy widok jest niszczony wywołuje się metoda OnDetaching, w której powinniśmy odpiąć wszystkie metody od eventów.

Prism cz.5 - CommandBehaviors

Analizując wcześniejsze przykłady pokazujące użycie DelegateCommand można odnieść wrażenie, że mimo tego iż są one bardzo użyteczne,ich użycie jest niestety bardzo ograniczone.
Jedynie niektóre komponenty WPF-a i Silverlight-a mają właściwość Command do której można zbindować nasz obiekt DelegateCommand.Ponadto właściwość ta reaguje jedynie na wybrane zahardcodowane w kontrolce zdarzenie (zdarzenie Click). Co w przypadku gdybyśmy chcieli zareagować np. na zdarzenie MouseMove za pomocą komendy? Nasz problem możemy rozwiązać poprzez:

  • Interactivity

  • Interactions

  • CommandBehaviors


W tym poście zostanie przedstawione rozwiązanie trzecie - CommandBehaviors

Pierwszą rzeczą jaką należy zrobić w celu stworzenia komendy reagującej na inne zdarzenie niż Click jest stworzenie klasy dziedziczącej po CommandBehaviorBase. Załóżmy, że zrobimy komendę reagującą na zdarzenie MouseMove

public class MouseMoveCommandBehavior : CommandBehaviorBase<Control>
{
private MouseOutCommandBehavior(Control control) : base(control)
{
control.MouseMove += (sender, args) =>
{
this.ExecuteCommand();
};
}
}

Piersza część już za nami, ale pojawia się teraz pytanie jak z tego skorzystać. Żeby mieć możliwość użycia powyżej klasy wykorzytamy attached dependency property. Musimy sobie stworzyć pomocniczą statyczną klasę, w której to zarejestrujemy nasze właściwości. Zacznijmy od zarejestrowania dependency property o nazwie Command - do tego obiektu będziemy bindowali komendy z ViewModelu (komendy reagujące na zdarzenie MouseMove)

public static class MouseMove
{

public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}

public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}

public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(MouseMove), new PropertyMetadata(OnSetCustomCommandCallback));
}

zauważmy, że podłączyliśmy event handlera reagującego na zmianę naszej nowo stworzonej właściwości Command. Funkcja OnSetCustomCommandCallback wygląda następująco:

private static void OnSetCustomCommandCallback(DependencyObject dep, DependencyPropertyChangedEventArgs e)
{
var control = dep as Control;
if (control != null)
{
MouseMoveCommandBehavior behavior = GetorCreateMouseMoveBehavior(control);
behavior.Command = e.NewValue as ICommand;
}
}

Funkcja ta po prostu w razie potrzeby tworzy nowy obiekt typu MouseMoveCommandBehavior (gdy ktoś zbinduje komende) i dopina go do naszej kontrolki. Pomocnicza funkcja GetorCreateMouseMoveBehavior wygląda tak:

private static MouseMoveCommandBehavior GetorCreateMouseMoveBehavior(Control listBox)
{
var behavior = listBox.GetValue(MouseMoveBehaviorProperty) as
MouseMoveCommandBehavior;
if (behavior == null)
{
behavior = new MouseMoveCommandBehavior(listBox);
listBox.SetValue(MouseMoveBehaviorProperty, behavior);
}
return behavior;
}

Obiekt rejestrowany poprzez funkcję GetorCreateMouseMoveBehavior jest dopinany do kolejnej attached property, która również została zadeklarowana w tym pliku

public static MouseMoveCommandBehavior GetMouseMoveBehavior(DependencyObject obj)
{
return (MouseMoveCommandBehavior)obj.GetValue(MouseMoveBehaviorProperty);
}

public static void SetMouseMoveBehavior(DependencyObject obj, MouseMoveCommandBehavior value)
{
obj.SetValue(MouseMoveBehaviorProperty, value);
}
public static readonly DependencyProperty MouseMoveBehaviorProperty =
DependencyProperty.RegisterAttached("MouseMoveBehavior", typeof(MouseMoveCommandBehavior), typeof(MouseMove), null );

Mając zdefiniowane wszystkie powyższe właściwości możemy już wykorzystać naszą komendę. Jednakże przydałoby się dodać jeszcze jedną właściwość (opcjonalnie), mianowicie właściwość CommandParameters

public static object GetCommandParametr(DependencyObject obj)
{
return (object)obj.GetValue(CommandParametrProperty);
}

public static void SetCommandParametr(DependencyObject obj, object value)
{
obj.SetValue(CommandParametrProperty, value);
}

public static readonly DependencyProperty CommandParametrProperty =
DependencyProperty.RegisterAttached("CommandParametr", typeof(object), typeof(MouseMove), new PropertyMetadata(OnSetCustomCommandParameterCallback));

W tym propertisie również reagujemy na przypisnie obiektu.Handler OnSetCustomCommandParameterCallback wygląda następująco

private static void OnSetCustomCommandParameterCallback(DependencyObject dep,
DependencyPropertyChangedEventArgs e)
{
var listBox = dep as Control;
if (listBox != null)
{
MouseMoveCommandBehavior behavior = GetorCreateMouseMoveBehavior(listBox); ;
behavior.CommandParameter = e.NewValue;
}
}

Mając już zdefiniowane wszystkie niezbędne właściwości możemy użyć ich w XAML-u. Po pierwsze musimy dodać alias do namespaca gdzie znajduje się nasza statycza klasa MouseMove. W moim przypadku wygląda to następująco:

xmlns:AttachedCommands="clr-namespace:MainModule.Commands"

Następnie bindujemy jakąś kontrolkę do naszego attached property Command. Wygląda to w następujący sposób:

<Button Name="txtButton" Grid.Column="2" AttachedCommands:MouseMove.Command="{Binding SomeCommandFromViewModel}" ></Button>

Od teraz za każdym razem jak najedziemy na txtButton wywoła się komenda SomeCommandFromViewModel.
Podsumowując trzeba się całkiem sporo napisać żeby zmusić do działania CommandBehaviors . W następnym poście przedstawię dwa sposoby na osiągnięcie tego samego efektu mniejszym kosztem.

Prism cz.4 - Komendy

1. DelegateCommands
DelegateCommands w Prismie są to obiekty, które implementują interfejs ICommand, służą one do interakcji widoku z ViewModelem. Korzystając z Prisma nie musimy już pisać własnej klasy implementującej ten interfejs (tak jak to zrobiliśmy tutaj). Konstruktor klasy DelegateCommand przyjmuje dwa parametry:

  • Action execteMethod - funkcja odpalania przy wywołaniu komendy,

  • Func<bool> canExecuteMethod - funkcja sprawdzająca czy daną komendę można wywołać


DelegateCommands z Prisma używamy w taki sam sposób w jaki używaliśmy komend w tym poście.

2. CompositeCommands
CompositeCommands są to obiekty, które przechowują kolekcją obiektów typu DelegateCommand. Tworzenie CompositeCommand jest bardzo proste.
Po pierwsze musimy stworzyć obiekt typu CompositeCommand

public CompositeCommand SomeCompositeCommand = new CompositeCommand();

następnie rejestrujemy w tym obiekcie obiekt implementujący interfejs ICommand, czyli w naszym przypadku jakiś obiekt typu DelegateCommand. Robimy to w następujący sposób:

DelegateCommand DistributeCommand = new DelegateCommand(DistributeCards);
SomeCompositeCommand .RegisterCommand(DistributeCommand);

w razie potrzeby możemy usunąć komendę z kolekcji komend przy pomocy następującej składni:

SomeCompositeCommand .UnregisterCommand(DistributeCommand);

Mając już stworzony obiekt typu CompositeCommand oraz zarejestrowane w nim obiekty DelegateCommand możemy zastosować bindowanie.



Od teraz, w chwili naciśnięcia przycisku, najpierw sprawdzane jest czy dana CompositeCommand może zostać wykonana. CompositeCommand może zostać wykonana tylko wtedy, gdy wszystkie komendy do niej podpięte mogą zostać wykonane. Jeżeli warunek ten zostanie spełniony następuje wywoływanie po kolei wszystkich metod execute z komend podpiętych do danej CompositCommand. CompositeCommand znajduje zastosowanie w przypadku gdy pozwalamy użytkownikowi na edycję wielu elementów jednocześnie i potrzebujemy w prosty sposób przeprowadzić walidację na poszczególnych widokach.

CompositeCommand może również wykorzystana np. do zapisania tylko aktywnego widoku. Dzięki temu, że DelegateCommand implementuje interfejs IActiveAware, CompositeCommand wie, który widok jest aktualnie zaznaczony i zapisze tylko wybrany widok.

Interfejs IActiveAware wygląda w następujący sposób

public interface IActiveAware
{
bool IsActive { get; set; }
event EventHandler IsActiveChanged;
}


  • bool IsActive - wskazuje czy dana komenda jest aktualnie aktywna

  • event EventHandler IsActiveChanged - informuje o zmianie aktywności komendy


W celu wykorzystania tego interfejsu przez CompositeCommand należy skorzystać z drugiego dostępnego konstruktora tej klasy

CompositeCommand SomeCompositeCommand = new CompositeCommand(true);

Od teraz po naciśnięciu przycisku zbindowanego do tej komendy, wywołana zostanie jedynie aktywna komenda - czyli taka, której właściwość IsActive została ustawiona na true. Niestety o ustawianie aktywności danej komendy musimy zadbać sami.

Prism cz. 3 - Regiony

Regiony są podstawą aplikacji napisanych za pomocą Prism'a. Ułatwiają nawigację po programie i pozwalają na dynamiczną lokację widoków w naszej aplikacji. Regiony dzielą graficzny interfejs użytkownika na obszary, do których wczytywane są odpowiednie widoki. Wystarczy w kodzie wpisać że do Regionu A trzeba załadować Widok B. Prosty podział standardowej aplikacji na regiony może wyglądać tak:

W xaml'u wyglądałoby to mniej więcej w ten sposób:
<Grid ...>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="4*"/>
</Grid.RowDefinitions>
<ContentControl Regions:RegionManager.RegionName="LogoRegion" Grid.ColumnSpan="2"/>
<ContentControl Regions:RegionManager.RegionName="NavigatorRegion" Grid.Row="1"/>
<ContentControl Regions:RegionManager.RegionName="MainRegion" Grid.Column="1" Grid.Row="1"/>
</Grid>

Tak jak widać powyżej regiony definiujemy poprzez AttachedProperty RegionName klasy RegionManager. Od tej pory jeżeli w kodzie wywołamy następującą metodę:
regionManager.Regions["MainRegion"].Activate(view);

w obszarze "MainRegion" pojawi się nasz widok. Oznacza to tyle samo, że właściwość Content naszej ContentControl zostanie wypełniona obiektem "view". Oczywiście regiony mogą być zagnieżdżone jeden w drugim i Prism bez problemu poradzi sobie z taką sytuacją.

Powyższe rozwiązanie aktywacji widoku powinno być opakowane w jakiś obiekt pośredni. Nie zawsze bowiem mamy dostęp do obiektu widoku, w momencie gdy chcemy go aktywować. Naturalny wydaje się poniższy zapis:
ViewService.ActivateView(Type type);
ViewService.ActivateView(string viewName);

W pierwszym zapisie tak czy inaczej musimy mieć dostęp do typu widoku. Drugie rozwiązanie wydaje się bardziej elastyczne. Możemy je zaimplementować bez konieczności dołączania referencji do projektu z interfejsem graficznym. Przykładowy kod może wyglądać tak:
public void ActivateView(string viewName)
{
foreach (IRegion region in regionManager.Regions)
{
object view;
if (RegionContainsViewName(region, viewName, out view))
{
region.Activate(view);
break;
}
}
}

Na koniec mała wskazówka jeżeli chodzi o utrzymanie kodu. Nasz kod może być trudny w utrzymaniu, jeżeli np. chcemy zmienić nazwę regionu dlatego dobrą praktyką jest trzymanie nazw jako stałych. Jest to bardzo proste do rozwiązania w WPF'ie, gdzie możemy się odwoływać do stałych poprzez x:Static. Jeżeli chodzi o Silverlight warto zastosować pewne obejście:
<Grid.Resources>
<ResourceDictionary>
<Resources:Names x:Key="Names"/>
</ResourceDictionary>
</Grid.Resources>
...
<ContentControl Regions:RegionManager.RegionName="{Binding NavigatorRegion, Source={StaticResource Names}}"/>
<ContentControl Regions:RegionManager.RegionName="{Binding MainRegion, Source={StaticResource Names}}"/>

gdzie klasa Names może wyglądać następująco:
public class Names
{
public string MainRegion { get { return "MainRegion"; } }
public string NavigatorRegion { get { return "NavigatorRegion"; } }
}

Prism cz. 2: Moduły

Czym tak naprawdę jest moduł? Moduł jest to zbiór widoków i funkcji aplikacji, które można odseparować od reszty. Moduł może być równie dobrze jedynie serwisem bez graficznego interfejsu użytkownika. Poszczególne moduły powinny móc komunikować się pomiędzy sobą, jednak komunikacja nie powinna naruszać ich niezależności. Sam użytkownik nie powinien mieć świadomości modularności aplikacji, która jest dla niego spójna i łatwa w użyciu.
No dobra, ale po co pisać aplikacje modułowe? Jeżeli stworzyliśmy aplikację modułową zgodnie ze sztuką, wówczas jest ona łatwa do testowania, utrzymania i rozwijania w przyszłości. Można bezproblemu dodawać nowe funkcjonalności nie ingerując w istniejący kod i rozwiązania. Każdy moduł może być osobno testowany wdrażany i rozwijany, ponieważ jest niezależną jednostką.

Bardzo prostym przykładem aplikacji modułowej może być program .NetTurnGames. Jest to aplikacja, która gromadzi w sobie różnego rodzaju gry turowe. Użytkownik po odpaleniu programu wybiera którą grę chce uruchomić. Każda mała gra turowa jest oddzielnym modułem. Wówczas użytkownik w łatwy sposób mógłby dodawać kolejne moduły np. kopiując pliki z nową grą do odpowiedniego katalogu.
Gdybyśmy chcieli ograniczyć się jedynie do gier karcianych, wówczas możemy interfejs użytkownika zamknąć w osobnym module natomiast zasady i logika każdej gry były by osobnym modułem. Podział aplikacji na moduły nie jest trywialnym zadaniem i leży po stronie projektanta aplikacji. Z reguły nie ma sensu wyodrębniać oddzielnego modułu, który byłby osobnym projektem i zawierał tylko jeden plik, a w nim jedną klasę z jedną metodą. Takie postępowanie z reguły prowadzi do nadmiaru projektów i małej czytelności aplikacji.
Powyższy przykład programu .NetTurnGames, powinien mieć wczytywane poszczególne gry w czasie działanie programu (runtime). Można to rozwiązać za pomocą reflekcji - sprawdzać czy odpowiedni moduł implementuje niezbędny interfejs. Jest to przykład wzorca Plugin.
Jednak moduły nie muszą dzielić jedynie aplikacji wg funkcjonalności, równie dobrze można podzielić aplikację warstwową ze względu na warstwy czyli otrzymalibyśmy mniej więcej takie moduły: UIModule, BuisinessLogicModule, DataAccessModule. Duże aplikacje mogą łączyć w sobie oba podejścia czyli dzielić moduły wg funkcjonalności i warstw jednocześnie.
W kontekście .NET doskonałym framework'iem, który ułatwia pisanie takich aplikacji jest Prism. Prism wręcz wymusza na nas, abyśmy pisali aplikacje modułowe.
Zazwyczaj przyjmuje się, że każdy moduł jest oddzielnym projektem (nic jednak nie stoi na przeszkodzie, aby moduł składał się z wielu projektów, lub jeden projekt z wielu modułów).

Każdy moduł powinien zawierać klasę implementującą interfejs IModule, dzięki czemu możemy go zainicjalizować.
public class MyModule : IModule
{
public void Initialize(){}
}

Podczas inicjalizacji, tworzymy wszystkie obiekty niezbędne do poprawnego działania modułu oraz rejestrujemy widoki.
Wszystkie informacje nt. modułów trzymane są w obiekcie klasy ModuleCatalog, który znajduje się w bootstrapperze. Również w bootstrapperze jest metoda, którą musimy przeciążyć, aby dodać przy starcie aplikacji wszystkie potrzebne moduły.
protected override IModuleCatalog CreateModuleCatalog()
{
IModuleCatalog moduleCatalog = new ModuleCatalog();
moduleCatalog.AddModule(typeof(CardGameModule.CardGameModule));
return moduleCatalog;
}

W obiekcie typu ModuleCatalog każdy moduł reprezentowany jest przez obiekt klasy ModuleInfo.
Moduły można wczytywać od razu lub doczytywać je na żądanie. Domyślnie każdy wczytywany jest tak szybko jak to tylko możliwe, natomiast można to zmienić dodając do metody AddModule argument:
moduleCatalog.AddModule(typeof(CardGameModule.CardGameModule), InitializationMode.OnDemand);


Wówczas w momencie gdy chcemy załadować moduł musimy na obiekcie typu IModuleManager wywołać metodę LoadModule
moduleManager.LoadModule("CardGameModule");


Moduły można dodawać również za pomocą pliku xaml. Taka implementacja wygląda wówczas następująco:
<Modularity:ModuleCatalog
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism">
<Modularity:ModuleInfoGroup Ref="CardGameModule.xap" InitializationMode="WhenAvailable">
<Modularity:ModuleInfo ModuleName="CardGameModule" ModuleType="CardGameModule.CardGameModule, CardGameModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</Modularity:ModuleInfoGroup>
</Modularity:ModuleCatalog>

protected override IModuleCatalog CreateModuleCatalog()
{
var moduleCatalog = Microsoft.Practices.Prism.Modularity.ModuleCatalog.CreateFromXaml(new Uri("/MVVMPrismSilverlightv3;component/ModulesCatalog.xaml", UriKind.Relative));
return moduleCatalog;
}

Ponieważ każdy moduł jest niezależną jednostką, zazwyczaj zachodzi potrzeba komunikacji pomiędzy modułami. Jeżeli moduły mają dostęp do tego samego interfejsu (typu) wówczas możemy wstrzyknąć do wielu różnych modułów ten sam obiekt danego typu. Proces wstrzykiwania opisuje pattern Dependency Injection (DI). W prismie możemy domyślnie korzystać z dwóch bibliotek wspierających DI: Unity oraz MEF, jednak nic nie stoi na przeszkodzie, aby podpiąć inną bibliotekę np StructureMap i wykorzystywać jej możliwości w projekcie opartym na PRISM (wykracza to poza temat tego posta). PRISM dostarcza nam konkretnego rozwiązania problemu komunikacji, opartego na patencie DI. Jest to EventAggregator. Jeżeli korzystamy z domyślnych klas PRISMA nie musimy rejestrować EventAggregator'a ponieważ jest on rejestrowany w bazowej metodzie ConfigureContainer() naszego bootstrappera.

Źródło wiedzy:

Developer Guide to Microsoft Prism 4

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Online Project management