DarioSantarelli.Blog("UgiDotNet"); http://blogs.ugidotnet.org/dsantarelli/Default.aspx &lt;sharing mode=”On” users=”*” /&gt; it-IT Dario Santarelli Subtext Version 2.6.0.0 DarioSantarelli.Blog("UgiDotNet"); http://blogs.ugidotnet.org/images/RSS2Image.gif http://blogs.ugidotnet.org/dsantarelli/Default.aspx 77 60 Reactive Extensions (Rx) e Weak events .NET http://blogs.ugidotnet.org/dsantarelli/archive/2012/03/25/reactive-extensions-rx-e-weak-events.aspx <p>Come molti sanno, il classico uso degli eventi in C# ha un effetto collaterale: se non viene gestita correttamente la strong reference che si forma tra l’oggetto che espone l’evento (<em>EventSource</em>) e gli oggetti che registrano gli event handlers (<em>EventListeners</em>), si rischia di generare dei <a href="http://en.wikipedia.org/wiki/Memory_leak">memory leak</a>. Infatti, se l’ <em>EventSource </em>ha un ciclo di vita più lungo rispetto agli <em>EventListeners</em>, questi ultimi, se non si deregistrano correttamente dall’evento, vengono tenuti in vita in memoria anche quando non ci sono più riferimenti attivi verso di essi e quindi non vengono reclamati dalla garbage collection fintantoché l’<em>EventSource</em> è attivo. <br /> Nella letteratura esistono diverse tecniche per evitare questa tipologia di memory leak: <a href="http://www.codeproject.com/Articles/29922/Weak-Events-in-C">questo articolo</a> (“Weak Events in C#”) in particolare ne parla in maniera molto chiara e presenta vari approcci per implementare degli “weak event” (listener-side o source-side), ovvero degli eventi a cui un <em>EventListener</em> può registrarsi senza incorrere nel rischio di diventare un memory leak. Le soluzioni ormai più diffuse sfruttano la classe <a href="http://msdn.microsoft.com/en-us/library/system.weakreference.aspx">WeakReference</a> , che permette di referenziare un oggetto in modo “<a href="http://en.wikipedia.org/wiki/Weak_reference">debole</a>”, ovvero mantenendolo comunque reclamabile dalla garbage collection. <br /> Giusto per citare un caso noto, WPF introduce un design pattern chiamato “<a href="http://msdn.microsoft.com/en-us/library/aa970850.aspx">Weak Event</a>”. Esso fondalmente si basa sull’implementazione concreta di un <a href="http://msdn.microsoft.com/en-us/library/system.windows.weakeventmanager.aspx">WeakEventManager</a>, ovvero un “dispatcher” centralizzato per ogni evento che vogliamo gestire in modalità weak (molti degli aspetti del data binding di WPF sono implementati tramite weak event). In questo pattern, poi, tutti i listener del weak event devono implementare l’interfaccia <a href="http://msdn.microsoft.com/en-us/library/system.windows.iweakeventlistener.aspx">IWeakEventListener</a> e quindi registrarsi/deregistrarsi all’evento passando per i metodi statici <em>AddListener()</em> e <em>RemoveLister()</em> che il <em>WeakEventManager</em> mette a disposizione. Come si può intuire, questa soluzione introduce dell’infrastruttura che non sempre è semplice gestire, soprattutto in applicazioni esistenti che fanno un uso complesso e massivo di eventi. <br /> Pochi giorni fa ho provato quindi ad affrontare la questione attraverso il <a href="http://msdn.microsoft.com/en-us/data/gg577609">Reactive Extensions (Rx) framework</a> e devo dire che le soluzioni che si possono trovare sono alquanto iteressanti e flessibili. Vorrei mostrarne una in particolare. <br /> <br /> Supponiamo di avere una classe <em>EventSource</em> che espone un evento pubblico <em>Event</em>.</p> <pre class="code"><span style="color: blue;">public class </span><span style="color: rgb(43, 145, 175);">EventSource <br /></span>{ <br /> <span style="color: blue;">public event </span><span style="color: rgb(43, 145, 175);">EventHandler </span>Event;<br /><br /> <span style="color: blue;">public void </span>FireEvent() <br /> { <br /> <span style="color: rgb(43, 145, 175);">EventHandler </span>handler = Event; <br /> <span style="color: blue;">if </span>(handler != <span style="color: blue;">null</span>) handler(<span style="color: blue;">this</span>, <span style="color: rgb(43, 145, 175);">EventArgs</span>.Empty); <br /> } <br /><br /> <span style="color: blue;">public int </span>GetEventListenersCount() <br /> { <br /> <span style="color: blue;">return </span>Event.GetInvocationList().Count(); <br /> } <br />}</pre> <p> </p> <p>Per fini di test esponiamo anche un metodo <em>FireEvent()</em> che scatena l’evento ed un metodo <em>GetEventListenersCount() </em>che ci restituisce il numero di listener correntemente registrati all’evento. Creiamo quindi uno <em>StrongEventListener</em>, ovvero un listener che crea una strong reference all’<em>EventSource</em> tramite la classica sintassi di registrazione ad un evento. Poichè questo oggetto non effettua esplicitamente la deregistrazione, diventa un potenziale memory leak.</p> <pre class="code"><span style="color: blue;">public class </span><span style="color: rgb(43, 145, 175);">StrongEventListener <br /></span>{ <br /> <span style="color: blue;">public </span>StrongEventListener(<span style="color: rgb(43, 145, 175);">EventSource </span>eventSource) <br /> { <br /> eventSource.Event += HandleEvent; <br /> }<br /><br /> <span style="color: blue;">private void </span>HandleEvent(<span style="color: blue;">object </span>sender, <span style="color: rgb(43, 145, 175);">EventArgs </span>eventArgs) <br /> { <br /> <span style="color: rgb(43, 145, 175);">Console</span>.WriteLine(<span style="color: rgb(163, 21, 21);">"StrongEventListener&gt; Event Received!"</span>); <br /> } <br />}</pre> <p> <br /> Per riuscire a implementare la corrispettiva versione weak (<em>WeakEventListener)</em> in modo semplice e veloce è possibile sfruttare le <a href="http://msdn.microsoft.com/en-us/data/gg577609">Reactive Extensions (Rx)</a>. <br /> Anzitutto partiamo dal risultato che vorremmo ottenere, ovvero un oggetto che possa registrarsi all’evento del nostro <em>EventSource</em> in modalità weak, ovvero senza correre il rischio di diventare un potenziale memory leak:</p> <pre class="code"><span style="color: blue;">public class </span><span style="color: rgb(43, 145, 175);">WeakEventListener <br /></span>{ <br /> <span style="color: blue;">public </span>WeakEventListener(<span style="color: rgb(43, 145, 175);">EventSource </span>eventSource) <br /> { <br /> eventSource.ObservableEvent() <br /> .WeakSubscribe(<span style="color: blue;">this</span>, (me, e) =&gt; me.HandleEvent(e.Sender, e.EventArgs)); <br /> } <br /><br /> <span style="color: blue;">private void </span>HandleEvent(<span style="color: blue;">object </span>sender, <span style="color: rgb(43, 145, 175);">EventArgs </span>eventArgs) <br /> { <br /> <span style="color: rgb(43, 145, 175);">Console</span>.WriteLine(<span style="color: rgb(163, 21, 21);">"WeakEventListener&gt; Event Received!"</span>); <br /> } <br />}</pre> <p> <br /> Per ottenere questo risultato le fasi da seguire sono due:</p> <p>1) Anzitutto, per leggibilità creiamo un extension method di <em>EventSource</em> che ne converte l’evento in un <em>IObservable</em> grazie al classico metodo <a href="http://msdn.microsoft.com/en-us/library/system.reactive.linq.observable.fromeventpattern(v=vs.103).aspx">Observable.FromEventPattern(...)</a> di Rx. Dietro le quinte viene creato e restituito un oggetto che sarà in grado di gestire la registrazione e la deregistrazione all’evento, aggiungendo e rimuovendo gli event handlers (strong references) verso l’istanza di <em>EventSource</em> in esame.  </p> <pre class="code"><span style="color: blue;">public static </span><span style="color: rgb(43, 145, 175);">IObservable</span>&lt;EventPattern&lt;<span style="color: rgb(43, 145, 175);">EventArgs</span>&gt;&gt; ObservableEvent(<span style="color: blue;">this </span><span style="color: rgb(43, 145, 175);">EventSource </span>eventSource) <br />{ <br /> <span style="color: blue;">return </span>Observable.FromEventPattern&lt;<span style="color: rgb(43, 145, 175);">EventHandler</span>, <span style="color: rgb(43, 145, 175);">EventArgs</span>&gt;(h =&gt; (s, e) =&gt; h(s, e),<br /> h =&gt; eventSource.Event += h,<br /> h =&gt; eventSource.Event -= h); <br />}</pre> <p> <br /> 2) Creiamo un extension method di <em>IObservable&lt;T&gt;</em> che chiamiamo <em>WeakSubscribe</em>. Esso ci permette di passare come argomento un oggetto <em>subscriber</em> ed una <em>action</em> da eseguire a fronte di una notifica (<em>onNext</em>) da parte della sequenza osservabile, in modo tale che il <em>subscriber</em> possa essere notificato tramite l’<em>action</em> in modalità “weak”, ovvero senza mantenere degli strong reference con la sequenza osservabile.</p> <pre class="code"><span style="color: blue;">public static </span><span style="color: rgb(43, 145, 175);">IDisposable </span>WeakSubscribe&lt;T, TSubscriber&gt;(<span style="color: blue;">this </span><span style="color: rgb(43, 145, 175);">IObservable</span>&lt;T&gt; observable,<br /> TSubscriber subscriber, <span style="color: rgb(43, 145, 175);">Action</span>&lt;TSubscriber, T&gt; action) <br /> <span style="color: blue;">where </span>TSubscriber : <span style="color: blue;">class <br /></span>{ <br /> <span style="color: blue;">if </span>(action.Target != <span style="color: blue;">null</span>) <span style="color: blue;">throw new </span><span style="color: rgb(43, 145, 175);">ArgumentException</span>(<span style="color: rgb(163, 21, 21);">"action target must NOT refer a class instance, " </span>+<br /> <span style="color: rgb(163, 21, 21);">"in order to avoid a strong reference to target"</span>); <br /> <span style="color: blue;">var </span>subscriberWeakReference = <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">WeakReference</span>(subscriber); <br /> <span style="color: rgb(43, 145, 175);">IDisposable </span>subscription = <span style="color: blue;">null</span>; <br /> <span style="color: rgb(43, 145, 175);">Action</span>&lt;T&gt; onNext = item =&gt; <br /> { <br /> <span style="color: blue;">var </span>subscriberObj = subscriberWeakReference.Target <span style="color: blue;">as </span>TSubscriber;<br /> <span style="color: blue;">if </span>(subscriberObj != <span style="color: blue;">null</span>) action(subscriberObj, item);<br /> <span style="color: blue;">else </span>subscription.Dispose();<br /> };<br /> subscription = observable.Subscribe(onNext);<br /> <span style="color: blue;">return </span>subscription; <br />}</pre> <p><br /> Qui il gioco si fa interessante ed anche abbastanza complicato: come si osserva, anzitutto il prerequisito è che venga mantenuta una weak reference sul <em>subscriber</em> tramite una <a href="http://msdn.microsoft.com/en-us/library/system.weakreference.aspx">WeakReference</a>. Dopodiché, con una lambda definiamo l’<em>onNext</em> di un <a href="http://blogs.microsoft.co.il/blogs/bnaya/archive/2010/02/26/rx-for-beginners-part-4-anonymous-observer-handler.aspx">AnonymousObserver</a> della sequenza osservabile, il quale ad ogni notifica controlla se il <em>subscriber</em> è ancora vivo (<em>subscriberWeakReference.Target != null</em>). In caso positivo, viene eseguita l’<em>action</em> che fa da “passacarte” tra l’observer ed il <em>subscriber</em>, altrimenti viene chiamata la <em>Dispose()</em> sulla <em>subscription</em> dell’observer (il che nel nostro esempio si traduce nella deregistrazione dall’evento dell’<em>EventSource</em> (<em>eventSource.Event -= h</em>)). <br /> <br /> Ora arriva il trucco: per evitare che il <em>subscriber</em> diventi un potenziale memory leak, <em>action.Target</em> non deve riferire direttamente l’istanza del <em>subscriber</em>, altrimenti significherebbe che il chiamante del nostro metodo sta passando il puntatore <em>this</em> su <em>action.Target</em> (in altre parole,<em> action.Target</em> == <em>null</em> significa che il delegato <em>action</em> invoca un metodo statico e non di istanza). La <em>action</em> quindi deve essere una lambda che chiama metodi di istanza del <em>subscriber</em> tramite il riferimento che essa stessa fornisce come parametro, piuttosto che catturare il riferimento implicito a <em>this</em>: <br /> <br /> .WeakSubscribe(<span style="color: blue;">this</span>, (me, e) =&gt; <span style="color: blue;"><strong>this</strong></span>.HandleEvent(e.Sender, e.EventArgs)); // –&gt; FAIL (ArgumentException) <br /> .WeakSubscribe(<span style="color: blue;">this</span>, (me, e) =&gt; <strong>me</strong>.HandleEvent(e.Sender, e.EventArgs)); // –&gt; OK</p> <p>Questo controllo ci garantisce che la lambda che poi specifichiamo sull’<em>onNext</em> non generi una strong reference verso il <em>subscriber</em> tramite l’<em>action.</em> Ciò renderebbe il <em>subscriber</em> un potenziale memory leak. Capiamo meglio il perché: nel nostro esempio, avendo usato un <em>Observable.FromEventPattern</em>, quando chiamiamo la <em>observable.Subscribe(onNext)</em> scateniamo internamente la registrazione all’evento dell’<em>EventSource</em> (<em>h =&gt; eventSource.Event += h</em>). <em>h</em> è un EventHandler che tramite una closure interna di Rx crea una strong reference tra l’<em>EventSource</em> e la <em>onNext</em>. La <em>onNext</em> a sua volta mantiene uno strong reference verso la <em>action</em>. Quindi, a cascata, se l’<em>action</em> che usiamo nella lambda fosse un metodo di istanza del <em>subscriber,</em> l’istanza stessa del <em>subscriber</em> diventerebbe un potenziale memory leak poiché manterrebbe uno strong reference attivo nella <em>subscription</em><em>.</em> <br /> </p> <p><font size="3"><strong>Conclusioni</strong></font></p> <p>Il grande <strong><u>vantaggio</u></strong> di questa soluzione è che può introdurre in progetti esistenti una semplice gestione thread-safe di weak events al costo di una banale AddReference a <em>System.Reactive.dll</em>. Infatti, a differenza del “Weak Event Pattern”, questo approccio ha praticamente impatto zero sull’ infrastruttura. <br /> L’unico <u><strong>svantaggio</strong></u> invece è che la <em>subscription.Dispose()</em> avviene eventualmente sull’ <em>onNext</em> della sequenza osservabile, il che tradotto nel nostro esempio significa che la deregistrazione dei delegati dall’evento dell’<em>EventSource</em> avviene eventualmente SOLO a fronte dello scatenare tale evento. Di conseguenza, la <em>onNext</em> che usiamo per fare <em>observable.Subscribe(<em>onNext</em>)</em> potrebbe confluire in dei memory leak fintantoché l’evento non viene scatenato dall’<em>EventSource </em>(come ho provato a dimostrare con lo Unit test qua sotto). I nostri <em>WeakEventListener</em> invece vivono felicemente il loro ciclo di vita sicuri di non diventare dei memory leak, poiché ovviamente non sono loro a registrasi direttamente all’evento dell’<em>EventSource</em>.  <br /> In conclusione, prima di considerare questa implementazione e comunque in generale, cerchiamo sempre di valutare attentamente qual’è il ciclo di vita degli <em>EventListener</em> rispetto agli <em>EventSource</em> :). <br /> </p> <pre class="code">[<span style="color: rgb(43, 145, 175);">TestMethod</span>] <br /><span style="color: blue;">public void </span>WeakEvent_With_Rx() <br />{ <br /> <span style="color: blue;">var </span>eventSource = <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">EventSource</span>(); <br /> <span style="color: blue;">var </span>strongListener = <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">StrongEventListener</span>(eventSource); <br /> <span style="color: blue;">var </span>weakListener = <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">WeakEventListener</span>(eventSource); <br /><br /> <span style="color: rgb(43, 145, 175);">Assert</span>.AreEqual(2, eventSource.GetEventListenersCount()); <br /><br /> eventSource.FireEvent(); <br /> <span style="color: green;">// OUTPUT:<br /> // StrongEventListener&gt; EventReceived! <br /> // WeakEventListener&gt; EventReceived! <br /><br /> </span>strongListener = <span style="color: blue;">null</span>; <br /> weakListener = <span style="color: blue;">null</span>; <br /><br /> <span style="color: rgb(43, 145, 175);">GC</span>.Collect(); <br /> <span style="color: rgb(43, 145, 175);">GC</span>.Collect(); <br /> <span style="color: rgb(43, 145, 175);">GC</span>.WaitForPendingFinalizers(); <br /> <span style="color: rgb(43, 145, 175);">GC</span>.Collect();<br /> <br /></pre> <pre class="code"> </pre> <pre class="code"><span style="color: rgb(43, 145, 175);"> Assert</span>.AreEqual(2, eventSource.GetEventListenersCount()); <br /> <span style="color: green;">// 2 Memory Leak <br /> // - StrongEventListener has NOT been garbage collected (Obviously)<br /> // - WeakEventListener has been garbage collected! <br /> // – onNext action delegate is still a memory leak</span><br /> <br /><br /> eventSource.FireEvent(); <br /> <span style="color: green;">// OUTPUT: <br /> // StrongEventListener&gt; EventReceived! <br /><br /> </span><span style="color: rgb(43, 145, 175);">Assert</span>.AreEqual(1, eventSource.GetEventListenersCount()); <br /> <span style="color: green;">// 1 Memory Leak (StrongEventListener (Obviously))</span> <br />}</pre> <p> <br /> HTH</p><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/100890.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2012/03/25/reactive-extensions-rx-e-weak-events.aspx Sun, 25 Mar 2012 17:15:31 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2012/03/25/reactive-extensions-rx-e-weak-events.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/100890.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/100890.aspx Spring.NET REST Client .NET http://blogs.ugidotnet.org/dsantarelli/archive/2011/12/10/spring.net-rest-client.aspx <p>Sono sempre stato un appassionato di soluzioni REST-ful e la nuova versione di <a href="http://www.springframework.net/rest/doc-latest/reference/html/">Spring.NET REST Client</a> costituisce a mio avviso un ottimo strumento per implementare il client di un qualunque servizio REST in maniera molto semplice e potente, non solo su framework 2.0/3.5/4.0, ma anche su Silverlight 3 e 4, Compact Framework 2.0/3.5 e Windows Phone 7. <br /> Queste API client fanno capo ad una classe di riferimento chiamata <strong>RestTemplate</strong>. Si tratta a tutti gli effetti di un helper che astrae in maniera molto semplice ognuno dei sei principali metodi HTTP che possono essere supportati da un servizio REST (GET, POST, HEAD, OPTIONS, PUT, DELETE). I metodi di questa classe infatti seguono una<strong> “naming convention”</strong> in cui la prima parte del metodo indica il verbo HTTP della richiesta e la seconda parte esplicita la rappresentazione della risposta, la quale può essere ad esempio un oggetto deserializzato o l'Uri di una risorsa. <br /> Ecco un esempio:</p> <pre class="code"><span style="color: blue;"> var </span>newCustomer = <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">Customer </span>{ FirstName = <span style="color: rgb(163, 21, 21);">"Dario"</span>, LastName = <span style="color: rgb(163, 21, 21);">"Santarelli" </span>};</pre> <pre class="code"> <span style="color: rgb(43, 145, 175);">RestTemplate </span>restTemplate = <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">RestTemplate</span>(<span style="color: rgb(163, 21, 21);">"http://localhost"</span>);</pre> <pre class="code"> </pre> <pre class="code"> <span style="color: green;">// POST (Creates) the Customer resource </span><span style="color: rgb(43, 145, 175);"> <br /> Uri </span>newCustomerUri = restTemplate.PostForLocation(<span style="color: rgb(163, 21, 21);">"/myRestService/Customers"</span>, newCustomer);</pre> <pre class="code"> </pre> <pre class="code"> <span style="color: green;">// GET the Customer resource <br /> </span><span style="color: rgb(43, 145, 175);">Customer </span>customerObj = restTemplate.GetForObject&lt;<span style="color: rgb(43, 145, 175);">Customer</span>&gt;(newCustomerUri); <span style="color: rgb(43, 145, 175);"> <br /> HttpResponseMessage</span>&lt;<span style="color: rgb(43, 145, 175);">Customer</span>&gt; customerMsg = restTemplate.GetForMessage&lt;<span style="color: rgb(43, 145, 175);">Customer</span>&gt;(newCustomerUri);<br /><span style="color: blue;"> string c</span>ustomerBodyString = restTemplate.GetForObject&lt;<span style="color: blue;">string</span>&gt;(newCustomerUri); <br /><span style="color: blue;"> byte</span>[] <span style="color: blue;">c</span>ustomerBodyBytes = restTemplate.GetForObject&lt;<span style="color: blue;">byte</span>[]&gt;(newCustomerUri);</pre> <pre class="code"> </pre> <pre class="code"> <span style="color: green;">// DELETE the Customer resource<br /> </span>restTemplate.Delete(newCustomerUri);</pre> <p> </p> <p>Anzitutto si chiede al servizio REST di creare una risorsa <em>Customer</em> tramite il metodo <em>PostForLocation()</em>, il quale restituisce la Uri della risorsa appena creata, che possiamo quindi accedere con una GET in varie forme: tramite il metodo <em>GetForObject&lt;T&gt;() </em>potremmo cercare di deserializzare la risposta HTTP direttamente in un oggetto <em>Customer</em> oppure potremmo ottenerne la rappresentazione raw (es. XML, JSON etc.) specificando il tipo T come <em>string</em><em>.</em> Infine, cancelliamo la risorsa tramite una DELETE. Da notare come <em>GetForObject&lt;T&gt;()</em> e <em>GetForMessage&lt;T&gt;()</em> dietro le quinte eseguano entrambi due GET HTTP ma ritornano nel primo caso un oggetto mentre nel secondo caso un message-wrapper di tipo <em>HttpResponseMessage </em>che espone informazioni aggiuntive come S<em>tatusCode </em>e <em>Header</em>. Per questi metodi esistono anche degli overload con il suffisso “Async” per gestire appunto pattern di invocazione asincrona (indispensabili su Silverlight e Windows Phone). </p> <p>Per completezza, c’è da dire che per effettuare le chiamate REST si può utilizzare anche un metodo alternativo <em>Exchange()</em> che permette di interagire direttamente con i verbi HTTP ed oggetti di tipo <em>HttpEntity</em> (da <a href="http://tools.ietf.org/html/rfc2616#section-7">specifica HTTP 1.1, section 7</a>) eventualmente per impostare degli header custom nella richiesta. Ad esempio...</p> <pre class="code"><span style="color: rgb(43, 145, 175);">HttpEntity </span>requestEntity = <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">HttpEntity</span>(); <br />requestEntity.Headers[<span style="color: rgb(163, 21, 21);">"headerKey"</span>] = <span style="color: rgb(163, 21, 21);">"headerValue"</span>;</pre> <pre class="code"><span style="color: rgb(43, 145, 175);">HttpResponseMessage</span>&lt;<span style="color: rgb(43, 145, 175);">Customer</span>&gt; response = restTemplate.Exchange&lt;<span style="color: rgb(43, 145, 175);">Customer</span>&gt;(<span style="color: rgb(163, 21, 21);">"/myRestService/Customers/{id}"</span>, <br /> <span style="color: rgb(43, 145, 175);">HttpMethod</span>.GET, requestEntity, 1);</pre> <pre class="code"><span style="color: blue;">string </span>responseHeader = response.Headers[<span style="color: rgb(163, 21, 21);">"responseHeaderKey"</span>]; <br /><span style="color: rgb(43, 145, 175);">Customer </span>customer = response.Body; <br /><span style="color: rgb(43, 145, 175);">HttpStatusCode </span>statusCode = response.StatusCode; <br /><span style="color: blue;">string </span>statusDescription = response.StatusDescription;</pre> <pre> </pre> <p>Per deserializzare correttamente il tipo T specificato ad esempio nei metodi <em>GetForObject&lt;T&gt;() </em>e <em>GetForMessage&lt;T&gt;(), </em>il<em> RestTemplate</em> usa internamente una classe chiamata <em>MessageConverterResponseExtractor&lt;T&gt;</em> che è in grado di convertire una risposta HTTP in un'istanza di tipo T tramite dei <a href="http://www.springframework.net/rest/doc-latest/reference/html/http-messageconversion.html"><strong>MessageConverters</strong></a>. A mio avviso la vera potenza di <em>RestTemplate</em> sta proprio in questa parte, ovvero nel fatto che può essere facilmente configurato per supportare diversi MessageConverters, delle classi il cui ruolo è convertire oggetti in messaggi HTTP e viceversa in base al<em> media type</em>. Le strategie di conversione sono veramente tante e possono sfruttare sia serializer “standard” del framework come <em>XmlSerializer</em>, <em>DataContractSerialier</em>, <em>DataContractJsonSerializer</em> piuttosto che altre strategie di conversione custom (es. basate su <a href="http://msdn.microsoft.com/en-us/library/system.servicemodel.syndication.syndicationfeed.aspx">SyndacationFeed</a> e <a href="http://msdn.microsoft.com/en-us/library/system.servicemodel.syndication.syndicationitem.aspx">SyndicationItem</a> per feed ATOM e RSS piuttosto che su <a href="http://json.codeplex.com/">Json.NET</a> per accedere ai valori JSON in modo diretto etc.). Ad esempio, possiamo fare in modo che i messaggi HTTP con media type <em>application/json</em> vengano gestiti dal <em>DataContractJsonSerializer</em> del framework semplicemente scrivendo…</p> <pre class="code"><span style="color: rgb(43, 145, 175);">RestTemplate </span>restTemplate = <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">RestTemplate</span>(<span style="color: rgb(163, 21, 21);"><span style="color: rgb(163, 21, 21);">"http://localhost"</span></span>);<br />restTemplate.MessageConverters.Add(<span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">JsonHttpMessageConverter</span>());</pre> <pre class="code"><font color="#2b91af"> </font></pre> <p>N.B.: i MessageConverters <a href="http://www.springframework.net/rest/doc-latest/reference/html/resttemplate.html#resttemplate-messageconverters">caricati per default</a> cambiano in base al target framework di compilazione. Tuttavia, tutti i converter devono implementare l’interfaccia <em><a href="http://www.springframework.net/rest/doc-latest/reference/html/http-messageconversion.html">IHttpMessageConverters</a>,</em> quindi possiamo facilmente implementarcene uno custom e renderlo operativo in modo molto semplice ed intuitivo.</p> <p>Oltre ai MessageConverters , Spring.NET REST Client supporta anche altre features molto interessanti:</p> <ul> <li><em>RestTemplate</em> espone una proprietà <a href="http://www.springframework.net/rest/doc-latest/reference/html/resttemplate.html#resttemplate-requestfactory">RequestFactory</a> che offre la possibilità di personalizzare il meccanismo di generazione delle richieste HTTP. Per default questa proprietà viene valorizzata con un’ istanza della classe <em>WebClientHttpRequestFactory</em>, che a sua volta internamente usa la classe <a href="http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.aspx">HttpWebRequest</a> del framework. Ciò offre gratuitamente la possibilità di supportare proxy HTTP ad esempio. </li> <li>Altra proprietà interessante di <em>RestTemplate</em> è <a href="http://www.springframework.net/rest/doc-latest/reference/html/resttemplate.html#resttemplate-requestinterceptor">RequestInterceptors</a>, ovvero una collezione di interceptors che ci permette di inserirci sia in modo sincorno che in modo asincrono in un qualunque punto della pipeline di crezione ed esecuzione della richiesta HTTP, magari per modificare la richiesta al volo piuttosto che per iniettare gestioni custom di error management, logging, etc. </li> </ul> <div class="wlWriterEditableSmartContent" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:b932ea09-7e47-4452-bfad-e602f6e29ec1" style="margin: 0px; padding: 0px; float: none; display: inline;">Technorati tags: <a rel="tag" href="http://technorati.com/tags/Spring.NET">Spring.NET</a>,<a rel="tag" href="http://technorati.com/tags/REST">REST</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/100613.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2011/12/10/spring.net-rest-client.aspx Sat, 10 Dec 2011 23:29:37 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2011/12/10/spring.net-rest-client.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/100613.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/100613.aspx [Entity Framework v4] Creazione dei tipi proxy Entity Framework http://blogs.ugidotnet.org/dsantarelli/archive/2011/11/27/entity-framework-v4-creazione-dei-tipi-proxy.aspx <p>L’approccio <a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd456853.aspx">POCO</a> di Entity Framework v4 permette di applicare la persistence-ignorance anche su oggetti custom attraverso la creazione a runtime (via <a href="http://msdn.microsoft.com/en-us/library/3y322t50.aspx">Reflection.Emit</a>) di proxy dinamici che li derivano in modo da permettere all’EF di implementarci sopra i suoi meccanismi di change tracking e lazy loading. In particolare, il change tracking è una feature che un oggetto proxy potrebbe continuare a mantenere anche quando attraversa tier diversi, il che ovviamente sposta l’attenzione verso problematiche legate alla serializzazione di oggetti il cui tipo effettivo non è disponibile a compile time. </p> <p>Ad esempio, se volessimo serializzare e deserializzare su tier diversi un nostro oggetto <em>Customer</em>, il processo che si troverebbe a deserializzare il corrispondente tipo proxy autogenerato dall’EF dovrebbe essere in grado di riconoscere un oggetto di tipo...</p> <p><em>System.Data.Entity.DynamicProxies.Customer_1941BFC82D29CB600101CF80564EA2CCAE83E1ACF5EF4B32E482FBDF13337DC9</em> </p> <p>definito nell’assembly (fittizio)</p> <p><em>EntityFrameworkDynamicProxies-&lt;NostroAssembly&gt;, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</em></p> <p>che ovviamente non è un “known type” nel suo AppDomain. <br /> <br /> Ora, se utilizzassimo WCF come infrastruttura di comunicazione, non avremmo alcuna feature “gratis” per serializzare e deserializzare direttamente i tipi proxy di EF poiché il <a href="http://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractserializer.aspx">DataContractSerializer</a> è in grado di gestire solo “known types” e purtroppo i proxy dinamici non lo sono. Quindi, le principali opzioni per aggirare il problema sono:</p> <ul> <li>Disabiltare la creazione dei proxy tramite la proprietà <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontextoptions.proxycreationenabled.aspx">ProxyCreationEnabled</a> (es. <em>context.ContextOptions.ProxyCreationEnabled = false;</em>) in modo da lavorare direttamente sui known types, ma perdendo ovviamente il change tracking automatico di EF.</li> <li>Usare la classe <a href="http://msdn.microsoft.com/en-us/library/system.data.objects.proxydatacontractresolver.aspx">ProxyDataContractResolver</a> per mappare i tipi proxy direttamente sui nostri tipi custom (<a target="_blank" href="http://blogs.msdn.com/b/adonet/archive/2010/01/05/poco-proxies-part-2-serializing-poco-proxies.aspx">esempio</a>) che però a quel punto non supporterebbero più né il change tracking né tantomeno il lazy loading di EF. Soluzione sicuramente migliore per continuare a mantenere il change tracking su più tier è far riferimento a “self-tracking entities” ovvero ad oggetti che non hanno nessuna dipendenza con EF e definiscono internamente la loro logica di change tracking (ad esempio implementando l’interfaccia <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx">INotifyPropertyChanged</a>).</li> </ul> <p>Senza WCF c’è sempre la serializzazione “old-fashioned”. Ad esempio quella binaria. Affinché i tipi proxy siano resi disponibili alla deserializzazione binaria, EF ci espone un metodo <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.createproxytypes.aspx">CreateProxyTypes</a> sull’oggetto ObjectContext per assicurare che i tipi proxy relativi ad un dato <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.data.metadata.edm.metadataworkspace.aspx">MetadataWorkspace</a> siano deserializzabili all’interno di un AppDomain diverso da quello che invece li serializza. Occorre tenere a mente che la deserializzazione dei proxy ricostruisce degli oggetti che ovviamente non mantengono il loro comportamento originale (quindi niente change tracking). </p> <p>Il seguente metodo, invocato ad esempio allo startup di una applicazione, è in grado di scatenare nell'AppDomain corrente la creazione di tutti i tipi proxy corrispondenti alle entity definite negli EDM (<em>Entity Data Model</em>) deployati come risorse all’interno di una lista di assembly, in modo che il <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter.aspx">BinaryFormatter</a> sia poi in grado di trattare tali tipi come “known types”.</p> <p> </p> <pre class="code"><span style="color: blue;">public static void </span>CreateProxyTypes(<span style="color: rgb(43, 145, 175);">Assembly</span>[] assembliesToConsider) <br />{</pre> <pre class="code"> <span style="color: blue;">var </span>metadataWorkspace = <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">MetadataWorkspace</span>(<span style="color: blue;">new string</span>[] { <span style="color: rgb(163, 21, 21);">"res://*/" </span>}, assembliesToConsider); <br /> <span style="color: blue;">using </span>(<span style="color: blue;">var </span>entityConnection = <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">EntityConnection</span>(metadataWorkspace, <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">SqlConnection</span>())) <br /> { <br /> <span style="color: blue;">using </span>(<span style="color: blue;">var </span>context = <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">ObjectContext</span>(entityConnection)) <br /> { <br /> <span style="color: blue;">foreach </span>(<span style="color: blue;">var </span>assembly <span style="color: blue;">in </span>assembliesToConsider) <br /> { <br /> context.CreateProxyTypes(assembly.GetTypes()); <br /> } <br /> } <br /> } <br />}</pre> <p> </p> <p>Da notare come la classe <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.data.entityclient.entityconnection.aspx">EntityConnection</a> richieda nel costruttore una connessione ad un data source che ai nostri fini però non è importante specificare. Per questo viene passata una SqlConnection “vuota”. Ciò che conta è referenziare gli EDM deployati come risorse all’interno degli assembly specificati. CreateProxyTypes è un metodo intelligente in quanto è in grado di ignorare tutti i tipi che non sono rappresentati negli EDM, quindi possiamo tranquillamente passargli come argomento tutti i tipi definiti all’interno di un assembly: esso internamente sarà in grado di controllare la sua cache di tipi e di trasformare in known types soltanto i tipi proxy di EF che ancora non sono stati creati. Per avere controprova dei tipi proxy correntemente registrati nel nostro AppDomain, basta chiamare il metodo statico <a href="http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.getknownproxytypes.aspx">ObjectContext.GetKnownProxyTypes()</a>. </p> <div class="wlWriterEditableSmartContent" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:bf7843cd-c0a7-4084-8871-1198d539fdeb" style="margin: 0px; padding: 0px; float: none; display: inline;">Technorati tags: <a rel="tag" href="http://technorati.com/tags/EntityFramework">EntityFramework</a>,<a rel="tag" href="http://technorati.com/tags/Serialization">Serialization</a>,<a rel="tag" href="http://technorati.com/tags/DynamicProxy">DynamicProxy</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/100561.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2011/11/27/entity-framework-v4-creazione-dei-tipi-proxy.aspx Sun, 27 Nov 2011 20:55:21 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2011/11/27/entity-framework-v4-creazione-dei-tipi-proxy.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/100561.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/100561.aspx [AOP] UI e dintorni con PostSharp .NET WPF http://blogs.ugidotnet.org/dsantarelli/archive/2011/10/16/aop-ui-e-dintorni-con-postsharp.aspx <p>L’AOP ormai sta diventando un paradigma di programmazione sempre più diffuso specialmente nel mondo .NET dove strumenti come <a href="http://www.postsharp.org/">PostSharp</a>, <a href="http://dotspect.tigris.org/">DotSpect</a> (<em>Compile-time</em> s<em>tatic injection) </em>e <a href="http://www.springframework.net/">Spring.NET</a>, <a href="http://www.castleproject.org/container/index.html">Castle Windsor</a>, <a href="http://www.codeplex.com/entlib">Enterprise Library</a> <a href="http://msdn.microsoft.com/en-us/library/dd139982.aspx">Policy Injection Application Block</a> (<em>Dynamic Proxy</em>) rendono sempre più semplice la gestione dei “<em><a target="_blank" href="http://en.wikipedia.org/wiki/Cross-cutting_concern">cross-cutting concerns</a></em>” che provengono dal mondo Object-Oriented. Personalmente sono stato sempre un entusiasta di <a target="_blank" href="http://www.sharpcrafters.com/">PostSharp</a> e in questo post vorrei citare un paio di aspect che ho sempre valutato positivamente nella gestione di codice “vicino” alla UI, specialmente nel mondo Windows Forms e WPF.</p> <p>Il primo aspect ha a che vedere con il DataBinding. Sappiamo tutti come implementare l’interfaccia <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx">INotifyPropertyChanged</a> sia noioso ed anche a fronte di snippet per velocizzare il processo di scrittura il risultato che otteniamo si allontana dall’idea di avere codice (almeno) esteticamente “impeccabile”. Per questo l’aspect che PostSharp introduce a riguardo è tanto semplice quanto geniale in quanto permette, tramite un banale attributo, di agganciare l’implementazione dell’interfaccia <em>INotifyPropertyChanged</em> ad un oggetto ed eventualmente alle sue specializzazioni. Ad esempio, se definissimo un oggetto <em>Customer</em> nel seguente modo…</p> <pre class="code"><strong>[<span style="color: rgb(43, 145, 175);">NotifyPropertyChanged</span>]</strong> <br /><span style="color: blue;">public class </span><span style="color: rgb(43, 145, 175);">Customer <br /></span><span style="color: rgb(43, 145, 175);">{ </span></pre> <pre class="code"> <span style="color: blue;">public string </span>Name { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; } <br /> <span style="color: blue;">public string </span>Surname { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; } <br /> <span style="color: blue;">public string </span>Email { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }<br /> ... </pre> <pre class="code">}</pre> <p><br /> …tramite l’attributo <em>[NotifyPropertyChanged]</em> impostato a livello di classe otteniamo nell’assembly di output una injection della solita implementazione dell’interfaccia <em>INotifyPropertyChanged</em>. L’ implementazione dell’aspect, riportata anche nel sito di PostSharp, è la seguente…</p> <pre class="code">[<span style="color: rgb(43, 145, 175);">Serializable</span>] <br />[<span style="color: rgb(43, 145, 175);">IntroduceInterface</span>(<span style="color: blue;">typeof</span>(<span style="color: rgb(43, 145, 175);">INotifyPropertyChanged</span>),OverrideAction = <span style="color: rgb(43, 145, 175);">InterfaceOverrideAction</span>.Ignore)] <br />[<span style="color: rgb(43, 145, 175);">MulticastAttributeUsage</span>(<span style="color: rgb(43, 145, 175);">MulticastTargets</span>.Class, Inheritance = <span style="color: rgb(43, 145, 175);">MulticastInheritance</span>.Strict)] <br /><span style="color: blue;">public class </span><span style="color: rgb(43, 145, 175);">NotifyPropertyChangedAttribute </span>: <span style="color: rgb(43, 145, 175);">InstanceLevelAspect</span>, <span style="color: rgb(43, 145, 175);">INotifyPropertyChanged <br /></span>{ <br /> [<span style="color: rgb(43, 145, 175);">ImportMember</span>(<span style="color: rgb(163, 21, 21);">"OnPropertyChanged"</span>, IsRequired = <span style="color: blue;">false</span>)] <br /> <span style="color: blue;">public </span><span style="color: rgb(43, 145, 175);">Action</span>&lt;<span style="color: blue;">string</span>&gt; OnPropertyChangedMethod; <br /><br /> [<span style="color: rgb(43, 145, 175);">IntroduceMember</span>(Visibility = <span style="color: rgb(43, 145, 175);">Visibility</span>.Family, <br /> IsVirtual = <span style="color: blue;">true</span>, <br /> OverrideAction = <span style="color: rgb(43, 145, 175);">MemberOverrideAction</span>.Ignore)] <br /> <span style="color: blue;">public void </span>OnPropertyChanged(<span style="color: blue;">string </span>propertyName) <br /> { <br /> <span style="color: blue;">if </span>(<span style="color: blue;">this</span>.PropertyChanged != <span style="color: blue;">null</span>) <br /> { <br /> <span style="color: blue;">this</span>.PropertyChanged(<span style="color: blue;">this</span>.Instance, <span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">PropertyChangedEventArgs</span>(propertyName)); <br /> } <br /> } <br /><br /> [<span style="color: rgb(43, 145, 175);">IntroduceMember</span>(OverrideAction = <span style="color: rgb(43, 145, 175);">MemberOverrideAction</span>.Ignore)] <br /> <span style="color: blue;">public event </span><span style="color: rgb(43, 145, 175);">PropertyChangedEventHandler </span>PropertyChanged; <br /><br /> [<span style="color: rgb(43, 145, 175);">OnLocationSetValueAdvice</span>] <br /> [<span style="color: rgb(43, 145, 175);">MulticastPointcut</span>(Targets = <span style="color: rgb(43, 145, 175);">MulticastTargets</span>.Property, Attributes = <span style="color: rgb(43, 145, 175);">MulticastAttributes</span>.Instance)] <br /> <span style="color: blue;">public void </span>OnPropertySet(<span style="color: rgb(43, 145, 175);">LocationInterceptionArgs </span>args) <br /> { <br /> <span style="color: blue;">if </span>(args.Value == args.GetCurrentValue()) <span style="color: blue;">return</span>; <br /> args.ProceedSetValue(); <br /> <span style="color: blue;">this</span>.OnPropertyChangedMethod.Invoke(args.Location.Name); <br /> } <br />}</pre> <p><br /> Il codice è abbastanza autoesplicativo. La feature degna di nota in particolare è la possibilità di estendere automaticamente questo aspect anche alle classi ereditate semplicemente impostando a livello di classe <span style="color: rgb(43, 145, 175);">MulticastAttributeUsage.</span>Inheritance = <span style="color: rgb(43, 145, 175);">MulticastInheritance</span>.Strict.  </p> <p>Un altro aspect che ho sempre ritenuto molto utile ha a che vedere con il thread dispatching, ovvero con la gestione dell’esecuzione di un metodo sul thread dell’interfaccia piuttosto che su un worker thread (es. in WPF controllando il thread associato al <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchersynchronizationcontext.aspx">DispatcherSynchronizationContext</a> corrente). Anche in questo ambito PostSharp permette di definire due attributi<em> </em><em>[OnGuiThread]</em> e <em>[OnWorkerThread]</em> a livello di metodo, che iniettano la comune logica di controllo del SynchronizationContext in modo da forzare l’esecuzione del metodo rispettivamente sul thread dell’interfaccia o su un thread secondario (<a target="_blank" href="http://www.sharpcrafters.com/solutions/multithreading">link al codice sorgente</a>). <br /> Come risultato, diventa molto semplice applicare questi aspect anche su codice legacy.</p> <pre class="code"><strong>[<span style="color: rgb(43, 145, 175);">OnWorkerThread</span>]</strong> <br /><span style="color: blue;">void </span>SaveCustomer() <br />{ <br /> Customer.Save(); <br /> ShowMessage(<span style="color: rgb(163, 21, 21);">"Customer Saved!"</span>);</pre> <pre class="code">} <br /><br /><strong>[<span style="color: rgb(43, 145, 175);">OnGuiThread</span>]</strong> <br /><span style="color: blue;">void </span>ShowMessage(<span style="color: blue;">string </span>message) <br />{ <br /> <span style="color: rgb(43, 145, 175);">MessageBox</span>.Show(message); <br />}</pre> <p><br /> Il pacchetto di istallazione di PostSharp (disponibile anche su <a target="_blank" href="http://nuget.codeplex.com/wikipage?title=Getting%20Started">NuGet</a>) aggiunge a Visual Studio due toolbox veramente utili: l’<em>Aspect Browser</em> che ci permette navigare su quelli che sono gli aspect utilizzati nell’intera solutione e l’ <em>Affected code</em> toolbox che ci riassume tutte le classi e i metodi che sono soggetti ad aspects. Inoltre, l’intellisense viene esteso in modo da visualizzare dei summary sulle static injections che gli aspect introducono nell’assembly. Ad esempio, per quanto riguarda il precedente aspect <em>NotifyPropertyChanged</em>, i tooltip che otteniamo sono i seguenti:</p> <p><img alt="" src="http://img534.imageshack.us/img534/4948/customer.jpg" />  <br /> <img alt="" src="http://img14.imageshack.us/img14/3687/customersetter.png" /> </p> <div class="wlWriterEditableSmartContent" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:87428868-7cd2-42df-9e21-563fdb52b8bf" style="margin: 0px; padding: 0px; float: none; display: inline;">Technorati tags: <a rel="tag" href="http://technorati.com/tags/AOP">AOP</a>,<a rel="tag" href="http://technorati.com/tags/WPF">WPF</a>,<a rel="tag" href="http://technorati.com/tags/GUI">GUI</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/100454.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2011/10/16/aop-ui-e-dintorni-con-postsharp.aspx Sun, 16 Oct 2011 21:29:26 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2011/10/16/aop-ui-e-dintorni-con-postsharp.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/100454.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/100454.aspx [WPF] Drag and Drop con Rx .NET WPF http://blogs.ugidotnet.org/dsantarelli/archive/2011/08/25/wpf-drag-and-drop-con-rx.aspx Le <a target="_blank" href="http://msdn.microsoft.com/en-us/data/gg577609">Reactive Extensions (Rx)</a> del framework .NET sono un set di API dedicate a ciò che nella letteratura prende il nome di “<a href="http://en.wikipedia.org/wiki/Reactive_programming">reactive programming</a>”. Rx nasce dalla distinzione di due scenari: <ul> <li><em>pull</em>: un programma agisce estraendo dati dall’ambiente in modo interattivo (es. un’iterazione su una sequenza di dati prelevati dall’ambiente).</li> <li><em>push</em>: un programma reagisce ai dati che l’ambiente “spinge” verso di esso (es. un event-handler è un esempio di reazione in uno scenario push)</li> </ul> <p>Data questa distinzione, Rx si basa sulla <strong><a href="http://en.wikipedia.org/wiki/Duality_%28mathematics%29"><em>dualità</em></a> tra il pattern <a href="http://en.wikipedia.org/wiki/Iterator_pattern">Iterator</a> e il pattern <a href="http://en.wikipedia.org/wiki/Observer_pattern">Observer</a></strong>. In breve, il pattern <em>Observer</em> in uno scenario <em>push</em> è analogo al pattern <em>Iterator</em> in uno scenario <em>pull</em>. Nel pattern Iterator, infatti, l’Iterator è l’attore che accede all'insieme degli elementi di un contenitore in modo interattivo. Il pattern Observer può essere interpretato in modo equivalente se si considera che l’ambiente genera spontaneamente i singoli elementi e li spinge verso il programma, ad esempio attraverso eventi. Quando uno stesso evento viene scatenato più volte, di fatto viene prodotta una sequenza di elementi di tipo “evento”, il che rende ovvia a questo punto la relazione con il pattern Iterator. Rx è stato introdotto quindi per mettere a disposizione degli sviluppatori un modello di programmazione <em>push-based</em>, duale rispetto al ben più conosciuto <em>pull-based, </em>pur<em> </em>non aggiungendo nulla di nuovo rispetto a quest’ultimo. Semplicemente, il modello <em>push-based</em> offre modi diversi se non più semplici per approcciare problemi comuni legati alla programmazione asincrona ed alla composizione di eventi. </p> <p>Rx implementa il pattern Observer tramite le due interfacce <a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd783449.aspx">IObserver&lt;T&gt;</a> e <a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd990377.aspx">IObservable&lt;T&gt;</a>, corrispondenti a IEnumerator/IEnumerable del pattern Iterator. IObservable&lt;T&gt; costituisce una sorta di “collezione asincrona” di elementi (generati in momenti diversi quando si parla di eventi). La relazione tra IObservable&lt;T&gt; e IObserver&lt;T&gt; si trova nel fatto che ad un IObservable&lt;T&gt; è possibile registrare uno o più IObserver&lt;T&gt; (tramite il metodo <a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd782981.aspx">IObservable&lt;T&gt;.Subscribe(IObserver&lt;T&gt; observer)</a> ). IObserver&lt;T&gt; possiede tre metodi: <em>OnNext(…)</em>, <em>OnCompleted()</em> e <em>OnError(…)</em>. Essi vengono invocati dall’ IObservable&lt;T&gt; rispettivamente quando possiede nuovi dati, completa un’operazione e si verifica un errore. </p> <p><span style="font-size: small;"><strong>Drag and Drop e LINQ-to-events</strong></span> </p> <p>Nella pratica, uno dei (classici) casi in cui Rx mostra la sua potenza è nell’implementazione del Drag and Drop di un oggetto grafico. Infatti, a differenza delle comuni soluzioni event-based che intercettano eventi e sincronizzano variabili di stato con lo scopo di aggiornare correttamente la posizione dell’oggetto grafico, <span style="text-decoration: underline;">le Reactive Extensions consentono di convertire gli eventi .NET di interesse in sequenze osservabili che possiamo interrogare tramite query LINQ</span>. Vediamo un esempio concreto. Uno dei classici approcci per implementare il drag and drop in WPF (come in Windows Froms o Silverlight) si basa sostanzialmente sulla sincronizzazione di tre eventi logici di un oggetto grafico: <em>MouseDown</em>, <em>MouseUp</em> e <em>MouseMove</em>. Nello specifico, in WPF, supponendo di avere il seguente XAML… </p> <pre style="margin: 0px; padding: 0px; width: 100%; font-family: consolas,&quot;Courier New&quot;,courier,monospace; font-size: 12px;"><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">Window</span> <span style="color: rgb(255, 0, 0);">x</span>:<span style="color: rgb(255, 0, 0);">Class</span>=<span style="color: rgb(0, 0, 255);">"WebRequestWithRxSample.MainWindow"</span></pre> <pre style="margin: 0px; padding: 0px; font-family: consolas,&quot;Courier New&quot;,courier,monospace; font-size: 12px;"> <span style="color: rgb(255, 0, 0);">xmlns</span>="<span style="color: rgb(0, 0, 255);">http://schemas.microsoft.com/winfx/2006/xaml/presentation</span>"</pre> <pre style="margin: 0px; padding: 0px; font-family: consolas,&quot;Courier New&quot;,courier,monospace; font-size: 12px;"> <span style="color: rgb(255, 0, 0);">xmlns</span>:<span style="color: rgb(255, 0, 0);">x</span>="<span style="color: rgb(0, 0, 255);">http://schemas.microsoft.com/winfx/2006/xaml"</span></pre> <pre style="margin: 0px; padding: 0px; font-family: consolas,&quot;Courier New&quot;,courier,monospace; font-size: 12px;"> <span style="color: rgb(255, 0, 0);">Title</span>=<span style="color: rgb(0, 0, 255);">"MainWindow"</span> <span style="color: rgb(255, 0, 0);">Loaded</span>=<span style="color: rgb(0, 0, 255);">"Window_Loaded"</span><span style="color: rgb(0, 0, 255);">&gt;</span></pre> <pre style="margin: 0px; padding: 0px; font-family: consolas,&quot;Courier New&quot;,courier,monospace; font-size: 12px;"> <span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">Canvas</span><span style="color: rgb(0, 0, 255);">&gt;</span></pre> <pre style="margin: 0px; padding: 0px; font-family: consolas,&quot;Courier New&quot;,courier,monospace; font-size: 12px;"> <span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">StackPanel</span> <span style="color: rgb(255, 0, 0);">Name</span>=<span style="color: rgb(0, 0, 255);">"myPanel"</span><span style="color: rgb(0, 0, 255);">&gt;</span></pre> <pre style="margin: 0px; padding: 0px; font-family: consolas,&quot;Courier New&quot;,courier,monospace; font-size: 12px;"> <span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">Rectangle</span> <span style="color: rgb(255, 0, 0);">Fill</span>=<span style="color: rgb(0, 0, 255);">"Maroon"</span> <span style="color: rgb(255, 0, 0);">Width</span>=<span style="color: rgb(0, 0, 255);">"100"</span> <span style="color: rgb(255, 0, 0);">Height</span>=<span style="color: rgb(0, 0, 255);">"100"</span> <span style="color: rgb(0, 0, 255);">/&gt;</span></pre> <pre style="margin: 0px; padding: 0px; font-family: consolas,&quot;Courier New&quot;,courier,monospace; font-size: 12px;"> <span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">StackPanel</span><span style="color: rgb(0, 0, 255);">&gt;</span></pre> <pre style="margin: 0px; padding: 0px; font-family: consolas,&quot;Courier New&quot;,courier,monospace; font-size: 12px;"> <span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">Canvas</span><span style="color: rgb(0, 0, 255);">&gt;</span></pre> <pre style="margin: 0px; padding: 0px; font-family: consolas,&quot;Courier New&quot;,courier,monospace; font-size: 12px;"><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">Window</span><span style="color: rgb(0, 0, 255);">&gt;</span></pre> <p> </p> <p>… per gestire il drag and drop dello StackPanel “myPanel” potremmo sincronizzarne i tre eventi <em>MouseLeftButtonDown</em>, <em>MouseLeftButtonUp</em> e <em>MouseMove</em> in modo da gestirne il riposizionamento nel Canvas per ogni evento <em>MouseMove</em> compreso temporalmente tra un <em>MouseLeftButtonDown</em> ed un <em>MouseLeftButtonUp</em>. Quindi, ogni volta che si verifica l’evento <em>MouseLeftButtonDown, </em>per ogni successivo evento <em>MouseMove</em> aggiorniamo la posizione dello StackPanel all’interno del Canvas, tenendo conto della posizione del cursore relativa all’angolo in alto a sinistra dello StackPanel. Il tutto finché non viene scatenato l’evento <em>MouseLeftButtonUp</em>. </p> <p> </p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="color: rgb(43, 145, 175);"><span style="font-size: 8.5pt;">Point</span></span><span style="font-size: 8.5pt;"> relativeMouseDownPosition = <span style="color: rgb(0, 0, 255);">new</span> <span style="color: rgb(43, 145, 175);">Point</span></span><span style="font-size: 8.5pt;">();</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;"> </span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">myPanel.MouseLeftButtonDown += (s, e) =&gt;</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">{</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">  </span><span style="font-size: 8.5pt;"><span style="color: rgb(0, 0, 255);">if</span></span><span style="font-size: 8.5pt;"> (myPanel.CaptureMouse())</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">   {</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">    relativeMouseDownPosition = e.GetPosition(myPanel);</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">   }</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">};</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;"> </span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">myPanel.MouseLeftButtonUp += (s, e) =&gt; { myPanel.ReleaseMouseCapture(); };</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;"> </span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">myPanel.MouseMove += (s, e) =&gt;</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">{</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;"> </span><span style="font-size: 8.5pt;"><span style="color: rgb(0, 0, 255);">  if</span></span><span style="font-size: 8.5pt;"> (myPanel.IsMouseCaptured)</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">   {</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;"> </span><span style="font-size: 8.5pt;"><span style="color: rgb(43, 145, 175);">    Point</span> absoluteMousePosition = e.GetPosition(<span style="color: rgb(0, 0, 255);">this</span></span><span style="font-size: 8.5pt;">);</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;"> </span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;"> </span><span style="font-size: 8.5pt;"><span style="color: rgb(43, 145, 175);">    Point</span> newPanelPosition = <span style="color: rgb(0, 0, 255);">new</span> <span style="color: rgb(43, 145, 175);">Point</span></span><span style="font-size: 8.5pt;">(absoluteMousePosition.X - relativeMouseDownPosition.X,</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">                                        absoluteMousePosition.Y - relativeMouseDownPosition.Y);</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;"> </span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;"> </span><span style="font-size: 8.5pt;"><span style="color: rgb(43, 145, 175);">    Canvas</span></span><span style="font-size: 8.5pt;">.SetLeft(myPanel, newPanelPosition.X);</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;"> </span><span style="font-size: 8.5pt;"><span style="color: rgb(43, 145, 175);">    Canvas</span></span><span style="font-size: 8.5pt;">.SetTop(myPanel, newPanelPosition.Y);</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;"> </span><span style="font-size: 8.5pt;">  }</span></span></p> <p class="MsoNormal" style="margin: 0px; line-height: normal;"><span style="font-family: Courier New;"><span style="font-size: 8.5pt;">};</span></span></p> <p> </p> <p>Il codice qua sotto implementa la stessa cosa tramite Rx. All’inizio la sintassi può sembrare un po’ meno chiara, ma con il tempo ci si accorge di come in realtà sia molto più leggibile e soprattutto manutenibile rispetto al codice precedente, in quanto si basa su un modello “push” query-based. </p> <p> </p> <pre style="margin: 0px;"><span style="color: rgb(0, 0, 255);"><span style="font-size: 8.5pt;"> var </span></span><span style="font-size: 8.5pt;">relativeMouseDownPositions = <span style="color: rgb(43, 145, 175);">Observable</span>.FromEventPattern&lt;<span style="color: rgb(43, 145, 175);">MouseButtonEventArgs</span>&gt;(myPanel, <span style="color: rgb(163, 21, 21);">"MouseLeftButtonDown"</span></span><span style="font-size: 8.5pt;">)</span></pre> <pre style="margin: 0px;"> <span style="font-size: 8.5pt;">.Do(evt =&gt; myPanel.CaptureMouse())</span></pre> <pre style="margin: 0px;"><span style="font-size: 8.5pt;"> .Select(evt =&gt; evt.EventArgs.GetPosition(myPanel));</span></pre> <pre style="margin: 0px;"> </pre> <pre style="margin: 0px;"><span style="color: rgb(0, 0, 255);"><span style="font-size: 8.5pt;"> var </span></span><span style="font-size: 8.5pt;">mouseUpEvents = <span style="color: rgb(43, 145, 175);">Observable</span>.FromEventPattern&lt;<span style="color: rgb(43, 145, 175);">MouseButtonEventArgs</span>&gt;(myPanel, <span style="color: rgb(163, 21, 21);">"MouseLeftButtonUp"</span></span><span style="font-size: 8.5pt;">)</span></pre> <pre style="margin: 0px;"> <span style="font-size: 8.5pt;">.Do(evt =&gt; myPanel.ReleaseMouseCapture());</span></pre> <pre style="margin: 0px;"> </pre> <pre style="margin: 0px;"><span style="color: rgb(0, 0, 255);"><span style="font-size: 8.5pt;"> var </span></span><span style="font-size: 8.5pt;">absoluteMousePositions = <span style="color: rgb(43, 145, 175);">Observable</span>.FromEventPattern&lt;<span style="color: rgb(43, 145, 175);">MouseEventArgs</span>&gt;(myPanel, <span style="color: rgb(163, 21, 21);">"MouseMove"</span>) <br /> </span><span style="font-size: 8.5pt;">.Select(evt =&gt; evt.EventArgs.GetPosition(<span style="color: rgb(0, 0, 255);">this</span></span><span style="font-size: 8.5pt;">));</span></pre> <pre style="margin: 0px;"> </pre> <pre style="margin: 0px;"><span style="color: rgb(43, 145, 175);"><span style="font-size: 8.5pt;"> IObservable</span></span><span style="font-size: 8.5pt;">&lt;<span style="color: rgb(43, 145, 175);">Point</span>&gt; dragPoints = <span style="color: rgb(0, 0, 255);">from</span> relativeMouseDownPosition <span style="color: rgb(0, 0, 255);">in</span></span><span style="font-size: 8.5pt;"> relativeMouseDownPositions</span></pre> <pre style="margin: 0px;"><span style="font-size: 8.5pt;"> </span><span style="font-size: 8.5pt;"><span style="color: rgb(0, 0, 255);">from</span> absoluteMousePosition <span style="color: rgb(0, 0, 255);">in</span></span><span style="font-size: 8.5pt;"> absoluteMousePositions.TakeUntil(mouseUpEvents)</span></pre> <pre style="margin: 0px;"><span style="font-size: 8.5pt;"> </span><span style="font-size: 8.5pt;"><span style="color: rgb(0, 0, 255);">select</span> <span style="color: rgb(0, 0, 255);">new</span> <span style="color: rgb(43, 145, 175);">Point</span></span><span style="font-size: 8.5pt;">(absoluteMousePosition.X - relativeMouseDownPosition.X,</span></pre> <pre style="margin: 0px;"><span style="font-size: 8.5pt;"> absoluteMousePosition.Y - relativeMouseDownPosition.Y); <br /></span></pre> <pre style="margin: 0px;"><span style="font-size: 8.5pt;"> <br /><br /> dragPoints.ObserveOn(</span><span style="font-size: 8.5pt;"><span style="color: rgb(43, 145, 175);">SynchronizationContext</span></span><span style="font-size: 8.5pt;">.Current).Subscribe(point =&gt;</span></pre> <pre style="margin: 0px;"><span style="font-size: 8.5pt;"> {</span></pre> <pre style="margin: 0px;"><span style="font-size: 8.5pt;"> </span><span style="font-size: 8.5pt;"><span style="color: rgb(43, 145, 175);">Canvas</span></span><span style="font-size: 8.5pt;">.SetLeft(myPanel, point.X);</span></pre> <pre style="margin: 0px;"><span style="font-size: 8.5pt;"> </span><span style="font-size: 8.5pt;"><span style="color: rgb(43, 145, 175);">Canvas</span></span><span style="font-size: 8.5pt;">.SetTop(myPanel, point.Y);</span></pre> <pre style="margin: 0px;"><span style="font-size: 8.5pt;"> }); </span></pre> <p> </p> <p>Alcune considerazioni </p> <ol> <li>La prima cosa che salta all’occhio è come sia possibile convertire un evento .NET in una sequenza osservabile tramite il metodo <span style="color: rgb(43, 145, 175);"><a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.reactive.linq.observable.fromeventpattern(v=vs.103).aspx">Observable.FromEventPattern</a>(…)</span> , il quale permette di specificare il tipo di <em>EventArgs</em> dell’evento, l’oggetto che espone l’evento da convertire ed il nome dell’evento. In particolare, la seconda riga di codice denota come sia possibile convertire l’evento <em>MouseLeftButtonUp</em> dello StackPanel in una sequenza osservabile di tipo <span style="color: rgb(43, 145, 175);"><span style="font-size: 8.5pt;">IObservable</span></span><span style="font-size: 8.5pt;">&lt;<span style="color: rgb(43, 145, 175);">EventPattern</span>&lt;<span style="color: rgb(43, 145, 175);">MouseButtonEventArgs</span></span><span style="font-size: 8.5pt;">&gt;&gt;</span>.</li> <li>Rx definisce un suo set di extension methods per comporre sequenze osservabili nello stesso modo in cui LINQ to Objects compone sequenze enumerabili. Ad esempio, il metodo <a target="_blank" href="http://msdn.microsoft.com/en-us/library/hh229530(v=VS.103).aspx">Observable.TakeUntil&lt;TSource, TOther&gt;(…)</a> ritorna tutti gli elementi della sequenza osservabile sorgente finché l’altra sequenza osservabile non produce un valore. Nel nostro esempio, questo extension method è stato usato per filtrare gli eventi <em>MouseMove</em> tra un evento <em>MouseLeftButtonDown</em> ed un <em>MouseLeftButtonUp</em>.</li> <li>Il metodo <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.reactive.linq.observable.observeon(v=VS.103).aspx">Observable.ObserveOn&lt;TSource&gt;(…)</a> permette di fare in modo che le notifiche asincrone agli observer vengano effettuate su un thread o uno scheduler specificato. Nel mondo WPF, come in altre piattaforme che supportano un <a target="_blank" href="http://msdn.microsoft.com/it-it/library/system.threading.synchronizationcontext(v=vs.95).aspx">SynchronizationContext</a>, per evitare eccezioni cross-thread quando si interagisce con la UI di solito si interagisce con il contesto di sincronizzazione tramite la chiamata <span style="font-size: 8.5pt;">ObserveOn(</span><span style="font-size: 8.5pt;"><span style="color: rgb(43, 145, 175);">SynchronizationContext</span></span><span style="font-size: 8.5pt;">.Current) </span>che in WPF in particolare è costituito da un oggetto di tipo <a target="_blank" href="http://msdn.microsoft.com/it-it/library/system.windows.threading.dispatchersynchronizationcontext.aspx">DispatcherSynchronizationContext</a>.</li> </ol> <p> <strong>Note di installazione</strong>: se stiamo lavorando su Visual Studio 2010, il modo più veloce per integrare Rx nel nostro progetto è <a target="_blank" href="http://www.nuget.org/">NuGet</a>. Digitando “Rx” sulla textbox di ricerca dovremmo ottenere la seguente schermata. In particolare, il package <em>Rx-Main</em> referenzia l’assembly System.Reactive.dll mentre il package <em>Rx-Testing</em> referenzia l’assembly Microsoft.Reactive.Testing.dll.</p> <p>  <img alt="" src="http://img695.imageshack.us/img695/1229/immaginepgx.jpg" /></p><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/100299.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2011/08/25/wpf-drag-and-drop-con-rx.aspx Thu, 25 Aug 2011 17:27:33 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2011/08/25/wpf-drag-and-drop-con-rx.aspx#feedback 2 http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/100299.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/100299.aspx System.Collections.Concurrent: via tutti i lock! .NET http://blogs.ugidotnet.org/dsantarelli/archive/2011/07/03/system.collections.concurrent-via-tutti-i-lock.aspx <p>Per garantire la <a target="_blank" href="http://en.wikipedia.org/wiki/Thread_safety">thread-safety</a> nell’accesso a collezioni di oggetti in contesti multi-thread, fino ad oggi siamo stati abituati ad utilizzare i più svariati meccanismi di lock, magari prevedendo diversi livelli di granularità. <br /> In questo ambito il framework 4.0 fornisce il namespace <em>System.Collections.Concurrent</em> che offre un set di classi thread-safe che si possono usare al posto di quelle di <em>System.Collections</em> e <em>System.Collections.Generic.</em> </p> <p>Anzitutto, salutiamo con una certa soddisfazione la classe <strong><a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd287191.aspx">ConcurrentDictionary&lt;TKey, TValue&gt;</a></strong>, versione thread-safe della classe <a target="_blank" href="http://msdn.microsoft.com/en-us/library/xfhwa508.aspx">Dictionary</a>. <br /> <br /> Vengono forniti poi tre oggetti che implementano <a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd287147.aspx">IProducerConsumerCollection&lt;T&gt;</a>, un’interfaccia che definisce due metodi <em>TryAdd</em> e <em>TryTake</em> per scenari “producer/consumer”, ovvero scenari in cui più thread aggiungono o rimuovono elementi dalla stessa collection in modo concorrente. Gli oggetti in questione sono:</p> <ul> <li><strong><a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd381779.aspx">ConcurrentBag&lt;T&gt;</a></strong>: implementazione thread-safe di una collezione non ordinata di elementi (eventualmente duplicabili). Questo oggetto è particolarmente ottimizzato per scenari in cui lo stesso thread produce e consuma gli elementi del bag. <br /> </li> <li><strong><a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd267265.aspx">ConcurrentQueue&lt;T&gt;</a></strong>: versione concorrente di una coda FIFO. A differenza di quanto si sente dire spesso erroneamente, questa classe non costituisce un semplice wrapper della classe <em>Queue </em>( tipo <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.collections.queue.synchronized.aspx">Queue.Synchronized()</a> ), bensì qualcosa di più potente e performante che internamente fa uso di meccanismi di sincronizzazione e validazione più ottimizzati basati sulle classi <a target="_blank" href="http://msdn.microsoft.com/it-it/library/system.threading.thread.spinwait.aspx">SpinWait</a> e <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.threading.interlocked.aspx">Interlocked</a>. <br /> </li> <li><strong><a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd267331.aspx">ConcurrentStack&lt;T&gt;</a></strong>: versione concorrente di uno stack LIFO.  <br /> </li> </ul> <p>Infine abbiamo l’oggetto <a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd267312.aspx"><strong>BlockingCollection&lt;T&gt;</strong></a>, che di fatto costituisce una classe di più alto livello. Una <em>BlockingCollection</em> è a tutti gli effetti un wrapper di un oggetto che implementa l’interfaccia <a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd287147.aspx">IProducerConsumerCollection&lt;T&gt;</a> (come i sopra citati <em>ConcurrentBag&lt;T&gt;</em>, <em>ConcurrentQueue&lt;T&gt;</em> e <em>ConcurrentStack&lt;T&gt;</em>). Ciò significa che di suo non implementa un meccanismo di storage interno, bensì introduce soltanto la logica di gestione thread-safe per scenari producer/consumer. Infatti, se non forniamo al costruttore un <em>IProducerConsumerCollection</em>, per default viene istanziata internamente una <em>ConcurrentQueue</em> (FIFO). Poiché la <em>BlockingCollection</em> è utile come buffer di scambio condiviso tra più thread, essa fornisce gratis anche dei meccanismi di blocco (metodi <em><a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd287137.aspx">Add()</a></em> e <em><a target="_blank" href="http://msdn.microsoft.com/en-us/library/dd287085.aspx">Take()</a></em>) grazie ai quali possiamo fare in modo che i thread “producer” si blocchino se la collezione è piena (aspettando quindi che almeno un elemento venga rimosso dalla collezione) oppure viceversa che i thread “consumer” si blocchino quando cercano di rimuovere un elemento dalla collection interna vuota.</p><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/100192.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2011/07/03/system.collections.concurrent-via-tutti-i-lock.aspx Sun, 03 Jul 2011 15:31:01 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2011/07/03/system.collections.concurrent-via-tutti-i-lock.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/100192.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/100192.aspx [Silverlight 5] ICustomTypeProvider Silverlight http://blogs.ugidotnet.org/dsantarelli/archive/2011/05/17/silverlight-5-icustomtypeprovider.aspx <p>La beta di Silverlight 5 ha introdotto una nuova interfaccia <a target="_blank" href="http://msdn.microsoft.com/en-sg/library/system.reflection.icustomtypeprovider.aspx">ICustomTypeProvider</a> che permette di effettuare il binding tra elementi della UI ed oggetti la cui struttura non è nota a compile-time. Nello specifico, l’esigenza che questa interfaccia soddisfa è evidente quando si lavora con dati provenienti da fonti eterogenee (database, istanze XML, oggetti JSON, dati binari, csv etc.) che si desiderano presentare senza ricompilare ogni volta che viene aggiunto/rimosso un attributo, una colonna o un campo dalla sorgente dati. In questi contesti si può valutare di rendere flessibile il meccanismo di binding permettendo di aggiungere/rimuovere proprietà a runtime lasciando al motore di databinding il compito di determinarne il tipo senza obbligare lo sviluppatore a ri-compilare/ri-deployare l’applicazione ogni volta che si necessita di una modifica al model. L’interfaccia <a target="_blank" href="http://msdn.microsoft.com/en-sg/library/system.reflection.icustomtypeprovider.aspx">ICustomTypeProvider</a> è così definita: <br /> <span><font color="#0000ff"><font style="font-size: 8.3pt;"> <br /> public</font></font></span><font style="font-size: 8.3pt;"> <span><font color="#0000ff">interface</font></span> <span><font color="#2b91af">ICustomTypeProvider</font></span> <br /> { <br />     <span><font color="#2b91af">Type</font></span> GetCustomType(); <br /> }</font></p> <p>Quando la si implementa, viene chiesto di ritornare un oggetto di tipo <a target="_blank" href="http://msdn.microsoft.com/it-it/library/system.type(v=vs.80).aspx">System.Type</a>. Oggi l’engine di Silverlight 5 controlla in fase di binding se l’oggetto che stiamo passando implementa o meno l’interfaccia <a target="_blank" href="http://msdn.microsoft.com/en-sg/library/system.reflection.icustomtypeprovider.aspx">ICustomTypeProvider</a> per capire se usare i metadati del nostro tipo custom piuttosto che del fallback System.Type. <br /> Bisogna dire che l’implementazione di questa interfaccia richiede un certo sforzo iniziale, poiché implica la creazione di un tipo custom in grado di gestire ad esempio un “repository” di proprietà (<a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.reflection.propertyinfo.aspx">PropertyInfo</a>) che possiamo aggiungere/rimuovere dinamicamente. Inoltre, non dobbiamo dimenticarci della solita <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx">INotifyPropertyChanged</a> utile per il corretto funzionamento del binding e di <a target="_blank" href="http://msdn.microsoft.com/it-it/library/system.componentmodel.inotifydataerrorinfo(v=vs.95).aspx">INotifyDataErrorInfo</a> per la validazione. I benefici che otteniamo sono tuttavia notevoli rispetto alle versioni precedenti di Silverlight, dove non è possibile derivare Type o PropertyInfo per questi scopi. </p> <p><strong>Esempio di utilizzo <br /> </strong>A titolo di esempio, proviamo a definire una classe <em>DictionaryObject</em> che, come il nome fa intendere, espone a mo’ di dictionary l’elenco delle sue proprietà in modo tale che possono essere aggiunte e rimosse a runtime. L’obiettivo è rendere “binding-friendly” queste coppie chiave-valore!</p> <p><span><font color="#0000ff"><font style="font-size: 8.3pt;">public</font></font></span><font style="font-size: 8.3pt;"> <span><font color="#0000ff">class</font></span> <span><font color="#2b91af">DictionaryObject</font></span> : <span><font color="#2b91af"><strong>ICustomTypeProvider</strong></font></span>, <span><font color="#2b91af">INotifyPropertyChanged</font></span> <br /> { <br />     <span><font color="#0000ff">private</font></span> <span><font color="#2b91af">Dictionary</font></span>&lt;<span><font color="#0000ff">string</font></span>, <span><font color="#0000ff">object</font></span>&gt; _properties = <span><font color="#0000ff">null</font></span>; <br /> <br />     <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">event</font></span> <span><font color="#2b91af">PropertyChangedEventHandler</font></span> PropertyChanged; <br /> <br />     <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">object</font></span> <span><font color="#0000ff">this</font></span>[<span><font color="#0000ff">string</font></span> propertyName] <br />     { <br />         <span><font color="#0000ff">get</font></span> { <span><font color="#0000ff">return</font></span> _properties.ContainsKey(propertyName) ? _properties[propertyName] : <span><font color="#0000ff">null</font></span>; } <br />         <span><font color="#0000ff">set</font></span> <br />         { <br />             AddProperty(propertyName); <br />             <span><font color="#0000ff">if</font></span> (_properties[propertyName] != <span><font color="#0000ff">value</font></span>) <br />             { <br />                 _properties[propertyName] = <span><font color="#0000ff">value</font></span>; <br />                 OnPropertyChanged(propertyName); <br />             } <br />         } <br />     } <br /> <br />     <span><font color="#0000ff">public</font></span> DictionaryObject() <br />     {  <br />         _properties = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Dictionary</font></span>&lt;<span><font color="#0000ff">string</font></span>, <span><font color="#0000ff">object</font></span>&gt;();  <br />     } <br /> <br />     <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">bool</font></span> AddProperty(<span><font color="#0000ff">string</font></span> name)  <br />     {  <br />         <span><font color="#0000ff">return</font></span> AddProperty(name, <span><font color="#0000ff">null</font></span>);  <br />     } <br /> <br />     <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">bool</font></span> AddProperty(<span><font color="#0000ff">string</font></span> name, <span><font color="#0000ff">object</font></span> value) <br />     { <br />         <span><font color="#0000ff">if</font></span> (!_properties.ContainsKey(name)) <br />         { <br />             _properties.Add(name, value); <br />             <span><font color="#0000ff">return</font></span> <span><font color="#0000ff">true</font></span>; <br />         } <br />         <span><font color="#0000ff">else</font></span> <span><font color="#0000ff">return</font></span> <span><font color="#0000ff">false</font></span>; <br />     } <br /> <br />     <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">bool</font></span> RemoveProperty(<span><font color="#0000ff">string</font></span> name) <br />     { <br />         <span><font color="#0000ff">if</font></span> (_properties.ContainsKey(name)) <br />         { <br />             _properties.Remove(name); <br />             <span><font color="#0000ff">return</font></span> <span><font color="#0000ff">true</font></span>; <br />         } <br />         <span><font color="#0000ff">else</font></span> <span><font color="#0000ff">return</font></span> <span><font color="#0000ff">false</font></span>; <br />     } <br /> <br />     <strong><span><font color="#0000ff">public</font></span> <span><font color="#2b91af">Type</font></span> GetCustomType()  <br />     {  <br />         <span><font color="#0000ff">return</font></span> <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">DictionaryObjectType</font></span>(_properties); <br />     } <br /> </strong> <br />     <span><font color="#0000ff">protected</font></span> <span><font color="#0000ff">void</font></span> OnPropertyChanged(<span><font color="#0000ff">string</font></span> key) <br />     { <br />         <span><font color="#0000ff">if</font></span> (PropertyChanged != <span><font color="#0000ff">null</font></span>) PropertyChanged(<span><font color="#0000ff">this</font></span>, <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">PropertyChangedEventArgs</font></span>(key)); <br />     } <br /> }</font></p> <p>Affinché il runtime di Silverlight tratti le coppie chiave-valore alla stregua di proprietà vere e proprie di un generico oggetto del CLR, il trucco sta nel definire un tipo custom in grado di fornire al volo tutti i metadati che servono in fase di binding. Riportiamo solo le righe di codice rilevanti…</p> <p><span><font color="#0000ff"><font style="font-size: 8.3pt;">public</font></font></span><font style="font-size: 8.3pt;"> <span><font color="#0000ff">class</font></span> <span><font color="#2b91af">DictionaryObjectType</font></span> : <span><font color="#2b91af">Type</font></span> <br /> { <br /> <span><font color="#0000ff">   private</font></span> <span><font color="#2b91af">Type</font></span> _proxyType = <span><font color="#0000ff">typeof</font></span>(<span><font color="#2b91af">DictionaryObject</font></span>); <br /> <span><font color="#0000ff">   private</font></span> <span><font color="#2b91af">Dictionary</font></span>&lt;<span><font color="#0000ff">string</font></span>, <span><font color="#0000ff">object</font></span>&gt; _propertiesDictionary = <span><font color="#0000ff">null</font></span>; <br /> </font><font style="font-size: 8.3pt;">   <font size="4">…</font></font><font style="font-size: 8.3pt;"> <br /> <span><font color="#0000ff"> </font></span>  <br /> <span><font color="#0000ff">   public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#2b91af">PropertyInfo</font></span>[] GetProperties(<span><font color="#2b91af">BindingFlags</font></span> bindingAttr) <br />    { <br />       <span><font color="#2b91af">PropertyInfo</font></span>[] properties = _proxyType.GetProperties(bindingAttr); <br /> <br />       <span><font color="#0000ff">if</font></span> (<span><font color="#2b91af">BindingFlags</font></span>.Instance == (bindingAttr &amp; <span><font color="#2b91af">BindingFlags</font></span>.Instance) &amp;&amp; <br />           <span><font color="#2b91af">BindingFlags</font></span>.Public == (bindingAttr &amp; <span><font color="#2b91af">BindingFlags</font></span>.Public)) <br />           { <br />              <strong><span><font color="#0000ff">return</font></span> GetDynamicProperties().ToArray();</strong> <br />           } <br /> <br />       <span><font color="#0000ff">return</font></span> properties; <br />    } <br />  <span><font color="#0000ff">  </font></span></font><font style="font-size: 8.3pt;">   <br /> <span><font color="#0000ff">   private</font></span> <span><font color="#2b91af">List</font></span>&lt;<span><font color="#2b91af">PropertyInfo</font></span>&gt; GetDynamicProperties() <br />    { <br />      <span><font color="#2b91af">List</font></span>&lt;<span><font color="#2b91af">PropertyInfo</font></span>&gt; properties = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">List</font></span>&lt;<span><font color="#2b91af">PropertyInfo</font></span>&gt;(); <br /> <br />      <span><font color="#0000ff">foreach</font></span> (<span><font color="#0000ff">string</font></span> key <span><font color="#0000ff">in</font></span> _propertiesDictionary.Keys) <br />      {                 <br />         <span><font color="#0000ff">if</font></span> (_propertiesDictionary[key] != <span><font color="#0000ff">null</font></span>)  </font><span><font color="#008000"><font style="font-size: 8.3pt;">// null values will be ignored!</font></font></span><font style="font-size: 8.3pt;"> <br />         { <br />             properties.Add(<span><font color="#0000ff">new</font></span> <span><font color="#2b91af">DictionaryObjectPropertyInfo</font></span>(_propertiesDictionary[key].GetType(), <span><font color="#0000ff">typeof</font></span>(<span><font color="#2b91af">DictionaryObject</font></span>), key)); <br />         } <br />      } <br /> <br />      <span><font color="#0000ff">return</font></span> properties; <br />    } <br /> }</font></p> <p>Per gli oggetti che implementano l’interfaccia <em>ICustomTypeProvider</em>, abbiamo la possibilità di intercettare le richieste di informazioni di reflection che provengono dal motore di databinding e fornire metadati personalizzati. Nel nostro esempio, per comunicare al runtime come fare la <em>get</em> e la <em>set</em> sulle proprietà di un <em>DictionaryObject</em>, abbiamo bisogno di ereditare anche <em>PropertyInfo.</em></p> <p><span><font color="#0000ff"><font style="font-size: 8.3pt;">public</font></font></span><font style="font-size: 8.3pt;"> <span><font color="#0000ff">class</font></span> <span><font color="#2b91af">DictionaryObjectPropertyInfo</font></span> : <span><font color="#2b91af">PropertyInfo</font></span> <br /> { <br />     <span><font color="#0000ff">private</font></span> <span><font color="#2b91af">Type</font></span> _propertyType; <br />     <span><font color="#0000ff">private</font></span> <span><font color="#2b91af">Type</font></span> _declaringType; <br />     <span><font color="#0000ff">private</font></span> <span><font color="#0000ff">string</font></span> _name; <br /> <br />     <span><font color="#0000ff">public</font></span> DictionaryObjectPropertyInfo(<span><font color="#2b91af">Type</font></span> propertyType, <span><font color="#2b91af">Type</font></span> declaringType, <span><font color="#0000ff">string</font></span> propertyName) <br />     { <br />         _propertyType = propertyType; <br />         _declaringType = declaringType; <br />         _name = propertyName; <br />     } <br />   <br />     ... <br /> </font><font style="font-size: 8.3pt;">    <br />     <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">object</font></span> GetValue(<span><font color="#0000ff">object</font></span> obj, <span><font color="#2b91af">BindingFlags</font></span> invokeAttr, <span><font color="#2b91af">Binder</font></span> binder, <span><font color="#0000ff">object</font></span>[] index, System.Globalization.<span><font color="#2b91af">CultureInfo</font></span> culture) <br />     { <br />         <span><font color="#2b91af">DictionaryObject</font></span> dictionaryObject = (<span><font color="#2b91af">DictionaryObject</font></span>)obj;             <br />         <span><font color="#0000ff">return</font></span> dictionaryObject[Name]; <br />     } <br />        <br />     <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">void</font></span> SetValue(<span><font color="#0000ff">object</font></span> obj, <span><font color="#0000ff">object</font></span> value, <span><font color="#2b91af">BindingFlags</font></span> invokeAttr, <span><font color="#2b91af">Binder</font></span> binder, <span><font color="#0000ff">object</font></span>[] index, System.Globalization.<span><font color="#2b91af">CultureInfo</font></span> culture) <br />     { <br />         <span><font color="#2b91af">DictionaryObject</font></span> dictionaryObject = (<span><font color="#2b91af">DictionaryObject</font></span>)obj; <br />         dictionaryObject[Name] = value; <br />     } <br /> }</font></p> <p>OK non abbiamo bisogno di altro! Proviamo a utilizzare concretamente la classe <em>DictionaryObject</em>. Creiamo una view con una DataGrid le cui colonne vengono autogenerate. L’obiettivo è di rendere il databinding bidirezionale e consistente rispetto al tipo CLR di ciascuna proprietà esposta.</p> <p><span><font color="#0000ff"><font style="font-size: 8.3pt;">&lt;</font></font></span><font style="font-size: 8.3pt;"><span><font color="#a31515">UserControl</font></span><span><font color="#ff0000"> x</font></span><span><font color="#0000ff">:</font></span><span><font color="#ff0000">Class</font></span><font color="#0000ff"><span>=</span><span>"ICustomTypeProviderSample.MainPage"</span></font> <br />    <span><font color="#ff0000">xmlns</font></span><font color="#0000ff"><span>=</span><span>"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</span></font> <br />    <span><font color="#ff0000">xmlns</font></span><span><font color="#0000ff">:</font></span><span><font color="#ff0000">x</font></span><font color="#0000ff"><span>=</span><span>"http://schemas.microsoft.com/winfx/2006/xaml"</span></font>     <br />    <span><font color="#ff0000">xmlns</font></span><span><font color="#0000ff">:</font></span><span><font color="#ff0000">sdk</font></span><font color="#0000ff"><span>=</span><span>"http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"</span><span>&gt;</span></font> <br /> <span><font color="#a31515">    </font></span><span><font color="#0000ff">&lt;</font></span><span><font color="#a31515">StackPanel</font></span><span><font color="#ff0000"> x</font></span><span><font color="#0000ff">:</font></span><span><font color="#ff0000">Name</font></span><font color="#0000ff"><span>=</span><span>"LayoutRoot"</span></font><span><font color="#ff0000"> HorizontalAlignment</font></span><font color="#0000ff"><span>=</span><span>"Center"</span></font><span><font color="#ff0000"> Margin</font></span><font color="#0000ff"><span>=</span><span>"20"</span><span>&gt;</span></font> <br /> <span><font color="#a31515">        </font></span><strong><span><font color="#0000ff">&lt;</font></span><span><font color="#a31515">sdk</font></span><span><font color="#0000ff">:</font></span><span><font color="#a31515">DataGrid</font></span><span><font color="#ff0000"> AutoGenerateColumns</font></span><font color="#0000ff"><span>=</span><span>"True"</span></font><span><font color="#ff0000"> ItemsSource</font></span><span><font color="#0000ff">="{</font></span><span><font color="#a31515">Binding</font></span><span><font color="#ff0000"> Items</font></span><font color="#0000ff"><span>}</span><span>"</span><span> /&gt;</span></font></strong><span><font color="#a31515">        </font></span> <br /> <span><font color="#a31515">    </font></span><span><font color="#0000ff">&lt;/</font></span><span><font color="#a31515">StackPanel</font></span><span><font color="#0000ff">&gt;</font></span> <br /> <span><font color="#0000ff">&lt;/</font></span><span><font color="#a31515">UserControl</font></span></font><span><font color="#0000ff" style="font-size: 8.3pt;">&gt;</font></span></p> <p>Nel viewmodel definiamo le proprietà degli items come coppie chiave-valore.</p> <p><span><font color="#0000ff"><font style="font-size: 8.3pt;">public</font></font></span><font style="font-size: 8.3pt;"> <span><font color="#0000ff">class</font></span> <span><font color="#2b91af">MainPageViewModel</font></span> : <span><font color="#2b91af">INotifyPropertyChanged</font></span> <br /> { <br />     ... <br /> </font><font style="font-size: 8.3pt;"> <br />     <span><font color="#0000ff">public</font></span> <span><font color="#2b91af">ObservableCollection</font></span>&lt;<span><font color="#2b91af">DictionaryObject</font></span>&gt; Items { <span><font color="#0000ff">get</font></span>; <span><font color="#0000ff">private</font></span> <span><font color="#0000ff">set</font></span>; }         <br /> <br />     <span><font color="#0000ff">public</font></span> MainPageViewModel() <br />     {             <br />         Items = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">ObservableCollection</font></span>&lt;<span><font color="#2b91af">DictionaryObject</font></span>&gt;(); <br />              <br />         </font><span><font color="#2b91af"><font style="font-size: 8.3pt;">DictionaryObject</font></font></span><font style="font-size: 8.3pt;"> newItem = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">DictionaryObject</font></span>();         <br />         newItem[<span><font color="#a31515">"ID"</font></span>] = 1; <br />         newItem[<span><font color="#a31515">"Name"</font></span>] = <span><font color="#a31515">"Item 1"</font></span>; <br />         newItem[<span><font color="#a31515">"UnitPrice"</font></span>] = 5.3; <br />         newItem[<span><font color="#a31515">"Created"</font></span>] = <span><font color="#2b91af">DateTime</font></span>.Now;     <br /> </font><font style="font-size: 8.3pt;">     <br />         Items.Add(newItem); <br /> <br />         ... <br />    } <br /> }   </font></p> <p>La view risultante è coerente rispetto ai tipi delle proprietà. L’oggetto <em>DictionaryObject</em> implementa l’interfaccia <em>INotifyPropertyChanged</em> (e preferibilmente <em>INotifyDataErrorInfo</em>) quindi la nostra interazione con le celle della DataGrid si riflette direttamente sul valore delle proprietà coinvolte nel databinding.</p> <p><img alt="" src="http://img691.imageshack.us/img691/2720/immagine1uy.jpg" />  <br /> <img alt="" src="http://img823.imageshack.us/img823/7738/immagine2ha.jpg" /></p> <p>L’esempio mostrato è molto banale ma fa intendere quanti miglioramenti possono essere concepiti. Il codice completo di questo esempio è scaricabile al seguente <a target="_blank" href="http://cid-fd34d9886368eda3.office.live.com/self.aspx/Pubblica/ICustomTypeProviderSample.zip">link</a>.</p> <p><strong>Ma non esisteva qualcosa del genere anche in WPF?  <br /> </strong>A chi possiede familiarità con l’interfaccia <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.icustomtypedescriptor.aspx">ICustomTypeDescriptor</a> di WPF (la quale risolve lo stesso problema di ICustomTypeProvider di Silverlight 5… i nomi addirittura sono più o meno gli stessi!) verrà subito in mente la domanda “Perché non hanno aggiunto direttamente l’interfaccia ICustomTypeDescriptor”? <br /> Leggendo la documentazione MSDN non sono riuscito ad estrarre una risposta chiara. Sembrerebbe che il motivo (per ora almeno) sia dovuto al fatto che <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.icustomtypedescriptor.aspx">ICustomTypeDescriptor</a> richiede una sua gerarchia di classi  (<em>TypeDescriptor</em>, <em>EventDescriptor</em>, etc.) che di fatto duplica la gerarchia di reflection, il che incrementerebbe la dimensione del pacchetto Silverlight da installare client-side. </p> <p><strong>E il DLR? <br /> </strong>Questa feature in realtà non ha nulla a che vedere con il DLR. Oggetti del DLR come <a href="http://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject.aspx">ExpandoObject</a> o <a href="http://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject.aspx">DynamicObject</a> (o qualunque implementazione dell’interfaccia <a href="http://msdn.microsoft.com/en-us/library/system.dynamic.idynamicmetaobjectprovider.aspx">IDynamicMetaObjectProvider</a>) non apportano alcun tipo di informazione sulle loro proprietà. Il motore del databinding ha invece bisogno di queste informazioni per effettuare correttamente conversioni di tipo per tutto ciò che non sia una semplice stringa. Ad esempio, se abbiamo una TextBox in binding con una proprietà DateTime di un oggetto, quando inseriamo un nuovo valore nella TextBox l’engine valuta il tipo della proprietà e converte il testo contenuto nella TextBox da stringa a DateTime e viceversa. Se invece si effettua il binding su una proprietà di un ExpandoObject, il valore della proprietà sarà convertito semplicemente a stringa e non possono essere fatte valutazioni di binding. Insomma, in WPF è possibile associare elementi del DLR agli elementi della UI, ma non avviene nessuna conversione di tipo… tutto ciò su cui possiamo lavorare sono solo stringhe. <br /> In definitiva, mentre il DLR è conveniente quando si interagisce con altri linguaggi o altre piattaforme, non è consigliabile per il databinding e l’interazione con la UI.</p> <p><strong>Riferimenti <br /> </strong>Segnalo i due interessanti articoli da cui ho preso spunto:</p> <ul> <li><a href="http://www.damonpayne.com/post/2011/04/14/Using-ICustomTypeProvider-in-Silverlight-5.aspx">Using ICustomTypeProvider in Silverlight 5</a> </li> <li><a target="_blank" href="http://blogs.msdn.com/b/silverlight_sdk/archive/2011/04/25/binding-to-dynamic-properties-with-icustomtypeprovider-silverlight-5-beta.aspx">Binding to Dynamic Properties with ICustomTypeProvider (Silverlight 5 Beta)</a> </li> </ul><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/100041.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2011/05/17/silverlight-5-icustomtypeprovider.aspx Tue, 17 May 2011 00:51:56 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2011/05/17/silverlight-5-icustomtypeprovider.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/100041.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/100041.aspx Validare documenti XHTML con XmlPreloadedResolver .NET http://blogs.ugidotnet.org/dsantarelli/archive/2011/04/20/validare-contenuti-xhtml-con-xmlpreloadedresolver.aspx <p> Una classe molto interessante che ci fornisce il Framework 4.0 è <a target="_blank" href="http://msdn.microsoft.com/it-it/library/system.xml.resolvers.xmlpreloadedresolver(v=VS.100).aspx">XmlPreloadedResolver</a>. Come si intuisce dal nome, il suo scopo è quello di risolvere risorse pre-caricate che possono essere referenziate da un’istanza XML. Questa funzionalità è veramente comoda quando non è desiderabile creare chiamate di rete per reperire risorse esterne (es. un DTD) o quando si lavora offline, con evidenti benefici sulle performance. Un esempio concreto di utilizzo di questa classe potrebbe intervenire nella validazione di contenuti XHTML 1.x, magari all’interno di un HttpModule ASP.NET… tipo <a target="_blank" href="http://www.thejoyofcode.com/validator_module.aspx">questo</a>. <br /> Il plus risiede nel fatto che nell’assembly <em>System.Xml.Utils </em>sono già “embeddati” i DTD dello standard <strong>XHTML 1.0 Transitional, Strict e Frameset</strong> (incluse le entities speciali), nonché dello standard <strong>RSS 0.91</strong>.  </p> <p> <img alt="" src="http://img846.imageshack.us/img846/3001/immaginevj.jpg" /></p> <p>Insistendo sul discorso della validazione di contenuti XHTML precaricati, a titolo esemplificativo ho provato a realizzare una semplice classe <em>XhtmlValidator</em>, che espone un metodo <em>Validate()</em> (la validazione del contenuto) ed il relativo <em>Resolver</em> (che deve reperire l’eventuale DOCTYPE esterno da processare nella validazione). <br /> N.B.: Per versioni del Framework precedenti alla 4.0, possiamo ovviamente decidere di svilupparci in casa un Resolver custom (ereditando la classe <a target="_blank" href="http://msdn.microsoft.com/it-it/library/system.xml.xmlresolver.aspx"> XmlResolver</a>) che sostanzialmente assolva lo stesso compito.</p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font color="#0000ff"><font style="font-size: 8.5pt;"> public</font></font></span><font style="font-size: 8.5pt;"><span> </span><span><font color="#0000ff">class</font></span><span> </span></font><span><font color="#2b91af" style="font-size: 8.5pt;">XhtmlValidator</font></span></font><span> </span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">{</font></font></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">   </font></span> <font style="font-size: 8.5pt;"><span><font color="#0000ff">public</font></span><span> </span><span><font color="#2b91af">XmlResolver</font></span><span> Resolver { </span><span><font color="#0000ff"> get</font></span><span>; </span><span><font color="#0000ff">set</font></span></font><span><font style="font-size: 8.5pt;">; }</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><span style="mso-spacerun: yes;"><font face="Courier New"><font style="font-size: 8.5pt;">  </font></font></span></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">   </font></span> <font style="font-size: 8.5pt;"><span><font color="#0000ff">public</font></span></font><span><font style="font-size: 8.5pt;"> XhtmlValidator() { }</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">   </font></span> <font style="font-size: 8.5pt;"><span><font color="#0000ff">public</font></span><span> XhtmlValidator(</span><span><font color="#2b91af">XmlResolver</font></span></font><span><font style="font-size: 8.5pt;"> resolver) { Resolver = resolver; }</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><span style="mso-spacerun: yes;"><font face="Courier New"><font style="font-size: 8.5pt;">  </font></font></span></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">   </font></span> <font style="font-size: 8.5pt;"><span><font color="#0000ff">public</font></span><span> </span><span><font color="#0000ff">bool</font></span><span> Validate(</span><span><font color="#0000ff">string</font></span><span> xhtml, </span><span><font color="#0000ff">out</font></span><span> </span><span><font color="#2b91af">List</font></span><span>&lt;</span><span><font color="#2b91af">XmlValidationError</font></span></font><span><font style="font-size: 8.5pt;">&gt; errors)</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">   {</font></font></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">      </font></span><font style="font-size: 8.5pt;"><span><font color="#2b91af">List</font></span><span>&lt;</span><span><font color="#2b91af">XmlValidationError</font></span><span>&gt; validationErrors = </span> <span><font color="#0000ff">new</font></span><span> </span><span><font color="#2b91af">List</font></span><span>&lt;</span><span><font color="#2b91af">XmlValidationError</font></span></font><span><font style="font-size: 8.5pt;">&gt;();     </font></span></font> </p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">                                                    </font></font></span> </p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">      </font></span><font style="font-size: 8.5pt;"><span><font color="#2b91af">XmlReaderSettings</font></span><span> settings = </span><span><font color="#0000ff">new</font></span><span> </span><span><font color="#2b91af">XmlReaderSettings</font></span></font><span><font style="font-size: 8.5pt;">();</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">      settings.ValidationType = </font></span><font style="font-size: 8.5pt;"><span><font color="#2b91af">ValidationType</font></span></font><span><font style="font-size: 8.5pt;">.DTD;</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">      settings.ValidationFlags = </font></span><font style="font-size: 8.5pt;"><span><font color="#2b91af">XmlSchemaValidationFlags</font></span></font><span><font style="font-size: 8.5pt;">.ReportValidationWarnings;</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">      settings.DtdProcessing = </font></span><font style="font-size: 8.5pt;"><span><font color="#2b91af">DtdProcessing</font></span></font><span><font style="font-size: 8.5pt;">.Parse;</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">      </font></span><font style="font-size: 8.5pt;"><span><font color="#0000ff">if</font></span><span> (Resolver != </span><span><font color="#0000ff">null</font></span></font><span><font style="font-size: 8.5pt;">) settings.XmlResolver = Resolver;</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">      settings.ValidationEventHandler += (s, e) =&gt; validationErrors.Add(</font></span><font style="font-size: 8.5pt;"><span><font color="#0000ff">new</font></span><span> </span><span><font color="#2b91af">XmlValidationError</font></span></font><span><font style="font-size: 8.5pt;">(e.Message,e.Severity));            </font></span></font> </p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><span style="mso-spacerun: yes;"><font face="Courier New"><font style="font-size: 8.5pt;">  </font></font></span></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">      </font></span><font style="font-size: 8.5pt;"><span><font color="#2b91af">Stream</font></span><span> xhtmlStream = </span><span><font color="#0000ff">new</font></span><span> </span><span><font color="#2b91af">MemoryStream</font></span><span>(</span><span><font color="#2b91af">UTF8Encoding</font></span></font><span><font style="font-size: 8.5pt;">.UTF8.GetBytes(xhtml));   </font></span> </font> </p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><span style="mso-spacerun: yes;"><font face="Courier New"><font style="font-size: 8.5pt;">  </font></font></span></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">      </font></span><font style="font-size: 8.5pt;"><span><font color="#0000ff">using</font></span><span> (</span><span><font color="#2b91af">XmlReader</font></span><span> xmlReader = </span> <span><font color="#2b91af">XmlReader</font></span></font><span><font style="font-size: 8.5pt;">.Create(xhtmlStream, settings)) </font></span></font> </p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">      { </font></font></span> </p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">        </font></span><font style="font-size: 8.5pt;"><span><font color="#0000ff">try</font></span><span> { </span><span><font color="#0000ff">while</font></span></font><span><font style="font-size: 8.5pt;"> (xmlReader.Read()) ; }</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">        </font></span><font style="font-size: 8.5pt;"><span><font color="#0000ff">catch</font></span><span> (</span><span><font color="#2b91af">Exception</font></span><span> ex) { validationErrors.Add(</span><span><font color="#0000ff">new</font></span><span> </span><span><font color="#2b91af">XmlValidationError</font></span><span>(ex.Message, </span><span><font color="#2b91af">XmlSeverityType</font></span></font><span><font style="font-size: 8.5pt;">.Error)); }                </font></span></font> </p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">      }</font></font></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><span style="mso-spacerun: yes;"><font face="Courier New"><font style="font-size: 8.5pt;">  </font></font></span></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">      errors = validationErrors;</font></font></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><span style="mso-spacerun: yes;"><font face="Courier New"><font style="font-size: 8.5pt;">  </font></font></span></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">      </font></span><font style="font-size: 8.5pt;"><span><font color="#0000ff">return</font></span></font><span><font style="font-size: 8.5pt;"> (validationErrors.Count == 0);</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">    }      </font></font></span> </p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">}</font></font></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><span style="mso-spacerun: yes;"><font face="Courier New"><font style="font-size: 8.5pt;">  </font></font></span></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><font style="font-size: 8.5pt;"><span><font color="#0000ff"> public</font></span><span> </span><span><font color="#0000ff">class</font></span><span> </span></font><span><font color="#2b91af" style="font-size: 8.5pt;">XmlValidationError</font></span></font><span /></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">{</font></font></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">   </font></span> <font style="font-size: 8.5pt;"><span><font color="#0000ff">public</font></span><span> </span><span><font color="#0000ff">string</font></span><span> Message { </span><span><font color="#0000ff"> get</font></span><span>; </span><span><font color="#0000ff">private set</font></span></font><span><font style="font-size: 8.5pt;">; }</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">   </font></span> <font style="font-size: 8.5pt;"><span><font color="#0000ff">public</font></span><span> </span><span><font color="#2b91af">XmlSeverityType</font></span><span> Severity { </span><span><font color="#0000ff">get</font></span><span>; </span><span><font color="#0000ff">private set</font></span></font><span><font style="font-size: 8.5pt;">; }</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><span style="mso-spacerun: yes;"><font face="Courier New"><font style="font-size: 8.5pt;">  </font></font></span></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <font face="Courier New"><span><font style="font-size: 8.5pt;">   </font></span> <font style="font-size: 8.5pt;"><span><font color="#0000ff">public</font></span><span> XmlValidationError(</span><span><font color="#0000ff">string</font></span><span> message, </span><span><font color="#2b91af">XmlSeverityType</font></span></font><span><font style="font-size: 8.5pt;"> severity)</font></span></font></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">   {</font></font></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">     Message = message;</font></font></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">     Severity = severity;</font></font></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">   }</font></font></span></p> <p class="MsoNormal" style="background: white; margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"> <span><font face="Courier New"><font style="font-size: 8.5pt;">}</font></font></span></p> <p>  </p> <p>La classe <em>XmlValidationError</em> è stata introdotta semplicemente come “astrazione” rispetto a due tipologie di notifiche: quelle derivanti dal parsing della struttura del documento (le Read() dell’<a target="_blank" href="http://msdn.microsoft.com/it-it/library/system.xml.xmlreader(v=vs.80).aspx">XmlReader</a>) e quelle derivanti dal processo di validazione (i ValidationEvent scatenati dalla classe <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.xml.xmlreadersettings.aspx">XmlReaderSettings</a>).  <br /> Vediamo quindi come possiamo sfruttare <em>XmlPreloadedResolver</em> in questo contesto tramite una banale applicazione Console…</p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <font face="Courier New"><span> <font color="#0000ff"><font style="font-size: 8pt;">static</font></font></span><span><font style="font-size: 8pt;"> <span><font color="#0000ff">void</font></span> Main(<span><font color="#0000ff">string</font></span>[] args)</font></span></font></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <font style="font-size: 8pt;">{</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><span><font color="#008000" style="font-size: 8pt;">////GOOD XHTML 1.0 (conformant)</font></span></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><span><font color="#008000" style="font-size: 8pt;">//string xhtml = "&lt;!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"&gt;" +</font></span></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><span><font color="#008000" style="font-size: 8pt;">//<span style="mso-spacerun: yes;">               </span>"&lt;html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"it\" lang=\"it\"&gt;" +</font></span></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><span><font color="#008000" style="font-size: 8pt;">//<span style="mso-spacerun: yes;">               </span>"&lt;head&gt;&lt;title&gt;GOOD XHTML 1.0 Document&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;A paragraph here is OK&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;";</font></span></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <font style="font-size: 8pt;"> </font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><span><font color="#008000" style="font-size: 8pt;">//// BAD XHTML 1.0 (non conformant)</font></span></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><font style="font-size: 8pt;"><span><font color="#0000ff">string</font></span> xhtml = <span><font color="#a31515">"&lt;!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"&gt;"</font></span> +</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">                  </font></span><font style="font-size: 8pt;"><span><font color="#a31515">"&lt;html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"it\" lang=\"it\"&gt;"</font></span> +</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">                  </font></span><font style="font-size: 8pt;"><span><font color="#a31515">"&lt;head&gt;&lt;title&gt;BAD XHTML 1.0 Document&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;span&gt;A span here is NOT OK&lt;/span&gt;&lt;/body&gt;&lt;/html&gt;"</font></span>;</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><span style="mso-spacerun: yes;"> <font face="Courier New"><font style="font-size: 8pt;">        </font></font></span></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><font style="font-size: 8pt;"><span><font color="#2b91af">XmlPreloadedResolver</font></span> resolver = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af"> XmlPreloadedResolver</font></span>(<span><font color="#2b91af">XmlKnownDtds</font></span>.Xhtml10);</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><font style="font-size: 8pt;"><span><font color="#2b91af">XhtmlValidator</font></span> validator = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af"> XhtmlValidator</font></span>(resolver);</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <font style="font-size: 8pt;"> </font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><font style="font-size: 8pt;"><span><font color="#2b91af">List</font></span>&lt;<span><font color="#2b91af">XmlValidationError</font></span>&gt; validationErrors = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">List</font></span>&lt;<span><font color="#2b91af">XmlValidationError</font></span>&gt;();</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><font style="font-size: 8pt;"><span><font color="#0000ff">bool</font></span> success = validator.Validate(xhtml, <span><font color="#0000ff">out</font></span> validationErrors);</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <font style="font-size: 8pt;"> </font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><font style="font-size: 8pt;"><span><font color="#0000ff">if</font></span> (success) <span><font color="#2b91af">Console</font></span>.WriteLine(<span><font color="#a31515">"Success"</font></span>);</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><span><font color="#0000ff" style="font-size: 8pt;">else</font></span></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span><font style="font-size: 8pt;">{</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">      </font></span><font style="font-size: 8pt;"><span><font color="#2b91af">Console</font></span>.WriteLine(<span><font color="#a31515">"Errors: "</font></span> + validationErrors.Count.ToString());</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <span><font face="Courier New"> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">      </font></span><font style="font-size: 8pt;"><span><font color="#0000ff">foreach</font></span> (<span><font color="#2b91af">XmlValidationError </font></span>error <span><font color="#0000ff"> in</font></span> validationErrors) <span><font color="#2b91af">Console</font></span>.WriteLine(<span><font color="#0000ff">string</font></span>.Format(<span><font color="#a31515">"- Message: {0} (Severity: {1})"</font></span>, error.Message, error.Severity));</font><span style="mso-spacerun: yes;"><font style="font-size: 8pt;">                </font></span></font></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; mso-layout-grid-align: none;"> <font face="Courier New"><span> <span style="mso-spacerun: yes;"><font style="font-size: 8pt;">   </font> </span></span><span style="font-family: ;"><font style="font-size: 8pt;">}</font><span style="mso-spacerun: yes;"><font style="font-size: 8pt;">           </font></span></span></font> </p> <span style="line-height: 9pt; font-family: ; mso-ansi-language: it; mso-fareast-language: en-us; mso-bidi-language: ar-sa; mso-fareast-font-family: calibri; mso-fareast-theme-font: minor-latin;"> <font face="Courier New"><font style="font-size: 8pt;">}</font></font></span> <p>  </p> <p> Eseguendo il codice precedente senza essere connessi alla rete, si osserva come il <em>XmlPreloadedResolver</em> faccia il suo lavoro e ci permetta di validare il contenuto XHTML 1.0 Strict di esempio tramite il relativo DTD embeddato nell’assembly <em>System.Xml.Utils</em>. </p> <p> <img alt="" src="http://img17.imageshack.us/img17/3971/immaginelab.jpg" /></p> <p> <strong><em>Domanda</em></strong>: E se volessimo usare <em>XmlPreloadedResolver</em> su documenti <strong>XHTML 1.1</strong>? <br /> Ebbene, sembra che la strada dello standard XHTML 1.1 non sia stata battuta, forse per la forte modularizzazione della specifica o forse per l'effettiva scarsa adozione dello standard nel web. Fatto sta che in questo contesto non abbiamo "known" DTD pre-caricati (neanche quelli “core” definiti nel <a target="_blank" href="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">DTD principale</a>), quindi dobbiamo pre-caricarceli “a mano” in questo modo… </p> <p> <br /> <font face="Courier New"><span> <font color="#2b91af"><font style="font-size: 8pt;">XmlPreloadedResolver</font></font></span><span><font style="font-size: 8pt;"> xmlPreloadedResolver = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">XmlPreloadedResolver</font></span>(<span><font color="#2b91af">XmlKnownDtds</font></span>.Xhtml10); <br /> </font></span></font><span> <font face="Courier New"><font style="font-size: 8pt;">xmlPreloadedResolver.Add(<span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Uri</font></span>(<span><font color="#a31515">"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"</font></span>), <span><font color="#2b91af">File</font></span>.ReadAllBytes(<span><font color="#a31515">"C:\\...\\xhtml11.dtd"</font></span>)); <br /> </font></font></span><span> <font face="Courier New"><font style="font-size: 8pt;">xmlPreloadedResolver.Add(<span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Uri</font></span>(<span><font color="#a31515">"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod"</font></span>), <span><font color="#2b91af">File</font></span>.ReadAllBytes(<span><font color="#a31515">"C:\\...\\xhtml-inlstyle-1.mod"</font></span>)); <br /> </font></font></span><span> <font face="Courier New"><font style="font-size: 8pt;">xmlPreloadedResolver.Add(<span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Uri</font></span>(<span><font color="#a31515">"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod"</font></span>), <span><font color="#2b91af">File</font></span>.ReadAllBytes(<span><font color="#a31515">"C:\\...\\xhtml-text-1.mod"</font></span>)); <br /> </font></font></span><span> <font face="Courier New"><font style="font-size: 8pt;">xmlPreloadedResolver.Add(<span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Uri</font></span>(<span><font color="#a31515">"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod"</font></span>), <span><font color="#2b91af">File</font></span>.ReadAllBytes(<span><font color="#a31515">"C:\\...\\xhtml-hypertext-1.mod"</font></span>)); <br /> </font></font></span><span> <font face="Courier New"><font style="font-size: 8pt;">xmlPreloadedResolver.Add(<span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Uri</font></span>(<span><font color="#a31515">"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod"</font></span>), <span><font color="#2b91af">File</font></span>.ReadAllBytes(<span><font color="#a31515">"C:\\...\\xhtml-list-1.mod"</font></span>)); <br /> etc... </font></font></span> </p> <p>  </p> <div class="wlWriterEditableSmartContent" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:f1996e40-eea3-44ef-8a1f-fee1e6307ac7" style="margin: 0px; padding: 0px; float: none; display: inline;"> Tag di Technorati: <a rel="tag" href="http://technorati.com/tags/XHTML">XHTML</a>,<a rel="tag" href="http://technorati.com/tags/.NET">.NET</a>,<a rel="tag" href="http://technorati.com/tags/C%23">C#</a>,<a rel="tag" href="http://technorati.com/tags/DTD">DTD</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/99974.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2011/04/20/validare-contenuti-xhtml-con-xmlpreloadedresolver.aspx Wed, 20 Apr 2011 21:23:22 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2011/04/20/validare-contenuti-xhtml-con-xmlpreloadedresolver.aspx#feedback 1 http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/99974.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/99974.aspx [WCF 4.0] Default Endpoints WCF http://blogs.ugidotnet.org/dsantarelli/archive/2011/02/19/wcf-4.0-default-endpoints.aspx <p>Un’ approccio introdotto in WCF 4.0 è la “<a href="http://en.wikipedia.org/wiki/Convention_over_configuration">Convention Over Configuration</a>”. Sostanzialmente essa permette di assegnare una configurazione predefinita ad un endpoint in assenza di una configurazione esplicita (cosa impossibile in WCF 3.x). Ciò avviene tramite la definizione di un mapping tra lo schema/protocollo (es. http, tcp etc.) e le varie tipologie di binding (es. BasicHttpBinding, NetTcpBinding etc.) all’interno del file: <br /> <em>%WINDIR%\Microsoft.NET\Framework\v4.0.30319\Config\machine.config.comments</em> </p> <font face="Consolas"> <p class="MsoNormal" style="margin: 0cm 0cm 10pt; line-height: 13pt;"> <span class="style1" lang="EN-US" style="line-height: 9pt; mso-ansi-language: en-us;"><font color="#0000ff">&lt;</font></span><font class="style2"><span class="style1" lang="EN-US" style="line-height: 9pt; mso-ansi-language: en-us;"><font color="#a31515">protocolMapping</font></span><span lang="EN-US" style="line-height: 9pt; mso-ansi-language: en-us;"><font color="#0000ff"><span class="style1">&gt; </span> <br class="style1" /> <span class="style1" style="mso-spacerun: yes;">    </span> <span class="style1">&lt;</span></font></span><span class="style1" lang="EN-US" style="line-height: 9pt; mso-ansi-language: en-us;"><font color="#a31515">clear</font></span><span lang="EN-US" style="line-height: 9pt; mso-ansi-language: en-us;"><font color="#0000ff"><span class="style1"> /&gt; </span> <br class="style1" /> <span class="style1" style="mso-spacerun: yes;">    </span> <span class="style1">&lt;</span></font></span><span class="style1" lang="EN-US" style="line-height: 9pt; mso-ansi-language: en-us;"><font color="#a31515">add</font><font color="#0000ff"> </font><font color="#ff0000">scheme</font><font color="#0000ff">=</font></span></font><span lang="EN-US" style="line-height: 9pt; mso-ansi-language: en-us;"><font style="font-size: 8pt;">"</font><span class="style1"><font color="#0000ff">http</font></span>"<span class="style1"><font color="#0000ff"> </font><font color="#ff0000">binding</font><font color="#0000ff">=</font></span>"<span class="style1"><font color="#0000ff">basicHttpBinding</font></span>"<span class="style1"><font color="#0000ff"> </font><font color="#ff0000">bindingConfiguration</font><font color="#0000ff">=</font>""</span><span><font color="#0000ff"><span class="style1"> /&gt; </span> <br class="style1" /> <span class="style1" style="mso-spacerun: yes;">    </span> <span class="style1">&lt;</span></font></span><span class="style1"><font color="#a31515">add</font><font color="#0000ff"> </font><font color="#ff0000">scheme</font><font color="#0000ff">=</font></span>"<span class="style1"><font color="#0000ff">net.tcp</font></span>"<span class="style1"><font color="#0000ff"> </font><font color="#ff0000">binding</font><font color="#0000ff">=</font></span>"<span class="style1"><font color="#0000ff">netTcpBinding</font></span>"<span class="style1"><font color="#0000ff"> </font><font color="#ff0000">bindingConfiguration</font><font color="#0000ff">=</font>""</span><span><font color="#0000ff"><span class="style1"> /&gt; </span> <br class="style1" /> <span class="style1" style="mso-spacerun: yes;">    </span> <span class="style1">&lt;</span></font></span><span class="style1"><font color="#a31515">add</font><font color="#0000ff"> </font><font color="#ff0000">scheme</font><font color="#0000ff">=</font></span>"<span class="style1"><font color="#0000ff">net.pipe</font></span>"<span class="style1"><font color="#0000ff"> </font><font color="#ff0000">binding</font><font color="#0000ff">=</font></span>"<span class="style1"><font color="#0000ff">netNamedPipeBinding</font></span>"<span class="style1"><font color="#0000ff"> </font><font color="#ff0000">bindingConfiguration</font><font color="#0000ff">=</font>""</span><span><font color="#0000ff"><span class="style1"> /&gt; </span> <br class="style1" /> <span class="style1" style="mso-spacerun: yes;">    </span> <span class="style1">&lt;</span></font></span><span class="style1"><font color="#a31515">add</font><font color="#0000ff"> </font><font color="#ff0000">scheme</font><font color="#0000ff">=</font></span>"<span class="style1"><font color="#0000ff">net.msmq</font></span>"<span class="style1"><font color="#0000ff"> </font><font color="#ff0000">binding</font><font color="#0000ff">=</font></span>"<span class="style1"><font color="#0000ff">netMsmqBinding</font></span>"<span class="style1"><font color="#0000ff"> </font><font color="#ff0000">bindingConfiguration</font><font color="#0000ff">=</font>""</span><span><font color="#0000ff"><span class="style1"> /&gt; </span> <br class="style1" /> <span class="style1">&lt;/</span></font></span><span class="style1"><font color="#a31515">protocolMapping</font><font color="#0000ff" style="font-size: 8pt;">&gt;</font></span></span></p> </font> <p>Volendo verificare la cosa, creiamo un’ interfaccia di prova</p> <span lang="EN-US" style="line-height: 12pt; mso-ansi-language: en-us; mso-fareast-language: it; mso-bidi-language: ar-sa;"><font face="Consolas">[<span><font color="#2b91af">ServiceContract</font></span>] <br /> <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">interface</font></span> <span><font color="#2b91af">IDummyService</font></span> <br /> { <br />    [<span><font color="#2b91af">OperationContract</font></span>(IsOneWay=<span><font color="#0000ff">true</font></span>)] <br />    <span><font color="#0000ff">void</font></span> DoNothing();          <br /> }</font></span> <p><br /> Puntualizziamo il fatto che la scelta di impostare l’operation contract come “OneWay” deriva dal fatto che un contratto esposto da un endpoint che utilizza <a href="http://msdn.microsoft.com/it-it/library/system.servicemodel.netmsmqbinding.aspx">NetMsmqBinding</a> può supportare solo operazioni one-way. Implementiamo quindi l’interfaccia <em>IDummyService</em>...</p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;"><font face="Consolas"><span lang="EN-US" style="mso-ansi-language: en-us; mso-fareast-font-family: &quot;Times New Roman&quot;; mso-fareast-language: it;"><font color="#0000ff">public</font></span></font><span lang="EN-US" style="mso-ansi-language: en-us; mso-fareast-font-family: &quot;Times New Roman&quot;; mso-fareast-language: it;"><font face="Consolas"> <span><font color="#0000ff">class</font></span> <span><font color="#2b91af">MyDummyService</font></span> : <span><font color="#2b91af">IDummyService</font></span> <br /> { <br />    <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">void</font></span> DoNothing() { }        <br /> }</font></span></p> <p> <br /> Costruiamo un test per verificare quanto asserito, ponendo attenzione a non specificare alcuna configurazione per gli endpoint:</p> <span lang="EN-US" style="line-height: 12pt; mso-ansi-language: en-us; mso-fareast-font-family: &quot;Times New Roman&quot;; mso-fareast-language: it; mso-bidi-language: ar-sa;"><font color="#2b91af" face="Consolas">ServiceHost</font></span><span lang="EN-US" style="line-height: 12pt; mso-ansi-language: en-us; mso-fareast-font-family: &quot;Times New Roman&quot;; mso-fareast-language: it; mso-bidi-language: ar-sa;"><font face="Consolas"> host = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">ServiceHost</font></span>(<span><font color="#0000ff">typeof</font></span>(<span><font color="#2b91af">MyDummyService</font></span>), <br />                                  <span style="mso-spacerun: yes;">  </span><span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Uri</font></span>(<span><font color="#a31515">"http://localhost:8080/MyDummyService"</font></span>), <br />                                    <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Uri</font></span>(<span><font color="#a31515">"net.tcp://localhost:8081/MyDummyService"</font></span>), <br />                                    <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Uri</font></span>(<span><font color="#a31515">"net.pipe://localhost/MyDummyService"</font></span>), <br />                                    <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Uri</font></span>(<span><font color="#a31515">"net.msmq://localhost/private/MyDummyService"</font></span>));                        <br /> host.Open();             <br /> <span style="mso-spacerun: yes;"> </span> <br /> <span><font color="#2b91af">Assert</font></span>.AreEqual(4, host.Description.Endpoints.Count);             <br /> <span><font color="#2b91af">Assert</font></span>.IsTrue(host.Description.Endpoints[0].Binding <span><font color="#0000ff">is</font></span> <span><font color="#2b91af">BasicHttpBinding</font></span>); <br /> <span><font color="#2b91af">Assert</font></span>.IsTrue(host.Description.Endpoints[1].Binding <span><font color="#0000ff">is</font></span> <span><font color="#2b91af">NetTcpBinding</font></span>); <br /> <span><font color="#2b91af">Assert</font></span>.IsTrue(host.Description.Endpoints[2].Binding <span><font color="#0000ff">is</font></span> <span><font color="#2b91af">NetNamedPipeBinding</font></span>);    <br /> <span><font color="#2b91af">Assert</font></span>.IsTrue(host.Description.Endpoints[3].Binding <span><font color="#0000ff">is</font></span> <span><font color="#2b91af">NetMsmqBinding</font></span>);</font></span> <p><br /> Verde! <br /> Ovviamente, se ne abbiamo la necessità, possiamo effettuare l’override di questi mappings sia a livello di machine che a livello di application. Ad esempio, se specificassimo la seguente configurazione nell’app.config…</p> <font face="Consolas"><span style="line-height: 12pt; mso-ansi-language: it; mso-fareast-font-family: &quot;Times New Roman&quot;; mso-fareast-language: it; mso-bidi-language: ar-sa;"><font color="#0000ff">&lt;</font></span><span style="line-height: 12pt; mso-ansi-language: it; mso-fareast-font-family: &quot;Times New Roman&quot;; mso-fareast-language: it; mso-bidi-language: ar-sa;"><font color="#a31515">configuration</font></span><span style="line-height: 12pt; mso-ansi-language: it; mso-fareast-font-family: &quot;Times New Roman&quot;; mso-fareast-language: it; mso-bidi-language: ar-sa;"><font color="#0000ff">&gt;</font></span></font><span style="line-height: 12pt; mso-ansi-language: it; mso-fareast-font-family: &quot;Times New Roman&quot;; mso-fareast-language: it; mso-bidi-language: ar-sa;"> <br /> <font face="Consolas"><span><font color="#0000ff">  &lt;</font></span><span><font color="#a31515">system.serviceModel</font></span></font><font face="Consolas"><font color="#0000ff"><span>&gt;</span> <br /> <span>    &lt;</span></font><span><font color="#a31515">protocolMapping</font></span></font><font face="Consolas"><font color="#0000ff"><span>&gt;</span> <br /> <span>      &lt;</span></font><span><font color="#a31515">add</font></span><span><font color="#0000ff"> </font></span><span><font color="#ff0000">scheme</font></span><span><font color="#0000ff">=</font></span>"<span><font color="#0000ff">http</font></span>"<span><font color="#0000ff"> </font></span><span><font color="#ff0000">binding</font></span><span><font color="#0000ff">=</font></span>"<span><font color="#0000ff">webHttpBinding</font></span>"</font><font face="Consolas"><font color="#0000ff"><span>/&gt;</span> <br /> <span>    &lt;/</span></font><span><font color="#a31515">protocolMapping</font></span></font><font face="Consolas"><font color="#0000ff"><span>&gt;</span> <br /> <span>  &lt;/</span></font><span><font color="#a31515">system.serviceModel</font></span></font><font face="Consolas"><font color="#0000ff"><span>&gt;</span> <br /> <span>&lt;/</span></font><span><font color="#a31515">configuration</font></span><span><font color="#0000ff">&gt;</font></span></font></span> <p><br /> …il test precedente fallirebbe poiché lo schema http è stato assegnato al WebHttpBinding e non più al BasicHttpBinding. Invece, il seguente test avrebbe successo</p> <span style="line-height: 12pt; mso-ansi-language: it; mso-fareast-font-family: &quot;Times New Roman&quot;; mso-fareast-language: it; mso-bidi-language: ar-sa;"><font color="#2b91af" face="Consolas">ServiceHost</font></span><span style="line-height: 12pt; mso-ansi-language: it; mso-fareast-font-family: &quot;Times New Roman&quot;; mso-fareast-language: it; mso-bidi-language: ar-sa;"><font face="Consolas"> host = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">ServiceHost</font></span>(<span><font color="#0000ff">typeof</font></span>(<span><font color="#2b91af">MyDummyService</font></span>),<span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Uri</font></span>(<span><font color="#a31515">"http://localhost:8080/MyDummyService"</font></span>)); <br /> host.Open(); <br /> <span><font color="#2b91af">Assert</font></span>.IsTrue(host.Description.Endpoints[0].Binding <span><font color="#0000ff">is</font></span> <span><font color="#2b91af">WebHttpBinding</font></span>);</font></span> <p><br /> Per avere più informazioni riguardo al funzionamento di questa feature e delle altre novità di WCF 4.0 rimando a questo link MSDN: <br /> <a href="http://msdn.microsoft.com/en-us/library/ee354381.aspx">http://msdn.microsoft.com/en-us/library/ee354381.aspx</a> (A Developer's Introduction to Windows Communication Foundation 4)</p> <div class="wlWriterEditableSmartContent" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:fac97a97-662d-4c1b-b933-529a57410aa7" style="margin: 0px; padding: 0px; float: none; display: inline;">Tag di Technorati: <a rel="tag" href="http://technorati.com/tags/WCF">WCF</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/99741.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2011/02/19/wcf-4.0-default-endpoints.aspx Sat, 19 Feb 2011 17:34:24 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2011/02/19/wcf-4.0-default-endpoints.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/99741.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/99741.aspx Migliorare le performance di un’applicazione ASP.NET MVC ASP.NET MVC http://blogs.ugidotnet.org/dsantarelli/archive/2011/01/11/migliorare-le-performance-di-unapplicazione-asp.net-mvc.aspx <p>Esistono molti aspetti da tenere in considerazione quando si parla di performance di applicazioni Web. Infatti, le problematiche legate al protocollo HTTP spesso scavalcano il prode tentativo di scrivere codice server-side e client-side ottimizzato, poiché migliorare le performance di un’ applicazione Web significa anche minimizzare il traffico dati tra client e server. Le best practices per velocizzare il caricamento delle pagine web non sono poche, come si può appurare semplicemente consultando l’utilissimo documento di <a href="http://developer.yahoo.com/">Yahoo Developer Network</a> dal titolo “<a href="http://developer.yahoo.com/performance/rules.html">Best Practices for Speeding Up Your Web Site</a>”  (parliamo di 35 best practices divise in 7 categorie!) <br /> In questo post ho cercato di aggregare una serie di appunti/approfondimenti relativi a tecniche comuni di miglioramento delle performance di un’applicazione ASP.NET MVC (e non solo) in base alla mia esperienza. I punti su cui cercherò di soffermarmi in particolare sono:</p> <ul> <li>Output Cache </li> <li>Compressione HTTP  </li> <li>Minimizzazione e combinazione di risorse esterne (JavaScript e Css)  </li> <li>Minimizzazione del markup HTML  </li> <li>Ottimizzazione della generazione degli URL </li> <li>Note di configurazione </li> </ul> <p><strong><font size="3"> <br /> Output cache <br /> <br /> </font></strong>Un’ applicazione Web dovrebbe sempre prevedere una politica di <a href="http://en.wikipedia.org/wiki/HTTP_cache">caching</a> delle risposte HTTP sia client-side (browser + proxy) che server-side, in modo tale che il contenuto di una risposta HTTP non venga rigenerato inutilmente ogni volta che il client ne effettua la richiesta. Il modo più semplice ed immediato per abilitare il caching in un’applicazione MVC è aggiungere l’attributo [<a href="http://msdn.microsoft.com/en-us/library/system.web.mvc.outputcacheattribute.aspx">OutputCache</a>] a livello di action. Questa funzionalità è praticamente identica a quella che incontriamo nelle Web Forms, ma cambia nel fatto che non deve essere applicata a livello di View (quindi non dovremmo usare la direttiva <em>&lt;%@ OutputCache %&gt;</em>), anche perché non avrebbe molto senso nel pattern MVC, in cui la View dovrebbe essere agnostica rispetto alle politiche di caching. L’unica proprietà che non è supportata nella versione MVC è <em>VaryByControl</em>. <br /> In genere è sempre consigliabile cercare di estrarre le impostazioni di caching nel web.config definendo dei profili, in modo da non dover ricompilare il sorgente ogni volta che cambiamo le nostre strategie di caching. Quindi, nel controller dovremmo scrivere qualcosa del genere:</p> <pre style="margin: 0cm 0cm 0pt;"><span lang="EN-US" style="font-family: ;"><font face="Consolas"><span><font color="#0000ff">public</font></span> <span><font color="#0000ff">class</font></span> <span><font color="#2b91af">HomeController</font></span> : <span><font color="#2b91af">Controller<br /></font></span>{<br /> [<span><font color="#2b91af">OutputCache</font></span>(CacheProfile=<span><font color="#a31515">"MyCacheProfile"</font></span>)]<br /> <span><font color="#0000ff">public</font></span> <span><font color="#2b91af">ActionResult</font></span> Index()<br /> {                             <br /> <span><font color="#0000ff">return</font></span> View();<br /> }</font></span></pre> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US"><font face="Consolas"><font style="font-size: 11pt;">}</font></font></span><span lang="EN-US" /></p> <span lang="EN-US"> <span lang="EN-US"> <span lang="EN-US"> <span lang="EN-US"> <p> <br /> e nel web.config</p> <pre style="margin: 0cm 0cm 0pt;"><font face="Consolas"><span lang="EN-US"><font color="#0000ff">&lt;</font></span><span lang="EN-US"><font color="#a31515">configuration</font></span></font><font face="Consolas"><span lang="EN-US"><font color="#0000ff">&gt; <br /> &lt;</font></span><span lang="EN-US"><font color="#a31515">system.web</font></span><span lang="EN-US"><font color="#0000ff">&gt;</font></span></font><span lang="EN-US" /></pre> <span lang="EN-US"> <span lang="EN-US"> <span lang="EN-US"> <span lang="EN-US"> <pre style="margin: 0cm 0cm 0pt;"><font face="Consolas"><span lang="EN-US"><font color="#0000ff"><span>   </span>&lt;</font></span><span lang="EN-US"><font color="#a31515">caching</font></span><span lang="EN-US"><font color="#0000ff">&gt;</font></span></font><span lang="EN-US"><br /><font face="Consolas"><span><font color="#0000ff"> &lt;</font></span><span><font color="#a31515">outputCacheSettings</font></span><span><font color="#0000ff">&gt;</font></span><br /><span><font color="#0000ff"> &lt;</font></span><span><font color="#a31515">outputCacheProfiles</font></span><span><font color="#0000ff">&gt;</font></span><br /><span><font color="#0000ff"> &lt;</font></span><span><font color="#a31515">add</font></span><span><font color="#0000ff"> </font></span><span><font color="#ff0000">name</font></span><span><font color="#0000ff">=</font></span>"<span><font color="#0000ff">MyCacheProfile</font></span>"<span><font color="#0000ff"> </font></span><span><font color="#ff0000">enabled</font></span><span><font color="#0000ff">=</font></span>"<span><font color="#0000ff">true</font></span>"<span><font color="#0000ff"> </font></span><span><font color="#ff0000">duration</font></span><span><font color="#0000ff">=</font></span>"<span><font color="#0000ff">600</font></span>"<span><font color="#0000ff"> </font></span><span><font color="#ff0000">varyByParam</font></span><span><font color="#0000ff">=</font></span>"<span><font color="#0000ff">None</font></span>"<span><font color="#0000ff"> </font></span><span><font color="#ff0000">location</font></span><span><font color="#0000ff">=</font></span>"<span><font color="#0000ff">Any</font></span>"</font><font face="Consolas"><span><font color="#0000ff"> /&gt;<br /> <span>          </span>...</font></span><br /><span><font color="#0000ff"> &lt;/</font></span><span><font color="#a31515">outputCacheProfiles</font></span><span><font color="#0000ff">&gt;<br /> </font></span><span><font color="#0000ff">&lt;/</font></span><span><font color="#a31515">outputCacheSettings</font></span><span><font color="#0000ff">&gt;<br /> </font></span><span><font color="#0000ff">&lt;/</font></span><span><font color="#a31515">caching</font></span><span><font color="#0000ff">&gt;<br /> </font></span></font></span><font face="Consolas"><font color="#0000ff"><span>&lt;/</span></font><span><font color="#a31515">system.web</font></span><span><font color="#0000ff">&gt;</font></span></font><span /></pre> <span> <span> <span> <span> <pre style="margin: 0cm 0cm 0pt;"><font face="Consolas"><span><font color="#0000ff">&lt;/</font></span><span><font color="#a31515">configuration</font></span><span><font color="#0000ff">&gt;</font></span></font><span /></pre> <span> <span> <span> <span> <p> <br /> Ovviamente il controllo programmatico della cache di ASP.NET è sempre a disposizione tramite la solita classe <a href="http://msdn.microsoft.com/en-us/library/system.web.caching.cache.aspx">System.Web.Caching.Cache</a>. L’unica e non indifferente limitazione di questo approccio risiede nel fatto che la cache è in-process rispetto all’applicazione web e dunque non è distribuita. In applicazioni che richiedono un alto grado di scalabilità, invece, sarebbe preferibile prendere in considerazione architetture di caching distribuito (es. Velocity). A riguardo, consiglio la lettura di questo articolo di Stephen Walther: <a href="http://stephenwalther.com/blog/archive/2008/08/28/asp-net-mvc-tip-39-use-the-velocity-distributed-cache.aspx">ASP.NET MVC Tip #39 – Use the Velocity Distributed Cache</a>. </p> <p> <br /> <strong><font size="3">Compressione HTTP <br /> </font></strong> <br /> In conformità alla <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">specifica HTTP/1.1</a>, i web server ed i browser moderni supportano l’elaborazione di <a href="http://en.wikipedia.org/wiki/HTTP_compression">contenuti compressi</a> secondo standard di compressione come il <a href="http://www.ietf.org/rfc/rfc1952.txt">GZIP</a> ed il (migliore) <a href="http://www.ietf.org/rfc/rfc1951.txt">DEFLATE</a> al fine di ridurre sensibilmente la banda occupata dal traffico HTTP. Lato server, la soluzione più scalabile per comprimere contenuti statici e dinamici prevede lo sfruttamento delle feature del Web Server piuttosto che della web application, ovviamente a fronte di ragionevoli <a href="http://weblogs.asp.net/owscott/archive/2009/02/22/iis-7-compression-good-bad-how-much.aspx">valutazioni sul carico computazionale</a> che il server deve sostenere soprattutto per gestire la compressione di contenuti dinamici. Nelle versioni di IIS precedenti alla 6.0, la funzionalità di compressione HTTP non è built-in e richiede comunque l’utilizzo di strumenti di terze parti (come <a href="https://secure.xcache.com/">XCompress</a>). </p> <p>In IIS6.0, invece, abbiamo l’introduzione di features di compressione statica (con cache su disco) e dinamica (senza cache su disco) che richiedono una modifica al metabase (%WINDIR%\system32\inetsrv\metabase.xml), al fine di abilitare gli schemi di compressione gzip e deflate su contenuti identificabili solamente <strong>in base al tipo di estensione</strong> (es. .aspx, .html etc). Per questo motivo, le applicazioni ASP.NET MVC deployate su IIS6 vanno incontro ad una serie di problematiche legate al fatto che le URL senza estensione forniscono un bel 404, ed anche se si implementa un URL rewrite o un “wildcard” mapping la compressione built-in di contenuti dinamici di IIS6.0 non ha più effetto poiché non sussiste alcun match con le estensioni specificate nel metabase. Quindi, se vogliamo avere la compressione sotto controllo, dobbiamo ricorrere ad un HttpModule, come viene spiegato molto bene in <a href="http://omaralzabir.com/deploy_asp_net_mvc_on_iis_6__solve_404__compression_and_performance_problems/">questo post</a>. </p> <p>Il problema viene risolto alla radice a partire da IIS7.0 grazie alla nuova sezione di configurazione <a href="http://www.iis.net/ConfigReference/system.webServer/httpCompression">&lt;httpCompression&gt;</a>, che va a sostituire le precedenti configurazioni del metabase di IIS6.0 e ci permette di abilitare la compressione <strong>in base ai tipi MIME</strong> a livello di server, web site o web application attraverso diverse strade: IIS Manager, la command line di Appcmd.exe, il web.config e le API managed. Un grande vantaggio di IIS7+ inoltre è la capacità automatica di interrompere/riprendere la compressione a seconda che il carico di CPU superi/scenda sotto una soglia configurabile (vedi attributi <em>dynamicCompressionDisableCpuUsage</em> e <em>dynamicCompressionEnableCpuUsage</em><font face="Verdana">). </font></p> <p>Nel caso in cui non fosse disponibile la possibilità di accedere alla configurazione del web server, siamo costretti a prendere la strada applicativa. Analogamente al classico mondo ASP.NET, anche in ASP.NET MVC la gestione della compressione dei contenuti può essere ottenuta tramite un HttpModule (ad esempio <a href="http://blowery.org/httpcompress/">HttpCompress</a>) in grado di applicare la compressione all’output stream della risposta HTTP. In genere, questa è una soluzione semplice e riusabile (forse meno performante) che permette di non intaccare almeno nella teoria una web application preesitente. </p> <p>Un’ulteriore possibilità totalmente programmatica per ASP.NET MVC è la realizzazione di un filtro custom che a livello di controller o di singola action vada ad intercettare la risposta HTTP ed applicare al volo la compressione (come farebbe un HttpModule) sfruttando le classi del framework <a href="http://msdn.microsoft.com/it-it/library/system.io.compression.gzipstream.aspx">GZipStream</a> e <a href="http://msdn.microsoft.com/it-it/library/system.io.compression.deflatestream.aspx">DeflateStream</a>. Ecco un esempio di action filter:</p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font color="#0000ff" face="Consolas">public</font></span><span lang="EN-US" style="font-family: ;"><font face="Consolas"> <span><font color="#0000ff">class</font></span> <span><font color="#2b91af">CompressFilterAttribute</font></span> : <span><font color="#2b91af">ActionFilterAttribute</font></span> <br /> { <br />     <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">void</font></span> OnResultExecuted(<span><font color="#2b91af">ResultExecutedContext</font></span> filterContext) <br />     { <br />        <span><font color="#2b91af">HttpRequestBase</font></span> request = filterContext.HttpContext.Request; <br />        <span><font color="#0000ff">string</font></span> acceptEncoding = request.Headers[<span><font color="#a31515">"Accept-Encoding"</font></span>]; <br />              <br />        <span><font color="#0000ff">if</font></span> (<span><font color="#0000ff">string</font></span>.IsNullOrEmpty(acceptEncoding)) <span><font color="#0000ff">return</font></span>; <br /> <span> </span> <br />        acceptEncoding = acceptEncoding.ToLowerInvariant(); <br /> <br />        <span><font color="#2b91af">HttpResponseBase</font></span> response = filterContext.HttpContext.Response; <br /> <span> </span> <br />        <span><font color="#0000ff">if</font></span> (acceptEncoding.Contains(<span><font color="#a31515">"deflate"</font></span>)) <span><font color="#008000">// Priority to DEFLATE compression schema</font></span> <br />        { <br />          response.AppendHeader(<span><font color="#a31515">"Content-Encoding"</font></span>, <span><font color="#a31515">"deflate"</font></span>); <br />          response.Filter = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">DeflateStream</font></span>(response.Filter, <span><font color="#2b91af">CompressionMode</font></span>.Compress); <br />        } <br />        <span><font color="#0000ff">else</font></span> <span><font color="#0000ff">if</font></span> (acceptEncoding.Contains(<span><font color="#a31515">"gzip"</font></span>)) <br />        { <br />           response.AppendHeader(<span><font color="#a31515">"Content-Encoding"</font></span>, <span><font color="#a31515">"gzip"</font></span>); <br />           response.Filter = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">GZipStream</font></span>(response.Filter, <span><font color="#2b91af">CompressionMode</font></span>.Compress); <br />        } <br />     } <br /> }</font></span></p> <p> </p> <p><strong><font size="3">Minimizzazione e combinazione di risorse JavaScript e CSS <br /> </font></strong> <br /> Un’altra importante tip per ottimizzare il traffico HTTP tra client e server è data dalla minimizzazione e la combinazione delle risorse esterne ad una pagina Web, in modo da ridurre massicciamente il numero complessivo di richieste HTTP generate dal browser. Esiste un progetto molto interessante su CodePlex che affronta questa problematica sia per applicazioni ASP.NET Web Forms che per applicazioni ASP.NET MVC. Si tratta di <a href="http://combres.codeplex.com/"><strong>Combres</strong></a>, una libreria .NET che permette di organizzare JavaScript e CSS in diversi insiemi, ad ognuno dei quali viene associata una sezione di configurazione nel web.config. Le risorse specificate in ciascun insieme vengono minimizzate, combinate, compresse e messe in cache in modo da poter essere trasmesse in un singolo round-trip HTTP. Per informazioni dettagliate sull’utilizzo di questa libreria rimando al completo <a href="http://www.codeproject.com/KB/aspnet/combres2.aspx">articolo su CodeProject</a>.</p> <p> <br /> <strong><font size="3">Minimizzazione del markup HTML <br /> </font></strong> <br /> Anche la rimozione degli spazi bianchi che si frappongono tra i tag all’interno di un documento HTML può ridurre il tempo di caricamento di una pagina Web, poiché ne riduce le dimensioni nonché ne favorisce il parsing da parte del browser. Anche questa funzionalità può essere ottenuta tramite un modulo HTTP, come <a href="http://madskristensen.net/post/A-whitespace-removal-HTTP-module-for-ASPNET-20.aspx">questo</a> di Mads Kristensen. In ASP.NET MVC inoltre si può pensare di realizzare un action filter. Molto semplicemente:</p> <pre style="margin: 0cm 0cm 0pt;"><span style="font-family: ;"><font face="Consolas"><span><font color="#0000ff">public</font></span> <span><font color="#0000ff">class</font></span> <span><font color="#2b91af">HtmlWhitespaceFilter</font></span> : <span><font color="#2b91af">ActionFilterAttribute<br /></font></span>{<br /><span><font color="#0000ff"> public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">void</font></span> OnResultExecuted(<span><font color="#2b91af">ResultExecutedContext</font></span> filterContext)<br /> {<br /><span><font color="#2b91af"> HttpResponseBase</font></span> response = filterContext.HttpContext.Response;            <span><font color="#0000ff"> </font></span>     <br /> <span><font color="#0000ff">string</font></span> contentType = response.ContentType.Trim().ToLowerInvariant();                 <br /> <span><font color="#0000ff">if</font></span> (contentType == <span><font color="#a31515">"text/html"</font></span> || contentType == <span><font color="#a31515">"application/xhtml+xml"</font></span> ||<br /> contentType == <span><font color="#a31515">"text/xml"</font></span> || contentType == <span><font color="#a31515">"application/xml"</font></span>)<br /> {                <br /> response.Filter = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">HtmlWhitespaceFilterStream</font></span>(response.Filter);<br /> }           <br /> }<br />}</font></span></pre> <p> </p> <p>Riporto per completezza l’implementazione della classe <em>HtmlWhitespaceFilterStream</em>, che internamente utilizza delle banali regular expression per eliminare gli spazi vuoti tra i vari tag del documento (X)HTML.</p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font color="#0000ff" face="Consolas">public</font></span><span lang="EN-US" style="font-family: ;"><font face="Consolas"> <span><font color="#0000ff">class</font></span> <span><font color="#2b91af">HtmlWhitespaceFilterStream</font></span> : <span><font color="#2b91af">Stream</font></span> <br /> { <br /> <span><font color="#0000ff"><font color="#000000">  </font>private</font></span> <span><font color="#2b91af">Stream</font></span> _stream; <br />                        <span><font color="#0000ff">        </font></span> <br />   <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">bool</font></span> CanRead { <span><font color="#0000ff">get</font></span> { <span><font color="#0000ff">return</font></span> <span><font color="#0000ff">true</font></span>; } } <br />   <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">bool</font></span> CanSeek { <span><font color="#0000ff">get</font></span> { <span><font color="#0000ff">return</font></span> <span><font color="#0000ff">true</font></span>; } } <br /> <span>  </span><span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">bool</font></span> CanWrite { <span><font color="#0000ff">get</font></span> { <span><font color="#0000ff">return</font></span> <span><font color="#0000ff">true</font></span>; } } <br /> <span>  </span><span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">void</font></span> Flush() { _stream.Flush(); } <br /> <span>  </span><span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">long</font></span> Length { <span><font color="#0000ff">get</font></span> { <span><font color="#0000ff">return</font></span> _stream.Length; } }<span> </span> <br /> <span><font color="#0000ff">  public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">long</font></span> Position <br />   { <br />     <span><font color="#0000ff">get</font></span> { <span><font color="#0000ff">return</font></span> _stream.Position; } <br />     <span><font color="#0000ff">set</font></span> { _stream.Position = <span><font color="#0000ff">value</font></span>; } <br />   } <br /> <span> </span> <br /> <span>  </span><span><font color="#0000ff">public</font></span> HtmlWhitespaceFilterStream(<span><font color="#2b91af">Stream</font></span> stream) { _stream = stream; } <br /> <span> </span> <br /> <span><font color="#0000ff"><font color="#000000">  </font>public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">int</font></span> Read(<span><font color="#0000ff">byte</font></span>[] buffer, <span><font color="#0000ff">int</font></span> offset, <span><font color="#0000ff">int</font></span> count) </font></span><span lang="EN-US" style="font-family: ;"><font face="Consolas">{ <span><font color="#0000ff">return</font></span> _stream.Read(buffer, offset, count); } <br /> <span>  </span><span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">long</font></span> Seek(<span><font color="#0000ff">long</font></span> offset, <span><font color="#2b91af">SeekOrigin</font></span> origin) { <span><font color="#0000ff">return</font></span> _stream.Seek(offset, origin); } <br /> <span>  </span><span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">void</font></span> SetLength(<span><font color="#0000ff">long</font></span> value) { _stream.SetLength(value); } <br /> <span>  </span><span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">void</font></span> Close() { _stream.Close(); } <br /> <span>  <br />   </span><span><font color="#0000ff">public</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">void</font></span> Write(<span><font color="#0000ff">byte</font></span>[] buffer, <span><font color="#0000ff">int</font></span> offset, <span><font color="#0000ff">int</font></span> count) <br />   {            <br />     <span><font color="#0000ff">string</font></span> html = System.Text.<span><font color="#2b91af">Encoding</font></span>.UTF8.GetString(buffer); <br /> <strong>    </strong></font></span><span style="font-family: ;"><font face="Consolas"><strong>html = <span><font color="#2b91af">Regex</font></span>.Replace(html, <span><font color="#a31515">@"\s+&lt;"</font></span>, <span><font color="#a31515">"&lt;"</font></span>, <span><font color="#2b91af">RegexOptions</font></span>.Singleline); <br />     html = <span><font color="#2b91af">Regex</font></span>.Replace(html, <span><font color="#a31515">@"&gt;\s+"</font></span>, <span><font color="#a31515">"&gt;"</font></span>, <span><font color="#2b91af">RegexOptions</font></span>.Singleline);</strong></font></span><span lang="EN-US" style="font-family: ;"><font face="Consolas"> <br />     <span><font color="#0000ff">byte</font></span>[] outdata = System.Text.<span><font color="#2b91af">Encoding</font></span>.UTF8.GetBytes(html); <br />     _stream.Write(outdata, 0, outdata.Length); <br />   } <br /> } </font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Consolas">            </font></span></p> <p><font size="3"><strong>Ottimizzazione della generazione degli URL</strong></font></p> <p>Un aspetto che può intaccare le performance di applicazioni ASP.NET MVC stressate da migliaia richieste al minuto è il modo in cui vengono generati gli URL virtuali nelle View tramite gli helper <em>Html.ActionLink()</em>, <em>Html.RouteLink()</em>, <em>Url.Action()</em> e <em>Url.RouteUrl()</em>. In <a href="http://www.chadmoran.com/blog/2009/4/21/optimizing-url-generation-in-aspnet-mvc-part-1.html">questo post</a> di Chad Moran viene mostrato in dettaglio come un performance test sulla generazione massiva di URL ci dovrebbe spingere verso la rinuncia delle comodità che ci vengono offerte dalle lambda expression e dagli anonymous object. <br /> Sostanzialmente, per beneficiare di performance migliori, dovremmo preferire una sintassi di questo tipo…</p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Consolas">Html.ActionLink(<span><font color="#a31515">"Link"</font></span>, <span><font color="#a31515">"Index"</font></span>, <span><font color="#a31515">"Home"</font></span>, <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">RouteValueDictionary</font></span> { { <span><font color="#a31515">"name"</font></span>, <span><font color="#a31515">"Mario"</font></span> }, { <span><font color="#a31515">"age"</font></span>, 56 } })</font></span></p> <p> <br /> …ad una di questo tipo…</p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Consolas">Html.ActionLink(<span><font color="#a31515">"Link"</font></span>, <span><font color="#a31515">"Index"</font></span>, <span><font color="#a31515">"Home"</font></span>, <span><font color="#0000ff">new</font></span> { name = <span><font color="#a31515">"Mario"</font></span>, age = 56 })</font></span></p> <p> <br /> …o ancora peggio (per le performance) di questo tipo … <br /> <br /> <span lang="EN-US" style="line-height: 12pt; font-family: ;"><font face="Consolas">Html.ActionLink&lt;HomeController&gt;(c =&gt; c.Index(<span><font color="#a31515">"Mario"</font></span>,56), <span><font color="#a31515">"Link"</font></span>)</font></span> <br /> <br /> Come è facile intuire, i tempi di compilazione delle lambda expression e di reflection sugli anonymous objects introducono a run-time delle latenze che rallentano sensibilmente la generazione delle URL (fino ad un ordine di grandezza!) rispetto all’esecuzione su creazioni dirette di <span><font color="#2b91af"><a href="http://msdn.microsoft.com/en-us/library/system.web.routing.routevaluedictionary.aspx">RouteValueDictionary</a></font></span>. Contrariamente, dal punto di vista dello sviluppo, proprio l’ultimo metodo è preferibile per il type checking a compile-time. </p> <p> <br /> <font size="3"><strong>Note di configurazione</strong></font></p> <p>In quest’ultima parte del post vorrei semplicemente ricordare delle semplici accortezze che però sono fondamentali per rendere più performante un’ applicazione ASP.NET.</p> <ul> <li>Impostare <font face="Calibri"><span class="code-keyword1"><span style="line-height: 13pt;"><font color="#0000ff"><font style="font-size: 11pt;">&lt;</font></font></span></span><font style="font-size: 11pt;"><span class="code-leadattribute1"><span style="line-height: 13pt;"><font color="#800000">compilation</font></span></span></font><span style="line-height: 13pt;"><font style="font-size: 11pt;"> <span class="code-attribute1"><font color="#ff0000">debug</font></span><span class="code-keyword1"><font color="#0000ff">="false"</font></span> </font><span class="code-keyword1"><font color="#0000ff" style="font-size: 11pt;">/&gt;</font></span></span></font> nel web.config della nostra applicazione nell’ambiente di produzione, in modo tale che il codice venga eseguito più velocemente. <br /> </li> <li>Rimuovere i moduli HTTP che non sono utilizzati. Nel machine.config o nel root-level web.config del framework troviamo una <a href="http://msdn.microsoft.com/en-us/library/9b9dh535.aspx">configurazione di default</a> che specifica il caricamento di diversi moduli che invece potremmo non usare nella nostra applicazione. Nel web.config possiamo quindi esplicitare l’esclusione dei moduli di cui non abbiamo bisogno dalla pipeline di ASP.NET (sia classica che integrata), facendo attenzione a non rompere le dipendenze tra i vari moduli. <br /> <br /> (Pipeline classica) <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><font face="Consolas"><span><font color="#0000ff">&lt;</font></span><span><font color="#a31515">httpModules</font></span><span><font color="#0000ff">&gt;</font></span></font><span> <br /> <font face="Consolas"><span><font color="#0000ff">      &lt;</font></span><span><font color="#a31515">remove</font></span><span><font color="#0000ff"> </font></span><span><font color="#ff0000">name</font></span><span><font color="#0000ff">=</font></span>"<span><font color="#0000ff">Session</font></span>"</font><font face="Consolas"><font color="#0000ff"><span>/&gt;</span> <br /> <span>      &lt;</span></font><span><font color="#a31515">remove</font></span><span><font color="#0000ff"> </font></span><span><font color="#ff0000">name</font></span><span><font color="#0000ff">=</font></span>"<span><font color="#0000ff">WindowsAuthentication</font></span>"</font><font face="Consolas"><font color="#0000ff"><span> /&gt; <br /> <font face="Consolas"><span><font color="#0000ff">      &lt;</font></span><span><font color="#a31515">remove</font></span><span><font color="#0000ff"> </font></span><span><font color="#ff0000">name</font></span><span><font color="#0000ff">=</font></span>"<span><font color="#0000ff">PassportAuthentication</font></span>"</font><font face="Consolas"><font color="#0000ff"><span>/&gt;</span></font></font> <br />       ...      </span><span>            </span> <br /> <span>&lt;/</span></font><span><font color="#a31515">httpModules</font></span><span><font color="#0000ff">&gt; <br /> <br /> </font></span></font></span>(Per la pipeline integrata il discorso è analogo. Basta posizionarsi nella sezione <em>system.webServer\modules)</em>.</p> </li> </ul> <p>HTH</p> <div class="wlWriterEditableSmartContent" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:00aaae71-a891-4c99-9123-52341933fea6" style="margin: 0px; padding: 0px; float: none; display: inline;">Tag di Technorati: <a rel="tag" href="http://technorati.com/tags/MVC">MVC</a>,<a rel="tag" href="http://technorati.com/tags/ASP.NET">ASP.NET</a>,<a rel="tag" href="http://technorati.com/tags/Performance">Performance</a></div> </span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/99616.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2011/01/11/migliorare-le-performance-di-unapplicazione-asp.net-mvc.aspx Tue, 11 Jan 2011 21:31:54 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2011/01/11/migliorare-le-performance-di-unapplicazione-asp.net-mvc.aspx#feedback 4 http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/99616.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/99616.aspx [WPF 4.0] Esportare dati dal controllo DataGrid WPF http://blogs.ugidotnet.org/dsantarelli/archive/2010/11/16/wpf-4.0-esportare-dati-dal-controllo-datagrid.aspx <div> <div> Il controllo DataGrid di WPF 4.0 permette di ottenere “gratis” l’esportazione dei dati associati alla griglia in diversi formati tra cui plain text, CSV e HTML. Tale funzionalità è esposta semplicemente tramite l’invocazione del comando <a href="http://msdn.microsoft.com/en-us/library/system.windows.input.applicationcommands.copy.aspx"> Copy</a> sul controllo, che trasferisce nella Clipboard i dati contenuti nelle celle selezionate, supportando i formati sopra citati (ovviamente i formati CSV e HTML sono i più interessanti nell’ottica dell’esportazione verso altre applicazioni). Nel seguente esempio viene mostrato come generare un file CSV contenente tutte le righe visualizzate in un DataGrid, includendo le intestazioni di colonna sulla prima riga.Il codice è veramente banale:<br /> <br /> </div> </div> <div class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"> <span style="font-family: consolas;">myDataGrid.SelectAllCells(); <br /> myDataGrid.ClipboardCopyMode = <span style="color: rgb(43, 145, 175);">DataGridClipboardCopyMode</span>.IncludeHeader; <br /> <span style="color: rgb(43, 145, 175);">ApplicationCommands</span>.Copy.Execute(<span style="color: rgb(0, 0, 255);">null</span>, myDataGrid); <br /> myDataGrid.UnselectAllCells(); <br /> <span style="color: rgb(0, 0, 255);">string</span> csvData = (<span style="color: rgb(0, 0, 255);">string</span>)<span style="color: rgb(43, 145, 175);">Clipboard</span>.GetData(<span style="color: rgb(43, 145, 175);">DataFormats</span>.CommaSeparatedValue); <br /> <span style="color: rgb(43, 145, 175);">Clipboard</span>.Clear(); <br /> <span style="color: rgb(0, 0, 255);">using</span> (<span style="color: rgb(43, 145, 175);">StreamWriter</span> writer = <span style="color: rgb(0, 0, 255);">new</span> <span style="color: rgb(43, 145, 175);"> StreamWriter</span>(<span style="color: rgb(163, 21, 21);">"C:\\test.csv"</span>)) { writer.Write(csvData); }</span></div> <span> <div> <br /> Se invece si sceglie l’HTML come formato di esportazione, ci dobbiamo attendere un simpatico header aggiuntivo collocato fuori dal DOM che indica dove iniziano e dove finiscono le differenti porzioni di dati. Si possono utilizzare tali informazioni per esportare sia una pagina web intera piuttosto che soltanto il frammento relativo alla tabella dati da inserire eventualmente in un’altro documento. In questo caso l’esempio precedente va semplicemente modificato specificando <a href="http://msdn.microsoft.com/en-us/library/system.windows.dataformats.html.aspx"> DataFormats.Html</a> come formato di estrazione dati dalla clipboard. Il risultato che otteniamo è il seguente:</div> <div><br /> </div> <span style="font-family: consolas;"><span style="font-family: consolas;"><span style="font-family: consolas;"> <span style="font-family: consolas;"> <div class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"> <span style="font-family: consolas;">Version:1.0</span></div> <div class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"> <span style="font-family: consolas;">StartHTML:00000097</span></div> <div class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"> <span style="font-family: consolas;">EndHTML:00000672</span></div> <div class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"> <span style="font-family: consolas;">StartFragment:00000133</span></div> <div class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"> <span style="font-family: consolas;">EndFragment:00000636</span></div> <div class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"> <span style="font-family: consolas;"><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(163, 21, 21);">HTML</span><span style="color: rgb(0, 0, 255);">&gt;</span></span></div> <div class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"> <span style="font-family: consolas;"><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(163, 21, 21);">BODY</span><span style="color: rgb(0, 0, 255);">&gt;</span></span></div> <div class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"> <div> <span style="font-family: consolas;"><span style="color: rgb(0, 128, 0);">&lt;!--StartFragment--&gt;</span></span></div> <div> <span style="font-family: consolas;"><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(163, 21, 21);">TABLE</span><span style="color: rgb(0, 0, 255);">&gt;</span></span><span style="font-family: consolas;"><span style="font-family: consolas;"><span style="color: rgb(0, 0, 255);">...Dati...</span></span><span style="font-family: consolas;"><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(163, 21, 21);">TABLE</span><span style="color: rgb(0, 0, 255);">&gt;</span></span><br /> </span><span style="font-family: consolas;"><span style="color: rgb(0, 128, 0); font-family: consolas;">&lt;!--EndFragment--&gt;<br /> </span><span style="font-family: consolas;"><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(163, 21, 21);">BODY</span><span style="color: rgb(0, 0, 255);">&gt;</span></span></span></div> </div> <div class="MsoNormal" style="margin: 0cm 0cm 10pt; line-height: 13pt;"> <span style="font-family: consolas;"><span style="line-height: 12pt;"><span style="color: rgb(0, 0, 255);"> &lt;/</span></span><span style="line-height: 12pt;"><span style="color: rgb(163, 21, 21);">HTML</span></span><span style="line-height: 12pt;"><span style="color: rgb(0, 0, 255);">&gt;</span></span></span></div> </span></span></span></span></span> <br /> <div class="wlWriterEditableSmartContent" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:06882eda-9e32-4547-a6db-fd3b23ac7c14" style="margin: 0px; padding: 0px; float: none; display: inline;"> Tag di Technorati: <a rel="tag" href="http://technorati.com/tags/WPF">WPF</a>,<a rel="tag" href="http://technorati.com/tags/DataGrid">DataGrid</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/99486.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2010/11/16/wpf-4.0-esportare-dati-dal-controllo-datagrid.aspx Tue, 16 Nov 2010 00:40:04 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2010/11/16/wpf-4.0-esportare-dati-dal-controllo-datagrid.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/99486.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/99486.aspx [ASP.NET MVC 2] Controller asincroni ASP.NET MVC http://blogs.ugidotnet.org/dsantarelli/archive/2010/10/12/asp.net-mvc-2-controller-asincroni.aspx <p>Una feature molto importante del framework ASP.NET MVC ha a che vedere con la capacità di gestire operazioni asincrone a livello di controller. Di fatto il movente è lo stesso che si trova dietro le <a target="_blank" href="http://msdn.microsoft.com/en-us/magazine/cc163725.aspx">Asynchronous Pages in ASP.NET 2.0</a>, ovvero evitare che si verifichino condizioni di thread starvation nella nostra Web Application per poi vedersi ritornare un simpatico status code 503 (Server too busy). Come sappiamo, quando una richiesta viene ricevuta dal Web Server, il processamento viene affidato ad un thread del threadpool dell’applicazione. In uno scenario sincrono, tale thread rimane in vita fintantoché non vengono effettuate tutte le operazioni previste. <br /> Quindi, potremmo dire a grandi linee che la pipeline asincrona è preferibile in scenari in cui la logica prevede colli di bottiglia che si formano in corrispondenza di operazioni bloccanti, a causa di latenze non legate strettamente al tempo di CPU (es. chiamate ad un WS). <br /> Partendo dal presupposto che una richiesta asincrona possiede lo stesso tempo di esecuzione di una richiesta sincrona, minimizzare il numero di thread in attesa di operazioni bloccanti è una pratica molto apprezzata dal Web Server quando una Web Application viene letteralmente bombardata da centinaia di richieste concorrenti. <br /> <br /> Vediamo ora un esempio molto banale di controller asincorno, in cui l’action <em>List</em> è stata creata secondo il pattern asincrono per gestire chiamate “lunghe” ad un Web Service:</p> <font face="Courier New"> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><font face="Courier New"><span lang="EN-US" style="font-family: courier new;"><font color="#0000ff"><font style="font-size: 8pt;">public</font></font></span><span lang="EN-US" style="font-family: courier new;"><font style="font-size: 8pt;"> <span><font color="#0000ff">class</font></span> <span><font color="#2b91af">CustomersController</font></span> : </font><span><font color="#2b91af" style="font-size: 8pt;">AsyncController</font></span></span></font></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><font style="font-size: 8pt;">{</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">   </font></span></font></span><span lang="EN-US" style="font-family: ;"><font face="Courier New"><font style="font-size: 8pt;">[<span><font color="#2b91af">AsyncTimeout</font></span>(10000)]</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">   </font></span><font style="font-size: 8pt;"><span><font color="#0000ff">public</font></span> <span><font color="#0000ff">void</font></span> ListAsync()</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">   </font></span><font style="font-size: 8pt;">{</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">     </font></span><font style="font-size: 8pt;">AsyncManager.OutstandingOperations.Increment();</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">     </font></span><font style="font-size: 8pt;"><span><font color="#2b91af">Task</font></span>.Factory.StartNew(() =&gt;</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">     </font></span><font style="font-size: 8pt;">{</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">       </font></span><font style="font-size: 8pt;"><span><font color="#0000ff">try</font></span> { AsyncManager.Parameters[<span><font color="#a31515">"result"</font></span>] = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">MyServiceClient</font></span>().GetCustomers(); }</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">       </font></span><font style="font-size: 8pt;"><span><font color="#0000ff">catch</font></span> (<span><font color="#2b91af">Exception</font></span> ex) { ... }</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">       </font></span><font style="font-size: 8pt;"><span><font color="#0000ff">finally</font></span> { AsyncManager.OutstandingOperations.Decrement(); }</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">     </font></span><font style="font-size: 8pt;">);</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">   </font></span><font style="font-size: 8pt;">}</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><font style="font-size: 8pt;"> </font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">   </font></span><font style="font-size: 8pt;"><span><font color="#0000ff">public</font></span> <span><font color="#2b91af">ActionResult</font></span> ListCompleted(<span><font color="#2b91af">List</font></span>&lt;<span><font color="#2b91af">Customer</font></span>&gt; result)</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">   </font></span><font style="font-size: 8pt;">{</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">     </font></span><font style="font-size: 8pt;"><span><font color="#0000ff">return</font></span> View(<span><font color="#a31515">"List"</font></span>, result);</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">   </font></span><font style="font-size: 8pt;">} <br /> <br />    ...</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><span><font face="Courier New"><font style="font-size: 8pt;">       </font></font></span></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">   </font></span><font style="font-size: 8pt;"><span><font color="#0000ff">protected</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">void</font></span> OnException(<span><font color="#2b91af">ExceptionContext</font></span> filterContext)</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">   </font></span><font style="font-size: 8pt;">{</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">     </font></span><font style="font-size: 8pt;"><span><font color="#0000ff">if</font></span> (filterContext.Exception <span><font color="#0000ff">is</font></span> <span><font color="#2b91af">TimeoutException</font></span>)</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">     </font></span><font style="font-size: 8pt;">{</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">       </font></span><font style="font-size: 8pt;">filterContext.Result = RedirectToAction(<span><font color="#a31515">"TryAgainLater"</font></span>);</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">       </font></span><font style="font-size: 8pt;">filterContext.ExceptionHandled = <span><font color="#0000ff">true</font></span>;</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">     </font></span><font style="font-size: 8pt;">}</font><span><font style="font-size: 8pt;">            </font></span></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">     </font></span><font style="font-size: 8pt;"><span><font color="#0000ff">base</font></span>.OnException(filterContext);</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><span lang="EN-US" style="font-family: ;"><font face="Courier New"><span><font style="font-size: 8pt;">   </font></span><font style="font-size: 8pt;">}</font></font></span><span lang="EN-US" style="font-family: ;"><span><font face="Courier New"><font style="font-size: 8pt;">    </font></font></span></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: normal;"><font face="Courier New"><span style="font-family: ;"><font style="font-size: 8pt;">}</font></span></font></p> </font> <br /> <p>Alcune importanti osservazioni:</p> <ul> <li>Un controller asincrono deve ereditare la classe <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.web.mvc.asynccontroller.aspx">AsyncController</a>. (Gli action methods sincroni sono comunque supportati) </li> <li>Il nome del metodo del controller che scatena l’operazione asincrona deve essere seguito dal suffisso “<em>Async</em>”. </li> <li>Il nome del metodo del controller che viene invocato dal framework quando l’operazione asincrona viene completata deve essere seguito dal suffisso “<em>Completed</em>” </li> <li>Sebbene il pattern asincrono consiste di due metodi, l’invocazione da browser segue la stessa route che si utilizzerebbe per le action sincrone ( …/Customers/List ) </li> <li>La proprietà <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.web.mvc.async.asyncmanager.parameters.aspx">AsyncManager.Parameters</a> è un <em>IDictionary&lt;string,object&gt;</em> che permette di definire i parametri che vengono passati al metodo xxxCompleted, evitando allo sviluppatore l’onere di definire delle variabili a livello di classe che siano visibili da entrambi i metodi <em>xxxAsync</em> e <em>xxxCompleted</em>. Occorre inoltre porre attenzione al nome che si assegna alle key dei parametri. Essi devono infatti corrispondere a quelli dichiarati dal metodo <em>xxxCompleted.</em> </li> <li>I metodi <em>AsyncManager.OutstandingOperations.Increment(…)</em> e <em>AsyncManager.OutstandingOperations.Decrement()</em> sono necessari rispettivamente per determinare il numero di operazioni asincrone che il controller deve ancora completare e che ha completato. Attenzione a non ometterli ed a gestirli in modo consistente!!! </li> <li>L’attributo <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.web.mvc.asynctimeoutattribute.aspx">AsyncTimeout</a> specificato a livello di metodo <em>xxxAsync</em> è utile per definire un timeout per le operazioni a lunga latenza (default: 45 secondi). Se lo si vuole eliminare si può utilizzare il filtro [<a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.web.mvc.noasynctimeoutattribute.aspx">NoAsyncTimeout</a>] oppure l’equivalente <em>[AsyncTimeout(Timeout.Infinite)]</em>. Se invece lo si vuole gestire per fornire un feedback diverso rispetto al global exception handler di ASP.NET, è possibile svilupparsi un exception filter personalizzato oppure semplicemente fare l’override del metodo <em>OnException(…)</em> del controller, in modo da gestire l’eccezione in modo custom (come in questo caso). </li> </ul> <p><strong>Non è tutto oro ciò che luccica…</strong></p> <ul> <li>I controller asincroni sono significativamente più complessi rispetto a quelli sincroni, oltre ad essere difficilmente testabili. </li> <li>Non ha alcun senso utilizzare controller asincroni se si vogliono semplicemente eseguire dei task paralleli legati alle prestazioni della CPU. </li> <li>Occorre assicurarsi che il nostro Web Server sia opportunamente configurato per beneficiare della gestione asincrona delle richieste web: ad esempio, se usiamo IIS7 dobbiamo configurare correttamente il valore della proprietà <em>MaxConcurrentRequestsPerCPU</em> nella sezione <a target="_blank" href="http://msdn.microsoft.com/it-it/library/dd560842.aspx">&lt;applicationPool&gt;</a> di &lt;system.web&gt;, impostandolo ad un valore sufficientemente alto. </li> </ul> <div class="wlWriterEditableSmartContent" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:4235d7fc-f7c1-4d7f-9276-86199baa1102" style="margin: 0px; padding: 0px; float: none; display: inline;">Tag di Technorati: <a rel="tag" href="http://technorati.com/tags/AsyncController">AsyncController</a>,<a rel="tag" href="http://technorati.com/tags/ASP.NET">ASP.NET</a>,<a rel="tag" href="http://technorati.com/tags/MVC">MVC</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/99343.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2010/10/12/asp.net-mvc-2-controller-asincroni.aspx Tue, 12 Oct 2010 21:00:57 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2010/10/12/asp.net-mvc-2-controller-asincroni.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/99343.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/99343.aspx Riflessioni sulle basi culturali dei metodi agili http://blogs.ugidotnet.org/dsantarelli/archive/2010/09/10/riflessioni-sulle-basi-culturali-dei-metodi-agili.aspx Non posso non condividere questo interessante <a target="_blank" href="http://blog.lupi-software.com/2010/09/08/csma-2010-le-basi-culturali-dei-metodi-agili/">post</a> di <a target="_blank" href="http://blog.lupi-software.com/">Roberto Lupi</a> circa le basi culturali che si trovano a fondamento dei metodi agili...<br /> Buona lettura :)<img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/99186.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2010/09/10/riflessioni-sulle-basi-culturali-dei-metodi-agili.aspx Fri, 10 Sep 2010 15:15:43 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2010/09/10/riflessioni-sulle-basi-culturali-dei-metodi-agili.aspx#feedback 1 http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/99186.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/99186.aspx [WPF] TextBox e input filtering WPF http://blogs.ugidotnet.org/dsantarelli/archive/2010/09/07/wpf-textbox-e-input-filtering.aspx <p>Quando si realizza un’interfaccia grafica per l’inserimento dati, è sempre buona prassi prevedere una prima "validazione" dell'input utente. Una tecnica molto semplice per raggiungere tale obiettivo potrebbe basarsi sull’idea di filtrare l’input in modo da non permettere l’inserimento di caratteri non validi. Nel caso della TextBox, in particolare, si potrebbe pensare di estendere il controllo affinché il filtraggio avvenga a fronte di un matching con un generica espressione regolare… una RegexTextBox dunque :).</p> <pre style="MARGIN: auto"><font face="Consolas"><span><font color="#0000ff">public</font></span> <span><font color="#0000ff">class</font></span> <span><font color="#2b91af">RegexTextBox</font></span> : System.Windows.Controls.<span><font color="#2b91af">TextBox</font></span></font><br /><font face="Consolas">{                </font><br /><font face="Consolas">  <span><font color="#0000ff">public</font></span> <span><font color="#0000ff">string</font></span> Pattern { <span><font color="#0000ff">get</font></span>; <span><font color="#0000ff">set</font></span>; }</font><br /><font face="Consolas"> </font><br /><font face="Consolas">  <span><font color="#0000ff">public</font></span> RegexTextBox()</font><br /><font face="Consolas">  {</font><br /><font face="Consolas">     <span><font color="#2b91af">CommandBinding</font></span> pasteCommandBinding = <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">CommandBinding</font></span>(<span><font color="#2b91af">ApplicationCommands</font></span>.Paste);</font><br /><font face="Consolas">     pasteCommandBinding.CanExecute += <span><font color="#0000ff">delegate</font></span>(<span><font color="#0000ff">object</font></span> sender, <span><font color="#2b91af">CanExecuteRoutedEventArgs</font></span> e) </font><br /><font face="Consolas">     { </font><br /><font face="Consolas">       e.Handled = !IsValid(<span><font color="#2b91af">Clipboard</font></span>.GetText()); </font><br /><font face="Consolas">     };</font><br /><font face="Consolas">     <span><font color="#0000ff">this</font></span>.CommandBindings.Add(pasteCommandBinding);</font><br /><font face="Consolas">  }</font><br /><font face="Consolas">        </font><br /><font face="Consolas">  <span><font color="#0000ff">protected</font></span> <span><font color="#0000ff">override</font></span> <span><font color="#0000ff">void</font></span> OnPreviewTextInput(System.Windows.Input.<span><font color="#2b91af">TextCompositionEventArgs</font></span> e)</font><br /><font face="Consolas">  {                        </font><br /><font face="Consolas">     <span><font color="#0000ff">string</font></span> newText = Text + e.Text;</font><br /><font face="Consolas">     e.Handled = !IsValid(newText);            </font><br /><font face="Consolas">     <span><font color="#0000ff">base</font></span>.OnPreviewTextInput(e);</font><br /><font face="Consolas">  }</font><br /><font face="Consolas"> </font><br /><font face="Consolas">  <span><font color="#0000ff">private</font></span> <span><font color="#0000ff">bool</font></span> IsValid(<span><font color="#0000ff">string</font></span> text)</font><br /><font face="Consolas">  {</font><br /><font face="Consolas">     <span><font color="#0000ff">if</font></span> (<span><font color="#0000ff">string</font></span>.IsNullOrEmpty(text)) <span><font color="#0000ff">return</font></span> <span><font color="#0000ff">true</font></span>;</font><br /><font face="Consolas">     <span><font color="#0000ff">return</font></span> <span><font color="#0000ff">new</font></span> <span><font color="#2b91af">Regex</font></span>(Pattern).IsMatch(text);</font><br /><font face="Consolas">  }</font><br /><font face="Consolas">}</font></pre> <p><br /> Un esempio di utilizzo potrebbe essere il seguente (ammissione di soli numeri naturali):</p> <pre style="MARGIN: auto"><font face="Consolas"><span><font color="#0000ff">&lt;</font></span><span><font color="#a31515">src</font></span><span><font color="#0000ff">:</font></span><span><font color="#a31515">RegexTextBox</font></span><span><font color="#ff0000"> Pattern</font></span><span><font color="#0000ff">=</font></span><span><font color="#0000ff">"^[0-9]+$"</font></span><span><font color="#0000ff">&gt;&lt;/</font></span><span><font color="#a31515">src</font></span><span><font color="#0000ff">:</font></span><span><font color="#a31515">RegexTextBox</font></span><span><font color="#0000ff">&gt;</font></span></font><br /></pre> <p><br /> <br /> Nel codice sopra riportato viene mostrata anche un’ accortezza che può risultare utile, ovvero il filtraggio dell’input in caso di operazioni di Cut/Copy and Paste. Tale controllo avviene agganciando la nostra RegexTextBox all’application command <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.windows.input.applicationcommands.paste.aspx">ApplicationCommands.Paste</a>, in modo da poter validare il contenuto della clipboard.</p> <div style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; FLOAT: none; PADDING-TOP: 0px" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:437f1953-8400-4693-98e6-b79417991a0c" class="wlWriterEditableSmartContent">Technorati Tags: <a rel="tag" href="http://technorati.com/tags/WPF">WPF</a>,<a rel="tag" href="http://technorati.com/tags/TextBox">TextBox</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/99167.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2010/09/07/wpf-textbox-e-input-filtering.aspx Tue, 07 Sep 2010 22:13:49 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2010/09/07/wpf-textbox-e-input-filtering.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/99167.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/99167.aspx [WPF] Evidenziare testo in una RichTextBox WPF http://blogs.ugidotnet.org/dsantarelli/archive/2010/08/24/wpf-evidenziare-testo-in-una-richtextbox.aspx <p>Una funzionalità fondamentale per text-editor/reader è l’evidenziazione programmatica di testo, magari utilizzando stili di formattazione diversi. In questo post vorrei mostrare una possibile implementazione di tale funzionalità utilizzando il controllo RichTextBox di WPF. Immaginando un approccio top-down, potremmo partire da un metodo <em>HilightText(…),</em> il cui compito sia proprio quello di evidenziare del testo all’interno di una RichTextBox. </p> <div style="FONT-FAMILY: courier new; FONT-SIZE: 8pt"><span style="mso-ansi-language: en-us" lang="EN-US"><font color="#0000ff">public</font> </span><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#0000ff">void</font> </span>HilightText(<span><font color="#0000ff">string</font> </span>pattern) <br /> </span><span style="mso-ansi-language: en-us" lang="EN-US">{ <span style="mso-spacerun: yes">             <br /> </span></span><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af"><font color="#000000">  </font>List</font></span>&lt;<span><font color="#2b91af">TextRange</font></span>&gt; ranges = GetMatchingTextRanges(pattern); <br />   </span><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#0000ff">foreach </font></span>(<span><font color="#2b91af">TextRange</font> </span>textRange <span><font color="#0000ff">in</font> </span>ranges) HilightTextRange(textRange, <span><font color="#2b91af">Brushes</font></span>.Black, <span><font color="#2b91af">Brushes</font></span>.Yellow); <span style="mso-spacerun: yes">            <br /> </span></span><span>} <br /> <br /> </span></div> L’idea è quella di applicare un background giallo ed un foreground nero a tutti gli intervalli di testo (<a target="_blank" href="http://msdn.microsoft.com/it-it/library/system.windows.documents.textrange.aspx">TextRange</a>) che soddisfano un dato pattern-matching. <br /> <div style="FONT-FAMILY: courier new; FONT-SIZE: 8pt"><br /> <span style="mso-ansi-language: en-us" lang="EN-US"><font color="#0000ff">private</font> </span><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#0000ff">void</font> </span>HilightTextRange(<span><font color="#2b91af">TextRange</font> </span>textRange, <span><font color="#2b91af">Brush</font> </span>foregroundBrush, <span><font color="#2b91af">Brush</font> </span>backgroundBrush) <br /> </span><span style="mso-ansi-language: en-us" lang="EN-US">{ <br />   </span><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#0000ff">if </font></span>(foregroundBrush != <span><font color="#0000ff">null</font></span>) textRange.ApplyPropertyValue(<span><font color="#2b91af">TextElement</font></span>.ForegroundProperty, foregroundBrush); <br />   </span><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#0000ff">if </font></span>(backgroundBrush != <span><font color="#0000ff">null</font></span>) textRange.ApplyPropertyValue(<span><font color="#2b91af">TextElement</font></span>.BackgroundProperty, backgroundBrush); <br /> </span><span>} <br /> </span></div> <br /> Ora ci troviamo di fronte all’esigenza di determinare gli intervalli di testo coinvolti nel matching. Definiamo dunque un metodo <em>GetMatchingTextRanges(…)</em>, il cui compito è di navigare l’intero documento della RichTextBox (un <a target="_blank" href="http://msdn.microsoft.com/it-it/library/system.windows.documents.flowdocument.aspx">FlowDocument</a>) analizzando esclusivamente gli elementi (inlines) di tipo <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.windows.documents.run.aspx">Run</a>, ovvero quelli che contengono testo. <div style="FONT-FAMILY: courier new; FONT-SIZE: 8pt"><br /> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><font color="#0000ff">private</font> </span><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af">List</font></span>&lt;<span><font color="#2b91af">TextRange</font></span>&gt; GetMatchingTextRanges(<span><font color="#0000ff">string</font> </span>pattern) <br /> </span><span style="mso-ansi-language: en-us" lang="EN-US">{ </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af"><font color="#000000">   </font>List</font></span>&lt;<span><font color="#2b91af">TextRange</font></span>&gt; ranges = <span><font color="#0000ff">new</font> </span><span><font color="#2b91af">List</font></span>&lt;<span><font color="#2b91af">TextRange</font></span>&gt;(); </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US">  </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af"><font color="#000000">   </font>TextPointer </font></span>textNavigator = richTextBox.Document.ContentStart; </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span><font color="#0000ff"><font color="#000000">   </font>while </font></span><span>(textNavigator.CompareTo(richTextBox.Document.ContentEnd) &lt; 0) </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span><span style="mso-spacerun: yes">   </span></span><span style="mso-ansi-language: en-us" lang="EN-US">{ </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af"><font color="#000000">      </font>TextPointerContext </font></span>context = textNavigator.GetPointerContext(<span><font color="#2b91af">LogicalDirection</font></span>.Backward); </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#0000ff"><font color="#000000">      </font>if </font></span>(context == <span><font color="#2b91af">TextPointerContext</font></span>.ElementStart &amp;&amp; textNavigator.Parent <span><font color="#0000ff">is</font> </span><span><font color="#2b91af">Run</font></span>) </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US">      { </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af"><font color="#000000">        </font>List</font></span>&lt;<span><font color="#2b91af">TextRange</font></span>&gt; runRanges = GetMatchingTextRanges((<span><font color="#2b91af">Run</font></span>)textNavigator.Parent, pattern); </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">        </span><span><font color="#0000ff">if</font> </span>(runRanges.Count &gt; 0) ranges.AddRange(runRanges); </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">      </span>} <br /> </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US">      textNavigator = textNavigator.GetNextContextPosition(<span><font color="#2b91af">LogicalDirection</font></span>.Forward); </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">   </span></span><span>} </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span>  </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span><span style="mso-spacerun: yes">   </span><span><font color="#0000ff">return</font> </span>ranges; </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span>} <br /> <br /> </span></p> </div> A questo punto, per ogni Run trovato, è possibile controllare se esistono porzioni di testo in esso contenuti che soddisfano il pattern di ricerca. In caso positivo, possiamo aggiungerle alla lista dei TextRange da evidenziare. <br /> <div style="FONT-FAMILY: courier new; FONT-SIZE: 8pt"><br /> <span style="mso-ansi-language: en-us" lang="EN-US"><font color="#0000ff">private</font> </span><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af">List</font></span>&lt;<span><font color="#2b91af">TextRange</font></span>&gt; GetMatchingTextRanges(<span><font color="#2b91af">Run</font> </span>run, <span><font color="#0000ff">string</font> </span>pattern) </span> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US">{ <span style="mso-spacerun: yes">            </span></span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af"><font color="#000000">   </font>List</font></span>&lt;<span><font color="#2b91af">TextRange</font></span>&gt; ranges = <span><font color="#0000ff">new</font> </span><span><font color="#2b91af">List</font></span>&lt;<span><font color="#2b91af">TextRange</font></span>&gt;(); </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af"><font color="#000000">   </font>MatchCollection </font></span>matches = <span><font color="#2b91af">Regex</font></span>.Matches(run.Text, pattern); <span style="mso-spacerun: yes">            </span></span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US">  </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#0000ff"><font color="#000000">   </font>foreach </font></span>(<span><font color="#2b91af">Match</font> </span>match <span><font color="#0000ff">in</font> </span>matches) </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US">   { </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af"><font color="#000000">     </font>TextPointer </font></span>startPosition = GetContentTextPointer(run.ContentStart, match.Index - 1); </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af"><font color="#000000">     </font>TextPointer </font></span>endPosition = GetContentTextPointer(run.ContentStart, match.Index + match.Length - 1); </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US">     ranges.Add(<span><font color="#0000ff">new</font> </span><span><font color="#2b91af">TextRange</font></span>(startPosition, endPosition)); </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span>   } <br /> </span><span><span><font color="#0000ff"><font color="#000000">   </font>return </font></span>ranges; </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span>} <br /> <br /> </span></p> </div> Come si stabiliscono gli estremi dei TextRange da evidenziare all’interno di ciascun Run? Anzitutto, tramite la classe <a target="_blank" href="http://msdn.microsoft.com/it-it/library/system.text.regularexpressions.regex(VS.80).aspx">Regex</a> riusciamo a capire per ciascun match quali sono gli indici dei caratteri iniziali all’interno del testo del Run. Poi, ricordando che per calcolare l’offset corretto all’interno di ciascun Run non dobbiamo considerare i caratteri (un Run può contenere altri tipi di simboli) bensì i <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.windows.documents.textpointer.aspx">TextPointer</a> che sono adiacenti al testo di interesse, definiamo il seguente metodo: <div style="FONT-FAMILY: courier new; FONT-SIZE: 8pt"> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"> </p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><font color="#0000ff">private</font> </span><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af">TextPointer</font> </span>GetContentTextPointer(<span><font color="#2b91af">TextPointer</font> </span>contentStart, <span><font color="#0000ff">int</font> </span>offset) <br /> <span style="mso-ansi-language: en-us" lang="EN-US">{ <br /> </span><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#2b91af"><font color="#000000">  </font>TextPointer </font></span>result = contentStart; </span></span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#0000ff"><font color="#000000">  </font>int </font></span>i = 0; </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#0000ff"><font color="#000000">  </font>while </font></span>(i &lt; offset &amp;&amp; result != <span><font color="#0000ff">null</font></span>) </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US">  { </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#0000ff"><font color="#000000">    </font>if </font></span>(result.GetPointerContext(<span><font color="#2b91af">LogicalDirection</font></span>.Backward) == <span><font color="#2b91af">TextPointerContext</font></span>.Text || </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US">        result.GetPointerContext(<span><font color="#2b91af">LogicalDirection</font></span>.Backward) == <span><font color="#2b91af">TextPointerContext</font></span>.None) </span><span style="mso-ansi-language: en-us" lang="EN-US">i++; <br /> <span style="mso-ansi-language: en-us" lang="EN-US"><span><font color="#0000ff"><br />     if </font></span>(result.GetPositionAtOffset(1, <span><font color="#2b91af">LogicalDirection</font></span>.Forward) == <span><font color="#0000ff">null</font></span>) <span><font color="#0000ff">return</font> </span>result; </span></span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US">  </span></p> <p style="LINE-HEIGHT: normal; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="mso-ansi-language: en-us" lang="EN-US">    result = result.GetPositionAtOffset(1, <span><font color="#2b91af">LogicalDirection</font></span>.Forward); <br /> <span>  } <br /> <span><span><font color="#0000ff">  return</font> </span>result; <br /> </span><span style="LINE-HEIGHT: 9pt; mso-ansi-language: it; mso-fareast-font-family: calibri; mso-fareast-theme-font: minor-latin; mso-fareast-language: en-us; mso-bidi-language: ar-sa">} </span><br /> <br /> </span></span></p> </div> Finalmente il gioco è fatto. Di seguito riportiamo un esempio di output ottenuto invocando HilightText(“labor”); <br /> <br /> <img alt="" src="http://img839.imageshack.us/img839/6521/immaginemm.jpg" /> <br /> <br /> <div style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; FLOAT: none; PADDING-TOP: 0px" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:7056ac30-06f1-4add-9788-79850a3d44b0" class="wlWriterEditableSmartContent">Technorati Tags: <a rel="tag" href="http://technorati.com/tags/WPF">WPF</a>,<a rel="tag" href="http://technorati.com/tags/RichTextBox">RichTextBox</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/99110.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2010/08/24/wpf-evidenziare-testo-in-una-richtextbox.aspx Tue, 24 Aug 2010 23:01:53 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2010/08/24/wpf-evidenziare-testo-in-una-richtextbox.aspx#feedback 1 http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/99110.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/99110.aspx [WPF] MVVM e Splash Screen WPF http://blogs.ugidotnet.org/dsantarelli/archive/2010/05/30/wpf-mvvm-e-splash-screen.aspx <p>Durante lo sviluppo di applicazioni WPF di una certa “pesantezza”, può essere utile ricorrere a Splash Screen aggiornabili in tempo reale che notifichino all’utente lo stato di avanzamento del caricamento dei vari moduli, ad esempio tramite elementi di testo piuttosto che barre di progresso. A volte, infatti, può non essere sufficiente una semplice immagine impostabile tramite la Build Action "SplashScreen" di Visual Studio.<br /> In questo post vorrei quindi mostrare una possibile implementazione di uno Splash Screen aggiornabile in tempo reale usando il pattern MVVM.  <br /> <br /> Per semplificare, supponiamo di posizionare nel Main la pesantissima sequenza di caricamento della nostra applicazione:</p> <p style="LINE-HEIGHT: normal; MARGIN-BOTTOM: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: blue; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">public</span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"> <span style="COLOR: blue">static</span> <span style="COLOR: blue">class</span> <span style="COLOR: #2b91af">Program <br /> </span></span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">[<span style="COLOR: #2b91af">STAThread</span>] <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">public</span> <span style="COLOR: blue">static</span> <span style="COLOR: blue">void</span> Main(<span style="COLOR: blue">string</span>[] args) <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: #2b91af">App</span> app = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">App</span>(); <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">app.InitializeComponent();  <span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: green">// Load resources...</span></span></span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">            </span></span></p> <p style="LINE-HEIGHT: normal; MARGIN-BOTTOM: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: #2b91af"><font color="#000000">    </font>MainWindow</span> mainWindow = <span style="COLOR: blue">null</span>; <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">using</span> (<span style="COLOR: #2b91af">SplashScreenViewModel</span> splashScreenViewModel = <strong><span style="COLOR: #2b91af">SplashScreen</span>.ShowSplashScreen()</strong>) <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />       </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">splashScreenViewModel.StatusText = <span style="COLOR: #a31515">"Initializing..."</span>;  <br /> </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">               </span></span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">   <br /> <span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: green">     // Loading application modules...</span></span> <br /> <span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: #2b91af"><font color="#000000">      </font>Thread</span>.Sleep(3000); <span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: green">// Simulate delay...</span></span>  <br /> </span></span><br /> <span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">      </span>mainWindow = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">MainWindow</span>();</span></span> </p> <p> <span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">      </span>splashScreenViewModel.StatusText = <span style="COLOR: #a31515">"Initialized."</span>;  <br /> </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">      </span><span style="COLOR: #2b91af">Thread</span>.Sleep(1000); <br />    </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">} </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt"><span style="mso-spacerun: yes">            </span></span></p> <p style="LINE-HEIGHT: normal; MARGIN-BOTTOM: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt"><span style="mso-spacerun: yes">     </span>app.Run(mainWindow); <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">}<span style="mso-spacerun: yes">        <br /> </span></span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">} </span></p> <p><br /> Al fine di evitare fastidiosi effetti di freezing della GUI mentre il thread principale sta caricando i vari pezzi della nostra applicazione, la window dello Splash Screen va fatta girare necessariamente su un thread separato. Al termine della sequenza di caricamento, il thread principale deve poter chiudere lo Splash Screen utilizzando il <a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.aspx">Dispatcher</a> corretto. <br /> Vediamo quindi il codice della view:</p> <p style="LINE-HEIGHT: normal; MARGIN-BOTTOM: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: blue; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">public</span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"> <span style="COLOR: blue">partial</span> <span style="COLOR: blue">class</span> <span style="COLOR: #2b91af">SplashScreen</span> : <span style="COLOR: #2b91af">Window <br /> </span></span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">private</span> <span style="COLOR: blue">static</span> <span style="COLOR: blue">object</span> splashScreenlockObj = <span style="COLOR: blue">new</span> <span style="COLOR: blue">object</span>(); </span> </p> <p style="LINE-HEIGHT: normal; MARGIN-BOTTOM: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue"><font color="#000000">  </font>public</span> SplashScreen() <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">InitializeComponent();  <br /> </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">    </span>DataContext = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">SplashScreenViewModel</span>(); <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">} </span></p> <p> <span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue"><font color="#000000">  </font>public</span> <span style="COLOR: blue">static</span> <span style="COLOR: #2b91af">SplashScreenViewModel</span> ShowSplashScreen() <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">lock</span> (splashScreenlockObj) <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />      </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: #2b91af">SplashScreenViewModel</span> splashScreenViewModel = <span style="COLOR: blue">null</span>;  <br /> </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">     </span><span style="COLOR: #2b91af">ManualResetEvent</span> resetEvent = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">ManualResetEvent</span>(<span style="COLOR: blue">false</span>); </span></p> <p><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">     </span><span style="COLOR: #2b91af">Thread</span> splashScreenThread = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">Thread</span>(() =&gt; <br />      </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />        </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: #2b91af">Dispatcher</span>.CurrentDispatcher.BeginInvoke((<span style="COLOR: #2b91af">Action</span>)<span style="COLOR: blue">delegate</span>()  <br />        </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />          </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: #2b91af">SplashScreen</span> splashScreenWindow = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">SplashScreen</span>(); <br />          </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">splashScreenViewModel = (<span style="COLOR: #2b91af">SplashScreenViewModel</span>)splashScreenWindow.DataContext; <br />          </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">splashScreenViewModel.Dispatcher = <span style="COLOR: #2b91af">Dispatcher</span>.CurrentDispatcher; <br />          </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">resetEvent.Set(); <br />          </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">splashScreenWindow.Show();  <br />        </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">}); <br />        </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: #2b91af">Dispatcher</span>.Run(); </span></p> <p style="LINE-HEIGHT: normal; MARGIN-BOTTOM: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">      </span>}); </span></p> <p>              <span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">splashScreenThread.SetApartmentState(<span style="COLOR: #2b91af">ApartmentState</span>.STA); <br />       </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">splashScreenThread.IsBackground = <span style="COLOR: blue">true</span>;  <br />       </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">splashScreenThread.Start(); </span></p> <p style="LINE-HEIGHT: normal; MARGIN-BOTTOM: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">      </span></span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">resetEvent.WaitOne(); <span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: green">// Wait for viewmodel initialization...</span></span> <br /> <br />       </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt"><span style="COLOR: blue">return</span> splashScreenViewModel; <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">} <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">} <br /> </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">} </span></p> <p>Il metodo statico ShowSplashScreen() visualizza lo splash screen e comunica al ViewModel quale Dispatcher utilizzare per la chiusura (<a target="_blank" href="http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.invokeshutdown.aspx">InvokeShutdown()</a>).</p> <p style="LINE-HEIGHT: normal; MARGIN-BOTTOM: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: blue; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">public</span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"> <span style="COLOR: blue">class</span> <span style="COLOR: #2b91af">SplashScreenViewModel</span> : <span style="COLOR: #2b91af">ViewModelBase</span>, <span style="COLOR: #2b91af">IDisposable <br /> </span></span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">private</span> <span style="COLOR: blue">string</span> _statusText = <span style="COLOR: blue">null</span>; <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">public</span> <span style="COLOR: blue">string</span> StatusText <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">get</span> { <span style="COLOR: blue">return</span> _statusText; } <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">set</span> <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />       </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">if</span> (_statusText == <span style="COLOR: blue">value</span>) <span style="COLOR: blue">return</span>;  <br /> </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="mso-spacerun: yes">      </span>_statusText = <span style="COLOR: blue">value</span>; <br />       </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">OnPropertyChanged(<span style="COLOR: #a31515">"StatusText"</span>); <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">} <br />    </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">} <br /> <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">public</span> <span style="COLOR: #2b91af">Dispatcher</span> Dispatcher { <span style="COLOR: blue">get</span>; <span style="COLOR: blue">set</span>; } <br /> <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">public</span> <span style="COLOR: blue">void</span> Dispose() <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">if</span> (Dispatcher != <span style="COLOR: blue">null</span>) <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">{ <br />       </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">Dispatcher.InvokeShutdown(); <br />       </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">Dispatcher = <span style="COLOR: blue">null</span>; <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">} <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">} <br /> </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">} </span></p> <p> </p> <div style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; FLOAT: none; PADDING-TOP: 0px" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:3b55f672-d399-4f29-8b20-8f1e6d0a30d3" class="wlWriterEditableSmartContent">Technorati Tag: <a rel="tag" href="http://technorati.com/tags/WPF">WPF</a>,<a rel="tag" href="http://technorati.com/tags/MVVM">MVVM</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/98688.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2010/05/30/wpf-mvvm-e-splash-screen.aspx Sun, 30 May 2010 22:36:57 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2010/05/30/wpf-mvvm-e-splash-screen.aspx#feedback 3 http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/98688.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/98688.aspx [ASP.NET MVC 2] Localizzazione dell&rsquo;attributo DisplayName http://blogs.ugidotnet.org/dsantarelli/archive/2010/04/29/asp.net-mvc-2-localizzazione-dellrsquoattributo-displayname.aspx <p>Per localizzare i metadati delle classi che compongono il model della nostra applicazione MVC 2, principalmente sono due gli aspetti da prendere in considerazione:</p> <ul> <li>Localizzare i messaggi di validazione del model. In generale, la localizzazione dei messaggi di errore non è specifica di ASP.NET MVC, bensì è una caratteristica ereditata dai “<em>Data Annotations” </em>di<em> </em>ASP.NET Dynamic Data (namespace <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx" target="_blank">System.ComponentModel.DataAnnotations</a>), e si implementa specificando i parametri <em>ErrorMessageResourceName</em> e <em>ErrorMessageResourceType</em> dei vari <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.validationattribute.aspx" target="_blank">ValidationAttribute</a>. <br /></li> <li>Localizzare la proprietà <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.displaynameattribute.aspx" target="_blank">DisplayName</a> ove disponibile. </li> </ul> <p>Supponiamo di avere il seguente model:</p> <p style="line-height: normal; margin-bottom: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt">public</span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt"> <span style="color: blue">class</span> <span style="color: #2b91af">CreateCustomerModel <br /></span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">{ <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">[<span style="color: #2b91af">Required</span>(ErrorMessageResourceName = <span style="color: #a31515">"Required"</span>, ErrorMessageResourceType = <span style="color: blue">typeof</span>(<span style="color: #2b91af">ValidationStrings</span>))] <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">[<span style="color: #2b91af">DisplayName</span>(<span style="color: #a31515">"Name"</span>)] <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt"><span style="color: blue">public</span> <span style="color: blue">string</span> Name { <span style="color: blue">get</span>; <span style="color: blue">set</span>; } </span>  <br /></p> <p style="line-height: normal; margin-bottom: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">  [<span style="color: #2b91af">Required</span>(ErrorMessageResourceName = <span style="color: #a31515">"Required"</span>, ErrorMessageResourceType = <span style="color: blue">typeof</span>(<span style="color: #2b91af">ValidationStrings</span>))]  <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">  [<span style="color: #2b91af">DataType</span>(<span style="color: #2b91af">DataType</span>.EmailAddress)]  <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">  [<span style="color: #2b91af">DisplayName</span>(<span style="color: #a31515">"Email address"</span>)] <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt"><span style="color: blue">public</span> <span style="color: blue">string</span> Email { <span style="color: blue">get</span>; <span style="color: blue">set</span>; }  <br /> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">  [<span style="color: #2b91af">DataType</span>(<span style="color: #2b91af">DataType</span>.Url)] <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">[<span style="color: #2b91af">DisplayName</span>(<span style="color: #a31515">"Web site"</span>)] <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt"><span style="color: blue">public</span> <span style="color: blue">string</span> WebSiteUrl { <span style="color: blue">get</span>; <span style="color: blue">set</span>; }<span style="mso-spacerun: yes">        <br /></span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">} </span></p> <p> <br />La proprietà <em>DisplayName</em> può essere coinvolta sia nella validazione dei messaggi che utilizzano dei parametri per la formattazione, ma anche e soprattutto nel rendering della view:  </p> <table border="0" cellspacing="0" cellpadding="2" width="841"><tbody> <tr> <td valign="top" width="459"> <p style="line-height: normal; margin-bottom: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">&lt;</span><span style="font-family: &quot;Courier New&quot;; color: maroon; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">div</span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"> <span style="color: red">class</span><span style="color: blue">="editor-label"&gt; <br />  </span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="background: yellow; mso-highlight: yellow">&lt;%</span><span style="color: blue">:</span> Html.LabelFor(model =&gt; model.Name) <span style="background: yellow; mso-highlight: yellow">%&gt;</span> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">&lt;/</span><span style="color: maroon">div</span><span style="color: blue">&gt;</span> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">&lt;</span><span style="color: maroon">div</span> <span style="color: red">class</span><span style="color: blue">="editor-field"&gt;</span> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="background: yellow; mso-highlight: yellow">&lt;%</span><span style="color: blue">:</span> Html.TextBoxFor(model =&gt; model.Name) <span style="background: yellow; mso-highlight: yellow">%&gt;</span> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="background: yellow; mso-highlight: yellow">&lt;%</span><span style="color: blue">:</span> Html.ValidationMessageFor(model =&gt; model.Name) <span style="background: yellow; mso-highlight: yellow">%&gt;</span> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">&lt;/</span><span style="color: maroon">div</span><span style="color: blue">&gt; <br /></span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">&lt;</span><span style="color: maroon">div</span> <span style="color: red">class</span><span style="color: blue">="editor-label"&gt; <br />   </span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="background: yellow; mso-highlight: yellow">&lt;%</span><span style="color: blue">:</span> Html.LabelFor(model =&gt; model.Email) <span style="background: yellow; mso-highlight: yellow">%&gt;</span> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">&lt;/</span><span style="color: maroon">div</span><span style="color: blue">&gt;</span> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">&lt;</span><span style="color: maroon">div</span> <span style="color: red">class</span><span style="color: blue">="editor-field"&gt;</span> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="background: yellow; mso-highlight: yellow">&lt;%</span><span style="color: blue">:</span> Html.TextBoxFor(model =&gt; model.Email) <span style="background: yellow; mso-highlight: yellow">%&gt;</span> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="background: yellow; mso-highlight: yellow">&lt;%</span><span style="color: blue">:</span> Html.ValidationMessageFor(model =&gt; model.Email) <span style="background: yellow; mso-highlight: yellow">%&gt;</span> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">&lt;/</span><span style="color: maroon">div</span><span style="color: blue">&gt; <br /></span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">&lt;</span><span style="color: maroon">div</span> <span style="color: red">class</span><span style="color: blue">="editor-label"&gt;</span> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="background: yellow; mso-highlight: yellow">&lt;%</span><span style="color: blue">:</span> Html.LabelFor(model =&gt; model.WebSiteUrl) <span style="background: yellow; mso-highlight: yellow">%&gt;</span> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">&lt;/</span><span style="color: maroon">div</span><span style="color: blue">&gt;</span> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">&lt;</span><span style="color: maroon">div</span> <span style="color: red">class</span><span style="color: blue">="editor-field"&gt;</span> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="background: yellow; mso-highlight: yellow">&lt;%</span><span style="color: blue">:</span> Html.TextBoxFor(model =&gt; model.WebSiteUrl) <span style="background: yellow; mso-highlight: yellow">%&gt;</span> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="background: yellow; mso-highlight: yellow">&lt;%</span><span style="color: blue">:</span> Html.ValidationMessageFor(model =&gt; model.WebSiteUrl) <span style="background: yellow; mso-highlight: yellow">%&gt;</span> <br /></span><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt">&lt;/</span><span style="font-family: &quot;Courier New&quot;; color: maroon; font-size: 8pt">div</span><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt">&gt; </span></p> </td> <td valign="top" width="380">Ci troviamo in questa brutta situazione: <br /> <br /><a href="http://blogs.ugidotnet.org/images/blogs_ugidotnet_org/dsantarelli/WindowsLiveWriter/ASP.NETMVC2LocalizzazionedelModel_D6EF/immagine1xf%5B2%5D_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="immagine1xf[2]" border="0" alt="immagine1xf[2]" src="http://img710.imageshack.us/img710/6029/immagine2en.jpg" /></a></td> </tr> </tbody></table> <p> <br /></p> <p style="line-height: normal; margin-bottom: 0pt; mso-layout-grid-align: none" class="MsoNormal">Purtroppo, a differenza dei <em>ValidationAttribute</em>, la proprietà <em>DisplayName</em> non supporta nativamente la localizzazione e quindi non abbiamo gratis la possibilità di specificare una chiave di un file di risorsa (es. <em>ModelStrings.it.resx</em>) dal quale leggere la stringa localizzata. <br />Possiamo però estendere l’attributo <em>DisplayName</em> in modo da abilitare tale funzionalità tramite reflection.  </p> <p>  <br /><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">public</span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"> <span style="color: blue">class</span> <span style="color: #2b91af">LocalizedDisplayNameAttribute</span> : <span style="line-height: 115%; font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: it; mso-fareast-font-family: calibri; mso-fareast-theme-font: minor-latin; mso-fareast-language: en-us; mso-bidi-language: ar-sa">System.ComponentModel.<span style="color: #2b91af">DisplayNameAttribute</span></span> <span style="color: #2b91af"> <br /></span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />  </span><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">private</span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"> <span style="color: #2b91af">PropertyInfo</span> _namePropertyInfo = <span style="color: blue">null</span>; <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">private</span> <span style="color: #2b91af">Type</span> _resourceType = <span style="color: blue">null</span>; <br /> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">public</span> LocalizedDisplayNameAttribute(<span style="color: blue">string</span> displayNameKey) : <span style="color: blue">base</span>(displayNameKey) { } <br /> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">public</span> <span style="color: #2b91af">Type</span> ResourceType <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />    </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">get</span> { <span style="color: blue">return</span> _resourceType; } <br />    </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">set <br />    </span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />      </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">_resourceType = <span style="color: blue">value</span>;<span style="mso-spacerun: yes">  <br />      </span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">_namePropertyInfo = _resourceType.GetProperty(<span style="color: blue">base</span>.DisplayName, <span style="color: #2b91af">BindingFlags</span>.Static | <span style="color: #2b91af">BindingFlags</span>.Public); <br />    </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">} <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">} <br /> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">public</span> <span style="color: blue">override</span> <span style="color: blue">string</span> DisplayName <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />    </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">get <br />    </span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">{<span style="mso-spacerun: yes">                 <br />      </span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">if</span> (_namePropertyInfo == <span style="color: blue">null</span>) <span style="color: blue">return</span> <span style="color: blue">base</span>.DisplayName;<span style="mso-spacerun: yes">                <br />      </span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">return</span> (<span style="color: blue">string</span>)_namePropertyInfo.GetValue(_namePropertyInfo.DeclaringType, <span style="color: blue">null</span>); <br />    </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">} <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">} <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">} </span></p> <p>La classe <em>CreateCustomerModel</em> prima descritta diventa quindi… <br /><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"> <br />public</span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"> <span style="color: blue">class</span> <span style="color: #2b91af">CreateCustomerModel</span> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">[<span style="color: #2b91af">Required</span>(ErrorMessageResourceName = <span style="color: #a31515">"Required"</span>, ErrorMessageResourceType = <span style="color: blue">typeof</span>(<span style="color: #2b91af">ValidationStrings</span>))] <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">[<span style="color: #2b91af">LocalizedDisplayName</span>(<span style="color: #a31515">"Name"</span>, ResourceType = <span style="color: blue">typeof</span>(<span style="color: #2b91af">ModelStrings</span>))]<span style="mso-spacerun: yes">        <br />   </span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">public</span> <span style="color: blue">string</span> Name { <span style="color: blue">get</span>; <span style="color: blue">set</span>; } <br /> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">[<span style="color: #2b91af">Required</span>(ErrorMessageResourceName = <span style="color: #a31515">"Required"</span>, ErrorMessageResourceType = <span style="color: blue">typeof</span>(<span style="color: #2b91af">ValidationStrings</span>))] <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">[<span style="color: #2b91af">DataType</span>(<span style="color: #2b91af">DataType</span>.EmailAddress)] <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">[<span style="color: #2b91af">LocalizedDisplayName</span>(<span style="color: #a31515">"Email"</span>, ResourceType = <span style="color: blue">typeof</span>(<span style="color: #2b91af">ModelStrings</span>))]<span style="mso-spacerun: yes">   <br />   </span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">public</span> <span style="color: blue">string</span> Email { <span style="color: blue">get</span>; <span style="color: blue">set</span>; } <br /> <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">[<span style="color: #2b91af">DataType</span>(<span style="color: #2b91af">DataType</span>.Url)] <br />  </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US">[<span style="color: #2b91af">LocalizedDisplayName</span>(<span style="color: #a31515">"WebSite"</span>, ResourceType = <span style="color: blue">typeof</span>(<span style="color: #2b91af">ModelStrings</span>))]<span style="mso-spacerun: yes">   <br />   </span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="color: blue">public</span> <span style="color: blue">string</span> WebSiteUrl { <span style="color: blue">get</span>; <span style="color: blue">set</span>; }<span style="mso-spacerun: yes">        <br /></span></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt">} </span></p> <p>…il che ci fa ottenere il risultato desiderato :D <br /> <br /><a href="http://blogs.ugidotnet.org/images/blogs_ugidotnet_org/dsantarelli/WindowsLiveWriter/ASP.NETMVC2LocalizzazionedelModel_D6EF/immagine3qg%5B1%5D_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="immagine3qg[1]" border="0" alt="immagine3qg[1]" src="http://img18.imageshack.us/img18/5992/immagine3qg.jpg" /></a>  <br /> <br /></p> <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:da777216-6713-4117-8ad8-0974221e099c" class="wlWriterEditableSmartContent">Technorati Tag: <a href="http://technorati.com/tags/ASP.NET" rel="tag">ASP.NET</a>,<a href="http://technorati.com/tags/MVC" rel="tag">MVC</a>,<a href="http://technorati.com/tags/Localization" rel="tag">Localization</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/98465.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2010/04/29/asp.net-mvc-2-localizzazione-dellrsquoattributo-displayname.aspx Thu, 29 Apr 2010 21:20:15 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2010/04/29/asp.net-mvc-2-localizzazione-dellrsquoattributo-displayname.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/98465.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/98465.aspx [WPF] Ereditare controlli da classi generiche WPF http://blogs.ugidotnet.org/dsantarelli/archive/2010/03/29/wpf-ereditare-controlli-da-classi-generiche.aspx <!--DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"--> <p>In applicazioni WPF di una certa complessità è molto facile trovarsi in situazioni in cui è utile sfruttare classi base per i controlli (es. <em>UserControl e</em> <em>Window)</em>. In architetture MVVM, ad esempio, è possibile far ereditare i vari controlli da classi base generiche. Tuttavia, l’ implementazione dell’ereditarietà generica in WPF talvolta non è proprio immediata, soprattutto perché non sempre gli errori di build sono esplicativi.  <br /> In questo semplicissimo esempio di MVVM viene mostrato come si può specificare la classe base generica di una Window direttamente nello XAML. <br /> <br /> Partiamo dalla Window:</p> <p class="MsoNormal"><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: blue; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">public</span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"> <span style="COLOR: blue">partial</span> <span style="COLOR: blue">class</span> <span style="COLOR: #2b91af">CustomersWindow</span> : <span style="COLOR: #2b91af">WindowBase</span>&lt;<span style="COLOR: #2b91af">CustomersViewModel</span>&gt; <br /> </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{        <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">public</span> CustomersWindow() : <span style="COLOR: blue">base</span>(<span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">CustomersViewModel</span>()) <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{            <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">InitializeComponent();                         <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">} <br />   ... <br /> </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">}</span><span style="mso-ansi-language: en-us" lang="EN-US"> </span></p> <p>La classe base generica della Window potrebbe essere qualcosa del genere...</p> <p class="MsoNormal"><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: blue; FONT-SIZE: 8pt">public</span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt"> <span style="COLOR: blue">class</span> <span style="COLOR: #2b91af">WindowBase</span>&lt;T&gt; : <span style="COLOR: #2b91af">Window</span> <span style="COLOR: blue">where</span> T : <span style="COLOR: #2b91af">ViewModelBase <br /> </span></span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">public</span> T ViewModel { <span style="COLOR: blue">get</span>; <span style="COLOR: blue">set</span>; } <br /> <br />   <span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">public</span> WindowBase() { }<br /> </span><br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">public</span> WindowBase(T viewModel) <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">{ <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">this</span>.ViewModel = viewModel; <br />     </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: blue">base</span>.Dat</span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">aContext = viewModel; <br />   </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">} <br /> </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt">} </span></p> <p>Arriviamo quindi al punto. Lo XAML che permette di specificare l’ereditarietà da classi generiche diventa:</p> <p class="MsoNormal"><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: blue; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">&lt;</span><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: #a31515; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">src</span><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: blue; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">:</span><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: #a31515; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">WindowBase</span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"> <br />      </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: red">xmlns</span><span style="COLOR: blue">="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>" <br />      </span></span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: red">xmlns</span><span style="COLOR: blue">:</span><span style="COLOR: red">x</span><span style="COLOR: blue">="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>"</span>    <br />      </span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: red"><strong>xmlns</strong></span><strong><span style="COLOR: blue">:</span><span style="COLOR: red">src</span><span style="COLOR: blue">="clr-namespace:MyApplication" <br />      </span></strong></span><strong><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: red">x</span><span style="COLOR: blue">:</span><span style="COLOR: red">Class</span><span style="COLOR: blue">="MyApplication.CustomersWindow"</span> <br />      </span></strong><strong><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"><span style="COLOR: red">x:</span><span style="COLOR: red">TypeArguments</span><span style="COLOR: blue">="src:CustomersViewModel"</span></span></strong><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: blue; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US"> ...&gt;</span><span style="FONT-FAMILY: &quot;Courier New&quot;; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">    <br />  </span>... <br /> <span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: blue; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">&lt;/</span><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: #a31515; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">src</span><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: blue; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">:</span><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: #a31515; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">WindowBase</span><span style="FONT-FAMILY: &quot;Courier New&quot;; COLOR: blue; FONT-SIZE: 8pt; mso-ansi-language: en-us" lang="EN-US">&gt;</span><span style="mso-ansi-language: en-us" lang="EN-US"> </span></p> <p>Tramite <em>x:TypeArguments</em> possiamo specificare il tipo generico della classe base.  <br /> <br /> ATTENZIONE: questa tecnica è applicabile <strong>solo per il nodo root</strong> del documento XAML.<br /> </p> <div style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; FLOAT: none; PADDING-TOP: 0px" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:ba928733-0917-4f81-83e1-504edf2bc2a6" class="wlWriterEditableSmartContent">Technorati Tag: <a rel="tag" href="http://technorati.com/tags/WPF">WPF</a>,<a rel="tag" href="http://technorati.com/tags/Generics">Generics</a>,<a rel="tag" href="http://technorati.com/tags/Window">Window</a>,<a rel="tag" href="http://technorati.com/tags/Controls">Controls</a></div> <div style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; FLOAT: none; PADDING-TOP: 0px" class="wlWriterEditableSmartContent"> <p> </p> </div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/98276.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2010/03/29/wpf-ereditare-controlli-da-classi-generiche.aspx Mon, 29 Mar 2010 14:48:27 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2010/03/29/wpf-ereditare-controlli-da-classi-generiche.aspx#feedback 2 http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/98276.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/98276.aspx [Entity Framework] Gestione runtime dei metadati Entity Framework http://blogs.ugidotnet.org/dsantarelli/archive/2010/01/30/entity-framework-gestione-runtime-dei-metadati.aspx <p>Il comportamento di default del wizard dell’EF accorpa i metadati del modello come risorse del nostro assembly e inserisce una stringa di connessione nell’ app.config o nel web.config in modo da referenziare a runtime tali risorse: <br /> <br /><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">&lt;</span><span style="font-family: &quot;Courier New&quot;; color: #a31515; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">connectionStrings</span><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">&gt; <br /></span><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">&lt;</span><span style="font-family: &quot;Courier New&quot;; color: #a31515; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">add</span><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"> </span><span style="font-family: &quot;Courier New&quot;; color: red; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">name</span><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">=</span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">"<span style="color: blue">NorthwindEntities</span>"<span style="color: blue"> <br />     </span><span style="color: red">connectionString</span><span style="color: blue">=</span>"<span style="color: blue"><strong>metadata=res://*/NorthwindModel.csdl|res://*/NorthwindModel.ssdl|res://*/NorthwindModel.msl;...</strong></span>" <br />     <span style="color: red">providerName</span><span style="color: blue">=</span>"<span style="color: blue">System.Data.EntityClient</span>"<span style="color: blue"> /&gt; <br /></span></span><span style="line-height: 115%; font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-no-proof: yes">&lt;/</span><span style="line-height: 115%; font-family: &quot;Courier New&quot;; color: #a31515; font-size: 8pt; mso-no-proof: yes">connectionStrings</span><span style="line-height: 115%; font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-no-proof: yes">&gt;</span></p> <p>Tuttavia, “embeddare” i metadati del nostro modello come risorsa dell’assembly può costituire un limite: infatti sorgerebbero dei problemi qualora volessimo</p> <ul> <li>esporre i metadati alla modifica di agenti esterni </li> <li>referenziare tramite connection string dei metadati definiti altrove sul file system </li> <li>caricare i metadati on-demand magari leggendoli da stream di rete</li> </ul> <p>Infatti, esistono dei casi in cui può essere utile distinguere tra metadati di test e metadati di produzione o comunque prevedere delle politiche che possano richiedere storage model (SSDL) e mapping model (MSL) diversi, pur mantenendo l’integrità del modello concettuale (CSDL) utilizzato dallo sviluppatore.</p> <p>Una prassi molto comune è impostare l’opzione <em>'<strong>Metadata Artifact Processing</strong></em>' del designer a '<em><strong>Copy to Output Directory</strong></em>'.  <br /> <br /><a href="http://blogs.ugidotnet.org/images/blogs_ugidotnet_org/dsantarelli/WindowsLiveWriter/EntityFrameworkGestioneruntimedeimetadat_124D3/immaginejz%5B1%5D_2.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="immaginejz[1]" border="0" alt="immaginejz[1]" src="http://blogs.ugidotnet.org/images/blogs_ugidotnet_org/dsantarelli/WindowsLiveWriter/EntityFrameworkGestioneruntimedeimetadat_124D3/immaginejz%5B1%5D_thumb.jpg" width="244" height="115" /></a> <br /> <br />In questo modo nella bin del nostro progetto troviamo i metadati SSDL, MSL e CSDL separati fisicamente dalla .dll di output. <br />Quindi abbiamo già raggiunto l’obiettivo di poter modificare i metadati (solitamente SSDL e MSL) senza dover ricompilare la nostra applicazione. A questo punto possiamo scegliere di modificare la nostra connection string in modo che punti al corretto insieme di file: <br /> <br /><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">string</span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"> connectionString = <span style="color: #a31515">@"<strong>metadata=.\NorthwindModel.csdl|.\NorthwindModel.ssdl|.\NorthwindModel.msl</strong>; <br />                            </span></span><span style="font-family: &quot;Courier New&quot;; color: #a31515; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">provider=System.Data.SqlClient; provider connection string=""...</span><span style="font-family: &quot;Courier New&quot;; color: #a31515; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">"" "</span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">; <br /> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: blue">using</span> (<span style="color: #2b91af">NorthwindContext</span> context = <span style="color: blue">new</span> <span style="color: #2b91af">NorthwindContext</span>(connectionString)) </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">{ ... </span><span style="line-height: 115%; font-family: &quot;Courier New&quot;; font-size: 8pt; mso-no-proof: yes">}</span></p> <p> <br />Uno step ulteriore che si può prevedere è il caricamento dinamico di metadati non soltanto da una locazione fisica su file system, ma anche da un generico stream (es. da un web server via HTTP). A riguardo, il seguente esempio mostra come caricare dinamicamente CSDL, SSDL e MSL in modo da creare un oggetto di tipo <a href="http://msdn.microsoft.com/it-it/library/system.data.entityclient.entityconnection.aspx" target="_blank">EntityConnection</a> da passare al nostro ObjectContext: </p> <p class="MsoNormal"><span style="line-height: 115%; font-family: &quot;Courier New&quot;; color: green; font-size: 8pt; mso-no-proof: yes"> <br />// Get CSDL/SSDL/MSL from a generic stream ... <br /></span><span style="font-family: &quot;Courier New&quot;; color: blue; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">string</span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"> csdl = <span style="color: #a31515">"&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;Schema...&lt;/Schema&gt;"</span>; <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: blue">string</span> ssdl = <span style="color: #a31515">"&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;Schema...&lt;/Schema&gt;"</span>; <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: blue">string</span> msl = <span style="color: #a31515">"&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;Mapping...&lt;/Mapping&gt;"</span>; <p /> </span></p> <p style="line-height: normal; margin-bottom: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: #2b91af">XmlTextReader</span> csdlXmlReader = <span style="color: blue">new</span> <span style="color: #2b91af">XmlTextReader</span>(<span style="color: blue">new</span> <span style="color: #2b91af">StringReader</span>(csdl)); <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: #2b91af">XmlTextReader</span> ssdlXmlReader = <span style="color: blue">new</span> <span style="color: #2b91af">XmlTextReader</span>(<span style="color: blue">new</span> <span style="color: #2b91af">StringReader</span>(ssdl)); <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: #2b91af">XmlTextReader</span> mslXmlReader = <span style="color: blue">new</span> <span style="color: #2b91af">XmlTextReader</span>(<span style="color: blue">new</span> <span style="color: #2b91af">StringReader</span>(msl)); <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: #2b91af">EdmItemCollection</span> edmItemCollection = <span style="color: blue">new</span> <span style="color: #2b91af">EdmItemCollection</span>(<span style="color: blue">new</span>[] { csdlXmlReader }); <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: #2b91af">StoreItemCollection</span> storeItemCollection = <span style="color: blue">new</span> <span style="color: #2b91af">StoreItemCollection</span>(<span style="color: blue">new</span>[] { ssdlXmlReader }); <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: #2b91af">StorageMappingItemCollection</span> storageMappingItemCollection = <span style="color: blue">new</span> <span style="color: #2b91af">StorageMappingItemCollection</span>(edmItemCollection, storeItemCollection, <span style="color: blue">new</span>[] { mslXmlReader }); <br /> <p> <br /></p> </span><span style="line-height: 115%; font-family: &quot;Courier New&quot;; color: green; font-size: 8pt; mso-no-proof: yes">// Create the metadata workspace for the EntityConnection ... <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: #2b91af">MetadataWorkspace</span> metadataWorkspace = <span style="color: blue">new</span> <span style="color: #2b91af">MetadataWorkspace</span>(); <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">metadataWorkspace.RegisterItemCollection(edmItemCollection); <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">metadataWorkspace.RegisterItemCollection(storeItemCollection); <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">metadataWorkspace.RegisterItemCollection(storageMappingItemCollection); <br /> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: blue">string</span> sqlConnectionString = <span style="color: #a31515">@"Data Source=.\SQLSRV08;Initial Catalog=Northwind;User ID=XXX;Password=XXX;"</span>;</span></p> <p style="line-height: normal; margin-bottom: 0pt; mso-layout-grid-align: none" class="MsoNormal"><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: #2b91af">EntityConnection</span> entityConnection = <span style="color: blue">new</span> <span style="color: #2b91af">EntityConnection</span>(metadataWorkspace,<span style="color: blue">new</span> <span style="color: #2b91af">SqlConnection</span>(sqlConnectionString)); <br /> <br /></span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US"><span style="color: blue">using</span> (<span style="color: #2b91af">NorthwindContext</span> context = <span style="color: blue">new</span> <span style="color: #2b91af">NorthwindContext</span>(entityConnection)) </span><span style="font-family: &quot;Courier New&quot;; font-size: 8pt; mso-ansi-language: en-us; mso-no-proof: yes" lang="EN-US">{ ... </span><span style="line-height: 115%; font-family: &quot;Courier New&quot;; font-size: 8pt; mso-no-proof: yes">} <br /></span></p> <p> <br /></p> <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:6de21075-0887-4062-aded-878384be6ca9" class="wlWriterEditableSmartContent">Technorati Tag: <a href="http://technorati.com/tags/Entity+Framework" rel="tag">Entity Framework</a>,<a href="http://technorati.com/tags/EF" rel="tag">EF</a>,<a href="http://technorati.com/tags/metadati" rel="tag">metadati</a></div><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/97926.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2010/01/30/entity-framework-gestione-runtime-dei-metadati.aspx Sat, 30 Jan 2010 17:49:43 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2010/01/30/entity-framework-gestione-runtime-dei-metadati.aspx#feedback 2 http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/97926.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/97926.aspx nRoute e "Reverse Commands" Silverlight http://blogs.ugidotnet.org/dsantarelli/archive/2010/01/08/nroute-e-reverse-commands.aspx <p><a target="_blank" href="http://nroute.codeplex.com">nRoute</a> è uno dei framework a supporto di applicazioni Silverlight sviluppate secondo il pattern M-V-VM. In merito, segnalo <a target="_blank" href="http://www.orktane.com/Blog/post/2010/01/07/Reverse-ICommands-for-MVVM.aspx">questo interessante articolo</a> in cui si parla dell' introduzione di <strong>Reverse Commands</strong>. <br /> Da un punto di vista pratico viene introdotta una nuova interfaccia <em>IReverseCommand</em> che estende la solita <em>ICommand</em>. Tale specializzazione permette di definire un trigger "di ritorno" dal View-Model verso la View una volta che un command è stato eseguito. L'aspetto interessante si trova quindi nel fatto che il target dell' <em>IReverseCommand </em>è la View e non il ViewModel. <br /> Secondo i principi del pattern M-V-VM in Silverlight, esistono due meccanismi primari di comunicazione tra View e ViewModel:</p> <ul> <li>Cambiamenti di stato relativi a databinding tra oggetti della View e proprietà del ViewModel</li> <li>Invocazioni di azioni ( <em>ICommand</em> appunto ) dalla View verso il ViewModel</li> </ul> <p>Se in questi scenari architetturali gli <em>ICommand</em> sono concepiti per essere scatenati dalla View (e gestiti nel ViewModel), l'introduzione di ReverseCommands avviene per esplicitare il meccanismo inverso sempre in un'ottica strongly-typed.</p><img src="http://blogs.ugidotnet.org/dsantarelli/aggbug/97820.aspx" width="1" height="1" /> Dario Santarelli http://blogs.ugidotnet.org/dsantarelli/archive/2010/01/08/nroute-e-reverse-commands.aspx Fri, 08 Jan 2010 16:32:51 GMT http://blogs.ugidotnet.org/dsantarelli/archive/2010/01/08/nroute-e-reverse-commands.aspx#feedback http://blogs.ugidotnet.org/dsantarelli/comments/commentRss/97820.aspx http://blogs.ugidotnet.org/dsantarelli/services/trackbacks/97820.aspx