0 Comments
Mediator

U izradi Silverlight (ili WPF, WinForms…) aplikacija, često se javlja potreba za komunikacijom različitih dijelova aplikacije, poput različitih User Control-a, instanciranih objekata i slično. Najbolje da jednim primjerom objasnim problematiku: za izradu Silverlight aplikacije za praćenje riječkog karnevala (izrađeno 2008., nadam se da je još online!) morao sam implementirati "pametno" sučelje, koje ispisuje informacije o trenutnoj skupini koja prolazi, označiti ju na listi, i još par sitnica. Te informacije sam dobivao preko web servisa u lokalni "provider" podataka, i pomoću evenata/delegata u kodu se predplaćivao na taj "providera" i slao potrebne objekte svim pretplatnicima na evente. Kako se sučelje sastoji od dosta user controla i custom controla, orkestracija pretplate prilikom starta aplikacije odnosno kreiranja objekata je bila relativno kompleksna. Na kraju izrade bilo mi je jasno da to i nije nabolje rješenje i da mora postojati jednostavniji način. Nedugo potom sam naišao na jedan design pattern, za koji sam znao da postoji ali ga nikada nisam imao priliku iskoristiti, pretpostavljam zbog samom načina rada web aplikacija. Radi se o Mediator patternu, koji definira komunikaciju između objekata.

Mediator UML sequenceJedan od mojih prethodnih blog postova objašnjava izradu ViewModela, prema Model-View-ViewModel patternu. Mediator pattern se savršeno uklapa u naš ViewModel, stoga ćemo iskoristiti MVVM i dodati potrebne klase. 
U svojoj osnovi, stvar ovako funkcionira: objekti se pretplate (Subscribe(tip poruke)) na određene poruke Mediator objektu (MessageGateway u primjeru). Ti objekti mogu i poslati poruku (Publish(ChatMessage)) Mediator objektu, koji potom prosljeđuje poruku svim pretplatnicima koji su se pretplatili na taj tip poruke (ReceiveMessage(poruka)).[more]
Naša mediator klasa:

public class MessageGateway
{
    private static MessageGateway gateway;

    /// <summary>
    /// Singleton instanca MessageGateway objekta
    /// </summary>
    /// <value>Statična MessageGateway instanca</value>
    public static MessageGateway Instance
    {
        get
        {
            if(gateway==null)
                gateway=new MessageGateway();
            return gateway;
        }
    }

    /// <summary>
    /// Lista pretplatnika na tipove poruka; svaki tip poruke moze imati vise pretplatnika
    /// </summary>
    private Dictionary<Type,List<WeakReference>> subscribers;

    /// <summary>
    /// Pretplata na specifičnu poruku
    /// </summary>
    /// <param name="receiver" />Pretplatnik</param>
    /// <param name="message" />Tip poruke</param>
    public void Subscribe(IReceiver receiver,Type messageType)
    {
        if(messageType!=typeof(MessageBase) && !messageType.IsSubclassOf(typeof(MessageBase)))
            throw new ArgumentException("Parametar mora biti tipa MessageBase","messageType");

        if(subscribers==null)
            subscribers=new Dictionary<Type,List<WeakReference>>();

        if(!subscribers.ContainsKey(messageType))
            subscribers.Add(messageType, new List<weakreference>());  
         
        subscribers[messageType].Add(new WeakReference(receiver));
    }

    /// <summary>
    /// Objava poruke svim pretplatnicima
    /// </summary>
    /// <param name="message" />Poruka</param>
    public void Publish(MessageBase message)
    {
        if (subscribers == null) return;
        
        foreach(Type type in subscribers.Keys)
        {
            if(message.GetType() == type || message.GetType().IsSubclassOf(type))
            {
                foreach (var reference in subscribers[type])
                {
                    if (reference.IsAlive && reference.Target != null)
                        ((IReceiver)reference.Target).ReceiveMessage(message);
                }
            }
        }
    }
}
WeakReference: tip reference na objekt, koja dopušta Garbage Collectoru da uništi objekt iako je referenciran ovim tipom reference. Sa IsAlive provjeravamo dali referencirani objekt postoji
Dvije su glavne metoda u ovoj klasi: Subscribe i Publish. Objašnjenje:
Subscribe: svaki objekt koji se želi pretplatiti na poruke mora implementirati IReceiver interface. On mu omogućava primanje poruka. Metoda interno koristi Dictionary, gdje je za svaki ključ (Type, tip poruke), vezana lista pretplatnika. Ako ključ ne postoji, on se dodaje u Dictionary, kreira nova lista, i na kraju dodaje referenca (WeakReference) pretplatnika.
Publish: u Dictionaryu pretplatnika (nevezano, ali zar slicni takav dictionary sa tipovima nema i IoC kontejner?) se traže pretplatnici na određeni tip poruke, i svakom pretplatniku onda se šalje ta poruka. Ako se netko pretplatio na baznu klasu poruke (MessageBase), dobiti će sve poruke, jer sve poruke nasljeđuju baznu poruku. Logično, zar ne;)?
IReceiver interface koji moraju implementirati objekti koji žele dobivati poruke je jednostavan, kao i bazna MessageBase klasa:

public interface IReceiver
{
    void ReceiveMessage(MessageBase messsage);
}

public abstract class MessageBase
{
    public MessageBase(object sender)
    {
        Sender = sender;
    }

    public object Sender { get; protected set; }
}

/// <summary>
/// Primjer implementacije poruke
/// </summary>
public class ChatMessage : MessageBase
{
    public ChatMessage(object sender, string text) : base(sender)
    {
        Text = text;
    }

    public string Text { get; private set; }
}

Bazna ViewModel klasa, koju nasljeđuju sve ViewModel klase, sadrži logiku pretplate, primanja i slanja poruka mediator objektu:

public abstract class ViewModelBase : INotifyPropertyChanged, IReceiver
{
    protected void Subscribe(Type messageType)
    {
        MessengerInstance.Subscribe(this,messageType);
    }

    protected void Publish(MessageBase message)
    {
        MessengerInstance.Publish(message);
    }
        
    private MessageGateway MessengerInstance
    {
        get { return MessageGateway.Instance; }

    }

    public virtual void ReceiveMessage(MessageBase messsage) { }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void RaisePropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

kao što je vidljivo iz primjera, ViewModelBase apstraktna klasa ujedno je i IReceiver, znači se može pretplatiti na primanje poruka.
Da bi pretplatili na određene poruke, dovoljno je dodati u konstruktor našeg ViewModel-a (npr. DashboardViewModel, koji nasljeđuje ViewModelBase):

Subscribe(typeof(ChatMessage));

Za primanje poruka napravimo override ReceiveMessage metode:

public override void ReceiveMessage(MessageBase messsage)
{
    var chatmsg=(ChatMessage)message;
    // operacije nad primljenom porukom
}

Primjer slanja nove poruke:

Publish(new ChatMessage(this,"neki text"));

Cijeli projekt možete i skinuti lokalno, pa će sama implementacija biti jasnija, nadam se. 

Čisto za informaciju, slična arhitektura i način slanja poruka se koristi i u Service Bus frameworcima, pogodnim za SOA arhitekturu. Microsoft nema svoj ServiceBus framework (osim promoviranja JBOWS!), za razliku od Java svijeta, gdje su oni dosta popularni u enterprise primjeni. Bitna razlika od ove implementacije je asinkroni način slanja poruka putem Queue (MSMQ uglavnom), kojeg u ovom primjeru nema (sve je sinkrono, nema “reda” poruka i enkapsuliranja u transakcije), kao i “broker” objekt koji može sadržavati određenu logiku koja određuje kome se mora poslati koja poruka ovisno o danim parametrima. Dva popularna open source frameworka u .NET svijetu su nServiceBus i MassTransit, a uskoro se nadam da ću imati prilike i pozabaviti se i njima, nakon čeka naravno slijedi blog osvrt!

Download Silverlight projekta sa primjerom Mediator i MVVM patterna
(Download of Silverlight project with example of Mediator and MVVM patterns)

9 Comments
MVVM Silverlight WPF

Silverlight sa svojim codebehind konceptom je pogodan, štoviše i preporučen, za implementaciju nekog od patterna za separaciju poslovne logike i logike za pristup podacima. Od njegovog izlaska, zbog bliskosti sa WPF modelom, programeri su preuzeli MVVM (Model View ViewModel) pattern (obrazac?) koji je popularan u WPF svijetu, sa određenim preinakama. Uz MVC (Model View Controller) i MVP (Model View Presenter) patterne koji su svoju populatnost stekli u web aplikacijama, odnosno stateless okruženju, MVVM je posebno pogodan za Silverlight/WPF sučelja jer koristi njihovu bogatu mogućnost Data Bindinga, odnosno deklarativnog povezivanja objekata u XAML kodu.

MVVM se sastoji od tri komponente:
1. View: XAML sa svojim code behindeom. U XAMLu deklarativno povežemo propertye UI elemenata, a u code behindeu zbog nemogućnosti nativnog povezivanja za UI evente (Command Binding u WPFu), pozivamo metodu od ViewModela. Ovo se može zaobići dodavanje MVVM toolkit (verzija sa codeplexa i od GalaSofta) ili SilverlightFX frameworka u projekt.

2. ViewModel: klasa koja sadrži podatke potrebne za renderiranje sučelja, kao i metode za operacije nad modelom. Da bi sučelje reagiralo na promjeno stanja određenih propertya, potrebno je da ViewModel klasa implementira INotifyPropertyChanged interface.  U prijevodu, kada mi promijenimo vrijednost propertya u ViewModelu, želimo da se sučelje automatski osvježi sa novom vrijednosti. Povezivanje mora biti dvosmjerno, što znači da promjenom podataka na sučelju se mijenja i sadržaj u ViewModel objektu – potrebno za spremanje podataka na server/bazu/gdjegod

3. Model: objektni model podataka naše domene. Povezivanjem na WCF servis (ili “stari” ASMX web servis), Visual Studio generira proxy klase za komunikaciju, i potpunu presliku klasa koje web servis daje. Ako smo izradili LINQ2SQL ili Entity Framework klase, te iste klase će biti dostupne i u Silverlightu!

Krenimo u izradu jednostavne Silverlight MVVM aplikacije. Zadatak je prikazati formu za editiranje podataka. [more] Aplikacija će sadržavati ove klase:


Model klasa Person
:

public class Person
{
    public string Ime { get; set; }
    public string Prezime { get; set; }
}

Napomena: obično i model klase implementiraju INotifyPropertyChanged interface, da bi se preko ViewModel klase promjene dojavile i View-u (naravno ako ViewModel ima property tipa Person)!


ModelCatalog
: klasa za dohvat podataka sa servera koju ViewModel koristi preko interfacea. Pošto se radi o asinkronom dohvatu, ViewModel se mora pretplatiti na event koji će biti podignut kada podaci stignu sa servera.

public interface IModelCatalog
{
    void GetPerson();
    void SavePerson(Person person);
    event EventHandler<PersonEventArgs> PersonLoadCompleted;
}

ViewModel pozove GetPerson() i pretplati se na PersonLoadCompleted event, te primi učitane podatke preko PersonEventArgs argumenta:

public class PersonEventArgs : EventArgs
{
    public Person Data { get; private set; }
    public PersonEventArgs(Person result)
    {
        Data=result;
    }
}

da ne ulazimo u izradu web servisa i njegovo povezivanje sa Silverlightom, napraviti ćemo jedan “lažni” katalog podataka koji simulira komunikaciju sa web serverom:

public class FakeCatalog : IModelCatalog
{
    public void GetPerson()
    {
        // tu ide asinkroni poziv web servisa
        var person=new Person() { Ime="John", Prezime="Skeet" };
        var argument=new PersonEventArgs(person);

        // kada servis vrati podatke, dojavimo pretplaćenim metodama:
        if(PersonLoadCompleted!=null)
            PersonLoadCompleted(this,argument);
    }
    public void SavePerson(Person person)
    {
        // implementacija poziva web servisa i spremanje podataka
    }   

    public event EventHandler<PersonEventArgs> PersonLoadCompleted;
}

ViewModel:  klasa koju ćemo bindati za View, stoga mora sadržavati sve potrebne podatke za prikaz sučelja. U svom konstruktoru prima implementaciju IModelCatalog i poziva metodu za učitavanje. Također, sadrži implementaciju INotifyPropertyChanged interfacea.
Napomena: za potrebe ovog primjera, napraviti ćemo da se objekt Person, koji se dobije iz IModelCatalog-a, mapira na nekoliko propertya ViewModel klase.

public class MainViewModel : INotifyPropertyChanged
{
    private IModelCatalog modelCatalog;

    public MainViewModel(IModelCatalog mc)
    {
        modelCatalog=mc;
        modelCatalog.PersonLoadCompleted += (sender,args)=>
        {
            this.FirstName = args.Data.Ime;
            this.LastName  = args.Data.Prezime;
        }
        modelCatalog.GetPerson();
    }
    
    private string _name;
    public string FirstName
    {
        get { return _name; }
        set { _name=value; NotifyChange("FirstName"); }
    }

    private string _lastname;
    public string LastName
    {
        get { return _lastname; }
        set { _name=value; NotifyChange("LastName"); }
    }

    public void Save(object sender,EventArgs e)
    {
        var person=new Person() { Ime=this.FirstName, Prezime=this.LastName };
        modelCatalog.Save(person);
    }

    // Implementacija INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    public void NotifyChange(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

}

Naš ViewModel sada implementira propertye koje ćemo bindat na sučelje, kao i metode koje ćemo povezati s eventima od UI elemenata sučelja.

View (MainPage.xaml.cs): za povezivanje ViewModela za sučelje koristimo DataContext svojstvo od parcijalne codebehind klase.
Osim za povezivanje našeg ViewModela za View (XAML) i osnovne interakcije između View elemenata (prepisivanje propertya Button i slično), u codebehindeu se ne smije nalaziti nikakav kod koji uključuje poslovnu logiku, logiku pristupa podacima, manipulacije nad podacima i slično!

public partial class MainPage : UserControl
{
    private MainViewModel viewModel;

    public MainPage()
    {
        InitializeComponent();
        this.Load += MainPage_Loaded;
    }   
    
    public void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        // injektiranje IModelCatalog objekta može ići preko nekog IoC/DI frameworka ili posebnog “locator” objekta!
        viewModel=new MainViewModel(new FakeCatalog());

        // povezivanje XAML UIa sa našim objektom
        this.DataContext = viewModel;
        SaveButton.Click += viewModel.Save;
    }
}

U View-u (MainPage.xaml), odnosno XAMLu, bindamo UI elemente za propertye odnašeg ViewModela (primjer djelomičnog koda, bez definiranja UserControl elementa, xml namespace definicija) :

<TextBox Text="{Binding FirstName, Mode=TwoWay}" />
<TextBox Text="{Binding LastName, Mode=TwoWay}" />
<Button x:Name="SaveButton" Content="Spremi"/> 

ovo je naravno primjer XAMLa bez stilova, tako da ga svakako treba doraditi da izgleda imalo upotrebljivo. Bitno je da prilikom bindanja navedemo da dvosmjerni tip (Mode=TwoWay).
Ovime smo završili implementaciju MVVM patterna u našu malu Silverligh aplikaciju, i s time odvojili kod po ulogama i omogućili lakše održavanje i testiranje. U skorije vrijeme se nadam da ću dodati i automatsko bindanje metoda iz ViewModela za View evente (Button click), čime bi mogli izbjeći cijeli codebehind! Nadam se uskoro, čim pronađem najjednostavniji način bez upotrebe trećih frameworka. Ili naravno, vi predložite implementaciju tog featura!?