Programming Languages Hacks

Importanti regole per linguaggi di programmazione rilevanti come Java, C, C++, C#…

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

Archive for January, 2008

Creare un TreeView da un Oggetto di una Qualunque Classe

Posted by Ricibald on 31st January 2008

Molto utile il seguente progetto:

Object Hierarchical Data Source

Praticamente è un Hierarchical Data Source che si può associare a controlli come TreeView, Menu, SiteMapNavigation, … ma che si scandisce, tramite reflection, la classe dell’oggetto passato in modo ricorsivo per popolare il controllo gerarchico specificato.

Se quindi la classe è sufficientemente descrittiva ed è “piena di roba”, il ché comporterebbe una pagina aspx “piena di controlli”, risulta conveniente utilizzare invece questo approccio e generare la pagina dinamicamente in un TreeView.

Posted in .net | No Comments »

Gestione Globale e Centralizzata dell’Errore in ASP.NET

Posted by Ricibald on 30th January 2008

Tipicamente un’applicazione con interfaccia console o window la gestione globale dell’errore è piuttosto semplice.

Nel caso di interfaccia console:

static int Main(string[] args)
{
  try
  {
    // Do application logic here
    return 0;
  }
  catch (ParseCommandLineException pce)
  {
    Console.WriteLine("Usage error");
    return 27;
  }
  catch (Exception e) {
    Console.WriteLine(e);
    return 42;
  }
}

Nel caso di interfaccia WinForm:

[STAThread]
static void Main()
{
  Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
  Application.Run(new Form1());
}

private void button1_Click(object sender, System.EventArgs e)
{
  throw new Exception("Whoops");
}

private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
  MessageBox.Show(e.Exception.Message);
}

Nel caso invece di applicazione web verrebbe naturale ragionare nello stesso modo, scrivendo qualcosa come:

public class MyPage : System.Web.UI.Page
{
    private string _errorMessage;
    public string ErrorMessage { get { return _errorMessage; } }
    protected new PageMaster Master { get { return (PageMaster)Page.Master; } }

    protected void Page_Error(object sender, EventArgs e)
    {
    	Exception objErr = Server.GetLastError();
        EventLog.WriteEntry("MyWebform.aspx",err,EventLogEntryType.Error);
        _errorMessage = objErr.Message;
        Server.ClearError();
        Server.Transfer(this.AppRelativeVirtualPath);
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        /* Stampa messaggio di errore se presente */
        if (this.PreviousPage != null)
        {
            MyPage previousPage = (MyPage)this.PreviousPage;
            Master.ErrorLabel = previousPage.ErrorMessage;
        }
    }
}

Ma questo comporta il seguente problema:

Poiché la pagina deve poter essere completamente reinviata, perdo tutti i valori immessi dall’utente.

In altre parole, vorrei ottenere un PostBack con il messaggio di errore, ma il PostBack non può essere riprodotto.

Potremmo pensare diverse soluzioni:

  • Si potrebbe provare a simulare il PostBack, forzando il salvataggio dei dati immessi in sessione per poi ricaricarli, ma .NET non lo permette
  • Si potrebbe inviare una risposta HTTP personalizzata con il contenuto della pagina con associati i controlli immessi, ma non funziona
  • Potremmo utilizzare AJAX per aggiornare solo la differenza di ciò che è cambiato. In questo caso funzionerebbe, ma:
    • si obbligherebbe qualunque pulsante o qualunque metodo che potrebbe scatenare un’eccezione ad essere definito dentro un UpdatePanel, cosa sconsigliata per le prestazioni
  • Invece di ricaricare la stessa pagina che ha causato l’errore potremmo eseguire una Transfer verso una pagina di errore a cui passiamo il messaggio di errore tramite CrossPagePostback. A questo punto potremmo fornire un pulsante “Torna indietro” che esegue il “Back” nella cronologia del browser. In questo caso funzionerebbe, ma:
    • Dobbiamo “fidarci” della cache del browser
    • Dobbiamo obbligare il browser a fare uso di javascript per tornare indietro
    • In presenza di precedenti aggiornamenti AJAX il pulsante “Back” crea problemi: torna indietro solo del PostBack parziale. Potremmo utilizzare soluzioni articolate, come l’uso del server control History, ma non supportato nel framework 2.0

Dopo lunghe peripezie si può arrivare per ora ad una conclusione: bisogna abbandonare l’idea di gestire l’errore nel Page_Error. E allora come fare? Analizziamo quando possono avvenire gli errori nelle pagine ASP.NET:

  • nell’inizializzazione delle pagine
  • nelle PostBack, sincrone e asincrone

Per gestire l’errore “basterebbe” mettere un try/catch in tutte le parti di tutte le pagine. Per l’inizializzazione delle pagine si potrebbe pensare di gestire eventuali eccezioni in una classe Page customizzata che fa da padre per tutte le pagine che implementeremo. Potremmo quindi utilizzare il pattern Template come segue:

public abstract class MyPage : System.Web.UI.Page
{
    private string _errorMessage;
    public string ErrorMessage { get { return _errorMessage; } }
    protected new PageMaster Master { get { return (PageMaster)Page.Master; } }

    protected abstract void Specific_Page_Load(object sender, EventArgs e);

    protected void Page_Load(object sender, EventArgs e)
    {
        /* Stampa messaggio di errore se presente */
        if (this.PreviousPage != null)
        {
            MyPage previousPage = (MyPage)this.PreviousPage;
            Master.ErrorLabel = previousPage.ErrorMessage;
        }
        try
        {
            Specific_Page_Load(sender, e);
        }
        catch(Exception objErr)
        {
            _errorMessage = objErr.Message;
        }
    }
}

public class LoginPage : MyPage
{
    protected override void Specific_Page_Load(object sender, EventArgs e)
    {
        LoadControls(); // questo potrebbe causare un errore
    }
}

Ma esiste comunque un problema:

Il client che utilizza MyPage si deve ricordare di non implementare mai il Page_Load, ma di implementare solo ed esclusivamente il Specific_Page_Load ereditato.

Vorremmo invece che l’utente non sia costretto a seguire un ordine mentale: il sistema deve gestire il controllo in modo trasparente. Per fare questo bisogna capire se esiste un metodo perno di tutti i Load, il cui fallimento comporta un fallimento di un qualunque eventuale load specializzato. Questo metodo esiste, ed è OnLoad, che richiama tutti i Page_Load (non gli OnLoad) creati seguendo il cosiddetto “Principio Di Hollywood” . Basta quindi sovrascrivere OnLoad in questo modo:

public abstract class MyPage : System.Web.UI.Page
{
    protected new PageMaster Master { get { return (PageMaster)Page.Master; } }

    protected override void OnLoad(EventArgs e)
    {
        try
        {
            base.OnLoad(e);
        }
        catch(Exception objErr)
        {
            Master.ErrorLabel = objErr.Message;
        }
    }
}

In modo analogo si può sovrascrivere il metodo perno di tutti i PostBack, RaisePostBackEvent.

Vogliamo infine perfezionare questa gestione rendendola generica e riutilizzabile.

Metto quindi a disposizione di chiunque fosse interessato la mia soluzione per la gestione globale delle eccezioni. Chi utilizzerà questa classe è sufficiente che tutte le pagine ereditino dalla vostra pagina customizzata, che a sua volta eredita dalla mia. La vostra pagina customizzata dovrà quindi implementare il metodo HandleError e il gioco è fatto.

using System;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Drawing;
using System.Reflection;

namespace GenericException
{
    /// <summary>
    /// Inoltra tutte le richieste, ma gestisce l'eccezione e consente di visualizzarla
    /// </summary>
    public abstract class ExceptionManageablePage : System.Web.UI.Page
    {
        protected abstract void HandleError(Exception exception);

        private delegate void MethodToTryCatchOneParam<Param1>(Param1 arg1);
        private delegate void MethodToTryCatchTwoParam<Param1, Param2>(Param1 arg1, Param2 arg2);

        private void ManageErrors<Param1>(MethodToTryCatchOneParam<Param1> method, Param1 arg1)
        {
            try
            {
                method(arg1);
            }
            catch (Exception ex)
            {
                HandleError(ex);
            }
        }

        private void ManageErrors<Param1, Param2>(MethodToTryCatchTwoParam<Param1, Param2> method, Param1 arg1, Param2 arg2)
        {
            try
            {
                method(arg1, arg2);
            }
            catch (Exception ex)
            {
                HandleError(ex);
            }
        }

        protected override void OnAbortTransaction(EventArgs e)
        {
            ManageErrors(base.OnAbortTransaction, e);
        }

        protected override void OnCommitTransaction(EventArgs e)
        {
            ManageErrors(base.OnCommitTransaction, e);
        }

        protected override void OnDataBinding(EventArgs e)
        {
            ManageErrors(base.OnDataBinding, e);
        }

        protected override void OnError(EventArgs e)
        {
            ManageErrors(base.OnError, e);
        }

        protected override void OnInit(EventArgs e)
        {
            ManageErrors(base.OnInit, e);
        }

        protected override void OnInitComplete(EventArgs e)
        {
            ManageErrors(base.OnInitComplete, e);
        }

        protected override void OnLoad(EventArgs e)
        {
            ManageErrors(base.OnLoad, e);
        }

        protected override void OnLoadComplete(EventArgs e)
        {
            ManageErrors(base.OnLoadComplete, e);
        }

        /// <summary>
        /// Cattura eccezioni di qualunque postback, anche asincrono tramite Ajax. Ma in questo
        /// ultimo caso anche l'HandleError deve supportare aggiornamenti asincroni (Label dentro un UpdatePanel)
        /// </summary>
        protected override void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument)
        {
            ManageErrors(base.RaisePostBackEvent, sourceControl, eventArgument);
        }

    }
}

Bisogna infine dire che per i postback asincroni devo avere una gestione dell’errore che stampi un messaggio asincrono. Basta quindi inserire una label di errore con un UpdatePanel, o utilizzare una ModalPopupExtender. E ovviamente ogni consiglio è più che benvenuto!

Posted in .net | 13 Comments »

Eccezioni in C#: Critiche Progettuali

Posted by Ricibald on 23rd January 2008

In generale apprezzo come C# è stato concepito, ma una cosa che veramente non capisco è la gestione delle eccezioni.

In Java esiste la distinzione utilissima tra eccezioni checked e non. Le eccezioni checked consentono di forzare l’utilizzatore di un metodo a gestire o rilanciare solo le eccezioni dichiarate: niente di più, niente di meno. In C#, invece, tutte le eccezioni sono unchecked e questo pone il problema:

  • quali eccezioni devo gestire?
  • come forzare a sollevare solo determinati tipi di eccezione?

Si consideri infatti il codice Java:

public class Firefox {
      public void activatePlugins() {
            for(FirefoxPluginInterface plugin in this.plugins) {
                  try {
                        plugin.activate();
                  } catch (FirefoxPluginActivationException myEx) {
                        FirefoxHelper.manageActivationFromError(myEx);
                  } catch (Exception ex) {
                        FirefoxHelper.showError(ex);
                  }
           }
      }
}

public interface FirefoxPluginInterface {
     public void activate() throws FirefoxPluginActivationException;
}

Si consideri invece il codice C# analogo:

public class Firefox
{
      public void ActivatePlugins()
      {
            foreach(FirefoxPluginInterface plugin : _plugins)
            {
                  try
                  {
                        plugin.Activate();
                  }
                  catch (FirefoxPluginActivationException myEx)
                  {
                        FirefoxHelper.ManageActivationFromError(myEx);
                  }
                  catch (Exception ex)
                  {
                        FirefoxHelper.ShowError(ex);
                  }
           }
      }
}

public interface FirefoxPluginInterface {
     public void Activate();
}

Non siamo in grado di essere sicuri che tutte le persone che scriveranno plugin faranno buon uso della nostra eccezione. Speriamo in qualche modo nella bontà di chi scriverà… Una buona pezza era stata fornita nel framework 1.1 di .NET, inserendo la classe ApplicationException: in teoria l’utente che creava le proprie personali eccezioni doveva ereditare da questa classe, e non da Exception. In questo modo si era in grado di discriminare se l’eccezione apparteneva alle standard o se era una che avevamo lanciato noi. Successivamente, come riporta MSDN:

Per la maggior parte delle applicazioni, derivare le eccezioni personalizzate dalla classe Exception . L’idea iniziale di far derivare le eccezioni personalizzate dalla classe ApplicationException non ha prodotto alcun valore significativo in termini pratici.

Questa frase e questa organizzazione delle eccezioni mi lascia un po’ interdetto…

Esiste anche il problema della propagazione delle eccezioni. L’architettura di un sistema software è infatti normalmente definita a livelli o Layer: ogni livello rappresenta un diverso livello di astrazione. Il messaggio viene quindi passato attraverso vari livelli, in cui ad ogni livello eventuali errori devono essere riscritti a seconda del livello di astrazione. Ad esempio, un eccezione SQL al livello “Database” deve essere avvolta in una eccezione di livello più alto nel livello “Dominio” (ad es. una generica eccezione nella memorizzazione dei dati). In questa comunissima pratica ingegneristica abbiamo però alcuni problemi. Immaginiamo questa situazione:

try
{
}
catch (SQLException sqlEx)
{
    throw new PersistenceException("Errore nel salvataggio", sqlEx);
}
catch (Exception ex)
{
    throw new UnknownException("Errore sconosciuto durante il salvataggio nell'inserimento", ex);
}

L’eccezione PersistenceException viene ricatturata nel blocco “catch(Exception)”, comportamento assurdo in un codice Java, dove l’eccezione non sarebbe stata mai ricatturata. Per correggere questo comportamento si può eseguire un unico catch e il resto rilanciare:

try
{
}
catch (SQLException sqlEx)
{
    throw new PersistenceException("Errore nel salvataggio", sqlEx);
}
catch (Exception ex)
{
    // throw ex; NON UTILIZZARE: PERDO LO STACKTRACE!!
    throw; // OK!
}

Notare inoltre un altro problema: il “throw ex” causa la perdita di informazioni nello stacktrace: non parte più dall’origine dell’errore ma dall’origine del “throw ex”.

Realizzato il comportamento appena descritto nel codice, sarà il livello superiore che al momento della stampa del messaggio:

try
{
}
catch (Exception ex)
{
    string message = null;
    if(ex is PersistenceException) 
    {
        message = ex.Message;
    } 
    else if (ex is Exception)
    {
        message = "Errore sconosciuto durante il salvataggio nell'inserimento";
    }
    Trace.WriteLine(message);
}

Che cose articolate stranamente per realizzare un comportamento del tutto normale…

Posted in .net | 1 Comment »

Approcci in .NET e Windows Server 2003 per Ottenere la Scalabilità delle Applicazioni

Posted by Ricibald on 22nd January 2008

In .NET è spesso necessario ottenere la scalabilità delle applicazioni. Immaginiamo di aver scritto un bellissimo software che richiede a un sistema esterno il permesso per richiedere un mutuo aziendale. Saranno presenti numerosi scambi di messaggi tra il nostro sistema e il sistema esterno in questione, che possono comportare dei lunghi tempi di attesa.

Immaginiamo ora che in questo lasso di tempo avvengano 100.000 richieste verso la nostra applicazione. Possiamo immaginare soluzioni più o meno eleganti, come pool di thread o gerarchie di processi ma queste hanno efficacia solo con molteplici processori. Anche se è una soluzione che solo ora sta diventando praticabile per un computer “normale”, grazie ai processori multi core, non è comunque una soluzione “altamente” scalabile: è infatti subordinata all’architettura del sistema che esegue la nostra applicazione.

Si utilizzano quindi normalmente diversi approcci di computer clustering. Tra i possibili tipi di clustering, esistono:

  • Fail-over clustering: il funzionamento delle macchine è continuamente monitorato, e quando uno dei due host smette di funzionare l’altra macchina subentra. Lo scopo è garantire un servizio continuativo
  • Load-balancing clustering: sistema nel quale le richieste di lavoro sono inviate alla macchina con meno carico
  • High Performance Computing clustering: le macchine suddividono i processi di un job su più macchine, al fine di guadagnare in prestazioni

In realtà, i clustering descritti sono figli di due pattern (presi da POSA):

  • Broker (o la sua versione “leggera” Client-Dispatcher-Server): supporta il coordinamento e la comunicazione tra sistemi distribuiti eterogenei. Consente di mantenere trasparente la localizzazione dei Server, in modo da poterli cambiare dinamicamente per ottenere un bilanciamento di carico
  • Master-Slave: supporta tolleranza d’errore, calcolo parallelo e accuratezza computazionale tramite ridondanza. Esiste un componente Master che suddivide e distribuisce il lavoro a componenti Slave identici. Gli Slave calcolano il risultato parziale e lo restituiscono al Master, il quale assembla i risultati parziali ottenuti per ottenere quello definitivo

Il protocollo con cui questi sistemi dialogano non è un problema, se si combina anche il pattern Forward-Receiver, che consente di definire un protocollo di comunicazione trasparente.

Nel nostro caso, risulta eccessivo dividere il lavoro in più sottolavori: quello che vorremmo è semplicemente smistare la richiesta al server più libero in modo da ottenere una scalabilità. Vorremmo quindi implementare il pattern Broker.

Fortunatamente, .NET ci viene incontro e ci fornisce un pattern Broker “pronto all’uso”: il .NET remoting. In MSDN esiste infatti la guida su come implementare il pattern Broker utilizzando .NET remoting.

Ma se si utilizza Microsoft Windows Server 2003, allora è possibile utilizzare una grande scorciatoia per il Load-Balancing clustering (oltre a quella appena vista): le Windows Server 2003 Clustering Services. Consentono di costruire un cluster di server. Il cluster è dotato di un indirizzo IP virtuale (xxx.yyy.zzz.kkkk), che quando richiesto trova corrispondenza con un indirizzo IP reale della macchina fisica più libera. In questo modo, è sufficiente creare in alternativa:

  • una dll acceduta tramite .NET remoting
  • un web service

e replicarlo in tutte le macchine fisiche. A questo punto, quando accederemo all’indirizzo IP xxx.yyy.zzz.kkkk e richiederemo il servizio interessato, il sistema operativo ci smisterà automaticamente alla macchina più libera. Non c’è bisogno di alcuna modifica nel nostro codice, stupefacente no??

Per maggiori informazioni, si veda l’ottimo articolo su come scrivere applicazioni “clusterizzate” in c# di Gerald Gibson Jr.

Posted in .net | No Comments »

Memorizzare Richieste e Risposte SOAP con le SoapExtension

Posted by Ricibald on 15th January 2008

In .NET le richieste e le risposte vengono sottoposte a wrapping e serializzate nella nostra comoda struttura ad oggetti, come visto in questo precedente post. Ma a volte è necessario proprio conoscere la richiesta SOAP “nuda e cruda”. Anche se ciò che stiamo richiedendo non è niente di apparentemente complicato, la cosa si rileva però molto più complicata del previsto.

Il ciclo di vita di un web service è mostrato nella figura seguente, preso dall’articolo di Microsoft:

Ciclo di vita di un web service

Le varie fasi possono essere intercettate utilizzando le Soap Extension. Normalmente vengono utilizzate per decorare il messaggio SOAP con cifratura o compressione, ma nel nostro caso saranno semplicemente utilizzate per memorizzare le richieste e risposte SOAP.

Innanzitutto è necessario creare una classe che estende dalla classe SoapExtension:

public class MessagesPrinterSoapExtension : SoapExtension
{
    public override object GetInitializer(Type obj)
    {
        return null;
    }

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attr)
    {
        return null;
    }

    public override void Initialize(object initializer)
    {
    }
}

Bisogna poi implementare i seguenti metodi:

private Stream _workingStream = null;
private Stream _originalStream = null;
private static string _soapRequestMessage = null;
private static string _soapResponseMessage = null;
private SoapMessageStage? _previousStage = null;

/// <summary>
/// Allows a SOAP extension access to the memory buffer containing the SOAP request or response
/// </summary>
/// <param name="stream">A memory buffer containing the SOAP request or response.</param>
/// <returns>New memory buffer that this SOAP extension can modify</returns>
public override Stream ChainStream(Stream originalStream)
{
    _originalStream = originalStream;
    _workingStream = new MemoryStream();
    bool isResponseMessage = _previousStage != null && _previousStage == SoapMessageStage.AfterSerialize;
    if (isResponseMessage) // response message
    {
        // Get the SOAP body response as a string and copy content in a seekable stream
        _soapResponseMessage = GetMessageAndCopyStream(_originalStream, _workingStream);
    }
    return _workingStream;
}

public override void ProcessMessage(SoapMessage message)
{
    // a SOAP message has 4 stages. We're interested in AfterSerialize
    if (message.Stage == SoapMessageStage.AfterSerialize)
    {
        // Get the SOAP body request as a string and copy content in a seekable stream
        _soapRequestMessage = GetMessageAndCopyStream(_workingStream, _originalStream);
    }
    _previousStage = message.Stage;
}

Che utilizzano il metodo di utilità seguente:

/// <summary>
/// Extracts the content from input and copies it in the specified stream
/// </summary>
private static string GetMessageAndCopyStream(Stream inStream, Stream outStream)
{
    string inStreamContent;

    // If allowed, rewind
    if (inStream.CanSeek)
    {
        inStream.Position = 0;
    }

    // Read content
    StreamReader inReader = new StreamReader(inStream);
    inStreamContent = inReader.ReadToEnd();

    // Copy to output
    StreamWriter writer = new StreamWriter(outStream);
    writer.Write(inStreamContent);
    writer.Flush();

    // If allowed, rewind
    if (outStream.CanSeek)
    {
        outStream.Position = 0;
    }
    return inStreamContent;
}

Ma ora viene naturale chiedersi: come fa il mio proxy verso il web service a sapere che esisto? Semplice: basta dichiarare la classe appena creata nel file di configurazione:

<configuration>
  <system.web>
    <webServices>
      <soapExtensionTypes>
        <add type="MyNamespace.MessagesPrinterSoapExtension,MyDll" priority="1" group="Low" />
      </soapExtensionTypes>
    </webServices>
  </system.web>
</configuration>

E’ tutto! Consigli, critiche?…

Posted in .net, web service | 10 Comments »

Clear dei Web Control in .NET

Posted by Ricibald on 12th January 2008

Nelle pagine web si ha spesso l’esigenza di resettare il contenuto dei controlli a seguito della pressione di un pulsante.

Si immagini ad esempio una pagina di inserimento di un post in un blog. Dopo aver aggiunto correttamente la pagina nel blog si vuole ricevere la stessa pagina di composizione ma resettata di tutti i campi (come in WordPress).

In .NET per ottenere questo comportamento abbiamo diverse tecniche, che hanno però inconvenienti:

  • disabilitare il ViewState dai controlli
    • il ViewState non viene però conservato in caso di PostBack differenti, come errori nell’inserimento della pagina
  • resettare i controlli nel Page_Load, nel caso IsPostBack vale true
    • i controlli vengono però resettati per ogni PostBack, non per quello specifico del pulsante
    • è necessario resettare manualmente tutti i controll, che potrebbe essere un lavoro con errori di consistenza (come tutti i lavori “copia e incolla”)
  • resettare i controlli nel gestore del click del pulsante
    • è necessario resettare manualmente tutti i controll, che potrebbe essere un lavoro con errori di consistenza (come tutti i lavori “copia e incolla”)
  • eseguire il Response.Redirect verso sé stesso nel gestore del click del pulsante
    • perdo i valori di tutti i controlli: non posso selezionare quali controlli viceversa mantenere
    • si possono passare eventuali valori tramite QueryString:
      Response.Redirect(Request.Url.GetLeftPart(UriPartial.Path) + "?message="+messaggio);
  • eseguire il Server.Transfer verso sé stesso nel gestore del click del pulsante
    • perdo i valori di tutti i controlli: non posso selezionare quali controlli viceversa mantenere
    • si possono passare eventuali valori tramite CrossPagePostback

La tecnica migliore è senza dubbio l’ultima. Come al solito, non so se questa sia la tecnica migliore: sono più che curioso di scoprire se ci sono tecniche che non ho considerato…

Posted in .net | No Comments »

Confronto dei Metodi in .NET per Generare Proxy verso Web Service

Posted by Ricibald on 10th January 2008

Per dialogare verso un Web Service si utilizza normalmente un proxy per generare la versione “ad oggetti” delle richieste e delle risposte che dovranno viaggiare nella rete.

Tale versione ad oggetti è un rappresentante (proxy) verso la reale richiesta SOAP. Spesso quindi si parla di “proxy verso il web service”, ma solo nel significato inglese di “rappresentante”, non come riferimento verso il design pattern Proxy. In realtà, quello che viene fatto è costruire il wrapping delle richieste, il ché coincide con il design pattern Adapter.

Esistono due strumenti in .NET che consentono di creare un proxy verso il web service:

  • in Visual Studio, selezionando “add web reference”: tale metodo crea automaticamente il proxy verso il web service dichiarato
  • con il comando wsdl.exe

Viene quindi naturale cercare di capire quale metodo utilizzare, partendo dal presupposto che il primo metodo è molto più semplice e immediato. In realtà grosse differenze non ce ne sono:

  • entrambe generano classi che non dovrebbero mai essere modificate: per questo utilizzare altri approcci, come classi partial
  • entrambe possono essere customizzate:
    • in “wsdl.exe” tramite argomenti documentati qui
    • in “add web reference” configurando proprietà di “Reference.map” o creando una classe condivisa che restituisce un’istanza del proxy opportunamente configurata

Ma alcune differenze ci sono e, come è lecito aspettarsi, “wsdl.exe” è uno strumento leggermente più potente, anche se più complesso. Infatti, le seguenti personalizzazioni di wsdl.exe non trovano corrispondenza nella procedura di Visual Studio:

  • /sharetypes: consente di condividere tipi uguali utilizzati in servizi differenti (i quali altrimenti avrebbero un loro specifico proxy)
  • /server[interface]: consente di creare un servizio web service sulla base di un file wsdl. La procedura è opposta a quella utilizzata in Visual Studio, in cui quando creo un metodo si aggiorna il wsdl. L’approccio wsdl->codice è però preferibile in modo tale da mantenere l’interfaccia stabile verso i client
  • /parsableerrors: visualizza gli errori in un formato simile a quello dei compilatori
  • /order: genera identificatori di ordine espliciti

Le seguenti funzioni, viceversa, non trovano corrispondenza in “wsdl.exe”:

  • update dinamico: l’interfaccia può essere aggiornata tramite semplice click

A parte queste particolarità, i risultati prodotti sono equivalenti e quindi, a meno di esigenze specifiche come quelle elencate, consiglierei sempre di utilizzare la procedura di Visual Studio, molto più rapida.

Come al solito, non so se la mia analisi è corretta, se ci sono critiche scrivete così l’articolo verrà corretto (e imparerò dai miei errori…)

Posted in .net, web service | 31 Comments »