React Development Blog

Blog italiano sullo sviluppo e le tecnologie Microsoft

C# e dispatch singolo e multiplo

Cosa si intende per dispatch? Per rispondere a questa domanda, osserviamo innanzitutto gli snippet di codice qua sotto 

public class Base
{
    public virtual int Metodo() { return 0; }
}

public class A : Base
{
    public override int Metodo()
    {
        return 1;
    }
}

public class B : Base
{
    public override int Metodo()
    {
        return 2;
    }
}

Definite le classi Base,A,B come sopra, cosa scriverà in Console lo snippet seguente? 

static void Main(string[] args)
{
    Base b = new Base();

    var randInt = new Random().Next(3);

    if (randInt == 1)
        b = new A();
    else if (randInt == 0)
        b = new B();

    Console.Write(b.Metodo());

}

La risposta è: dipende! Il metodo che viene risolto dipende dal tipo a run-time della variabile b. Quindi in conclusione possiamo dire che la risoluzione di un metodo non finisce a compile-time ma può dipendere dai tipi a run-time degli oggetti.

Nel caso presentato sopra il metodo non ha parametri e l'unico oggetto coinvolto è l'istanza su cui chiamiamo il metodo. Ma cosa succede se il metodo ha parametri? Come si comporta in questo caso? Dipende nuovamente dai tipi a run-time?
Proponiamo un altro snippet di codice per mostrare la situazione di cui stiamo parlando.

public class Base
{
    public virtual int Metodo() { return 0; }

    public virtual string Metodo(BaseArgument obj)
    {
        return "Base + BaseArgument";
    }

    public virtual string Metodo(SpecialArgument obj)
    {
        return "Base + SpecialArgument";
    }
}

public class A : Base
{
    public override int Metodo()
    {
        return 1;
    }

    public override string Metodo(BaseArgument obj)
    {
        return "A + BaseArgument";
    }

    public override string Metodo(SpecialArgument obj)
    {
        return "A + SpecialArgument";
    }
}

public class B : Base
{
    public override int Metodo()
    {
        return 2;
    }

    public override string Metodo(BaseArgument obj)
    {
        return "B + BaseArgument";
    }

    public override string Metodo(SpecialArgument obj)
    {
        return "B + SpecialArgument";
    }
}

public class BaseArgument
{

}

public class SpecialArgument : BaseArgument
{

}

La classe Base da cui ereditano A e B ha un metodo con due overload, uno che prende un BaseArgument e uno che prende uno SpecialArgument. Vediamo qua sotto un esempio di chiamata. Cosa scriverà stavolta in Console?

static void Main(string[] args)
{
    Base b = new A();
    BaseArgument argomento = new SpecialArgument();

    Console.Write(b.Metodo(argomento));

}

La stringa stampata è "A + BaseArgument" in quanto il metodo viene risolto con il tipo a run-time dell'oggetto su cui è chiamato ma con il tipo a compile-time dell'argomento. Questo è il comportamento che si indica con dispatch singolo. Un dispatch multiplo permetterebbe di risolvere un metodo considerando i tipi a runtime di tutti gli oggetti coinvolti.

E' possibile simulare il dispatch multiplo in C#, in uno dei prossimi post faremo vedere 2 tecniche possibili per farlo.

Anamorfismi, Fibonacci e C#

Qualche tempo fa abbiamo visto qui come costruire la successione di Fibonacci sfruttando gli iteratori e l'interfaccia IEnumerable<T>. Come possiamo generalizzare questo approccio?

Quello che abbiamo fatto è stato generare a partire da un seme una successione di valori finché una certa condizione non diventasse falsa.

Questo procedimento non è nuovo ed è stato applicato più volte, rimanendo nel mondo .NET basta guardare alla funzione Seq.Unfold di F#. Per chi volesse approfondire gli aspetti teorici di questo problema rimandiamo alla pagina wikipedia sugli anamorfismi.

Idealmente la funzione andrebbe aggiunta alla classe statica Enumerable ma questo ovviamente non è possibile quindi la metteremo in una classe statica apposita.

Prendendo come riferimento l'articolo wikipedia implementeremo la prima versione della funzione che chiameremo Unfold seguendo l'esempio di F#. La prima versione prevede la presenza di 3 parametri in ingresso:
  1. Un valore iniziale
  2. Una funzione generatrice
  3. Una funzione che restituisce un booleano e che rappresenta la condizione da testare prima di ogni generazione

Nonostante tutto l'implementazione della funzione è molto semplice
public static class Ext
{
    public static IEnumerable<T> Unfold<T, U>(Func<U, Tuple<T, U>> generate,
                                                Func<U, bool> finished,
                                                U seed)
    {
        while (!finished(seed))
        {
            var next = generate(seed);
            yield return next.Item1;
            seed = next.Item2;
        }
    }
}
Il metodo risultante è generico con due Type Parameters, la parte più complicata è sicuramente la funzione generatrice che ha il compito di generare il valore da restituire e il seme dell'iterazione successiva.

Per chiarezza riproduciamo gli esempi della funzione Seq.Unfold presenti su MSDN. Come primo esempio vediamo come generare i numeri da 0 a 20:

Func<int, Tuple<int,int>> generate = p => new Tuple<int,int>(p,p+1);
Func<int,bool> finished = p => p > 20;

foreach(var item in Ext.Unfold(generate, finished, 0))
{
    Console.WriteLine(item);
}

La funzione generatrice restituisce come valore l'intero corrispondente a seed e crea il nuovo seed sommando 1. Vediamo invece come generare i numeri di Fibonacci. La questione purtroppo si complica molto in quanto il seme e quindi lo stato da passare di volta in volta al metodo è una coppia di valori (i due che vanno sommati)

quindi il seed è definito

var seed = new Tuple<int,int>(0,1);
Il test di conclusione della generazione è ancora abbastanza semplice:

Func<Tuple<int, int>, bool> finished = p => p.Item2 > 200;

Il vero mostro è la funzione generatrice che data una tupla di interi genera un'altra tupla che contiene un intero e una tupla di interi

Func<Tuple<int,int>,Tuple<int,Tuple<int,int>>> generate = 
    p => new Tuple<int,Tuple<int,int>>(p.Item2,new Tuple<int,int>(p.Item2, p.Item1+p.Item2));

Il codice per calcolare e stampare a video i numeri di Fibonacci è

var seed = new Tuple<int,int>(0,1);

Func<Tuple<int, int>, bool> finished = p => p.Item2 > 200;

Func<Tuple<int,int>,Tuple<int,Tuple<int,int>>> generate = 
    p => new Tuple<int,Tuple<int,int>>(p.Item2,new Tuple<int,int>(p.Item2, p.Item1+p.Item2));

foreach (var item in Ext.Unfold(generate, finished, seed))
{
    Console.WriteLine(item);
}

Ci rendiamo perfettamente conto che l'usabilità di questo metodo è molto bassa, il problema fondamentale è la presenza di tipi molto complessi. Infatti questo tipo di funzionalità, come si capisce anche dalle risorse che abbiamo linkato, è molto spesso legata ad un sistema di type inference più potente di quello di C# come ad esempio quello di F#.

Il team di sviluppo di C# sta pensando di aggiungere un modo più rapido e fruibile di utilizzare e istanziare le tuple, se sarà così potremo forse riscrivere e utilizzare più comodamente il metodo Unfold!

Snippet Visual Studio personalizzati

Creare degli snippet di codice personalizzati all'interno di Visual Studio può essere molto utile soprattutto se si hanno parti di codice che si ripetono spesso ma che non si possono "astrarre" in alcun modo.

Durante lo sviluppo di web form quando definiamo delle proprietà su una pagina, di default il loro valore non viene mantenuto tra i vari postback. Un modo per ovviare a questa mancanza è salvare le proprietà nella viewstate, questo significa che ogni volta che vogliamo accedere a questi valori dobbiamo controllare che l'oggetto nella viewstate sia non nullo, del tipo giusto e poi eseguire un cast al tipo voluto.

Volendo semplificare l'accesso alla viewstate utilizziamo le proprietà in questo modo:

public int MioIntero
{
    get
    {
        if (ViewState["MioIntero"] == null)
            return 0;

        return (int)ViewState["MioIntero"];
    }
    set
    {
        ViewState["MioIntero"] = value;
    }
}

In questo modo tramite il get ed il set della proprietà MioIntero salviamo nella viewstate e recuperiamo i valori in maniera semplice e veloce. Avendo diverse di queste proprietà, è necessario scrivere diverse volte lo stesso codice o effettuare un copia-incolla con il rischio di dimenticare di modificare le stringhe con cui recuperiamo dalla viewstate.

Visual Studio contiene diversi snippet di codice che permettono di inserire proprietà nelle classi, ad esempio "prop" o "propfull". Vediamo come creare uno snippet "propview" per inserire una proprietà del tipo visto poco fa.

Uno snippet di Visual Studio è un file xml contenente del markup particolare, come riferimento si possono prendere gli snippet di default presenti nella cartella (per una versione inglese di VS2013)

%programfiles(x86)%\Microsoft Visual Studio 12.0\VC#\Snippets\1033\Visual C#

In particolare guardiamo il file xml di "propfull"

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
	<CodeSnippet Format="1.0.0">
		<Header>
			<Title>propfull</Title>
			<Shortcut>propfull</Shortcut>
			<Description>Code snippet for property and backing field</Description>
			<Author>Microsoft Corporation</Author>
			<SnippetTypes>
				<SnippetType>Expansion</SnippetType>
			</SnippetTypes>
		</Header>
		<Snippet>
			<Declarations>
				<Literal>
					<ID>type</ID>
					<ToolTip>Property type</ToolTip>
					<Default>int</Default>
				</Literal>
				<Literal>
					<ID>property</ID>
					<ToolTip>Property name</ToolTip>
					<Default>MyProperty</Default>
				</Literal>
				<Literal>
					<ID>field</ID>
					<ToolTip>The variable backing this property</ToolTip>
					<Default>myVar</Default>
				</Literal>
			</Declarations>
			<Code Language="csharp"><![CDATA[private $type$ $field$;
 
	public $type$ $property$
	{
		get { return $field$;}
		set { $field$ = value;}
	}
	$end$]]>
			</Code>
		</Snippet>
	</CodeSnippet>
</CodeSnippets>
Le modifiche da apportare sono pochissime:
  • Title
  • Shortcut
  • Description
  • Author
  • Code
La scelta di "propfull" non è stata a caso, i tag declaration e literal ci permettono di definire quelle stringhe che si ripetono più volte e che vengono cambiate tutte in automatico modificandone una sola. Un comportamento utile e familiare a chiunque abbia usato propfull più di una volta. Vediamo quindi il markup xml definitivo



<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets
    xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Proprietà ViewState</Title>
      <Shortcut>propviewstate</Shortcut>
      <Description>Snippet di codice per proprietà viewstate</Description>
      <Author>React Consulting</Author>
   <SnippetTypes>
				<SnippetType>Expansion</SnippetType>
			</SnippetTypes>
		</Header>
		<Snippet>
			<Declarations>
				<Literal>
					<ID>type</ID>
					<ToolTip>Property type</ToolTip>
					<Default>int</Default>
				</Literal>
				<Literal>
					<ID>property</ID>
					<ToolTip>Property name</ToolTip>
					<Default>MyProperty</Default>
				</Literal>
			</Declarations>
      <Code Language="CSharp">
        <![CDATA[public $type$ $property$
    {
        get
        {
            if (ViewState["$property$"] == null)
                return default($type$);
 
            return ($type$)ViewState["$property$"];
        }
        set
        {
            ViewState["$property$"] = value;
        }
    }]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

Fibonacci Enumerable

Oggi costruiamo una classe che permette di produrre tutti i numeri di fibonacci (più piccoli di Int32.MaxValue). Per farlo implementiamo l'interfaccia IEnumerable<int> e quindi per ottenere i numeri in successione dovremo eseguire una foreach su una istanza della classe.

L'implementazione non comporta particolari difficoltà, l'unica attenzione va fatta alla necessità di restituire i fibonacci fino a Int32.MaxValue. Ovviamente qualunque intero è più piccolo di Int32.MaxValue e sommando due interi che insieme superano Int32.MaxValue, non si ottiene nessun tipo di eccezione. Quindi testare in questo modo non funziona

if(n+m<Int32.MaxValue)
    // fai qualcosa

Le strade, a nostro avviso, sono due:
- mettere tutto il codice in un blocco "checked" e quindi ottenere una eccezione di tipo overflow se superiamo Int32.MaxValue
- utilizzare delle variabili long in modo che la disuguaglianza n+m<Int32.MaxValue abbia senso.

Noi abbiamo scelto di utilizzare la seconda e castare a int i valori prima di restituirli. Detto questo l'implementazione è molto semplice e la riportiamo qua sotto
class FibonacciEnumerable : IEnumerable<int>
{
    public IEnumerator<int> GetEnumerator()
    {
        long n = 1;
        long n1 = 1;
        long Fib = 2;

        yield return 1;
        yield return 1;

        while (Fib < Int32.MaxValue)
        {
            yield return (int)Fib;
            n = n1;
            n1 = Fib;
            Fib = n + n1;
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

I primi tre valori sono scritti direttamente nel codice, gli altri calcolati.

Valore di default di una classe

Molto spesso può essere utile avere un modo rapido per ottenere un'istanza di default di una classe. Il valore 

default(T)

per una classe (non struct!) è sempre null e quindi non ci torna molto utile. Vediamo quindi come produrre un'istanza default un'unica volta e recuperarla ogni volta che ne abbiamo bisogno.

public class Dummy
{
    public static Dummy Default { get; private set; }
        
    static Dummy()
    {
        Default = new Dummy();
    }
}

Nel codice che abbiamo mostrato c'è una proprietà statica Default che ci permette di recuperare un'istanza della classe. La logica di creazione di questa istanza sta in un costruttore statico che viene chiamato dal Framework una sola volta prima di accedere ad una qualunque proprietà o metodo statico della classe a cui appartiene.

Per evitare che la proprietà Default sia modificata in qualunque modo abbiamo definito privato il set.

In questo modo abbiamo ottenuto un'istanza, a cui accedere rapidamente, la cui creazione avviene una sola volta. Il vantaggio di questo approccio sta esattamente nel fatto che non abbiamo bisogno di istanziare un nuovo oggetto ogni volta che ne abbiamo bisogno ma possiamo accedere sempre allo stesso. Un notevole vantaggio nel caso in cui la creazione dell'istanza sia "dispendiosa".
 

Bootlint, un aiuto durante lo sviluppo di pagine basate su Bootstrap

Oggi condividiamo con voi un metodo rapido e molto semplice per aiutarvi nella creazione di pagine web basate sul famoso framework html/css/javascript bootstrap.

Per verificare che il markup che avete costruito sia corretto rispetto ai requisiti dei componenti di bootstrap vi basta aprire Chrome, gli strumenti per sviluppatori e incollare nella console il seguente codice 

(function(){var s=document.createElement("script");s.onload=function(){bootlint.showLintReportForCurrentDocument([]);};s.src="https://maxcdn.bootstrapcdn.com/bootlint/latest/bootlint.min.js";document.body.appendChild(s)})();
Nella console stessa vedrete un log che vi segnalerà tutti gli errori presenti nella pagina. Facile no?

Per maggiori dettagli visitate la pagina github del progetto

Extension Method per il databind di DropDownList

Ogni volta che nello sviluppo di una pagina Web Form dobbiamo effettuare il databind di una DropDownList, vanno settate diverse proprietà e chiamato il metodo Databind() e se poi vogliamo aggiungere servono altre righe di codice. Se in una pagina abbiamo numerose DropDownList questo porta ad una notevole ripetizione di codice. Un extension method sulla classe DropDownList risolve questi problemi e ci permette di inserire anche dei parametri di default. 

Qui sotto il codice della nostra implementazione

public static class Extension
{
    public static void Binda<T>(this DropDownList dropdownlist, IEnumerable<T> source, string valueField = "Value", string textField = "Text", bool addDefaultItem = false, string defaultValue = "", string defaultText = "-----")
    {
        if (dropdownlist == null)
            throw new NullReferenceException("dropdownlist is null");
        if (source == null)
            throw new NullReferenceException("source is null");

        dropdownlist.DataSource = source;
        dropdownlist.DataTextField = textField;
        dropdownlist.DataValueField = valueField;
        dropdownlist.DataBind();

        if (addDefaultItem)
        {
            dropdownlist.Items.Insert(0, new ListItem { Text = defaultText, Value = defaultValue });
        }
    }
}

La prima cosa che facciamo è validare i dati in ingresso e poi segue semplicemente il codice per effettuare un databind della dropdownlist. L'utilizzo è molto semplice, mostriamo di seguito alcuni esempi:

// Un'unico parametro: nel caso in cui gli oggetti in source abbiamo una proprietà "Value" e una "Text"
//Non viene aggiunto nessun elemento di default
drop.Binda(source);

// 3 parametri: la proprietà "Id" è il valore, "Nome" è il testo visualizzato. 
//Non viene aggiunto nessun elemento di default
drop.Binda(source, "Id", "Nome");

// 4 parametri: la proprietà "Id" è il valore, "Nome" è il testo visualizzato. 
//Viene aggiunto un elemento di default con valore "" e testo "-----"
drop.Binda(source, "Id", "Nome", true);

// 6 parametri: la proprietà "Id" è il valore, "Nome" è il testo visualizzato. 
//Viene aggiunto un elemento di default con valore "0" e testo "Tutti"
drop.Binda(source, "Id", "Nome", true, "0", "Tutti");

Delegati, una semplice implementazione tramite interfacce

I delegati sono dei tipi che incapsulano uno o più metodi, nel framework .NET sono usati in abbondanza e sono assolutamente essenziali nella gestione degli eventi. Nella gestione degli eventi di una web form ASP.NET il framework rende molto facile e intuitivo utilizzarli anche senza capire come funzionano effettivamente. Anche LINQ fa un utilizzo massivo dei delegati nella forma di lambda expressions.

Per facilitare il lavoro degli sviluppatori nel framework sono presenti dei delegati generici che rappresentano dei tipi comuni:
Action e Func<T>. Entrambi hanno molti overload che permettono di avere tipi col più parametri in ingresso, rimandiamo a MSDN per i dettagli. È possibile tuttavia definire i propri tipi di delegati e utilizzarli come variabili o proprietà all'interno del codice. 

Potremmo avere quindi un metodo A che prende in ingresso un altro metodo B e dopo aver eseguito del codice lo invoca senza sapere nulla dell'implementazione di B. La situazione può complicarsi facilmente e diventare un vero rompicapo. 

Per capire meglio come funzionano i delegati si può pensare ad un tipo di delegato come ad una interfaccia contenente un solo metodo e alle istanze di questo delegato come a istanze di classi che implementano l'interfaccia.

Vediamo nel codice seguente come definire un delegato e due sue istanze utilizzando la keyword delegate e le lambda expressions:
public delegate int DelOperazione(int i, int j);

class Program
{
    static void Main(string[] args)
    {
        DelOperazione delSomma = (i, j) => i + j;
        DelOperazione delMoltiplicazione = (i, j) => i * j;

        int n = 2;
        int m = 3;


        var risu1 = delSomma(n, m);
        var risu2 = delMoltiplicazione(n, m);
    }
}

La sintassi è semplice, veloce e pulita e le variabile delSomma e delMoltiplicazione sono metodi che possiamo chiamare in qualunque momento come ogni altro metodo. Vediamo come ottenere lo stesso utilizzando interfacce e classi. La sintassi è decisamente più lunga:
public interface IOperazione
{
    int Calcola(int i, int j);
}

public class ClasseSomma : IOperazione
{
    public int Calcola(int i, int j)
    {
        return i + j;
    }
}

public class ClasseMoltiplicazione : IOperazione
{
    public int Calcola(int i, int j)
    {
        return i * j;
    }
}

class Program
{
    static void Main(string[] args)
    {
        IOperazione classeSomma = new ClasseSomma();
        IOperazione classeMoltiplicazione = new ClasseMoltiplicazione();

        int n = 2;
        int m = 3;

        var somma = classeSomma.Calcola(n, m);
        var prod = classeMoltiplicazione.Calcola(n, m);
    }
}

L'interfaccia IOperazione incapsula il metodo e possiamo quindi utilizzare le nostre classi ignorando effettivamente l'implementazione del metodo Calcola di cui conosciamo solamente la firma. Invocare i metodi è più indiretto del caso dei delegati.

La sintassi alternativa è decisamente più lunga e laboriosa, pensiamo però che possa aiutare a capire concettualmente i delegati.

Validazione .Net con stile Bootstrap

Tramite l'override della funzione javascript ValidatorUpdateDisplay(val) è possibile rendere tutti gli errori di validazione con lo stile Bootstrap. La funzione in oggetto è la seguente:

function ValidatorUpdateDisplay(val) {
    if (!val.isvalid) {
        $(val).parent().data('val-id', $(val).attr('id'));
        $(val).parent().addClass('has-error');
    }
    else {
        if ($(val).parent().data('val-id') == $(val).attr('id')) {
            $(val).parent().data('val-id', '');
            $(val).parent().removeClass('has-error');
        }
    }
}

Per rendere questo processo funzionante, è necessario ultilizzare nelle pagine la struttura Html suggerita da Bootstrap per i forms, vale a dire:

<div class="form-group">
    <label>Tipo prodotto</label>
    <asp:DropDownList runat="server" ID="DropDownList1" CssClass="form-control">
    </asp:DropDownList>
    <asp:RequiredFieldValidator runat="server" ControlToValidate="ddlTipoProdotto"></asp:RequiredFieldValidator>
</div>
<div class="form-group">
    <label>Prodotto</label>
    <asp:TextBox runat="server" ID="txtProdotto" CssClass="form-control"></asp:TextBox>
    <asp:RequiredFieldValidator runat="server" ControlToValidate="txtProdotto"></asp:RequiredFieldValidator>
</div>

SignalR, un semplice esempio.

Oggi utilizzeremo la libreria SignalR per creare una semplice pagina web (html!) che permetterà di "scambiare messaggi" tra due utenti diversi utilizzando un semplice input. SignalR è una libreria fornita da Microsoft, composta sia da codice da eseguire lato server sia codice da eseguire lato client. La libreria aggiunge delle funzionalità "real-time" alle pagine web e ci permetterà di eseguire codice javascript sul client con delle richieste che "partono" lato server. Per maggiori dettagli si può vedere qui

Per cominciare creiamo un nuovo progetto in visual studio, un semplice sito web sarà sufficiente. In seguito aggiungiamo tramite NuGet la libreria SignalR. 

Successivamente aggiungiamo al progetto una classe "owin startup" e modifichiamo il codice come segue:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
        app.MapSignalR();
    }
}
Aggiungiamo una pagina html, facendo attenzione a linkare le librerie javascript necessarie al funzionamento di SignalR

<script src="Scripts/jquery-1.9.0.js"></script>
<script src="Scripts/jquery.signalR-2.1.0.min.js"></script>
<script src="signalr/hubs"></script>
Adesso aggiungiamo una classe c# al progetto "CopyPasteHub.cs" e inseriamo il seguente codice

public class CopyPasteHub : Hub
{
    public void Send(string message)
    {
        Clients.All.broadcastMessage(message);
    }
}
Questo codice definisce nel nostro Hub un metodo "Send" che potremo chiamare via javascript dalle pagine web e che a sua volta chiamerà il metodo javascript broadcastMessage su tutti i client che attualmente stanno visitando la nostra pagina. L'ultimo passo è aggiungere il codice seguente alla pagina web
<input id="text" type="text" />
<button id="bottone">Salva</button>
<script type="text/javascript">
    $(function () {
        var chat = $.connection.copyPasteHub;
        chat.client.broadcastMessage = function (message) {
            console.log("broadcast");
            $('#text').val(message);
        };
 
        $.connection.hub.start().done(function () {
            $('#bottone').click(function () {
                chat.server.send($('#text').val());
            });
        });
    });
</script>
Queste righe di codice ci permettono di fare due cose:
  1. Definire il metodo client broadcastMessage su tutti i client del nostro Hub che prende il messaggio e lo mette nell'input.
  2. Al caricamento dell'hub agganciamo all'evento click del pulsante chiama il metodo server "send" che si occuperà di notificare tutti gli altri client del nuovo messaggio.
Con pochissimo codice e poca fatica abbiamo creato una pagina che permette la comunicazione di due utenti. Per testarlo basta aprire l'applicazione su due tab o finestre diverse del browser, scrivere qualcosa nella textbot e cliccare sul pulsante salva. Si potrà osservare che in tutte le finestre in cui è aperta la pagina viene visualizzato il messaggio. Ovviamente abbiamo solamente scalfito la superficie della libreria SignalR ma abbiamo iniziato ad usarla e capire i suoi meccanismi.