Il nuovo modello di accesso ai dati basato sui DataSourceControl consente con facilità di fare il binding di controlli complessi come al GridView, la DetailView e la FormView con una facilità impensabile. I controlli DataSourceControl hanno un design fortemente configurabile per adattarsi alle varie esigenze. Ad esempio è possibile con facilità parametrizzare il risultato della DataSource per mezzo di Parametri che possono essere passati come argomenti ai metodi Select, Insert, Update e Delete. I parametri vengono poi tradotti in modo conforme al tipo di DataSourceControl. Ad esempio nel caso della ObjectDataSource essi divengono parametri del metodo richiamato durante una operazione di accesso ai dati.

Esistono un buon numero di questi parametri, ad esempio Parameter, QueryStringParameter, SessionParameter, che hanno il compito di prelevare l'informazione da qualunque supporto sia necessario interfacciare. La struttura di questi parametri è molto semplice tanto che è possibile con semplicità crearne di personalizzati. Ecco ad esempio un tipo di parametro che legge il contenuto di Context.Items e lo passa alla DataSource:

public class ContextItemParameter : Parameter
{
    
private string itemName;

    
protected override object Evaluate(
        System.Web.HttpContext context, 
        System.Web.UI.Control control)
    {
        
if ((context != null) && (context.Items != null))
            
return context.Items[this.itemName];

        
return null;
    }

    
public string ItemName
    {
        
get return itemName; }
        
set { itemName = value; }
    }
}

Il nocciolo del lavoro si trova nel metodo Evaluate() che si occupa di trasformare il contenuto dell'item in un dato digeribile dai DataSourceControl

powered by IMHO 1.3

 


I Web User Controls, più spesso conosciuti con il diminutivo di "ascx" per l'estensione che contraddistingue il loro template, sono sovente sottovalutati per la loro intrinseca facilità di uso. Questo è un grave errore in cui si incorre nell'errata supposizione che la loro semplicità sia ottenuta sacrificando la l'estensibilità tipica dei Custom Web Controls. COn questo breve esempio vorrei dimostrarvi quanto sto dicendo; Poniamo di creare un semplice UserControl come segue:

<%@ Control 
    
Language="C#" 
    
CodeFile="TemplatedUserControl.ascx.cs" 
    
Inherits="TemplatedUserControl" %>
<asp:Table 
    
runat="server" 
    
ID="tblTris" 
    
CellPadding="5" 
    
CellSpacing="0" 
    
BorderStyle="None">
    <asp:TableRow>
        <asp:TableCell 
/>
        <asp:TableCell 
/>
        <asp:TableCell 
/>
    <
/asp:TableRow>
    <asp:TableRow>
        <asp:TableCell 
/>
        <asp:TableCell 
/>
        <asp:TableCell 
/>
    <
/asp:TableRow>
    <asp:TableRow>
        <asp:TableCell 
/>
        <asp:TableCell 
/>
        <asp:TableCell 
/>
    <
/asp:TableRow>
<
/asp:Table>

Come evidente si tratta di uno UserControl che contiene una semplice tabella con nove celle, distribuite su tre colonne per tre righe. Ora andiamo a modificare la classe che implementa la logica dello UserControl:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.ComponentModel;

public partial class TemplatedUserControl : System.Web.UI.UserControl
{
    
private ITemplate buttonTemplate;

    
protected override void OnInit(EventArgs e)
    {
        
int i = 0;
        
foreach (TableRow row in tblTris.Rows)
        {
            
foreach (TableCell cell in row.Cells)
            {
                ButtonTemplateContainer container = 
new ButtonTemplateContainer();

                ButtonTemplate.InstantiateIn(container);
                WebControl textControl = container.FindControl("TitleText") 
as WebControl;
                cell.Controls.Add(container);

                
if (textControl != null)
                {
                    textControl.Width = textControl.Height = Unit.Pixel(50);

                    
if (textControl is ITextControl)
                        ((ITextControl)textControl).Text = i.ToString();
                    
else if (textControl is IButtonControl)
                        ((IButtonControl)textControl).Text = i.ToString();
                }

                i++;
            }
        }

        
base.OnInit(e);
    }

    [PersistenceMode(PersistenceMode.InnerProperty)]
    [Browsable(
false)]
    [TemplateContainer(
typeof(ButtonTemplateContainer))]
    
public ITemplate ButtonTemplate
    {
        
get return buttonTemplate;  }
        
set { buttonTemplate = value; }
    }
}

public class ButtonTemplateContainer : 
    Control, 
    INamingContainer
{
}

Questo codice, trasforma lo UserControl appena creato in un semplice ed efficace TemplateControl. In pratica nelle nove celle viene iniettato il template generato sulla base di quanto richiesto nel markup. Ecco come è possibile usare lo UserControl:

<Elite:TemplatedUserControl runat="server" ID="ucTest">
    <ButtonTemplate>
        <asp:Button 
runat="server" ID="TitleText" />
    <
/ButtonTemplate>
<
/Elite:TemplatedUserControl>

In breve sarà possibile configurare un template in ogni istanza configurata dello UserControl allo stesso modo di quello che si farebbe con una GridView. Come evidente le potenzialità degli UserControls vanno ben aldilà di quallo che si può di primo acchito pensare.

powered by IMHO 1.3

 


MaintainScrollPositionOnPostback è un nuovo attributo della direttiva @Page che definirei senza ombra di dubbio impagabile. Per comprenderne il significato basta pensare a cosa succede alla pagina quando invochiamo un postback. Se ad esempio la pagina è molto lunga e scrolliamo verso il basso nel momento in cui interviene il postback normalmente il browser torna a posizionare la pagina all'inizio costringendoci ad un "lavoro di rotella" un po' scomodo. Impostando a true questa proprietà questo comportamento verrà modificato riportando sempre la pagina nella posizione in cui si trovava al momento del postback.

Un grazie a Davide Vernole che stamane mi ha segnalato questa novità...

powered by IMHO 1.3


L'accesso alla ViewState nell pagine ASP.NET, ma anche a Session, Cache ed Application tipicamente soffre della mancanza di type-safety che costringe a scrivere dei cast ripetuti. Con il breve e semplice metodo generico qui riportato è possibile semplificare la gestione di questi utili strumenti

public string MyValue
{
    
get return GetViewState<string>("MyValue", string.Empty); }
    
set { SetViewState<string>("MyValue", value); }
}

private T GetViewState<T>(string name, T defaultValue)
{
    
if (ViewState[name] == null)
        SetViewState<T>(name, defaultValue);

    
return (T)ViewState[name];
}

private void SetViewState<T>(string name, T value)
{
    ViewState[name] = 
value;
}

Il metodo descritto ha il vantaggio di consentire anche la lazy initialization del valore, così da evitare di avere dei fastidiosi null di ritorno.

powered by IMHO 1.3


Nel Framework 2.0 sono presenti una serie di controlli che sono in gradi di sfruttare un nuovo tipo di binding ai dati. Mi riferisco ai controlli gerarchici, come ad esempio il TreeView, il quale ricevendo una datasouce che implementi una determinata interfaccia è in grado di visualizzare dati che non siano delle semplici tabelle. L'implementazione della datasource gerarchica, richiede la realizzazione di una serie di classi a supporto che a loro volta si basano su interfacce e classi astratte.

Quest'oggi, nel districarmi in mezzo ad esse ho scoperto mio malgrado che quello che si trova in MSDN non è tutto oro colato. Per farla breve, dopo aver replicato esattamente un esempio della documentazione ed aver cercato per ore di capire dove stavo sbagliando mi sono deciso a compilare il codice dell'esempio per poi rendermi conto che esso sofriva dello stesso problema che stavo riscontrando nel mio. Niente meno che una StackOverflowException causata da una ricorsione infinita.

Mi chiedo quanti altri di questi esempi non testati esistano su MSDN.

powered by IMHO 1.3


Stamane ho fatto una scoperta che definirei sconcertante. La semplice lettura della proprietà Controls, in alcuni WebControl di ASP.NET causa notevoli malfunzionamenti al ciclo di vita della pagina. In particolare stavo utilizzando il metodo di cui ho parlato in un post precedente, per cercare dei controlli all'interno della gerarchia della pagina. Questa ricerca, se eseguita all'interno dell'OnInit, fa si che alcuni controlli non manifestino più gli eventi tipici. Ad esempio, un LinkButton non notificava più il "Click", e una FormView non era più in grado di gestire il postback correttamente.

Dopo una lunga estenuante ricerca, sono riuscito ad individuare la riga che era origine del problema, e con mio stupore mi sono reso contro che in quel punto non facevo altro che leggere la proprietà Controls per enumerare i controlli figlio. C'è voluta una breve indagine on il Reflector per arrivare ad una possibile spiegazione. La proprietà Controls non legge semplicemente il valore di un field, ma si occupa anche di inizializzare la collection nel caso in cui essa non sia già popolata; ecco il codice estratto da reflector:

public virtual ControlCollection Controls
{
      
get
      
{
            
if ((this._occasionalFields == null) || 
                (
this._occasionalFields.Controls == null))
            {
                  
this.EnsureOccasionalFields();
                  
this._occasionalFields.Controls = 
                      
this.CreateControlCollection();
            }
            
return this._occasionalFields.Controls;
      }
}

Come appare chiaro questo codice si occupa di istanziare una ControlCollection di default qualora essa non sia già presente. A quanto pare questa inizializzazione causa problemi se effettuata esternamente. Suppongo che da qualche parte, all'interno del ciclo di vita di un chiamata, esista una porzione di codice che verifica se tale proprietà è già inizializzata e si comporta di conseguenza in modi del tutto diversi. In effetti, a ben guardare la classe Control espone un metodo che compie questa verifica e che torna utile per aggirare questo malfunzionamento. Si tratta di HasControls(), un metodo che restituisce false se la proprietà non è inizializzata. Evidentemente in Microsoft il problema ho avevano previsto, e quindi hanno fornito un metodo per aggirarlo. Ecco quindi come dovrebbe essere modificato il codice dell'esempio precedente:

public static T FindControlRecursive<T>(Control root, string id)
    where T : Control
{
    
if (root.ID == id && root is T)
        
return root as T;

    
if (root.HasControls())
    {
        
foreach (Control child in root.Controls)
        {
            T foundControl = FindControlRecursive<T>(child, id);
    
            
if (foundControl != null)
                
return foundControl;
        }
    }

    
return default(T);
}

In questo modo non si modificherà lo stato del controllo. Rimane semplicemente da rilevare che si tratta di un comportamento decisamente subdolo, che andrebbe affrontato e corretto. Probabilmente sollevare un'eccezione sarebbe stato preferibile. A me è costato 3 ore di lavoro. Spero che a voi vada meglio.

powered by IMHO 1.3

 


Se si prova a dare alle webparts un aspetto un po' più accattivante rispetto a quello che hanno di default ci si scontra con un piccolo ma fastidioso bug, riconosciuto da Microsoft come tale.  Le webpart, volenti o nolenti hanno sempre un padding di 5 pixel attorno al contenuto e di 2 pixel attorno al titolo. Tale padding non è impostabile in alcun modo, ma per stessa ammissione del team di ASP.NET 2.0 è hard-coded all'interno del framework.

Esistono un paio di workaround suggeriti da Microsoft in ladybug, ma la loro applicazione è del tutto limitata e difficoltosa. L'unico modo veramente valido che ho trovato - suggerito da un utente - per superare questo problema è lo sfruttare la natura dei css. Chiunque usi i css sa che in cascata vengono applicati prima i fogli di style .css, poi il contenuto di <style> e infine il valore dell'attributo style di ogni tag con il risultato che quest'ultimo ha la precedenza sugli altri e risulta insuperabile. Detto questo è evidente che l'attributo style="padding:5px" che si trova sul <td> che incapsula il contenuto della webpart è pressochè impossibile da aggirare.

Esiste però una clausola nei fogli di stile che consente di sovrascrivere questo comportamento di default. Si tratta dello switch !important che se fatto seguire ad uno stile fa si che esso diventi preponderante rispetto a quelli di livello superiore.

.webPart
{
   padding: 0px !important;
}

Questo breve pezzetto di stile risolverà definitivamente il problema. Speriamo però che Microsoft ci metta una pezza al più presto.

powered by IMHO 1.3


Le nuove capacità di Profiling di ASP.NET 2.0 sono una feature molto apprezzabile anche in considerazione del fatto che sono in grado di gestire anche i profili anonimi, cioè consentono di persistere le impostazioni di profilazione anche per gli utenti che ancora non sono autenticati. Questo ad esempio torna utile nel momento in cui è necessario consentire la selezione della lingua anche ad un navigatore che non si è ancora registrato. Esiste però un piccolo problema per capire il quale vi farò un semplice esempio: immaginate di utilizzare il profilo anonimo appunto per persistere la lingua selezionata in un portale. Ora è evidente che qualora un utente anonimo imposti la lingua e in seguito si autentichi al portale, l'impostazione selezionata dovrà essere trasportata verso il profile autenticato. Per compiere questa migrazione esiste un evento apposito, gestibile nel global.asax in questo modo:

public void Profile_OnMigrateAnonymous(
    
object sender, 
    ProfileMigrateEventArgs args)
{
    ProfileCommon anonymousProfile = 
        Profile.GetProfile(args.AnonymousID);
        
    Profile.Culture = anonymousProfile.Culture;
    
    ProfileManager.DeleteProfile(args.AnonymousID);
    AnonymousIdentificationModule.ClearAnonymousIdentifier(); 
}

L'esempio è reperibile anche nella documentazione MSDN ma non è del tutto evidente.

powered by IMHO 1.3


Quest'oggi mi sono scontrato con un problema che pareva di banale soluzione ma che si è rivelato essere decisamente subdolo. L'obbiettivo da raggiungere era di caricare le webparts di una pagina dinamicamente da un database invece che inserirle nel markup come di consueto. A prima vista può sembrare che il framework offra quanto necessario per compiere questo compito. Il WebPartManager ad esempio espone il metodo AddWebPart() che sembra fatto a posta per quello, ma dopo un po' di tentativi si intuisce che c'è qualcosa che non va.

Il problema non è che il metodo in questione non funzioni, ma anzi, che funziona troppo. Infatti il difetto che si evidenzia quasi subito è che ad ogni postback le webpart vengono nuovamente aggiunte causando una crescita smisurata e inarrestabile. Qualunque tentativo di gestire questo comportamento infausto non ha i risultati sperati. A partire dal consueto "IsPostBack", fino alla verifica controllo per controllo che non si stiano inserendo duplicati tutti i tentativi falliscono meramente.

Il fatto è che, quando si aggiungono le webpart queste vengono persistite nel database di personalizzazioni. Perciò la volta successiva che la pagina viene costruita le personalizzazioni salvate causano la duplicazione delle webpart. Naturalmente a questopunto verrà persistita anche la duplicazione, con l'ovvio risultato che la volta successiva ci si ritrova con una triplicazione... e così via.

L'unica soluzione a questo problema l'ho trovata spulciando tra le sessioni della PDC'05. Il trucco sta nel creare un WebPartManager derivato da quello consueto e gestire al suo interno l'aggiunta delle webparts dinamiche nell'OnInit della pagina. In questo modo sarà possibile sfruttare la classe WebPartManagerInternals esposta per mezzo della proprietà Internals per settare le proprietà delle webpart aggiunte e farle per così dire "rientrare" nel normale ciclo di vita delle webpart.

Ecco un breve spezzone di codice:

// metodo chiamato nel Page.OnInit()
private void AddExternalWebParts()
{
    
// legge le webpart dallo storage
     
List<WebPartData> parts = 
         WebPartProvider.GetWebParts(
this.Page);

    
// cicla le webpart
    
foreach (WebPartData data in parts) 
    {
        
// istanzia lo usercontrol
        
Control uc = 
            
this.Page.LoadControl(data.AscxVirtualPath);
        
        
// crea la webPart che incapsula lo usercontrol
        
GenericWebPart webPart = 
            WebPartManager.CreateWebPart(uc);

        
// setta le proprietà        
        
Internals.SetZoneID(webPart, data.ZoneID);
        Internals.SetZoneIndex(webPart, data.ZoneIndex);
        Internals.SetIsShared(webPart, 
true);

        
// Aggiunge una WebPart statica
        
Internals.AddWebPart(webPart);
    }
}

Nell'esempio che ho linkato, nel folder ExternalWebPart è presente anche il codice per creare le connessioni tra webpart automaticamente. Ma questa è un'altra storia.

powered by IMHO 1.3


L'esperienza con le WebParts di questi giorni è stata l'occasione per scrivere un breve articolo per UgiDotNet che unitamente ad un utile Tip per aggirare un comportamento che potrebbe sembrare anomalo, descrive il meccanismo per cui ogni WebControl o UserControl può diventare una WebPart.

Buona lettura a tutti.

Link: http://www.ugidotnet.org/articles/articles_read.aspx?ID=108

powered by IMHO 1.3


Il web.config di ASP.NET 2.0 si sta rivelando una vera e propria miniera. Stamane, per puro caso ho scoperto un'altra chicca che mi accingo a proporvi. Celata, nei meandri del file di configurazione si trova una nuova sezione tagMapping che consente di sostituire tag all'interno di un Templated Control quale può essere una pagina, uno usercontrol o un qualunque controllo che esponga un template.

Per chiarire il concetto provate a pensare di aver utilizzato un WebControl all'interno di una vostra applicazione. Dopo che avete effettuato il deploy dell'applicazione in produzione vi rendete conto che una versione successiva dello stesso controllo utilizzata nell'applicazione gli darebbe una serie di feature importanti. Senza dover modificare il codice sarà sufficiente andare nel web.config e scrivere la seguente linea:

<pages>
    <tagMapping>
        <add 
            
mappedTagType="MyApp.Controls.NewControl" 
            
tagType="MyApp.Controls.OldControl"/>
    <
/tagMapping>
<
/pages>

Come risultato otterrete che l'applicazione istanzierà il nuovo webcontrol al posto di quello vecchio. La sostituzione del controllo avviene durante la fase di parsing del template per cui non introduce alcun overhead al momento dell'esecuzione.
Unico limite di questa tecnica è che il controllo da sostituire deve derivare da quello di partenza. In effetti più che comprensibile.

powered by IMHO 1.3


Se create un WebControl e poi decidete di applicare ad esso l'utilizzo dei temi è necessario avere un particolare accorgimento per le proprietà che questo espone se debbono contenere un url relativo alla root del tema.

Ad esempio, se create una proprietà del WebControl, che espone l'url di una immagine che utilizzate per il rendering (ButtonImageUrl) e poi impostate questa proprietà nel file .skin vi renderete immediatamente conto che l'url che viene passato a runtime al WebControl è relativo la root dell'applicazione e non a quella del tema. Per ottenere la trasformazione relativa al tema occorre applicare alla property un attributo di tipo UrlProperty:

[UrlProperty]
public string ButtonImageUrl
{
    
get return this.buttonImageUrl;  }
    
set this.buttonImageUrl = value; }
}

Interessante notare che tale attributo dispone anche di una proprietà Filter, che consente di discriminare il tipo di file che devono essere soggetti alla trasformazione. Ad esempio se scrivere l'attributo come segue, solo le immagini gif saranno ricalcolate sulla base della root del tema.

[UrlProperty(Filter="*.gif")]

powered by IMHO 1.3