Non chiedetemi perchè, ma sono sempre stato uno sfegatato appassionato di Javascript. Sì, avete letto bene. proprio lui, l'odiata bestia nera dei programmatori web, il mostro del debugger, l'antitesi della programmazione ordinata. Eppure con javascript ho fatto le mie più belle cose. Ricordo ad esempio nel lontano 2002 quando ho sviluppato una piattaforma di elearning che sviluppai appoggiandomi ad un componente oggi arcinoto, tale XmlHttpRequest, una specie di player per corsi in formato SCORM che sfruttava una sorta di antidiluviano AJAX per evitare il postback. Tra l'altro lo faceva con un frameset e incorporava un mini serializzatore/deserializzatore XML scritto tutto in Javascript ad oggetti.

Poi con la nascita dei "dialetti" di Javascript altenativi a Internet Explorer lavorare con esso è diventato solo fonte di repentini mal di capo perciò ho lasciato un po' perdere a favore del server-side. Da qualche giorno però è unscita una cosa che promette di regalare una nuova giovinezza al mio infantile amore. Non so quanti se ne siano accorti, ma assieme al rilascio del decantato ASP.NET AJAX 1.0 è arrivata una splendida libreria Javascript cross-browser. Non per svalutare AJAX per carità, conosco qualcuno che me la potrebbe far pagare per questo, ma devo dire che la in questione è infinitamente più apprezzata dal mio punto di vista.

Tanto per darvi un'idea, avete presente che cosa occorreva fare per una semplice getElementById()? Ecco, ora il codice si limita alla seguente:

var element = $get('elementid');

Il tutto naturalmente cross-browser. Questo esempio è banale, ma se si mettono in conto tutti gli oggetti creati in questa fantastica libreria la programmazione Javascript torna as essere qualcosa de prendere in considerazione.

Ecco in poche righe un altro esempio che ho messo in piedi nei giorni scorsi:

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Imagic</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" />
        <div id="main">
            <img src="~/images/s.gif" id="image" alt="image" runat="server" />
        </div>
    </form>
    <script type="text/javascript">
    $addHandler(window, "load", window_Resize);
    $addHandler(window, "resize", window_Resize);

    function window_Resize(eventElement)
    {
        var main = $get('main');
        var image = $get('image');
        var mainBounds = Sys.UI.DomElement.getBounds(main);
        var imageBounds = Sys.UI.DomElement.getBounds(image);
        image.style.top = (mainBounds.height - imageBounds.height) / 2;
        image.style.left = (mainBounds.width - imageBounds.width) / 2;
    }
    </script>
</body>
</html>

In questo modo il contenuto di "main" rimarra centrato nello schermo anche durante il resize del browser, grazie a posizionamento assoluto e Javascript... e naturalmente su tutti i browser!!!

Se questa non è magia...


Dopo il post di Dino Esposito che informava a proposito di un bug del framework ASP.NET AJAX 1.0 RTM, mi sono trovato repentinamente nella necessità di scovare un work-around che mi permettesse di implementare un meccanismo di attesa durante le chiamate. Il bug segnalato in questo post poneva di fronte al fatto che il controllo UpdateProgress ha problemi a lavorare in collaborazione con l'UpdatePanel qualora quest'ultimo abbia dei trigger specificati al suo interno. M'è perciò venuto in mente di provare ad usare la  di Simone Busoli, per vedere se in qualche modo riuscivo a dare un feedback all'utente di ciò che stava accadendo. Qui sotto vi propogno il pezzo di codice che genera lo script necessario ad agganciare gli eventi del PageRequestManager di ASP.NET AJAX.

/// <summary>
/// Creates the busybox script.
/// </summary>
private void CreateBusyboxScript()
{
    StringBuilder builder = new StringBuilder();

    builder.AppendLine(@"var isBusyBoxShow = false;");
    builder.AppendLine(@"Sys.Application.add_load(ApplicationLoadHandler);");
    builder.AppendLine(@"function ApplicationLoadHandler()");
    builder.AppendLine(@"{");
    builder.AppendLine(@" Sys.WebForms.PageRequestManager.getInstance()" +
            ".add_beginRequest(ajaxBeginRequest);");
    builder.AppendLine(@" Sys.WebForms.PageRequestManager.getInstance()" +
            ".add_pageLoaded(ajaxEndRequest);");
    builder.AppendLine(@"}");
    builder.AppendLine(@"function ajaxBeginRequest(sender, args)");
    builder.AppendLine(@"{");
    builder.AppendFormat(" if (!isSuppressBusyBox(args.get_postBackElement()))" +
            " {{ {0} isBusyBoxShow=true; }}\n", busybox.ShowFunctionCall);
    builder.AppendLine(@"}");
    builder.AppendLine(@"function ajaxEndRequest(sender, args)");
    builder.AppendLine(@"{");
    builder.AppendLine(@" if (isBusyBoxShow)");
    builder.AppendFormat(" {0}\n", busybox.HideFunctionCall);
    builder.AppendLine(@" isBusyBoxShow=false;");
    builder.AppendLine(@"}");
    builder.AppendLine(@"function isSuppressBusyBox(element)");
    builder.AppendLine(@"{");
    builder.AppendLine(" if (element.suppressBusyBox==null) return false;");
    builder.AppendLine(" return (element.suppressBusyBox.toLowerCase()==\"true\");");
    builder.AppendLine(@"}");

    this.Page.ClientScript.RegisterStartupScript(
            this.GetType(), 
            "showBusyBox", 
            builder.ToString(), true);
}

In sostanza grazie alle API di ASP.NET AJAX si predispongono due handler per gli eventi beginRequest e pageLoaded che scattano rispettivamente prima dell'invio del callback al server e immediatamente dopo la risposta. All'interno di questi handler è iniettata la chiamata alla funzione che provoca l'apparizione della busybox. La parte comoda di questa implementazione è che l'apertura e la chiusura del messaggio è completamente indipendente dal codice che si è realizzato. Per consentire un minimo i controllo ho deciso anche di adottare un meccanismo che consenta di specificare per quali controlli non mostrare la busybox. Per ottenere questo risultato ho sfruttato la capacità di ASP.NET di iniettare degli attributi custom nel markup.

<asp:LinkButton runat="server" ID="bButtonBar" suppressBusyBox="true" />

Il codice javascript controlla se l'elemento che ha scatenato il postback contiene questo attributo e qualora lo trovi valorizzato a "true" evita di mostrare la busybox. Naturalmente per adottare questo codice è necessario inserire nel markup il codice della busybox, ma per questo troverete una ottima documentazione sul sito del progetto.

Technorati tags: , , ,

Se qualcuno ha provato ad implementare un url rewrite ove già avesse utilizzato il VirtualPathProvider di ASP.NET forse si sarà già reso conto che ci sono alcuni problemi da tenere in considerazione. Innanzitutto bisogna tenere bene a mente come si comporta il runtime di ASP.NET quando compila le pagine. Poniamo ad esempio di avere una pagina ~/pippo.aspx, questa durante la compilazione darà luogo ad un assembly così denominato

App_Web_pippo.aspx.cdcab7d2.cdcaravs.dll

L'ultima parte del nome in realtà viene generata in modo casuale per evitare che possano esserci sovrapposizioni nei nomi. Questo pone un sottile problema. Nel caso di pagine di cui venga fatto il rewrite, mediante l'uso di una regular expression, come ormai abbastanza consueto, ci si può trovare di fronte al problema che venga compilato un assembly per ogni possibile combinazione di caratteri nel nome file. Ad esempio con un url fatto in questo modo:

~/articles/2837922.aspx

l'assembly diventerebbe

App_Web_2837922.aspx.cdcab7d2.cdcaravs.dll

Ma naturalmente ci sarebbe un assembly per ogni singolo articolo anche se in realtà la pagina che gestisce la visualizzazione è sempre la stessa. Personalmente mi sono trovato di fronte ad una condizione analoga implementando un VirtualPathProvider che mappa le pagine fisiche su un database. Per questo motivo alle fine ho dovuto implementare un rewrite in modo tale che l'url venisse mappato sempre e comunque su un nome pagina univoco, perchè solo in questo modo l'applicazione può essere fruibile.

Ora, dovendo realizzare un url rewriting la scelta è dovuta ricadere sull'uso di un HttpHandlerFactory per il semplice motivo che un rewrite implementato mediante HttpModule causa un comportamento anomalo della FormsAuthentication. Infatti, qualora si cerchi di accedere ad una pagina protetta, la FormsAuthentication effettua il redirect sulla pagina /login.aspx o su quella che è definita come tale in configurazione. A questo punto entra in gioco l'HttpModule che riscrive l'url convertendolo in qualcosa che tipicamente verrà riconosciuto dalla FormsAuthentication nuovamente come un url protetto e quindi avverrà dinuovo un redirect alla pagina di login causando un loop infinito.

L'unica soluzione che ho trovato è appunto realizzata mediante l'uso di una HttpHandlerFactory che però deve essere realizzata in modo molto particolare. In sostanza per realizzare questa factory occorre estendere la classe PageHandlerFactory - per inciso quella che normalmente intercetta le chiamate alle pagine aspx - perchè è proprio questa factory che contiene la logica relativa i VirtualPathProvider. Se si prova a scrivere un proprio HandlerFactory che faccia uso come di consueto del PageParser per creare l'istanza della pagina, il VirtualPathProvider non verrà più eseguito. Perciò ecco che l'unico sistema è quello di intervenire facendo l'override del metodo GetHandler() chiamando alla fine il metodo originale.

In questo modo potremo avere il meglio di entrambi i sistemi. Il VirtualPathProvider otterrà in input il virtualPath riscritto dal quale desumeremo la pagina da caricare, ma che sarà univoco e perciò darà luogo ad un solo assembly. D'altro canto l'HttpContext manterrà l'url originale e quindi saremo in grado di ingannare la FormsAuthentication e così evitare l'ingresso nel ciclo infinito nella login.


Tipicamente vengono descritti due modi per effettuare la registrazione di un VirtualPathProvider, ma entrambi non mi piacciono. Il primo è quello di lavorare nel Global.asax intercettanto l'inizializzazione dell'applicazione, mentre il secondo è quello di usare il metodo statico Init() all'interno della App_Code. L'idea di esporre questa attività in questi punti non mi piace perciò mi sono inventato un altro metodo che ritengo più pulito.

Quello che faccio è di estendere la classe HttpApplication e di effettuare l'override del metodo Init(). In seguito modifico il Global.asax in modo tale che esso erediti dalla mia classe estesa invece che da HttpApplication. Nel metodo init effettuo la chiamata che registra il nuovo Virtual PathProvider.

Technorati tags: ,

Lo sapevate che un'immagine può avere un'immagine di background? La cosa può sembrare assurda ma si rivela di una certa utilità quando si deve referenziare un'immagine in uno skin di ASP.NET. Avevo già accennato a questa tecnica in un mio precedente post, riguardo l'utilizzo di un div con immagine di background per poter spostare l'url di un'immagine nel css o nella skin. Tuttavia il div ha qualche limitazione dovuta al fatto che non ha una "consistenza solida" che invece un'immagine ha. Talvolta succede quindi che il div venga schiacciato oppure che non lo si riesca a linkare (così come è successo stamattina all'amico Andrea Dottor). La soluzione è proprio quella di adottare una immagine trasparente con un css analogo a quello del div che specifica un background.

<img src="images/spacer.gif" id="myImage" border="0" />

img#myImage
{
    background: url(images/my_image.gif);
    display: block;
    width: 20px;
    height: 20px;
}

Naturalmente l'immagine spacer.gif deve essere rigorosamente trasparente...

powered by IMHO 1.3


Chi avesse provato ad utilizzare le risorse localizzate, spostandone lo storage nel database come ho spiegato tempo fa, si sarà reso conto che spesso Visual Studio 2005 restituisce un errore di compilazione che in buona sostanza informa che l'IDE non è in grado di trovare la risorsa specificata.

Questo succede perchè le risorse localizzate vengono caricate già dall'ide di Visual Studio. In realtà quando inserite un ExpressionBuilder questo viene immediatamente convertito nel codice relativo ed eseguito, talvolta anche troppo rapidamente. Ora, nel mio esempio, ma anche in molti altri che si trovano in rete, il codice dell'ResourceProvider si appoggia alla istanza singleton di HttpContext, esposta dalla proprietà Current per determinare in base all'url chiamato il corretto set di risorse. Quando siamo a DesignTime in Visual Studio 2005, non esiste un context HTTP per cui il codice eseguito genererà nella maggioranza dei casi un'eccezione per cui l'ide mostrerà l'errore suddetto.

La soluzione a questo punto è banale: occorre testare il valore di HttpContext.Current per verificare che non sia null prima di interrogare il database. Nel mio caso, qualora il contesto http sia nullo mi premuro di fornire al ResourceProvider dei parametri di default che si occupano addirittura di creare la risorsa mancante riempiendola con un valore fittizio che rende semplice la sua ricerca e popolazione durante la fase di labeling dell'applicazione.

Il codice è semplice:

if (HttpContext.Current == null)
    MakeDefaultResourceContext();

Buone risorse a tutti...

powered by IMHO 1.3


Click here for English Translation

Come promesso ecco il codice di esempio relativo il Workaround del bug segnalato nei giorni scorsi. Innanzitutto occorre dire che, nonostante Microsoft sul Product Feedback Center continui a segnalare che non riesce riprodurre il comportamento, il bug è presente. Il problema probabilmente deriva dal fatto che la formattazione InvariantCulture è sostanzialmente analoga a quella es-US perciò il codice postato va in errore nelle culture diverse ma funziona perfettamente negli U.S.A.

Veniamo ora brevemente a descrivere il metodo utilizzato per aggirare il problema: il concetto si basa sull'utilizzo dell'evento updating della datasource e sull'osservazione che il bug segnalato si presenta esclusivamente quando nella datasource viene specificato l'attributo DataObjectTypeName. In questo caso solamente infatti la datasource tenta di creare una istanza del tipo specificato e di mappare i dati ricevuti dal controllo su questo oggetto, causando così il verificarsi dell'errore. Non specificando questo parametro però sostanzialmente si vanfica l'uso della ObjectDataSource perchè il metodo utilizzato per l'update riceverà tutti i campi separatamente anzichè una istanza dell'oggetto. E' del tutto evidente che è sicuramente possibile effettuare manualmente la mappatura di tutti i parametri ricevuti dal metodo sulla classe richiesta, tuttavia è altrettanto evidente che così facendo ci si espone ad un degrado della manutenibilità del codice in quanto ad esempio aggiungendo una proprietà alla classe target saremmo costretti ad intervenire nei metodi che effettuano la mappatura.

La mia tecnica invece si propone di ripristinare per quanto possibile il normale flusso di lavoro della datasource, intervenendo subito prima della chiamata del metodo update per effettuare la mappatura in modo generalizzato per ogni tipo di oggetto di destinazione. La tecnica comporta che si debbano prelevare i dati provenienti dal runtime nella proprietà InputParameters, mapparli sulla classe richiesta ed in seguito rimuovere tutti i parametri da questa collection ed aggiungervi invece l'oggetto mappato; ecco uno spezzone di codice:

/// 


/// Handle Updating event of ObjectDataSource
/// 

/// ObjectDataSource
/// 
event arguments
protected void Source_Updating(object sender, ObjectDataSourceMethodEventArgs e)
{
    
// instantiate utility class for property mapping
    
DictionaryMapper mapper = new DictionaryMapper();
    
// create target object running mapper
    
Employee employee = mapper.MapDictionary(e.InputParameters);
    
// clear input parameters
    
e.InputParameters.Clear();
    
// add the result object
    
e.InputParameters.Add("item", employee);
}

A questo scopo ho creato una classe, la DictionaryMapper che essendo appunto generica consente di mappare qualunque le proprietà su qualunque tipo di destinazione. La classe DictionaryMapper i buona sostanza utilizza la reflection per settare le proprietà dell'oggetto destinazione ed applica qualora serva un metodo di conversione corretto, basato su CultureInfo.CurrentUICulture:

/// 


/// Perform conversion of the given string to the required type
/// 

/// value to convert
/// 
typer to convert to
/// 
converted value
private object ConvertFromString(string value, Type targetType)
{
    
if (!(value is string))
        
return value;

    
// create the converter
    
TypeConverter converter = 
        TypeDescriptor.GetConverter(targetType);

    
// if value contains something
    
if (value != null)
        
// convert string to target type using not InvariantCulture
        
return converter.ConvertFromString(
            
null
            CultureInfo.CurrentUICulture, 
            (
string)value);

    
// create an empty instance of target type
    
return converter.CreateInstance(null);
}

Il processo di conversione utilizzato è analogo a quello che utilizza la ObjectDataSource, ma invece che invocare il metodo convertFromInvariantString() si fa uso di ConvertFromString() passando appunto il valore della Cultura corrente. Nel web.config del progetto che fa da Proof-of-concept ho forzato la cultura italiana in modo tale che l'errore si presenti ovunque.

Download: CultureBug_workaround.zip (4427 Bytes)

powered by IMHO 1.3


A proposito del bug che ho segnalato ieri sera, vorrei rispondere pubblicamente alla domanda che mi ha posto Michele con un commento. In effetti, la prima cosa che ho fatto, subito dopo aver segnalato il palese errore è stato di rivolgermi al Product Feedback Center dove ho trovato naturalmente che il bug era già stato segnalato. Ho provveduto così a informare che anche io ho validato il bug e speriamo che la prossima service pack del framework risolva la cosa. Per il momento però ho trovato un workaround, che però è parecchio laborioso. Portate pazienza, preparo un progettino e poi vi faccio sapere.

powered by IMHO 1.3


Abbiate pazienza, non è mia abitudine lamentarmi, ma stavolta lo sbottare ci sta davvero tutto. Ma prima del fatto è bene che vi racconti l'antefatto. Stasera, io e il collega Andrea Dottor ci siamo scontrati con un comportamento apparentemente inspiegabile. Un pezzo di codice apparentemente perfetto falliva miseramente e inspiegabilmente. In buona sostanza quello che stavamo facendo era tutto sommato semplice; Un campo di tipo Textbox riceveva una stringa in che rappresentava una valuta, per la precisione "0,5" in seguito tale stringa arrivava al validatore serverside che utilizzando decimal.TryParse() verificava che si trattasse effettivamente di un decimal. Questo controllo, applicato sia con la CultureInfo che senza veniva eseguito perfettamente restituendo un chiaro e lampante "true". Tuttavia, pochi millisecondi dopo, quando la datasource tentava di mappare i campi su un oggetto di business, l'operazione miseramente scoppiava, con il seguente errore: "0,5" is not a valid value for Decimal.

Alla fine, solo analizzanto il codice del framework linea per linea con Reflector siamo giunti a capire il motivo per cui questa banale operazione scoppiava:

private static object ConvertType(object value, Type type, string paramName)
{
      
string text1 = value as string;

      
if (text1 != null)
      {
            TypeConverter converter1 = TypeDescriptor.GetConverter(type);

            
if (converter1 == null)
            {
                
return value;
            }
            
try
            
{
                
value = converter1.ConvertFromInvariantString(text1);
            }
            
catch (NotSupportedException)
            {
                
throw new InvalidOperationException(
                    SR.GetString("ObjectDataSourceView_CannotConvertType", 
                        
new object[] { paramName, typeof(string).FullName, type.FullName }));
            }
            
catch (FormatException)
            {
                
throw new InvalidOperationException(
                    SR.GetString("ObjectDataSourceView_CannotConvertType", 
                        
new object[] { paramName, typeof(string).FullName, type.FullName }));
            }
      }
      
return value;
}

Questo codice, estratto dalla classe ObjectDataSourceView è la causa del misfatto e precisamente la riga che chiama il metodo ConvertFromInvariantString() del TypeConverter. La ObjectDataSourceView, per inciso, è l'oggetto che all'interno della ObjectDataSource si incarica di effettuare le operazioni incapsulando al suo interno una rappresentazine dello storage. La chiamata a ConvertFromInvariantString() scritta in questo modo impone che un dato decimal, ma con tutta probabilità anche una data o un double, debba essere formattato con cultura neutra, e cioè nel nostro caso con il punto al posto della virgola. Una chiamata corretta sarebbe stata la seguente:

value = converter1.ConvertFromString(null, CultureInfo.CurrentCulture, text);

In questo modo la datasource avrebbe tenuto conto della cultura corrente e quindi si sarebbe adeguata al contesto del thread, consentendo alla conversione di avvenire correttamente. Ora, purtroppo i metodi in questione sono privati e perciò non è possibile porre rimedio a questo problema cercando di sostituire la ObjectDataSourceView con un oggetto che sia stato patchato. In realtà credo di avere un'idea per una soluzione, che però tenterò domattina, ma lasciatemi dire che un errore del genere non me lo sarei mai aspettato.

powered by IMHO 1.3

 


Su xe.net è apparso il mio secondo articolo che questa volta porta con se un componente riutilizzabile da non perdere. Si tratta di un SiteMapProvider che semplifica enormemente la creazione di provider custom che attingano i dati della mappa da una qualsiasi sorgente. Nell'esempio allegato all'articolo oltre al codice di questo StaticSiteMapProvider che risolve i problemi di concorrenza che affliggono queste implementazioni, anche un semplice provider alimentato da database SqlServer

Link: Uno StaticSiteMapProvider molto flessibile

Con l'occasione ho anche postato il secondo screencast dedicato al pattern singleton

SCREENCAST: Implementare un Sigleton in C#


Continuo sull'argomento System.Configuration, proponendovi un esempio di come creare una collection di ConfigurationElement custom. Per capire l'applicazione dell'esempiuo che sto per introdurre bisogna una ttimo soffermarsi su una particolare tipologia di elementi di configurazione. Mi riferisco ad esempio al nodo <providers> all'interno di una sezione di configurazione di MembershipProvider piuttosto che magari la sezione dedicata ad httpHandler e httpModules. All'interno di questo tipo di sezioni è possibile aggiungere elementi, ma anche rimuoverne con le seguenti sintatti:

<add />

<remove />

<clear />

Nel framework 2.0 questo tipo di sezioni sono perfettamente definibili da parte dell'utente implementando una ConfigurationElementCollection. Si tratta in breve di estendere una classe del framework, implementando un certo numero di metodi: nell'esempio che riporto qui di seguito ecco come realizzare la versione base che accetterà l'aggiunta di nuovi elementi con <add />

public class ApplicationInfoCollection : ConfigurationElementCollection
{
    
protected override ConfigurationElement CreateNewElement()
    {
        
return new ApplicationInfo();
    }

    
protected override object GetElementKey(ConfigurationElement element)
    {
        
return ((ApplicationInfo)element).Name;
    }
}

Con queste poche righe di codice avremo definito una sezione di configurazione che accetta tutte le chiavi che abbiamo elencato poco fa, attribuendo loro la funzionalità che ci aspetteremo. In particolare i due metodi si occupano di creare una nuova istanza di elemento per la collection e di restituire il valore della proprietà considerata chiave all'interno di esso. Questo consente oltre che creare gli elementi con <add /> anche di rimuoverli specificando a quale di essis ci si riferisce. Non rimane ora che esporre una proprietà in una classe:

/// <summary>
/// 
Applicazioni registrate
/// </summary>
[ConfigurationProperty("applications", IsDefaultCollection = false)]
[ConfigurationCollection(
typeof(ApplicationInfo))]
public ApplicationInfoCollection Applications
{
    
get return (ApplicationInfoCollection)this["applications"]; }
    
set this["applications"] = value; }
}

Il primo attributo informa che ci stiamo riferendo ad una proprietà di configurazione che quindi avrà un corrispondente elemento nel file di configurazione. L'elemento in questo caso si chiamerà <applications>. Il secondo attributo invece consente di dire quale tipo di di elemento contiene la collection; Questo in effetti può sembrare ridondante... ma di questo non prendetevela con me.

powered by IMHO 1.3


License & Disclaimer

I contenuti disponibili in questo weblog sono resi disponibili sotto la licenza di tipo "Creative Commons License".
Questo blog non rappresenta una testata giornalistica in quanto viene aggiornato senza alcuna periodicità.
Non può pertanto considerarsi un prodotto editoriale ai sensi della legge n. 62 del 7.03.2001