Se usate la feature di publish del Web Application Project, potreste accorgervi che alcuni file con estensioni custom non vengono pubblicati. Per ovviare a questo problema bisogna impostare la Build Action per i file incriminati a "Content". Semplice, ma se non lo sapete potreste perdere parecchio tempo...


Stamane nei newsgroup è rispuntata una delle questioni ricorrenti che riguardano i temi. Mi riferisco alla possibilità di referenziare le immagini presenti in un tema direttamente dalla pagina aspx. Tempo fa avevo dato una che si basava sull'uso di CSS e DIV che con un po' di furbizia può diventare ospite di una immagine presa dalla cartella del tema corrente.

Pare però che a qualcuno questa soluzione non sia sufficiente e quindi ho tirato fuori dal cilindro un'altra possibilità interessante. Si tratta di scriversi un piccolo ExpressionBuilder che effettui la mappatura dell'immagine alla cartella del tema desunta dalla proprietà Page.Theme. Massimo, che ha posto la domanda ha scritto il codice secondo le mie indicazioni e lo ha postato nel suo weblog perciò mi limito a linkarlo anche se è scritto in vb.net.

La soluzione consente di scrivere il seguente codice:

<asp:Image runat="server" ImageUrl="<%$ ThemedImage:images/pippo.jpg %>" />

L'uso dell'ExpressionBuilder permette di rendere più scorrevole la scrittura del codice riunendo in un solo file il markup dell'immagine e il suo url a differenza di quello che succedeva con la mia precedente soluzione. L'unica obiezione che qualcuno potrebbe sollevare è che si dovrà riportare hard-coded la cartella del tema ~/App_Themes ma occorre tenere presente che nel framework stesso la situazione è analoga.

Technorati tags: , ,

Stamane, rispondendo ad alcuni quesiti nel newsgroup di ASP.NET (news://news.microsoft.com) è uscita una interessante segnalazione da parte di un utente. In sostanza questi due spezzoni di codice hanno un comportamento sostanzialmente diverso:

   1: string id = dropDownList.ID;
   2: string clientId = dropDownList.ClientID;
   3:  
   4: // qui id == null
   5:  
   6: string clientId = dropDownList.ClientID;
   7: string id = dropDownList.ID;
   8:  
   9: // qui id == "<id autogenerato>"

Se andate ad indagare per mezzo di Reflector, il motivo di questo strano comportamento diventa immediatamente chiaro, come ha segnalato :

   1: // dalla classe Control
   2:  
   3: ...omissis...
   4:  
   5: public virtual string get_ID()
   6: {
   7:     if (!this.flags[1] && !this.flags[0x800])
   8:     {
   9:         return null;
  10:     }
  11:     return this._id;
  12: }
  13:  
  14: public virtual string get_ClientID()
  15: {
  16:     this.EnsureID();
  17:     string uniqueID = this.UniqueID;
  18:     if ((uniqueID != null) && (uniqueID.IndexOf(this.IdSeparator) >= 0))
  19:     {
  20:         return uniqueID.Replace(this.IdSeparator, '_');
  21:     }
  22:     return uniqueID;
  23: }
  24:  
  25: ...omissis...

In buona sostanza, mentre la proprietà ClientID chiama per prima cosa this.EnsureID() - che per l'appunto genera un ID fittizio se questo non è stato valorizzato - la proprietà ID invece si limita a restituire null. Probabilmente la soluzione migliore sarebbe stata quella di verificare prima se ID==null e in questo caso chiamare this.EnsureID().

Alcune considerazioni sparse:

1) Le guideline di Microsoft consigliano di evitare che l'ordine di chiamata delle proprietà abbia un impatto sui risultati ottenuti dalle stesse, perciò questo comportamento va considerato ne più ne meno che un errore di design.

2) E' assolutamente sconsigliabile fare in modo che la chiamata ad un property-getter modifichi lo stato di una classe proprio perchè implicitamente quando si legge non si immagina di impostare un valore

3) Quanto detto prima naturalmente non vale per i Singleton e per gli Special Case. Tenete presente però che in questi casi la lettura di una proprietà modifica la proprietà stessa (in modo del tutto trasparente) e non un'altra...

4) Tutti possono sbagliare... quindi rilassatevi :)

Technorati tags: , , ,

Fra pochi minuti inizia il webcast di Davide Vernole della serie dedicata ad AJAX

Eccovi l'abstract:

ASP.NET 2.0 AJAX: Client Scripting, Debugging and Tracing

03/05/2007 Davide Vernole (Microsoft ASP.NET MVP)

Abstract:  Il framework ASP.NET 2.0 AJAX fornisce ai programmatori un ambiente facilmente estensibile. Cerchiamo di scoprire come sia posssibile estendere Javascript con ASP.NET AJAX creando dei nostri script personalizzati.  A corredo di questo introdurremo le tecniche di debugging e tracing per questo tipo applicazioni.

Technorati tags: , ,

Ho trovato un po' troppo laborioso e ripetitivo scrivere il codice necessario per inserire un contenuto realizzato con Silverlight in una pagina web. Comeprimo esercizio perciò mi sono creato un piccolo controllo ASP.NET che fa tutto ciò che serve. Non che sia difficile predisporre quel minimo di codice javascript che serve, ma secondo me il controllo è molto più efficace e immediato, a patto che usiate una pagina aspx per ospitarlo. Ecco come si inserisce:

 

SilverlightHost Control - Copy Code
1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> 2 <%@ Register TagPrefix="wpf" 3 Namespace="Elite.Silverlight.Commodities" 4 Assembly="Elite.Silverlight.Commodities" %> 5 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 6 <html xmlns="http://www.w3.org/1999/xhtml"> 7 <head runat="server"> 8 <title>Silverlight Test Page</title> 9 </head> 10 <body> 11 <form id="form1" runat="server"> 12 <asp:ScriptManager ID="ScriptManager1" runat="server" /> 13 <wpf:SilverlightHost 14 runat="server" 15 ID="host0" 16 Width="300px" 17 Height="300px" 18 BackColor="#dddddd" 19 XamlFile="~/myxaml.xaml" 20 MaxFrameRate="30" /> 21 </asp:Wizard> 22 </form> 23 </body> 24 </html>

Il controllo si occupa di: inserire un <div> che fa da host per il plugin, di immettere il codice Javascript personalizzabile mediante le sue proprietà e di includere il file aghost.js come risorsa embedded dall'assembly. Naturalmente è scritto in modo tale da permettere l'inserimento di più di un contenuto wpf per singola pagina.

Come al solito il codice è disponibile per il download:


Da programmatore ASP.NET quale sono c'è una cosa che invidio a Windows Presentation Foundation. Non si tratta certo di animazioni, 3D e quant'altro che sono lontane anni luce da quel poco che si può fare con ASP.NET e con le recenti librerie AJAX e quindi non vale nemmeno la pena di invidiarle. Piuttosto se guardiamo al DataBinding, laddove ci sono delle profonde somiglianze, vi è anche una marcata differenza per quanto riguarda l'uso che se ne può fare in WPF quando ad esempio si collegano più controlli tra di loro. La prima volta che ho visto uno dei tanti esempi di DataBinding usato per questo scopo, sono rimasto letteralmente affascinato pensando a quanto codice mi avrebbe risparmiato il poter usare una sintassi simile in ASP.NET. Nulla di trascendentale, magari un checkbox che abilita o disabilita un pannello, un porzione di pagina che cambia colore in accordo con il valore di una DropDowList e così via, tutte cose che grazie al BindingEngine di WPF sono di una banalità impressionante ma che richiedono molto codice ridondante in ASP.NET.

Beh, la bella notizia è che con un po' di astuzia e con una buona conoscenza del funzionamento del modello di compilazione di ASP.NET (detto con tutta la modestia possibile, sia chiaro) la cosa si può fare eccome, e le differenze con WPF sono davvero minime, dettate soprattutto dal flusso di eventi che porta al rendering della pagina più che da altre sottigliezze. Ma prima di entrare nel merito di questa proof-of-concept che ho realizzato nelle serate della recente vacanza in Svizzera e che potete scaricare in coda a questo post, vale la pena di approfondire meglio con un piccolo esempio il funzionamento di WPF.

Binding, ElementName & Path

In WPF per realizzare il DataBinding si fa uso di quella che comunemente viene definita una Markup Extension. Le Merkup Extension che ora non è il caso di approfondire sono delle estensioni che permettono di semplificare la sintassi di XAML per introdurre referenze ed estensioni che arricchiscono il suo significato. "Binding" è la markup extension che si incarica di collegare i dati agli elementi compositivi dell'interfaccia. La cosa curiosa è che mediante Binding è possibile specificare come sorgente dati un elemento dell'interfaccia stessa.

<Border Margin="{Binding ElementName=sldDistance, Path=Value}"></Border>

Con una sintassi come quella specificata nel riquadro è possibile ad esempio specificare uno slider come sorgente dati di un Border. Il risultato sarà che spostando lo slider varia il margine del bordo. ElementName infatti specifica il legame con un ipotetico controllo Slider che porta questo nome mentre path referenzia la sua proprietà Value.

Penso sia evidente a tutti la potenza di una sintassi del genere che nel caso di WPF consente di applicare delle logiche anche molto complesse grazie anche all'uso di Converter che nella fase di binding sono in grado di applicare delle conversioni ai dati che transitano in una direzione e nell'altra. Per un esempio completo di quanto descritto vi invito ad approfondire un tempo fa.

Do it in ASP.NET

E' giunto il momento di mettere assieme i tasselli della soluzione che ho architettato per mimare in ASP.NET questo stesso comportamento. Il primo di essi si chiama ExpressionBuilder. Il più usato e famoso degli ExpressionBuilder è quello che in ASP.NET consente di specificare le risorse localizzate in una pagina. Si tratta semplicemente di un codeblock il cui prefisso è il simbolo del dollaro ($) che si comporta in modo molto particolare. Esso infatti viene valutato al momento della compilazione della pagina e da adito ad una piccola porzione di codice che assegna una attributo di un server control. Questo pezzetto di codice, generato per mezzo di CodeDOM viene inserito nella pagina compilata dal runtime ed è in grado di valorizzare la proprietà dui è associato applicando delle logiche particolari. Per una spiegazine estensiva vi invito a leggere un .

L'idea che mi è venuta è quella di sfruttare un ExpressionBuilder per simulare la markup extension di WPF. Una cosa che occorre tenere presente è che la stringa che funge da parametro di un ExpressionBuilder viene passata per intero al metodo responsabile di generare il codice perciò è molto semplice introdurre una sintassi simile, se non identica a quella delle markup extension.

BackColor="<%$ Binding: ElementName=ddlColor, Path=SelectedValue %>"

L'idea era buona, ma il problema è che il codice generato dalla ExpressionBuilder deve condurre all'assegnazione della proprietà cui è associata. Per questo mi ci è voluto un po' per arrivare ad un metodo alternativo, ma poi, meditando sull'uso degli Anonymous Methods ho trovato la giusta soluzione che ora provo ad illustrare.

Il primo passo è stato creare un metodo statico, nella classe che rappresenta il Builder, il quale si occupi di raccogliere le informazioni dei controlli specificati nel Binding e le utilizzi nel metodo anonimo associato all'evento load della pagina. Ecco il metodo in questione:

public static T MakeBinding<T>( Page page, string boundControlId, string boundPropertyName, string targetControlId, string targetPropertyName, string converter, string converterParameter) { page.Load += delegate(object sender, EventArgs e) { Control bound = page.Form.FindControl(boundControlId); if (bound == null) return; Control target = page.Form.FindControl(targetControlId); if (target == null) return; Type boundType = bound.GetType(); PropertyInfo boundProperty = boundType.GetProperty(boundPropertyName); if (boundProperty == null) return; Type targetType = target.GetType(); PropertyInfo targetProperty = targetType.GetProperty(targetPropertyName); if (targetProperty == null) return; object targetValue = targetProperty.GetValue(target, null); IValueConverter converterInstance = CreateConverter(converter); boundProperty.SetValue(bound, converterInstance.Convert( targetValue, boundProperty.PropertyType, converterParameter, CultureInfo.CurrentUICulture), null); }; return default(T); }

Il metodo è un Generico perchè deve restituire un tipo conforme con la proprietà cui è associato il Binding. Esso poi riceve in ingresso i parametri che vengono estratti dalla stringa dell'ExpressionBuilder. In prima istanza non fa altro che agganciare un anonymous method all'evento load della pagina. Al momento in cui l'evento viene sollevato i parametri vengono usati per  trovare i controlli referenziati nella pagina e copiare i valori opportunamente trasformati dal convertitore prescelto oppure da quello di Default. Il convertitore altro non è che una classe che implementa l'interfaccia IValueConverter, la stessa che fa capo ai convertitori di WPF. Infine il metodo ritorna il valore di default per il tipo generico.

L'expressionBuilder non fa altro che forgiare una chiamata al metodo statico MakeBinding<T>() con gli opportuni parametri:

public override CodeExpression GetCodeExpression( BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context) { Dictionary<string, string> values = Parse(entry.Expression); return new CodeSnippetExpression( string.Format( @"Elite.Web.UIDataBinding.BindingExpressionBuilder.MakeBinding<{0}>" + @"(this, ""{1}"", ""{2}"", ""{3}"", ""{4}"", ""{5}"", ""{6}"")", entry.PropertyInfo.PropertyType.FullName, entry.ControlID, entry.PropertyInfo.Name, values["ElementName"], values["Path"], values.ContainsKey("Converter") ? values["Converter"] : null, values.ContainsKey("ConverterParameter") ? values["ConverterParameter"] : null)); }

Si potrebbe fare meglio, usando bene le classi e i metodi di CodeDOM, ma per questo esempio è sufficiente. Tutto sommato il mio obbiettivo è di provare che il meccanismo funziona a dovere. Scaricando il codice troverete un piccolo esempio che associa ad AJAX l'utilizzo dell'ExpressionBuilder. La cosa ce vi invito a notare è che per realizzarlo non è stato necessario scrivere alcun codice nel codebehind della pagina perchè tutte i comportamenti sono conferiti mediante l'Expression Builder. Chi intendesse utilizzarlo tenga presenta che si tratta di poco più che un test, e che ha alcune limitazioni rispetto a WPF, dovute più che altro alla mancata implementazione:

  1. La proprietà Path non consente di specificare un vero e proprio Path come in WPF ma solo il Control ID.
  2. I controlli vengono cercati solo al primo livello della gerarchia
  3. Specificare un converter è un po' difficile perchè richiede di immettere il namespace e la classe. Sarebbe meglio trovare un metodo per abbreviare la sintassi che altrimenti rischia di essere un po' troppo logorroico.-
  4. Le proprietà composte di ASP.NET non sono supportate (es. Font-Size)

Tutte queste limitazioni sono facilmente superabili e probabilmente un po' alla volta cercherò di pubblicare nuove versioni se il Binder incontrerà il successo che ha avuto ASP.NET. Per il momento vi invito a provare l'esempio di cui potete vedere uno spezzone in questo riquadro:

<div class="panel"> <asp:Panel id="pnlFont" runat="server" CssClass="scroll-panel" Height="100px"> <asp:Image id="imgSpiderman" runat="server" ImageUrl="images/spiderman.jpg" Width="<%$ Binding: ElementName=ddlSize, Path=SelectedValue, Converter=Elite.Web.UIDataBinding.UnitConverter %>" Height="<%$ Binding: ElementName=ddlSize, Path=SelectedValue, Converter=Elite.Web.UIDataBinding.UnitConverter %>" /> </asp:Panel> <p class="control-row"><label>Size:</label> <asp:DropDownList ID="ddlSize" runat="server" AutoPostBack="true"> <asp:ListItem Text="25%" Value="25" /> <asp:ListItem Text="50%" Value="50" /> <asp:ListItem Text="100%" Value="100" Selected="True" /> <asp:ListItem Text="200%" Value="200" /> <asp:ListItem Text="400%" Value="400" /> </asp:DropDownList></p> </div>

Download: Elite.Web.UIDataBinding.zip (23,4 Kb)

Video: Demo (72 KB)


Alle 14:30 di oggi si terrà il mio primo webcast in tema di ASP.NET. La cosa mi emoziona, un po' come se si trattasse del primo in assoluto. Il webcast a mio parere è di estremo interesse per chi sviluppa sul web, soprattutto per quelli che si occupano come me di Content Management System o magari della realizzazione di siti web con la pura e semplice piattaforma ASP.NET.

Si parlerà infatti di SEO, acronimo che come spiegherò si riferisce a quelle tecniche che servono a rendere le pagine di un sito web più appetibili per l'utente nascosto. Durante il webcast ci concentreremo con una serie di esempi su cosa bisogna fare, su siti erogati per mezzo di ASP.NET per ottenere i migliori risultati. Parleremo così di Url Rewriting, di Viewstate, di Meta tags, di Sitemap di Redirect di ASP.NET AJAX, e di altre cose del genere che toccano in qualche modo l'ambito della Search Engine Optimization. Il webcast sarà un po' meno denso dei precedenti. Ho scelto di ridurre le slide da 30 a 20 in modo da poter riservare più tempo a fare quattro chiacchiere con voi...

Accenneremo anche alle buzzword che ruotano attorno a questo "strano" settore, parlando di PageRank, di Doorway, di Cloacking, di Deceptive Redirects, ma già vi avverto che la mia attenzione si concentrerà molto più su ASP.NET piuttosto che sul SEO vero e proprio. Non sono un esperto di SEO, e il webcast si riferisce in massima parte ad una serie di esperienze che ho fatto negli ultimi mesi, ma se avete qualche domanda che esula da questo provate pure a pormela.

Iscrizioni e accesso: Tecniche di Search Engine Optimization con ASP.NET 2.0


Il collega ha proposto un interessante esperimento che può essere molto utile a chi vuole approfondire come funzionano i trigger dell'UpdatePanel. Si tratta di un Trigger Javascript, cioè in grado di far scattare l'update di un UpdatePanel in risposta ad un qualunque evento Javascript. Già mi immagino la proliferazione dei callback che potrebbe derivare da un trigger del genere... callback per onmousemove, onmouseover, onblur, onchanged potrebbero essere fortemente negativi, ma come tutte le cose basta conoscere lo strumento per usarlo bene... mai pensato di poter fare l'update di un pannello la prima volta che ci sovrapponete il mouse? Con il trigger di Andrea si può... e anche bene...

Fonte: UpdatePanel e trigger su evento JavaScript - JSAsyncPostBackTrigger


Attenzione, fra poco inizia il primo webcast di Davide Vernole su ASP.NET AJAX 1.0. Si tratta del primo di una serie su Ajax erogata per conto dello usergroup XeDotNet... Assolutamente da non perdere

Ecco il riassunto degli argomenti

ASP.NET 2.0 AJAX: Getting Started

 22/03/2007 Davide Vernole (Microsoft ASP.NET MVP)

Abstract:  Il rilascio del framework ASP.NET 2.0 AJAX non può più farci indugiare. È venuto il momento di conoscerlo e di utilizzarlo per le nostre applicazioni. Questo webcast è il primo di una serie dedicate all’implementazione Microsoft di AJAX in cui, procedendo in una panoramica generale dall’installazione al suo primo utilizzo, introdurremo le funzionalità incluse in questo framework.


Non so quanti di voi se ne siano accorti, ma l'introduzione di AJAX e di tutta una serie di feature client-based che popolano i moderni siti web ha scatenato la proliferazione degli include di Javascript. La "malattia" è piuttosto grave in quanto non capita di rado di trovare delle pagine web che arrivino a pesare 300/400 kilobyte. Se fate un esperimento, provando a salvare una pagina sul pc dal browser, immediatamente salterà all'occhio che gli script che popolano le moderne applicazioni appesantiscono in modo esagerato la pagina. Per darvi un'idea sappiate che il Javascript dei ASP.NET AJAX, in modalità "Release", contiene 89KB di codice,

Nonostante le linee broadband che sono disponibili oggi è palese che c'è qualcosa che non va, e che tale quantità di script deve essere sfoltita. Ecco perciò un paio di suggerimenti

1) Scegliete opportunamente la libreria Ajax più opportuna: La presenza di una "modalità" release degli script di ASP.NET AJAX, dimostra che qualcuno si è reso conto che essi sarebbero potuti diventare un problema e ne fa una ottima alleata. Lo stesso purtroppo non si può dire per l'AJAX Control Toolkit che ha svariati script molto pesanti. Una volta che avete scelto la libreria che fa per voi evitate accuratamente di mischiare altre librerie dato che in questo modo gli script tenderebbero fatidicamente a raddoppiare o triplicare. Ricordatevi inoltre che AJAX è una bella cosa, ma se in un ambiente intranet con una connessione a 100 mbit o più è accettabile, in un sito web pubblico, probabilmente è meglio lasciarlo da parte e se propro non potete farne a meno, almeno toglietelo dalla home page.

2) Attivate, se potete, la compressione HTTP includendo per le pagine dinamiche le seguenti estensioni: aspx, axd, e ashx. Quella che conta veramente è axd perchè è tramite esse che ASP.NET eroga molti deli script.

3) Comprimete gli script e i css: Ecco quelli che ho trovato migliori nel rapporto di compressione: Javascript Compressor, CSS Compressor. Il meccanismo su cui si basano è molto semplice e comporta il togliere spazi, commenti e qualunque altro carattere inutile. Il compressore Javascript è in grado di anche di usare una codifica particolare che poi viene scompattata dallo stesso Javascript nel momento in cui avviene il download. Il difetto è che hanno il potere di rendere illeggibile il codice perciò vi conviene mantenere una copia dell'originale.

4) Semplificate: ricordate che chiunque è in grado di complicare le cose aggiungendo ammenicoli, campi, e gadget alla pagina, ma alla fine la scelta giusta è quella di rinunciare a qualcosa. Ad esempio, in un recente progetto mi sono accorto che il controllino Calendar da solo generava 24KB di script. Il controllo è utilissimo, ma a mio parere in queste condizioni è meglio dare all'utente un normale validatore.

5) Organizzate la vostra pagine in modo tale che l'utente veda qualcosa da subito anche se poi deve attendere un po' per arrivare al completamento del caricamento. In questo gli iframe sono una vera manna dal cielo. Oggi, con il livello che hanno ottenuto i browser è davvero possibile beneficiarne... ma al solito enza esagerare

Technorati tags: , ,