Ecco un paio di simpatici tools per generare Sitemap. La prima (http://odetocode.com/Blogs/scott/archive/2005/11/29/2537.aspx) è una macro che genera il file web.sitemap dalla struttura del sito web ricavata da visual studio. Il secondo (http://weblogs.asp.net/bleroy/archive/2005/12/02/432188.aspx) scandisce la struttura del sito ASP.NET e genera un file xml adatto a Google Sitemaps.


Torno ancora sull'uso delle risorse in ASP.NET 2.0 perchè ho scoperto un comportamento che di primo acchito mi ha creato qualche problema. Il problema derivava dal fatto di aver implementato l'ExpressionBuilder di ho parlato in un precedente post ed aver inserito le espressioni all'interno di alcuni Literal nella pagina. Chi avesse provato questo codice si sarà reso conto che la Culture restituita dal Thread corrente è sempre quella di default del sistema e che ogni tentativo per modificarla non ha l'esito sperato. In sostanza quello che accade è che il momento in cui vengono valutate le espressioni nella pagina è precedente a qualunque altro evento della pagina stessa, precedente perfino al PreInit e quindi non è possibile impostare la cultura corrente prelevandola ad esempio da una Session oppure dal profilo utente.

Questo comportamento deriva dal fatto che le espressioni vengono valutare nel momento in cui la pagina viene costruita e non durante il normale flusso di esecuzione. Per capire meglio quanto sto dicendo, e la conseguente soluzione che proporrò, è interessante provare a fare un semplice test. Preso il codice del post precedente e creata una pagina funzionante che usa una espressione si può provare ad introdurre un errore nel codice generato dal CodeDom. Io ho scelto ad esempio di restituire il nome di un metodo che non esiste. La cosa curiosa è che Visual Studio genera immediatamente un errore, a differenza di quello che ci si potrebbe attendere, e in seguito a tale errore ci mostra il sorgente della pagina aspx generata. Ecco riportato il punto relativo la espression:

@__ctrl.Text = 
    System.Convert.ToString(
        SqlResource.GetObject1("hallo_world"), 
        System.Globalization.CultureInfo.CurrentCulture);

 

Riportato in rosso il codice generato da CodeDom, cui ho aggiunto l'1 finale per causare l'errore. Ora, se con un po' di pazienza si risale il flusso di esecuzione si arriverà a notare che il punto di partenza dello stesso è nel metodo override FrameworkInitialize(). FrameworkInitialize è un metodo che in .NET 1.1 non era documentato e il cui uso era sconsigliato. Risalendo ancora il flusso di esecuzione, stavolta affidandosi a Reflector in breve si vede che FrameWorkInitialize è il primo metodo che viene chiamato all'interno della pagina direttamente dalla ProcessRequest quindi molto prima che gli eventi PreInit e Init si verifichino. Ne deriva che l'unico punto possibile nella pagina per impostare la CultureInfo è il costruttore.

Per risolvere alla radice questo problema, dato che non mi piaceva l'idea di usare il costruttore per questo genere di attività, ho deciso di scrivere un piccolo HttpModule di poche righe che si incarichi di questa attività. Il vantaggio di usare l'HttpModule è che estende facilmente il comportamento a tutte le pagine/webservices sotto il controllo del runtime senza che sia necessario scrivere una classe base per ognuna di esse. Ecco riportato il codice del Modulo:

public class GlobalizationModule : IHttpModule
{
    
public void Dispose()
    {}

    
public void Init(HttpApplication context)
    {
        context.PreRequestHandlerExecute += 
            
new EventHandler(context_PreRequestHandlerExecute);
    }

    
void context_PreRequestHandlerExecute(object sender, EventArgs e)
    {
        Thread.CurrentThread.CurrentUICulture = 
            
this.CurrentCulture;
    }

    
private CultureInfo CurrentCulture
    {
        
get
        
{
            
return CultureInfo.CreateSpecificCulture(
                HttpContext.Current.Profile.GetPropertyValue("Culture") 
as string);
        }
    }
}

Il modulo fa uso dell'evento PreRequestHandlerExecute che viene eseguito subito prima che il runtime chiami la ProcessRequest della pagina. In questo modo l'impostazione della cultura avverrà dopo che il costruttore della pagina è stato eseguito, ma subito prima che si entri nel normale flusso di esecuzione. Personalmente ho scelto di prelevare la Culture corrente dal Profile dell'utente collegato. In questo modo si rende persistente la cultura utilizzata attraverso diverse sessioni oltre che su diverse chiamate.

Occorre infine segnalare un particolare rilevante. Visto quello che è il flusso di esecuzione della chiamata, non ho ancora trovato un metodo valido per cambiare la Culture allo scattare di un evento di un controllo. Mi spiego meglio: se ad esempio nella pagina disponiamo della classica DropDownList con tutte le lingue e volessimo intercettare l'evento SelectedIndexChanged per modificare la lingua della pagina corrente scopriremmo ben presto che questo ha un comportamento anomalo. Infatti a ben pensarci quando si entra nell'handler dell'evento e si imposta la nuova culture, la pagina è stata già generata nella lingua precedente. Perciò l'unica cosa da fare è di usare un Response.Redirect(Request.Path) dopo aver impostato la nuova cuture che otterrà l'effetto di ricaricare completamente la pagina. In questo modo però si perde completamente lo stato della pagina che poteva essere memorizzato nella ViewState. La morale di tutto ciò è quindi: Create sempre una pagina di "profilo utente" che permetta di impostare la Culture prescelta, e togliete di mezzo bandierine e tendine della lingua... non è una gran perdita.

powered by IMHO 1.3


Tempo fa, quando ancora il Framework 2.0 era solo in beta avevo accennato all'uso di un nuovo tipo di espressioni per il recupero di risorse. Oggi, la pratica lavorativa di tutti i giorni ha portato alla luce le potenzialità di questo tipo di espressioni che nel gergo del framework si chiamano ExpressionBuilders. La potenza degli ExpressionBuilder è davvero notevole dato che con semplicità è possibile crearne di nuovi per soddisfare le esigenze di ogni progetto.

Poniamo ad esempio di voler estrarre le risorse di una pagina ASP.NET (le stringhe localizzate ad esempio) da una tabella di un database SqlServer anzichè da un assembly satellite. Penso siamo tutti daccordo che questa operazione in ASP.NET 1.1 non è per nulla semplice e richiede la scrittura di parecchio codice. Con la versione 2.0 del framework si sarebbe tentati di scrivere una cosa del genere:

<asp:Literal runat="server" Text="<%$ SqlResource: hallo_world %>" />

E così facendo ottenere Hallo World!, Ciao Mondo! o Hallo Welt!, rispettivamente se abbiamo scelto la lingua inglese, italiana o tedesca. La keyword SqlResources non esiste nel framework, ma con un po' di codice ben piazzato è possibile registrare un nuovo ExpressionBuilder che risponda a tale keyword ritornandoci la risorsa corrispondente alla chiave e alla cultura selezionate, recuperandole da un database sql server. Ecco come lo si può registrare:

<compilation>
    <expressionBuilders>
        <add 
            
expressionPrefix="SqlResource" 
            
type="Elite.Utilities.Resources.SqlResourceExpressionBuilder, Elite.Utilities.Resources"/>
    <
/expressionBuilders>
<
/compilation>
 

Per creare un expression builder è necessario estendere l'omonima classe ExpressionBuilder implementando almeno un metodo. Tale metodo ha il compito di restituire una porzione di codice che reperisca la risorsa a runtime. In realtà questa è l'unica parte che presenta una certa difficoltà perchè occorre conoscere il CodeDom per generare uno spezzone di codice. Ecco nel riquadro un esempio tratto dal mio codice:

public override System.CodeDom.CodeExpression GetCodeExpression(
    System.Web.UI.BoundPropertyEntry entry, 
    
object 
parsedData, 
    ExpressionBuilderContext context)
{
    CodeMethodInvokeExpression invokeMethod = 
        
new 
CodeMethodInvokeExpression();

    invokeMethod.Method.TargetObject = 
        
new CodeTypeReferenceExpression(typeof
(SqlResource));
    invokeMethod.Method.MethodName = 
        "GetObject";
    invokeMethod.Parameters.Add(
        
new 
CodePrimitiveExpression(entry.Expression));

    
return 
invokeMethod;
}

Il codice così generato verrà inserito dal runtime di ASP.NET nella parte nascosta della partial class che rappresenta la pagina. Questo codice genererà una riga di codice come la seguente:

SqlResource.GetObject("hallo_world")

A questo punto è sufficiente scrivere questa classe con un metodo statico GetObject() che accetta la chiave della risorsa e lo preleva dal database. Il compilatore genererà il codice suddetto e lo inserirà nel flusso della pagina asp.net. Personalmente ho scelto di leggere in Cache una pagina intera di risorse sotto form di un DataTable, e poi se la cache è già presente usare la Select() per trovare il record che mi serve: Ecco come:

public static object GetObject(string key)
{
    
string 
path = HttpContext.Current.Request.Path;
    
string 
culture = Thread.CurrentThread.CurrentUICulture.Name;

    
// in particolare questo metodo preleva le risorse dal db
    
DataTable resources = GetCachedResources(path, culture);

    
if (resources != null
)
    {
        DataRow[] rows = 
            resources.Select(
                
string
.Format("ResourceKey='{0}'", key));

        
if 
(rows.Length > 0)
            
return 
rows[0]["ResourceValue"].ToString();
    }

    
return 
"resource not found";
}
 

In particolare il metodo GetCachedResources() non fa altro che cercare in cache la presenza del DataTable e nel caso in cui non lo reperisca in Cache ottiene i dati dal database e li memorizza nella medesima cache.

powered by IMHO 1.3

 


Se vi dovesse capitare di usare AuthorizationStoreRoleProvider da ASP.NET 2.0 non avrete alcun problema a farlo fintanto che la macchina in cui gira l'applicazione è Windows 2003. Tuttavia tipicamente le applicazioni vengono sviluppate su una macchina Windows XP quale è di solito quella dello sviluppatore. In questo caso sarà necessario installare dapprima il Windows Server 2003 Administration Tools Pack dato che su XP l'AuthorizationManager non è installato. Nell'adminpak, troverete anche uno script vbs che vi consente di sapere la sua versione se per caso fosse già installato. Vale la pena di lanciarlo prima di procedere all'installazione.

Questo però non basta. Infatti per poter usare AuthorizationStoreRoleProvider dalle pagine ASP.NET è richiesta l'installazione nalla GAC del AzMan Primary Interop Assembly che deve essere estratto dal Windows 2000 Authorization Manager Runtime (non è un errore, proprio Windows 2000). Per farlo procedete in questo modo:

  1. Scaricate il Windows 2000 Authorization Manager Runtime
  2. Lanciate l'eseguibile che creerà due directory delle quali una denominata \pia
  3. Nella directory \pia\1.2 troverete l'assembly Microsoft.Interop.Security.AzRoles.dll
  4. Installate nella gac l'assembly con il tool di configurazione oppure da linea di comando

Senza questa procedura ogni tentativo di usare l'AuthorizationStoreRoleProvider solleverà un'eccezione che vi informa appunto che tale componente non è presente sulla vostra macchina.

powered by IMHO 1.3

 


Chi avesse sperimentato la creazione di pagine ASP.NET con Visual Studio 2005 si sarà certamente accorto che le classi che implementano tali pagine vengono create in quello che comunemente va sotto il nome di "Global Namespace". Questa nuova feature per quanto comoda può alle volte creare qualche noia se ad esempio nel team le scelte di design richiedono che le classi siano poste in dei namespace ben definiti. Considerato che l'aggiungere la dichiarazione del namespace a mano è decisamente scomodo, dato che poi ci si deve anche assicurare che l'attributo Inherits della direttiva @Page sia correttamente assegnato è consigliabile predisporre un template da utilizzare ogniqualvolta si abbia la necessità di realizzare una nuova pagina. In questo post di Scott Guthrie è riportato uno step-by-step che spiega come confezionare questo template.

Link: http://weblogs.asp.net/scottgu/archive/2005/09/09/424780.aspx 

powered by IMHO 1.3


Ieri lavorando con i temi di ASP.NET 2.0 mi sono imbattuto nell'esigenza di impostare una immagine all'interno della pagina mediante il file skin del tema. La cosa di per se banale, ha molte diverse soluzioni. Quella che di primo acchito viene in mente è di usare un controllo impostandone lo SkinId e in seguito referenziarlo nel file della skin. Questo però è un approccio che può andare bene solo se le immagini nella pagina sono decisamente poche, altrimenti il numero di controlli web presenti che il runtime dovrà elaborare aumenta vertiginosamente. Alla fine ho scelto una soluzione, basata sull'uso dei css che mi è parsa degna di essere segnalata perchè nel mio caso mi ha esonerato dall'appensantire eccessivamente la pagina. L'idea è di usare un div al posto di un tag img e di impostarne il background via css.

<div id="myImage"></div>

Poi nel file css è sufficiente impostare il background del div come segue

div#myImage
{
    background-imageurl(images/my_image.gif);
    width:25px;
    height:25px;
}

In questo modo una immagine delle dimensioni di 25x25 pixels il cui vantaggio è di aver specificato l'url in uno stile. In questo modo è possibile usare un css all'interno di un tema di ASP.NET e soprattutto di evitare l'uso di innumerevoli webcontrol. Si tratta di un accorgimento banale, ma il risultato è degno di nota, anche poichè questa tecnica è valida con tutti i browser esistenti.

powered by IMHO 1.3


Valutando i servizi di connettività e di server virtuale di NGI, uno dei provider più apprezzati dai professionisti in Italia, mi è caduto l'occhio su una interessante news. NGI supporta ASP.NET 2.0 sui suoi servizi di hosting a dei prezzi tutto sommato accessibili anche se non paragonabili a quelli di provider più importanti quali webhost4life. Interessante anche il modo con cui NGI da la notizia, invitando a scaricare la versione express di Visual Studio 2005.

Link: http://www.ngi.it/virtuo/index.asp

powered by IMHO 1.3


Nel numero di ottobre di Computer Programming sarà pubblicato il primo articolo di una serie dedicata a ASP.NET 2.0. L'argomento della prima puntata saranno le Membership API con un esempio di creazione di Custom Membership Provider e Role Provider. Nei mesi prossimi la serie continuerà toccando altri argomenti di ASP.NET 2.0 come le MasterPages, i Temi, e i nuovi WebControls. Curiosamente in questi giorni è uscito un analogo articolo di Andrea Saltarello, ma per chi non mastica bene l'inglese, oppure, come me, non ama scervellarsi a tentare di comprendere un testo in tale lingua, il mio articolo sarà sicuramente gradito.

Buona lettura!

powered by IMHO 1.3


Curiosando un po' nel web ho scovato questo interminabile articolo su un argomento davvero basilare ma che mi sembra trattato con la massima precisione e con ilmassimo dettaglio possibile. L'articolo esplora con dovizia di particolari tutti gli aspetti della ViewState, cominciando dal suo semplice uso fino alle parti più ostiche come la crittazione e la persistenza.Ottima lettura per chi si avvicina ad ASP.NET

Link: ASP.NET Resources - ASP.NET State Management: View State

powered by IMHO