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!!!

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.

Disabilitare le zone di Orchard per la gestione dei Widgets

Nei vari Content management system presenti sul mercato, c’è sempre in qualche modo la possibilità di specificare nel layout del sito in costruzione alcune zone che possono essere utilizzate per inserire dinamicamente dei widgets dal pannello di amministrazione del CMS stesso.

In  WordPress, per esempio, le zone nelle quali si possono inserire i widgets all’interno della struttura di un tema si chiamano sidebar e sono registrabili in un file chiamato functions.php tramite la seguente funzione:

   <?php $args = array(
      'name'          => __( 'Sidebar name', 'theme_text_domain' ),
      'id'            => 'unique-sidebar-id',
      'description'   => '',
      'class'         => '',
      'before_widget' => '<li id="%1$s" class="widget %2$s">',
      'after_widget'  => '</li>',
      'before_title'  => '<h2 class="widgettitle">',
      'after_title'   => '</h2>' ); 
   ?>

Quando ho sviluppato Mango Theme, il mio primo tema per Orchard, ho deciso di non utilizzare tutte e 20 le zone rese disponibili dal tema installato di default (TheThemeMachine) e devo essere sincero ho storto un pò il naso sul fatto che credevo di non avere alcun modo, a parte la Theme Zone Preview, per poter notificare all’utente che alcune zone non erano viualizzabili con il mio tema.

Mi sono chiesto, forse per troppo tempo: “possibile che con una gestione così arguta dei layer non abbiano previsto un modo per risolvere la questione senza implementare qualche sporco workaround?”

Così, ultimamete, ripresentatomi nuovamente il problema, ho chiesto direttamente sul forum di Orchard. Bè la soluzione non solo è Semplice ma soprattutto è molto Elegante.

Infatti direttamente all’interno del file Manifest presente nella cartella del tema (con il nome di Theme.txt) è possibile tra le altre cose specificare quali sono le zone abilitate, ecco un esempio (che nello specifico riporta le 20 zone del tema TheMetroTheme del quale è disponibe anche il codice sorgente):

Name: The Metro Theme
Version: 0.5.0
Author: Marco Siniscalco
Tags: responsive, modern, modern iu, metro, metrostyle
Description: The Metro Theme is a clean and flexible multi-zone theme. It features 20 collapsible widget zones and is flexible enough to cover a wide range of layouts.
Website: http://orchardmetrotheme.codeplex.com/
Zones: Header, Navigation, Featured, BeforeMain, AsideFirst, Messages, BeforeContent, Content, AfterContent, AsideSecond, AfterMain, TripelFirst, TripelSecond, TripelThird, FooterQuadFirst, FooterQuadSecond, FooterQuadThird, FooterQuadFourth, Footer

vi mostro cosa, con la configurazione sovrastante, ritroviamo nell’area di amministrazione dei widgets

Orchard Active-Widgets-Zones

ma se avessimo scritto

Name: The Metro Theme
Version: 0.5.0
Author: Marco Siniscalco
Tags: responsive, modern, modern iu, metro, metrostyle
Description: The Metro Theme is a clean and flexible multi-zone theme. It features 20 collapsible widget zones and is flexible enough to cover a wide range of layouts.
Website: http://orchardmetrotheme.codeplex.com/
Zones: Header, Navigation, Content, Footer

avremmo avuto

Customize Orchard Active Widgets Zones

Ma attenzione!
Non è possibile solo disabilitare le zone che non gestiamo ma volendo anche aggiungerne di nuove!

Vi rimando quindi alla pagina della documentazione ufficiale di Orchad con la lista di tutte le voci utilizzabili all’interno dei file manifest di moduli e temi.

Modificare la User Interface senza disorientare l’utente finale?

Studio della User InterfaceVi siete mai chiesti cos’è che fa la differenza nel rendere agevole, talvolta anche gradevole, l’utilizzo di un software agli utenti finali che ne fanno utlilizzo? ..considerando che scrivo questo bog principalmente per un pubblico di addetti ai lavori spero proprio che la vostra risposta si avvicini ad un chiaro e deciso SI!

Se così non fosse… haimè l’unica cosa che posso dirvi è: male… veramente molto MALE!

Lo studio e la cura della user experience è una di quelle attività fondamentali che possono fare di un piccolo mucchio di righe di codice (su via un po’ di ironia :P) un software di successo!

Questa mattina, come consueto ormai, mi sono diretto su un motore di ricerca (uno a caso!?!) ed ho notato che per l’ennesima volta qualcosa è cambiato…

Google, in questo caso ma come molti altri, ci coccola da svariati anni con piccole modifiche ed accorgimenti alla UX (piccole per modo di dire visto che nascondono interminabili e complesse attività di profilazione utente) atti a migliorare la navigabilità nel sempre crescente set di funzionalità che ci offre.

Questa mattina è stata la volta del menu di navigazione presente sulla sinistra della pagina dei risultati, che è scomparso in favore di una meno invadente barra orizzontale appena sotto l’area di ricerca

Google nuova interfaccia grafica per la ricerca

La cosa sulla quale vogli portare oggi l’attenzione è che queste modifiche, quando necessario, sono spesso organizzate in più step in modo da non disorientarci. Prima di spostare un elemento fondamentale, come può essere un menù di navigazione, dovrebbe essere corretto domandarsi quanto questa scelta possa impattare sulle abitudini dell’utente finale ed in caso di probabili conseguenze negative organizzare lo spostamento in più step.

In altri casi, quando magari la modifica è imminente e non si hanno i tempi o il budget per poter programmare questi step intermedi, una valida alternativa puo’ essere aggiungere un menu-guida interattivo (maestro incontrastato in questo campo è Facebook.com) che ci guidi passo passo nel comprendere dove e perché siano stati spostati o aggiunti ulteriori elementi interattivi.

Voi cosa ne pensate? ..ma soprattutto quanto ci pensate prima di stravolgere le vostre UI ogni qual volta sia necessario o richiesta l’introduzione di nuove funzionalità?

Intanto grazie Google per non farmi uscire di senno ogni giorno!🙂

Visual Studio color schemes

Oggi parliamo di un argomento che mi sta molto a cuore.. la salute dei miei occhi :) ..anche dei vostri dai!

Qualche giorno fa.. ho dovuto cambiare gli occhiali e così ho ben pensato di approfittarne per fare un’accurata visita da un optometrista.. è stato veramente illuminante!!! Anche se ora che ci penso non so se anche per lui è stata un’esperienza gradevole.. visto che l’ho letteralmente tempestato di domande (purtroppo sono un tipo un curioso) avrà pensato che invece di vole fare una visita agli occhi forse volevo prendermi una laurea in materia…😀

Ma veniamo al motivo di questo post! …insieme a tutta una serie di attività che sto tentando di rispettare per prendermi un po’ più cura della salute dei miei occhi (anche se la soluzione ultima sarebbe spegnere definitivamente questo arnese infernale [Alias: PC, MAC, TABLET, SMARTPHONE, ecc…]) ho pensato di rendere “la faccia” di Visual Studio e similari un po’ meno stancante.. e così girando per la rete mi sono ritrovato su http://studiostyl.es/

Francamente oggi mi chiedo come mai non ci abbia pensato prima! ..infondo è una vita che Visual Studio implementa la possibilità di gestire i font ed i colori dell’area di lavoro.. spesso siamo talmente impegnati a risolvere i problemi degli altri che ci dimentichiamo di non crearne di nostri…😀

Non mi sento ovviamente di consigliarvi un tema in particolare visto che non sono un optometrista o un oculista e di conseguenza potrei dire solo un sacco di eresie.. Ma per condividere la mia scelta posso dirvi che sto optando per un tema con lo sfondo mediamente scuro ma evitando alti contrasti…

..e tu che tema scegli?