ASP.Net e Pattern Observer in Pratica – Quarta Parte

In questo post, come promesso in ASP.Net e Pattern Observer in Pratica – Terza Parte, utilizzeremo il pattern observer per assolvere al requisito prensentato nel primo post della serie ASP.Net e Pattern Observer in Pratica.
Allaciate le cinture che questa volta saremo un po’ lunghetti 🙂

Ricordiamo il Requisito

Implementazione di una pagina di ricerca dove sono presenti alcuni web user controls tra i quali uno in particolare si occupa di raccogliere l’input dell’utente per effettuare una ricerca tramite la pressione di un pulsante apposito (presente sullo stesso user control), l’operazione di ricerca deve in qualche modo essere notificata agli altri user controls presenti sulla stessa pagina che utilizzando gli stessi valori inseriti nel primo controllo dovranno filtrare e renderizzare i dati di dettaglio.

Ciò che implementeremo

Una serie di classi necessarie alla rappresentazione di un repository di Clienti, un unica pagina contenente due web user control uno per gestire l’operazione di ricerca (il pubblicatore) l’altro per la rappresentazione a video della lista filtrate di clienti (il sottoscrittore). Il controllo web che presenterà i dati filtrati riceverà da parte del controllo di ricerca una notifica ogni volta che l’utente modificherà i valori del filtro ed effettuerà una nuova ricerca. Insieme alla notifica viaggeranno anche i valori del filtro impostato da un controllo all’altro, il tutto senza scomodare in alcun modo la sessione.

Di seguito la screenshot dell’interfaccia web

DesignPattern - Esempio del pattern Observer

Per prima cosa andiamo a definire la classe “Customer”, che rappresenta il nostro modello

namespace DesingPattern.Sample.Observer.Model
{
    /// <summary>
    /// Rappresenta l'entità Cliente.
    /// </summary>
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public string City { get; set; }
    }
}

e la relativa “CustomerRepository” (che tramite alcuni metodi statici, vi ricordo che siamo in un esempio, ci fornirà la lista dei Clienti):

namespace DesingPattern.Sample.Observer.Data
{
    /// <summary>
    /// Rappresenta il repository per le entità Cliente.
    /// </summary>
    public class CustomerRepository
    {
        /// <summary>
        /// Restituisce la lista completa di tutti i Clienti.
        /// </summary>
        /// <returns></returns>
        public static IQueryable<Customer> GetAllCustomers()
        {
            List<Customer> customers = new List<Customer>();
            customers.Add(new Customer() { 
                Id = 1, 
                Surname = "Santaniello", 
                Name = "Ernesto", 
                City = "Napoli" 
            });
            customers.Add(new Customer() { 
                Id = 2, 
                Surname = "Tispo", 
                Name = "Armando", 
                City = "Napoli" 
            });
            // ecc..
            return customers.AsQueryable().OrderBy(c => c.Surname);
        }

        /// <summary>
        /// Restituisce una lista di clienti filtrata.
        /// </summary>
        /// <param name="filter">Rappresenta il filtro di ricerca.</param>
        /// <returns></returns>
        public static IQueryable<Customer> GetCustomers(CustomerFilter filter)
        {
            if (filter is NullCustomerFilter)
                return CustomerRepository.GetAllCustomers();

            if(filter.UseLikeFilter)
                return CustomerRepository.GetAllCustomers().Where(c =>
                        (string.IsNullOrEmpty(filter.Name) || c.Name.ToLower().Contains(filter.Name.ToLower()))
                        && (string.IsNullOrEmpty(filter.Surname) || c.Surname.ToLower().Contains(filter.Surname.ToLower()))
                        && (string.IsNullOrEmpty(filter.City) || c.City.ToLower().Contains(filter.City.ToLower()))
                    );
            else
                return CustomerRepository.GetAllCustomers().Where(c =>
                    (string.IsNullOrEmpty(filter.Name) || c.Name.Equals(filter.Name, StringComparison.OrdinalIgnoreCase))
                    && (string.IsNullOrEmpty(filter.Surname) || c.Surname.Equals(filter.Surname, StringComparison.OrdinalIgnoreCase))
                    && (string.IsNullOrEmpty(filter.City) || c.City.Equals(filter.City, StringComparison.OrdinalIgnoreCase))
                );
        }
    }
}

La classe “CustomerRepository” nel metodo “GetCustomers” accetta in ingresso un oggetto di tipo “CustomerFilter” (utilizzato per aggregare le informazioni di filtro) implementata come segue

namespace DesingPattern.Sample.Observer.Model
{
    /// <summary>
    /// Rappresenta il filtro di ricerca per i Clienti.
    /// </summary>
    public class CustomerFilter
    {
        public string Name { get; set; }
        public string Surname { get; set; }
        public string City { get; set; }
        public bool UseLikeFilter { get; set; }
    }

inoltre, utilizzando le nozioni alla base del pattern “NullObject” implementiamo anche la classe “NullCustomerFilter” che in questo caso si riduce ad essere una classe vuota che eredita da “CustomerFilter”

namespace DesingPattern.Sample.Observer.Model
{
    /// <summary>
    /// Rappresenta il filtro vuoto di ricerca per i Clienti.
    /// </summary>
    public class NullCustomerFilter : CustomerFilter
    {
    }
}

L’implementazione d’esempio del pattern observer l’abbiamo già vista, ma rispetto a quella dobbiamo aggiungere qualcosa che permetta ai sottoscrittori di ricevere dei dati durante la fase di notifica. A tale scopo modifichiamo la firma del metodo “Update” aggiungendo a questa un parametro in input (del tipo a noi necessario) così da poter passare le informazioni al sottoscrittore durante la chiamata al metodo “Notify” del pubblicatore:

Nota
E’ di estrema importanza comprendere che modificare la firma del metodo Update non vuol dire modificare la struttura del Pattern Observer, i pattern identificano il disegno della soluzione da implementare e non la loro esatta implementazione.

Se a questo punto siamo abbastanza furbi da utilizzare i generics possiamo costruire un interfaccia generica per qualsiasi tipo di sottoscrittore in questo modo:

namespace DesingPattern.Sample.Observer.Interfaces
{
    /// <summary>
    /// Interfaccia da implementare in tutti i sottoscrittori.
    /// </summary>
    public interface IObserver<T>
    {
        /// <summary>
        /// Aggiorna lo stato interno del sottoscrittore quando riceve 
        /// l'evento di notifica dal pubblicatore.
        /// </summary>
        /// <param name="data">Dati da passare durante la notifica.</param>
        void Update(T data);
    }
}

Nota
Attenzione a partire dalla versione 4.0 del Framework .Net l’interfaccia <a title="Interfaccia System.IObserver” href=”http://msdn.microsoft.com/it-it/library/dd783449(v=vs.100).aspx” target=”_blank”>IObserver<T> è già presente all’interno del namespace System quindi attenti agli using.

Allo stesso modo, sempre utilizzando i generics, modificheremo l’interfaccia ISubject come segue:

namespace DesingPattern.Sample.Observer.Interfaces
{
    /// <summary>
    /// Interfaccia da implementare sul pubblicatore.
    /// </summary>
    interface ISubject<T>
    {
        /// <summary>
        /// Registra un sottoscrittore all'evento di notifica. 
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        void Attach(IObserver<T> o);

        /// <summary>
        /// Rimuove un sottoscrittore dall'evento di notifica.
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        void Detach(IObserver<T> o);

        /// <summary>
        /// Notifica a tutti i sottoscrittori il cambio di stato.
        /// </summary>
        void Notify();
    }
}

A questo punto andiamo ad implementare una classe astratta che rappresenti la versione base del controllo web di ricerca che erediti da System.Web.UI.Usercontrol

namespace DesingPattern.Sample.Observer.WebUserControls
{
    /// <summary>
    /// Controllo di filtro base.
    /// </summary>
    /// <typeparam name="T">Tipo di filtro.</typeparam>
    public abstract class FilterControlBase<T> : System.Web.UI.UserControl, Interfaces.ISubject<T>
    {
        private T _filter;
        /// <summary>
        /// Lista interna degli sottoscrittori registrati all'evento di notifica.
        /// </summary>
        private List<Interfaces.IObserver<T>> _observers = new List<Interfaces.IObserver<T>>();

        /// <summary>
        /// Restituisce o imposta il valore del filtro.
        /// </summary>
        public T Filter
        {
            get { return _filter; }
            set
            {
                _filter = value;
                // Ogni volta che il valore del filtro cambia
                // viene spedita la notifica ai sottoscrittori.
                this.Notify();
            }
        }

        /// <summary>
        /// Sottoscrive un osservatore. 
        /// </summary>
        /// <param name="o">Riferimento al sottoscrittore.</param>
        public void Attach(Interfaces.IObserver<T> o)
        {
            _observers.Add(o);
        }

        /// <summary>
        /// Rimuove un osservatore.
        /// </summary>
        /// <param name="o">Riferimento al sottoscrittore</param>
        public void Detach(Interfaces.IObserver<T> o)
        {
            _observers.Remove(o);
        }

        /// <summary>
        /// Notifica, a tutti gli osservatori sottoscritti, 
        /// che il valore del filtro è stato modificato.
        /// </summary>
        public void Notify()
        {
            foreach (Interfaces.IObserver<T> o in _observers)
            {
                o.Update(this.Filter);
            }
        }
    }
}

La cose da notare nell’implementazione della classe “FilterControlBase” sono:

  1. La presenza della proporty “Filter” tipizzata tramite i generics;
  2. Il Setter della property “Filter” che invoca il metodo “Notify” ogni volta che viene cambiato lo stato del filtro;
  3. Nel metodo “Notify” a tutti i sottoscrittori viene notificato il valore del nuovo filtro.

Ormai ci siamo, le ultime cose di cui abbiamo bisogno sono i due web user control e la pagina che li ospiterà.

Il controllo Search.ascx

Il controllo Search.ascx identifica il nostro pubblicatore.
Nel markup inseriamo una serie di controlli per la raccolta dell’input utente e due pulsanti uno per effettuare la ricerca l’altro per ripulire la form.

<%@ Control Language="C#" 
    AutoEventWireup="true" 
    CodeBehind="Search.ascx.cs" 
    Inherits="DesingPattern.Sample.Observer.WebUserControls.Search" %>

<fieldset id="search">
    <legend>Search Fields</legend>
    <asp:CheckBox ID="chkUseLikeFilter" runat="server" /> Use Like Mode
    <div class="filters break">
        <div class="field">
            <asp:Label ID="lblSurname" runat="server" 
                       AssociatedControlID="txtSurname" Text="Surname" />
            <asp:TextBox ID="txtSurname" runat="server" />
        </div>
        <div class="field">
            <asp:Label ID="lblName" runat="server" 
                       AssociatedControlID="txtName" Text="Name" />
            <asp:TextBox ID="txtName" runat="server" />
        </div>
        <div class="field">
            <asp:Label ID="lblCity" runat="server" 
                       AssociatedControlID="txtCity" Text="City" />
            <asp:TextBox ID="txtCity" runat="server" />
        </div>
        <div class="buttons break">
            <asp:Button ID="btnApplyFilter" runat="server" 
                        Text="Apply Filter" OnClick="btnApplyFilter_Click" />
            <asp:Button ID="btnClearFilter" runat="server" 
                        Text="Clear Filter" OnClick="btnClearFilter_Click" />
        </div>
    </div>
</fieldset>

Il codice del controllo contiene i soli gestori d’evento dei due pulsanti dove vengono reimpostati i filtri. Inoltre ereditando dalla classe astratta “FilterControlBase” nel momento in cui viene reimpostata la property “Filter” viengono anche automaticamente notificati ai sottoscrittori i nuovi valori di filtro.

namespace DesingPattern.Sample.Observer.WebUserControls
{
    public partial class Search : WebUserControls.FilterControlBase<CustomerFilter>
    {
        protected void btnApplyFilter_Click(object sender, EventArgs e)
        {
            // Alla pressione del pulsante di ricerca sul controllo basterà 
            // aggiornare il valore del filtro intrinsecamente legato al pubblicatore.
            this.Filter = new CustomerFilter() { 
                UseLikeFilter = chkUseLikeFilter.Checked,
                Surname = txtSurname.Text,
                Name = txtName.Text,
                City = txtCity.Text
            };
        }

        protected void btnClearFilter_Click(object sender, EventArgs e)
        {
            // Alla pressione del pulsante Clear sul controllo basterà 
            // aggiornare il valore del filtro intrinsecamente legato al pubblicatore.
            this.Filter = new NullCustomerFilter();

            // Ripuliamo inoltre i controlli di input
            chkUseLikeFilter.Checked = false;
            txtSurname.Text = string.Empty;
            txtName.Text = string.Empty;
            txtCity.Text = string.Empty;
        }
    }
}

Il controllo SearchResults.ascx

Il controllo SearchResults.ascx identifica invece il nostro sottoscrittore.
Nella parte di marckup inseriamo un semplice repeater, come segue

<%@ Control Language="C#" 
    AutoEventWireup="true" 
    CodeBehind="SearchResults.ascx.cs" 
    Inherits="DesingPattern.Sample.Observer.WebUserControls.SearchResults" %>

<asp:Repeater ID="rptResults" runat="server">
    <HeaderTemplate>
        <table>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Surname</th>
                    <th>Name</th>
                    <th>City</th>
                </tr>
            </thead>
            <tbody>
    </HeaderTemplate>
    <ItemTemplate>
        <tr>
            <td><%#Eval("Id")%></td>
            <td><%#Eval("Surname")%></td>
            <td><%#Eval("Name")%></td>
            <td><%#Eval("City")%></td>
        </tr>
    </ItemTemplate>
    <FooterTemplate>
            </tbody>
        </table>
    </FooterTemplate>
</asp:Repeater>

mentre nella parte di codice inseriamo

  1. Il gestore di evento relativo al Load del controllo, che si occuperà di bindare la lista dei clienti al repeater durante il primo caricamento (ovvero quando la pagina viene caricata per la prima volta ed il filtro non è stato ancora impostato);
  2. Il metodo Update, relativo all’implementazione dell’interfaccia “Interfaces.IObserver” (che viene invocato dal publicatore durante l’esecuzione del metodo “Notify”) nel quale viene filtrata la lista dei clienti in base ai valori di filtro passati durante la notifica del pubblicatore.
namespace DesingPattern.Sample.Observer.WebUserControls
{
    public partial class SearchResults : System.Web.UI.UserControl, 
                                         Interfaces.IObserver<CustomerFilter>
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                // Binda i dati non filtrati sul repeater per la prima visualizzazione.
                rptResults.DataSource = CustomerRepository.GetAllCustomers();
                rptResults.DataBind();
            }
        }

        public void Update(CustomerFilter data)
        {
            // Binda i dati filtrati sul repeater.
            rptResults.DataSource = CustomerRepository.GetCustomers(data);
            rptResults.DataBind();
        }
    }
}

La pagina web contenitore

In fine nella pagina Default.aspx inseriamo i due web controls

<%@ Page Language="C#" AutoEventWireup="true" 
         CodeBehind="Default.aspx.cs" 
         Inherits="DesingPattern.Sample.Observer.Default" %>
<%@ Register src="WebUserControls/Search.ascx" 
             tagname="Search" tagprefix="uc" %>
<%@ Register src="WebUserControls/SearchResults.ascx" 
             TagName="SearchResults" tagprefix="uc" %>

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>ASP.Net &amp; Pattern Observer in Practice</title>
</head>
<body>
    <form id="form1" runat="server">
        <h1>ASP.Net &amp; Pattern Observer in Practice</h1>
        <uc:Search ID="ucSearch" runat="server" />
        <uc:SearchResults ID="ucSearchResults" runat="server" />
    </form>
</body>
</html>

e nel code-behind registriamo il sottoscrittore alla notifica nel gestore d’evento “Init” della pagina

namespace DesingPattern.Sample.Observer
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Init(object sender, EventArgs e)
        {
            // Registra il componente "ucSearchResults" alla notifica.
            ucSearch.Attach(ucSearchResults);
        }
    }
}

Bene se hai resistito fino a questo punto ti meriti di scaricare il progetto di esempio, ma prima di lasciarti voglio aggiungere ancora uno spunto..

Immagina che, nell’esempio implementato dopo l’operazione di ricerca, effettuando un click su una delle righe presenti nella griglia dei clienti tu debba visualizzare un dettaglio più corposo dei dati relativi al cliente selezionato… 😉
Chi vieta che un sottoscrittore (in questo caso SearchResults.ascx) sia contemporaneamente anche un pubblicaotore?

Sperando di averti stuzzicato abbasanza ti lascio come esercizio questo unltimo punto, ormai dovresti essere perfettamente a tuo agio con il pattern observer!!!

Annunci

ASP.Net e Pattern Observer in Pratica – Terza Parte

Nel post ASP.Net e Pattern Observer in Pratica – Seconda Parte abbiamo visto la teoria alla base del Pattern Observer e precedentemente in ASP.Net e Pattern Observer in Pratica – Prima Parte le motivazioni che mi hanno spinto ad affrontare la questione. Adesso vedremo un’implementazione didattica d’esempio per chiarire un po’ le idee prima di addentrarci nell’implementazione finale dello scenario presentato in principio.

Implementazione del Pattern Observer

Prima di iniziare ci tengo a ripetere che questa sarà un’implementazione ridotta al minimo, ovvero scritta al solo scopo di farti comprendere il disegno della soluzione proposta dal pattern, mentre nel prossimo post vedremo un’implementazione reale che, seppur semplice, potrebbe risultarti utile nel lavoro di tutti i giorni 😉

Iniziamo con la definizione dell’interfaccia che identifica il pubblicatore (ISubject)

    /// <summary>
    /// Interfaccia da implementare sul pubblicatore.
    /// </summary>
    interface ISubject
    {
        /// <summary>
        /// Registra un sottoscrittore all'evento di notifica.
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        void Attach(IObserver o);

        /// <summary>
        /// Rimuove un sottoscrittore dall'evento di notifica.
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        void Detach(IObserver o);

        /// <summary>
        /// Notifica a tutti i sottoscrittori il cambio di stato.
        /// </summary>
        void Notify();
    }

e poi quella che identifica gli eventuali sottoscrittori (IObserver)

    /// <summary>
    /// Interfaccia da implementare in tutti i sottoscrittori.
    /// </summary>
    public interface IObserver
    {
        /// <summary>
        /// Aggiorna lo stato interno del sottoscrittore quando riceve
        /// l'evento di notifica dal pubblicatore.
        /// </summary>
        void Update();
    }

ora veniamo all’implementazione dell’interfaccia ISubject

    /// <summary>
    /// Implementazione di esempio per il pubblicatore.
    /// </summary>
    public class Subject : ISubject
    {
        /// <summary>
        /// Lista interna degli oggetti sottoscritti all'evento di notifica.
        /// </summary>
        private List<IObserver> _observers = new List<IObserver>();

        /// <summary>
        /// Sottoscrive un sottoscrittore.
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        public void Attach(IObserver o)
        {
            if (!(o == null || _observers.Contains(o)))
                _observers.Add(o);
        }

        /// <summary>
        /// Rimuove un sottoscrittore.
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        public void Detach(IObserver o)
        {
            if(o != null)
                _observers.Remove(o);
        }

        /// <summary>
        /// Notifica a tutti i sottoscrittori il cambio di stato.
        /// </summary>
        public void Notify()
        {
            // Cicla tutti i sottoscrittori registrati e ne invoca il metodo Update.
            foreach (IObserver o in _observers)
            {
                o.Update();
            }
        }

        /// <summary>
        /// Metodo di esempio che muta lo stato interno dell'oggetto.
        /// </summary>
        public void ChangeInternalStatusMethod()
        {
            // Operazioni varie...
            // this.Operation1();
            // this.Operation2();
            // ecc..

            // Notifica
            this.Notify();
        }
    }

e poi l’implementazione dell’interfaccia IObserver

    /// <summary>
    /// Implementazione di esempio per il sottoscrittore.
    /// </summary>
    public class Observer : IObserver
    {
        /// <summary>
        /// Aggiorna lo stato interno del sottoscrittore quando riceve
        /// l'evento di notifica dal pubblicatore.
        /// </summary>
        public void Update()
        {
            // La scrittura nella finestra di debug serve solo per darti
            // la possibilità di verificare che la notifica è avvenuta.
            Debug.WriteLine(string.Format("Notifica avvenuta alla: {0}", DateTime.Now.ToString()));
        }
    }

Bene il codice relativo al pattern è completo, non ci resta che verificare come gira il tutto all’interno di un programma che per semplicità sarà una Console Application

class Program
    {
        static void Main(string[] args)
        {
            // Istanzia il pubblicatore.
            Subject subject = new Subject();

            // Istanzia e registra i sottoscrittori alla notifiche.
            IObserver observer1 = new Observer();
            subject.Attach(observer1);

            IObserver observer2 = new Observer();
            subject.Attach(observer2);

            // Il metodo "ChangeInternalStatusMethod" identifica un metodo di
            // esempio che dopo alcune elaborazioni cambia lo stato interno
            // del pubblicatore, al suo interno viene invocato il metodo Notify()
            // in modo che i sottoscrittori ricevano le nodifiche di avvenuto
            // cambio di stato.
            subject.ChangeInternalStatusMethod();

            Console.Read();
        }
    }

Come ho già detto questa è un’implementazione didattica al solo scopo di capire come gira il tutto ma così fatta è poco utile, in quanto nella maggior parte delle implementazioni reali al momento della notifica risulta necessario che ai sottoscrittori venga notificato non solo che c’è stato cambimento di stato ma anche cosa è cambiato nel contesto del pubblicatore in modo da poter reagire di conseguenza…

Bene nel prossimo post ne parleremo diffusamente con un esempio reale.

ASP.Net e Pattern Observer in Pratica – Seconda Parte

Nel post ASP.Net e Pattern Observer in Pratica – Prima Parte ho presentato un caso reale (anche se estremamente semplice) di uno di quei tantissimi casi in cui un cattivo (mancato) design possa portare sorprese inaspettate durante lo sviluppo di un’applicazione software.
Con molto dispiacere mi permetto di aggiungere, senza però voler aprire aspre ed infruttuose polemiche, che purtroppo queste situazioni non sono per nulla rare.
Tornando a noi…

Il Pattern Observer

Il pattern Observer, è un’elegante soluzione che permette di generare una relazione uno a molti tra diversi oggetti dove uno in particolare ha la possibilità di notificare agli altri un suo cambio di stato ed eventualmente i valori modificati senza violare le best practices per mantenere un corretto incapsulamento.

In sostanza il pattern segue il modello Publisher and Subscribe dove alcune entità chiamate sottoscrittori si registrano presso un pubblicatore che li informa ogni qualvolta ci siano delle nuove informazioni da notificare.

Di seguito vediamo il diagramma UML del pattern observer:

Design Pattern - Observer

Nel diagramma la classe Subject rappresenta il pubblicatore che espone pubblicamente i seguenti tre metodi

  • Attach (Utilizzato per registrare un sottoscrittore alle notifiche);
  • Detach (Utilizzato per la rimuovere la registrazione di un sottoscrittore alle notifiche);
  • Notify (Utilizzato per notificare ai sottoscrittori il cambio di stato).

Le classi Observer, Observer2 e Observer3 rappresentano invece tre implementazioni di sottoscrittori e espongono il metodo Update che sarà utilizzato all’interno di Notify del pubblicatore per aggiornarli relativamente al suo cambio di stato.

Ovviamente per far si che i sottoscrittori (classi Observer, Observer2, ecc..) possano ricevere le notifiche da parte del pubblicatore (classe subject) dovranno in qualche modo sottoscriversi alla notifica. Questo è possibile grazie all’evento “Attach(IObserver o);” esposto dal publicatore che accettando in ingresso un tipo IObserver da la possibilità di registrare un riferimento agli oggetti sottoscritti all’evento di notifica.

Di seguito vediamo un sequence diagram che dovrebbe chiarire un po’ le idee relativamente all’andamento della notifica.

Design Pattern - Observer sequence diagram

Nel prossimo post vedremo un’ implementazione di esempio del pattern Observer, d’altro canto il titolo del post promette un esempio pratico ma qualcuno potrebbe obbiettare che siamo già alla seconda parte e di pratica ne abbiamo fatta poca 😛
E’ vero ma un po’ di teoria iniziale per qualche lettore magari poteva risultare necessaria…

ASP.Net e Pattern Observer in Pratica – Prima Parte

Ieri mi è stata chiesta una piccola consulenza relativamente ad un bug presente su una pagina web sviluppata in ASP.Net Web Forms e vorrei condividere con voi sia lo scenario sia la soluzione implementata così da approfittare per parlare del Pattern Observer.

Il Requisito

Implementazione di una pagina di ricerca dove sono presenti alcuni web user controls tra i quali uno in particolare si occupa di raccogliere l’input dell’utente per effettuare una ricerca tramite la pressione di un pulsante apposito (presente sullo stesso user control), l’operazione di ricerca deve in qualche modo essere notificata agli altri user controls presenti sulla stessa pagina che utilizzando gli stessi valori inseriti nel primo controllo dovranno filtrare e renderizzare i dati di dettaglio.

La Soluzione originariamente implementata

La soluzione originariamente implementata, ovvero quella che generava bug, lavorava più o meno così:

  • Creazione di 3 user control
    • Ricerca.ascx
    • ListaDettagli.ascx
    • Dettaglio.ascx
  • L’utente inputa una serie di valori (tramite TextBox, CheckBox, ed altri controlli… presenti nel controllo Ricerca.ascx);
  • L’utente Clicca sul pulsante “Cerca” (sempre presente nel controllo Ricerca.ascx);
  • L’user control nel gestore di evento associato al click del bottone “cerca” istanzia un oggetto preposto con tutte le informazioni inserite dall’utente e lo memorizza in sessione;
  • Nell’evento Load degli altri user control viene letto l’oggetto in sessione e se questo non è null allora viene utilizzato come filtro.

Le problematiche riscontrate

Quello che il cliente lamenta è che la ricerca funziona a singhiozzi… ovvero:

  • La prima ricerca non funziona mai;
  • La seconda ricerca funziona se non vengono cambiati i dati inputati per la ricerca;
  • Le seguenti ricerche filtrano i dati dei dettagli con i dati inputati dall’utente per la ricerca precedente.

..ma come mai? ..e soprattutto sono solo questi i problemi effettivamente portati dalla soluzione implementata? ..oppure ce ne sono altri che l’utente (o addirittura il pogrammatore) non sta considerando? Inoltre qual’è il grado di portabilità e/o di estensione delle componenti software sviluppate?

La motivazione del problema ed una soluzione apparente

La motivazione del problema è molto semplice, nel ciclo di vita di una pagina ASP.Net  l’evento Postback viene scatenato dopo l’evento Load, quindi quando i controlli di dettaglio leggono il valore dalla sessione (nell’evento Load) lo leggono prima che l’user control Ricerca.ascx lo inserisca in sessione durante il Postback!

A questo punto, individuata l’origine del comportamento anomalo, potremmo essere portati a pensare che posticipare la lettura dei dati della sessione in un evento successivo al Postback (per esempio il PreRender) risolva il problema… bè diciamo che l’anomalia relativa alla ricerca effetivamente scompare ma questa è veramente una soluzione? ..oppure è quella che più comunemente  nel nostro campo viene definita come una pezza su un’implementazione poco controllabile?

Mi spiace dire che purtroppo siamo nel secondo caso…

..e allora cerchiamo di capire insieme come i design pattern possano aiutarci nell’implementare una soluzione più elegante ed affidabile, vediamo a cosa serve e come implementare il pattern observer.

Pattern MVP ed Asp.Net – Prima parte

La complessità delle interfacce grafiche, sia in applicazioni Client/Server che Web Based, è costantemente in aumento e quindi nel tempo sono stati proposti diversi strumenti per facilitarne lo sviluppo come l’approccio RAD (Rapid Application Development) e l’introduzione di widget e controlli sempre più complessi e con potenti meccanismi di databinding dichiarativo. Ovviamente tali facility, come ho gia affermato in altre occasioni,  hanno spostato l’attenzione del programmatore sempre più sulla configurazione che sullo sviluppo vero e proprio (in questo caso del layer di presentazione) portando da un lato ad un aumento della produttività iniziale ma dall’altro allo sviluppo di applicazioni monolitiche difficilmente testabili in ambiente isolato e di conseguenza difficilmente estendibili senza incorrere nella prolificazione di bug di riflessione (stiamo parlando dunque di applicazioni difficilmente manutenibili).

Fortunatamente esistono diversi pattern architetturali che tentano di risolvere questo tipo di problematiche. Negli ultimi anni è il pattern MVC (Model View Contoller – Model2) ad essere sotto i riflettori come scelta vincente in tutti quei casi in cui si voglia sviluppare una GUI (Graphic User Interface) fortemente disaccoppiata dalle logiche di business. In effetti MVC è un’elegante soluzione che assolve splendidamente a tale problema, ma in alcuni contesti tecnologici (vedi l’utilizzo di ASP.Net Web Form) che non offrono un supporto nativo a tale pattern “potrebbe” non essere conveniente prendersi l’onere di implementare tutta l’infrastruttura necessaria per la sua implementazione. In questi casi il Pattern MVP è una soluzione da tenere assolutamente in considerazione 😉

Il Pattern MVP

Il pattern MVP (Model View Presenter) risale all’anno 1979 ed il suo nome originario era Thing Model View Editor (coniato dalla Taligent), nel tempo poi evoluto grazie soprattutto a Martin Fowler che ne ha sviluppato due diverse implementazioni Supervising Presenter e Passive View. La soluzione proposta prevede il disaccoppiamento totale della View dalle logiche di manipolazione del Modello tramite l’introduzione di un componente, il Presenter, che funga da intermediario e da coordinatore in risposta alle interazioni con l’utente. Ma vediamo nel dettaglio gli attori coinvolti:

  • View (visualizza i dati contenuti nel model e raccoglie gli input dell’utente)
  • Model (rappresenta il dominio di busines ed incapsula lo stato dell’applicazione)
  • Presenter (interagisce con il model in base alle richieste ricevute dalla view)
Schema di interazione – MVP Supervising Controller
In questo tipo di implementazione la view conosce il modello e generalemente è connessa ad esso tramite un meccanismo di data binding dichiarativo che permette l’aggiornamento dei dati da visualizzare (in situazioni complesse dove il data bindig dichiarativo non è sufficiente a garantire tale l’aggiornamento è necessario l’intervento del presenter).
  • L’utente interagisce in qualche modo con la View;
  • La View notifica al Presenter dell’interazione;
  • Il Presenter opera sul Model eventualmente modificandone lo stato in rispetto all’interazione notificata dalla View;
  • La View viene aggiornata in base ai nuovi dati esposti dal Model tramite un meccanismo di data binding;
  • Il Presenter, se presenti, aggiorna i dati della view che non possono essere agganciati a quest’ultima tramite data binding dichiarativo;

Schema di interazione – MVP Passive View

In questo caso la view non conosce il modello (non vi è quindi alcuna dipendenza tra la View ed il Modello) ed il presenter è l’unico responsabile dell’aggiornamento dei dati visualizzati.

  • L’utente interagisce in qualche modo con la View;
  • La View notifica al Presenter dell’interazione;
  • Il Presenter opera sul Model eventualmente modificandone lo stato in rispetto all’interazione notificata dalla View;
  • Il Presenter aggiorna la View in base ai nuovi dati esposti dal Model;