Se provare a creare uno UserControl con ASP.NET 2.0 e poi tentate di registrarlo nel file di skin e applicando ad esso uno SkinId di settare alcune proprietà, vi troverete di fronte al seguente errore che ieri mi ha fatto perdere ben mezza giornata di lavoro:

Error 1 The control type 'ASP.MyControl_ascx' cannot be themed. C:\DEV\Test\Portal\App_Themes\my_theme\my_theme.skin 42 

Per risolvere questo problema occorre applicare un attributo alla classe che rappresenta lo UserControl in questo modo:

[Themeable(true)]
public partial class MyControl : UserControl
{
}

Chissa poi perchè di default gli UserControl non dovrebbero essere skinnabili? Ma questo è un altro paio di maniche...

powered by IMHO 1.3


Curiosamente stamane Simone mi ha preceduto di un soffio nel parlarvi di Url Rewriting. In questi giorni infatti mi sono trovato nella necessità di soddisfare una particolare esigenza di rewriting che mi ha fatto approfondire l'argomento e avevo in mente di scrivere questo post da un po'. Preso atto che la scelta di Simone è ricaduta sull'implementazine di un IHttpModule, è evidente che il mio post non è una ripetizione perchè in realtà il metodo che ho usato è basato su una tecnica che nell'articolo di MSDN che Simone ha citato viene appena sfiorata. Eccomi quindi a proporvi il metodo che ho recentemente utilizzato per il rewriting dell'url in una applicazione che vedeva le proprie pagine riportate parzialmente nel database e parzialmente sul filesystem.

La mia implementazione di rewrite è basata sulla creazione di un HttpHandlerFactory. Se consideriamo un HttpModule come un filtro che intercetta tutte le chiamate alle pagine di una applicazione, e un HttpHandler un gestore per particolari tipi di chiamate che si sostituisce a quello delle normali pagine, la HttpHandlerFactory invece è una classe che viene usata dal runtime per istanziare un HttpHandler specifico in base alla chiamata che esso riceve. Implementando l'interfaccia IHttpHandlerFactory, in sostanza ci si pone nella HttpPipeline al livello più alto possibile e si è in grado di decidere quale handler istanziare in base all'url che si riceve ed in seguito passare ad esso la chiamata.

Questa condizione nel mio caso era davvero esemplificativa. Si trattava infatti di effettuare una semplice query nel database - query che con l'implementazione di un meccanismo di caching è diventata unica in tutta la vita dell'applicazione - e in base al risultato decidere se istanziare l'handler delle pagine aspx normalmente oppure variando leggermente l'input per ottenere oltre al rewrite, il caricamento di una specifica pagina in base a quanto specificato nel database. Vedo di spiegarmi meglio; tanto per cominciare vediamo come fa il runtime ad istanziare l'handler per le pagine aspx.

IhttpHandler page = PageParser.GetCompiledPageInstance(url, pathTranslated, context);

Per mezzo di questa semplice riga, è possibile indicare al runtime di produrre una istanza di IHttpHandler del tutto uguale a quella che produce esso stesso in condizioni normali. I parametri indicano il path virtuale, il path fisico della pagina e l'HttpContext della chiamata. Ecco quindi che in realtà utilizzando tale metodo si può "imbrogliare" il runtime facendo caricare ad esso un file che si trovi in un percorso diverso da quello reale che esso ha calcolato sulla base della chiamata. Il trucco per fare il rewriting in questo caso è di avere una singola pagina "dbpage.aspx", nella root dell'applicazione cui redirigere tutte le chiamate che fanno riferimento al database e - nel mio caso - rifilare al runtime una pagina "fisica" scelta fra una rosa di possibili layout per mezzo di un campo nel database stesso. Ecco il codice:

public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
    PortalPage page = 
this.Indexer.FindPage(url);

    
if (page != null)
    {
        context.Items["CurrentPage"] = page;
        
return PageParser.GetCompiledPageInstance("~/dbpage.aspx", this.MapLayout(page.Layout), context);
    }

    
return PageParser.GetCompiledPageInstance(url, pathTranslated, context);
}

public string MapLayout(string layoutName)
{
    
return string.Format("~/layouts/{0}.aspx", layoutName);
}

Il codice parla da solo, ma vediamo lo stesso di spendere due parole. La prima riga reperisce l'istanza della PortalPage da un oggetto tenuto in cache, che rappresenta la struttura caricata dal database una volta per tutte. A questo punto, se la pagina viene trovata essa viene inserita nel contesto della chiamata e poi viene effettuato il rewrite modificando l'url in ~/dbpage.aspx e passando come path fisico una diversa pagina aspx - che per inciso contiene una serie di WebPartZones in un ContentPlaceHolder. Nel caso in cui la pagina non esista nel database, i parametri vengono passati così come sono ricevuti al PageParser che quindi costruirà l'IHttpHandler consueto.

Risultato di questo giochetto è che parte delle pagine dell'applicazione sono definite in una tabella del database e l'utente potrà navigare tale struttura come se fosse fusa con quella sul filesystem stesso.

Non mi rimane di segnalarvi che questa tecnica è utilizzabile anche con il framework 1.1, praticamente senza alcuna modifica.

powered by IMHO 1.3


Prosegue con questo post la saga, iniziata quando ho parlato degli ExpressionBuilders, dedicata alle risorse tipicamente utilizzate nelle applicazioni ASP.NET per depositare stringhe, e in genere proprietà da utilizzare nel rendering dell'interfaccia. In quel post spiegavo come realizzare un ExpressionBuilder che permettesse facilmente dei leggere le risorse da un database anziche da un più consueto file resx. Esiste tuttavia un altro metodo, per fare la medesima cosa, che in realtà è quello che il framework indica come quello corretto. Chi avesse provato a realizzare l'ExpressionBuilder forse si sarà reso conto che pur funzionando egregiamente esso ha delle limitazioni fastidiose. Giusto per indicarne una per tutte, quella che a me ha fatto approfondire ulteriormente l'argomento, qualora vi trovaste a localizzare un SiteMap ben presto vi renderete conto che in questo l'ExpressionBuilder non vi è di alcun aiuto. Il motivo è presto detto: la funzione degli ExpressionBuilder non è quella di leggere risorse, ma semplicemente quello che tipicamente viene usato per questa operazione, il ResourceExpressionBuilder è solo una minima sfaccettatura all'interno dell'architettura del framework dedicata all'argomento.

Se come ho fatto io imbracciate il vostro reflector, e lo puntate sulla classe System.Web.SiteMapNode, vi accorgerete che la proprietà Title (una di quelle che è passibile di localizzazione), racchiude al suo interno molta più logica di quella che ragionevolmente ci si potrebbe aspettare. Ecco un estratto da Reflector:

public virtual string Title
{
  
get
  
{
    
if (this._provider.EnableLocalization)
    {
      
string text1 = this.GetImplicitResourceString("title");
                 
      
if (text1 != null)
        
return text1;

      text1 = 
this.GetExplicitResourceString("title", this._title, true);

      
if (text1 != null)
        
return text1;
    }

    
if (this._title != null)
        
return this._title;

    
return string.Empty;
  }
}

Procedendo ulteriormente nell'esplorazione in breve si scoprira che il codice porta ad istanziare il medesimo ResourceExpressionBuilder e quindi in definitiva tentare di localizzare una proprietà della SiteMap corrisponde ad usare l'ExpressionBuilder "Resources". Di primo acchito questo mi è parso davvero strano. L'idea che mi ero fatto era che a quel punto si potessero usare esclusivamente i consueti file resx per localizzare una SiteMap.

In realtà nel framework esiste una interfaccia IResourceProvider, che è il punto giusto cui agganciarsi per spostare la fonte delle risorse dai normali file al database. Un po' celato all'interno del DOM del web.config si trova un attributo dell'elemento <globalization> denominato resourceProviderFactoryType. In tale attributo si dovrà immettere il tipo, derivato da ResourceProviderFactory, che ha il compito di creare il ResourceProvider deputato a recuperare le risorse. Mi rendo conto che detto così pare un po' complesso, ma questo meccanismo è molto efficace perchè consente di avere diversi ResourceProvider che lavorano assieme, istanziati alla bisogna in base al tipo di risorse che si desidera prelevare. In particolare il ResourceProviderFactory dispone di due metodi, uno per le risorse cosidette "locali" e uno per quelle "globali". Per comprendere la distinzione si dovra pensare alle risorse locali come quelle dedicate ad una singola pagina, infatti il relativo metodo richiede in ingresso il path della pagina, mentre per globali si intendono quello usate da tutte le pagine dell'applicazione. In quest'ultimo caso il metodo richiede una "classKey" cioè un qualificatore che permette di distinguere dei gruppi di risorse.

Non è finita però. Dopo aver implementato il ResourceProviderFactory dovremmo procedere all'implementazione di IResourceProvider che richiede la realizzazione di due metodi. Il primo per la lettura di una singola risorsa, e il secondo che restituisce un IResourceReader, che serve al runtime per enumerare le risorse presenti. La classe che implementa IResourceReader sarà lanostra terza ed ultima fatica. Ecco di seguito il codice di entrambe le classi:

public class SqlResourceProvider : IResourceProvider
{
    
public object GetObject(string key, CultureInfo culture)
    {
        
// leggi una singola risorsa 
        return
GetResourceFromDB(key, culture);
    }

    
public System.Resources.IResourceReader ResourceReader
    {
        
get
        
{
            
// leggi un set di risorse
            
return new SqlResourceReader(
                GetResourcesFromDB(CultureInfo.CurrentUICulture));
        }
    }
}
        
public class SqlResourceReader : IResourceReader
{
    
private Dictionary<stringobject> resources;

    
public SqlResourceReader(Dictionary<stringobject> resources)
    {
        
this.resources = resources;
    }

    
public IDictionaryEnumerator GetEnumerator()
    {
        
return resources.GetEnumerator();
    }

    
public void Close()
    { }
        
    
public IEnumerator IEnumerable.GetEnumerator()
    {
        
return resources.GetEnumerator();
    }

    
public void Dispose()
    {
        Close();
    }
}

L'istanza del ResourceProvider andrà creata nel ResourceProviderFactory che registreremo in configurazione. In questo modo avremo interposto il nostro provider a tutti i tentativi di lettura di risorse di localizzazione effettuati da parte dei componenti del framework, ed in questo modo potremmo facilmente localizzarli. Anche la SiteMap di cui ho detto poco fa.
 

powered by IMHO 1.3


Chiunque abbia sviluppto un'applicazione che si appoggia a diversi web-services si sarà trovato nella condizione di avere una moltitudine di proxy generati che fanno capo a diversi oggetti del dominio applicativo. Molto spesso ci si trova nella condizione di avere dei duplicati in questi oggetti che per il fatto stesso di essere classi diverse, pur avendo le medesime proprietà non sono "compatibili". Perciò, ad esempio avendo un webservice che restituisce un Ordine ed un secondo webservice che lo richiede in input non è possibile passare al secondo direttamente l'oggetto restituito dal primo.

Nel framework 2.0 esiste una soluzione a questo problema che però comporta la generazione "manuale" dei proxy richiamando direttamente l'eseguibile wsdl.exe con lo switch /sharedtypes e passando a quest'ultimo gli url dei webservices che condividono i tipi. In questo modo verranno generati i proxy facendo capo ad una gerarchia di oggetti unica.

Source: http://weblogs.asp.net/israelio/archive/2005/01/04/346337.aspx


Chi volesse creare un provider custom per sitemap, non avrà che da implementare i due metodi astratti della classe StaticSiteMapProvider, da cui già eredita la consueta XmlSiteMapProvider. In questo articoletto di Jeff Prosise, in breve viene spiegato come fare, con un piccolo esempio di codice nel quale viene realizzato un SiteMapProvider per Access.

Link: http://msdn.microsoft.com/msdnmag/issues/05/06/WickedCode/


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.


Chi di voi ha mai avuto a che fare con localizzazione e globalizzazione di applicazioni .NET sa bene come funziona il meccanismo del fallback delle culture. In soldoni, per chi non lo sapesse si tratta di quel pricipio per cui se tra le risorse non esiste quella nella cultura che è richiesta, il framework provvedde a trovarne una il più possibile adatta. Quello che forse non è chiaro a tutti è che implementando l'expression builder di cui ho parlato in un post precedente si perde questa utile funzionalità ed è necessario scrivere del codice per gestire l'eventualità. Io quest'oggi mi sono trovato in questa situazione e nel cercare di gestirla ho approfondito il modo con cui il framework codifica le culture e ho tratto da questo approfondimento una singola query che è in grado di risolvere il problema.

Se avete la pazienza di guardare le proprietà della classe CultureInfo noterete che essa ne espone una che si chiama LCID . L'LCID è un codice numerico a 32 bit che esprime la medesima forma che siamo abituati a vedere con i codici ISO che normalmente denominano le culture del framework. Di questi 32 bit infatti i primi 16 vengono utilizzati per esprimere il LanguageID mentre i seguenti 8 bit contengono il SortID. Ad esempio nel caso dell'inglese vedremo che LCID è 1033. Se convertiamo questo numero in esadecimale troveremo che equivale a 0x0409. La cosa notevole è che prendendo tutte le varianti della lingua inglese avremo:

English 0009
English - United States 0409
English - United Kingdom 0809
English - Australia 0c09
English - Canada 1009
English - New Zealand 1409
English - Ireland 1809
English - South Africa 1c09
English - Caribbean 2409
English - Belize 2809
English - Trinidad 2c09
English - Zimbabwe 3009
English - Philippines 3409
English - Indonesia 3809
English - Hong Kong SAR 3c09
English - India 4009
English - Malaysia 4409
English - Singapore 4809

In buona sostanza tutte le varianti della lingua inglese terminano per 09 e dispongono di un codice numerico che le ordina. Lo stesso principio si applica a tutte le altre culture. Ecco quindi che in breve ho prodotto una query che dato un LCID in ingresso trova quello disponibile che più si avvicina. La query sfrutta gli operatori booleani per separare dapprima LangId e SortId, usa il LangId del parametro di ingresso per isolare le culture analoghe e poi ordinando per SortId discendente prende la prima il cui SortId sia minore o uguale a quello fornito.

create function dbo.CultureGetFallbackByLCID(@lcid int)
    
returns int
as
begin
        
    return
    
(
        
select top 1 SortID LangID from
        
(
            
select distinct 
                
Culture 0xff00 as SortID
                
Culture 0xff as LangID 
            
from 
                
AvailableCultures 
        
A
        
where
            
A.LangID @lcid 0xff and
            
A.SortID <= @lcid 0xff00
        
order by SortID desc
    
);

end;

Naturalmente la query può fornire in uscita anche NULL qualora non trovi una cultura che si avvicina a quella fornita. E' questo il caso in cui si dovrà adottare la cultura neutra ovvero quella definita per il fallback finale.

powered by IMHO 1.3


Stamane ho avuto nuovamente problemi con la configurazione del AspNetActiveDirectoryMembershipProvider. Mi sono trovato a spostare l'installazione di ADAM dalla mia macchina locale ad un server 2003 configurato come controller di dominio. L'unica differenza apparente nell'installazione risiede nel fatto che data la presenza di Active Directory la porta su cui girerà ADAM deve essere cambiata e di default verrà messa a 50000. In realtà al termine della configurazione seguendo i passi del mio precedente post mi sono reso conto che l'utility di configurazione di ASP.NET continuava a darmi il seguente errore:

Logon failure: unknown user name or bad password.

Inutile dire che completati tutti i controlli su username e password il problema continuava a persistere. Dopo una mattinata di tentativi, spulciando la documentazione in rete ho scoperto che il problema deriva dal fatto che a quanto pare su un controller di dominio gli utenti di ADAM vengono creati come "disabilitati", mentre su una macchina XP essi sono normalmente abilitati. Indagando ulteriormente pare che questo comportamento derivi dal fatto che gli utenti vengono creati "senza password" e di conseguenza vengono disabilitati.

Per abilitare un utente occorre selezionarne le proprietà usando ADSI Edit, e modificare la proprietà msDS-UserAccountDisabled impostandola a False oppure a Not-Set.


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