XAML.cz Magazín moderních technologií založených na XAML

Coproject – demo RIA aplikace krok za krokem, díl 3.

Napsáno pro Silverlight od Augustin Šulc [27.01.2011]

V tomto díle začneme tvořit klientskou část aplikace, zapojíme do ní Caliburn.Micro a přidáme připravený styl aplikace.

Původní anglický článek najdete na blogu společnosti Baud.

Příprava projektu

Nejprve bychom si měli připravit strukturu klientského projektu Coproject. Smažte soubor MainPage.xaml a vytvořte složky jménem “Assets”, “ViewModels”, “ViewModels/Interfaces” a “Views”. Okno Solution Explorer by teď mělo vypadat asi takto:

Aby se dal projekt zkompilovat, tevřete soubor App.xaml.cs a odstraňte tělo funkce Application_Startup (zůstala zde reference na objekt typu MainPage, který jsme ale odstranili).

Caliburn.Micro

V Solution Exploreru klikněte pravým tlačítkem na projekt Coproject a přidejte referenci (Add Refrence…) na knihovnu Caliburn.Micro.dll. Ta by se měla nacházet v podložkách /Bin/Release/ tam, kam jste si v prvním díle stáhli a zkompilovali zdrojové kódy Caliburn.Micro. Dejte si pozor, abyste vybrali Silverlight verzi C.M.

Bootstrapper

Přímo do kořene projektu (vedle App.xaml) přidejte novou třídu, pojmenujte ji “AppBootstrapper” a upravte ji, aby byla potomkem třídy Caliburn.Micro.Bootstrapper. Tohle je místo, kde se konfiguruje Caliburn.Micro. První věc, kterou budeme chtít v Caliburn.Micro nakonfigurovat je použití MEF jako Dependency Injection kontejneru. Upravte tedy AppBootstrapper následovně:

public class AppBootstrapper : Bootstrapper
{
private CompositionContainer _container;

protected override void Configure()
{
InitializeContainer();
}


private void InitializeContainer()
{
AggregateCatalog catalog = new AggregateCatalog(
AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());

_container = CompositionHost.Initialize(catalog);

var batch = new CompositionBatch();

IEventAggregator eventAggregator = new EventAggregator();
IWindowManager windowManager = new WindowManager();
eventAggregator.Subscribe(windowManager);

batch.AddExportedValue<IEventAggregator>(eventAggregator);
batch.AddExportedValue<IWindowManager>(windowManager);
batch.AddExportedValue(_container);

_container.Compose(batch);
}

protected override object GetInstance(Type serviceType, string key)
{
string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
var exports = _container.GetExportedValues<object>(contract);

if (exports.Any())
{
return exports.First();
}

throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}

protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return _container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
}

protected override void BuildUp(object instance)
{
_container.SatisfyImportsOnce(instance);
}
}

Aby vše fungovalo, budete do projektu přidat následující reference:

System.ComponentModel.Composition
System.ComponentModel.Composition.Initialization

Určitě bude taky nutné přidat na začátek souboru AppBootstrapper.cs následující řádky:

using System.Linq;
using System.ComponentModel.Composition;

Těch usings budete muset přidat více, ovšem s těmi ostatními vám pomůže Visual Studio:

1. klikněte myší na vlnovkou podtržené slovo, potom najeďte na malý modrý obdélníček vlevo dole:

image

2. Klikněte a potom vyberte řádek “using …”

image

Stejnou věc můžete vyvolat stisknutím CTRL + . (tečka)

Zkuste projekt zkompilovat, mělo by to proběhnout bez problémů.

Asi by se slušelo v krátkosti vysvětlit, co jsme provedli. Funkce InitializeContainer, která se zavolá automaticky po spuštění bootstrapperu (přes Configure) si nejrpve vytvoří katalog všech komponent, které se nachází v aplikaci a následně vytvoří kontejner, do kterého informace o všech těchto komponentách umístí. V závěru do něj ještě ručně přidá další užitečné komponenty aplikace (EventAggregator a WindowManager).

Funkce GetInstance a GetAllInstances jsou potom místa, kam se Caliburn.Micro obrátí, pokud potřebuje vytvořit instanci nějaké komponenty, případně pokud ho o tu instanci sami požádáme (přes Caliburn.Micro.IoC).

Poslední funkce BuildUp do již vytvořené instance doplní všechny potřebné závislosti.

Tím bychom tedy měli nakonfigurován C.M, aby použil MEF, který je nastaven tak, že si všechny komponenty najde sám v aktuálně spuštěné aplikaci. Jistě jste si všimli, že C.M nemusí nutně využívat MEF, nebo nějakou jinou implementaci DI/IoC kontejneru – můžete využít jakýkoliv kontejner budete chtít (Unity, Castle Windsor, Ninject, apod.), případně se spokojit s původním využitím Activatoru bez DI schopností.

Pozn. Pokud byste si náhodou nebyli úplně jisti tím, k čemu se hodí Dependency Injection kontejner, pokusím se to zjednodušeně shrnout:

Dependency Injection

Většina aplikací se skládá z různých komponent. Řekněme, že v naší aplikací na odesílání zpráv budeme mít tyto komponenty dvě: MailSender a MailFormatter. MailSender bude ke své správné funkci odeslání zprávy potřebovat tuto nejprve zformátovat a k tomu využije MailFormatter. Nejpřímějším způsobem je tedy si přímo v těle MailSender vytvořit instanci formátovací komponenty:

...
MailFormatter formatter = new MailFormatter;
...

Jistě se vám taky nelíbí, že MailSender je nyní pevně vázán na MailFormatter. Co kdybychom chtěli formatter vyměnit za jiný? (Ano, na jednom místě v kódu je to zatím ok - prostě to změním a překompiluju- ale v případě větší aplikace už se může stát, že na některé místo zapomenu, případně pokazím něco jiného.) Co kdybychom chtěli používat různé formattery v závislosti na jiných podmínkách? Co kdybychom dokonce chtěli MailSender (unit) testovat bez závislosti na ostatních částech systému?

Nejjednodušším řešením nejspíš bude vytvořit interface IMailFormatter a nechat MailSender, aby správnou instanci formatteru dostal od toho, kdo ho vytváří. Vytváření senderu bude vypadat například takto:

var sender = new MailSender(new MailFormatter);

Díky tomu budeme schopni senderu předat přesně ten správný formatter, který má používat. Zde se používají typicky dva způsoby předání – buď přes konstruktor, jak je vidět výše, nebo přes nastaveni property:

public class MailSender
{
	...

	public IMailFormatter Formatter { get; set; }

	...

	public void SendMail(string message)
	{
		...
		string formattedMessage = Formatter.Format(message);
		...
	{
}

Potud je vše v pořádku, vyřešili jsme problémy předchozího řešení, bohužel jsme si zkomplikovali vytváření komponent – musíme jí vždy dodat všechny její závislosti, které však mohou mít samy vlastní závislosti a podobně. A tady přicházejí DI kontejnery, aby nám pomohly – DI kontejner funguje v podstatě tak, že mu řeknete o všech komponentách, které budete používat. Potom, když potřebujete nějakou komponentu vytvořit, “řekne si o ni” kontejneru, ten zjistí, jestli vaše komponenta vyžaduje nějaké závislosti a pokud ano, pokusí se je doplnit z těch komponent, o kterých ví. Tak je kontejner schopný doplnit celý strom závislostí. Snad se mi alespoň částečně podařilo tento koncept osvětlit – a pokud ne, tak vězte, že až to využijeme v praxi, snadno pochopíte.

Shell

Když už máme nastavený DI kontejner i Caliburn.Micro samotný, je načase vytvořit nějaké uživatelské rozhraní. Každá část uživatelského rozhraní se bude skládat ze dvou hlavních částí:

ViewModel – obsahuje vlastní logiku aplikace, komunikuje s datbází a podobně (jednoduše ta část, kterou budeme psát v C#)

View – definuje pouze vzhled a způsob zobrazení dat, které ViewModel připravil (takže to, co bude v .xaml souborech)

Hlavní okno aplikace se bude v našem případě nazývat “Shell”. Ve ViewModels/Interfaces vytvořte nový interface “IShell”. Nepřidávejte do něj žádné definice, využijeme ho pouze k označení opravdové implementace Shellu. Do složky ViewModels potom přidejte novou třídu “Shell” s následujícím obsahem":

[Export(typeof(IShell))]
public class ShellViewModel : Screen, IShell
{
}

Všimněte si atributu [Export]. Právě ten říká MEFu, že toto je komponenta typu IShell. Až si tedy někdo řekne o komponentu typu IShell, MEF mu předá ShellViewModel.

Když máme ViewModel, musíme si k němu připravit i View. Do složky Views přidejte nový Silverlight User Control jménem “ShellView”. Struktura projektu by teď měla vypadat takto:

Dovnitř view pak přidejte TextBlock s textem “Coproject”. To by mělo stačit k tomu, abychom poznali, že se nám zobrazilo správně view.

Příprava ke spuštění

Když už máme připravený shell, zbývají nám ještě dvě věci, aby se nám shell zobrazil: nastavit C.M, aby věděl, jaký shell má použít, a zařídit, aby se náš AppBootstrapper automaticky spustil při spuštění aplikace.

Nastavení shellu je velice snadné – upravte AppBootstrapperu, aby bázovou třídou nebyl Bootstrapper, ale Bootstrapper<IShell>.

Spuštění bootstrapperu se dá také zařídit velice elegantně – přidáme ho do resources celé aplikace, vše ostatní se zařídí “samo” (rozumějte Caliburn.Micro). Upravte tedy soubor App.xaml takto:

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
			 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
			 xmlns:app="clr-namespace:Coproject"
			 x:Class="Coproject.App">
	<Application.Resources>
		<app:AppBootstrapper x:Key="AppBootstrapper" />
	</Application.Resources>
</Application>

To je vše! Zkontrolujte, že Coproject.Web se spustí jako hlavní projekt celé solution (pokud není název projektu v Solution Exploreru tučným písmem, klikněte na něj pravým tlačítkem a vyberte Set as StartUp Project). Spusťte aplikaci. Mělo by se vám zobrazit ShellView:

Za povšimnutí jistě stojí, že ačkoliv jsme C.M řekli, jaký shell má použít, o tom, jaké view k danému shellu náleží, už nikde nepadlo ani slovo. Za to může opět C.M a jeho filozofie ”Convention over Configuration”, což znamená že místo psaní spousty řádků složité konfigurace stačí, abyste to udělali tak nějak “správně” (většinou jde o správné pojmenování) a vše bude fungovat. Spoustu dalších konvencí C.M uvidíme později.

Styly

Na závěr bych chtěl, aby naše aplikace vypadala trochu lépe. Ve složce Assets vytvořte novou složku “Cosmopolitan” a vložte do ní všech sedm souborů z tohoto archivu.

Do klientského projektu pak ještě přidejte reference na tyto části:

System.Windows.Controls
System.Windows.Controls.Data
System.Windows.Controls.Data.Input
System.Windows.Controls.Data.DataForm.Toolkit
System.Windows.Controls.DataVisualization.Toolkit
System.Windows.Controls.Input
System.Windows.Controls.Input.Toolkit
System.Windows.Controls.Layout.Toolkit
System.Windows.Controls.Navigation
System.Windows.Controls.Toolkit

Abychom přinutili aplikaci tyto styly používat, musíme je zaregistrovat do resources. Upravte tedy App.xaml takto:

...
<Application.Resources>
	<ResourceDictionary>
		<ResourceDictionary.MergedDictionaries>
			<ResourceDictionary Source="Assets/Cosmopolitan/Styles.xaml"/>
			<ResourceDictionary Source="Assets/Cosmopolitan/CoreStyles.xaml"/>
			<ResourceDictionary Source="Assets/Cosmopolitan/SDKStyles.xaml"/>
			<ResourceDictionary Source="Assets/Cosmopolitan/ToolkitStyles.xaml"/>
			<ResourceDictionary Source="Assets/Cosmopolitan/Custom.xaml"/>
			<ResourceDictionary>
				<app:AppBootstrapper x:Key="AppBootstrapper" />
			</ResourceDictionary>
		</ResourceDictionary.MergedDictionaries>
	</ResourceDictionary>
</Application.Resources>
...

Na závěr ještě upravte ShellView takto:

<Border x:Name="LayoutRoot" Style="{StaticResource ContentBorderStyle}">
<Grid>
<Border Style="{StaticResource LeftBorderStyle}"/>
<ContentControl Style="{StaticResource LogoIcon}"/>

<TextBlock Text="Coproject" Style="{StaticResource ApplicationTitleStyle}" />
</Grid>
</Border>

Když teď aplikaci spustíte, měla by vypadat takto:

Komentáře

ukládám komentář, vyčkejte prosím..
  1. Buďte první, kdo napíše komentář.

@xamlcz

  • RT @jvanrhyn: XAML, It's a bit like olives. Takes a while to get used to. But once you're used to it. It is actually pretty good. <3 XAML
  • RT @moser_christian: WPF Inspector 0.9.7 is released. It supports .NET 3.5 and 4.0 The project is now open source and available on CodeP ...
  • Jeff Handley oznámil vydání WCF RIA Services v.1.0 SP1 RTM http://bit.ly/gOgckn ke stažení na http://bit.ly/gVAXdK
  • jedna výzva pro Brno. Byl někdo z vás na přednášce o RIA v MS Akvárku? Dejte o sobě vědět. Děkuji
  • také jste uvažovali o tom, že zkusíte na projekt použít Caliburn Micro nebo naopak Prism 4? A co tak obojí, šlo by to nebo ne? Již brzy