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 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


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

 


"Ci ho buttato quasi una notte intera, e un pezzetto di questo pomeriggio, ma alla fine ne sono uscito vivo". Così comincia l'articolo che ho appena postato a testimonianza delle difficoltà che ho incontrato per completare la configurazione di ADAM, la versione applicativa di Active Directory e il provider per Active directory di ASP.NET. Ho deciso di mettere tutto assieme in un unico articolo perchè nele ricerche in rete non ho trovato una sola fonte che spiegasse la cosa nella sua interezza.

Link: http://blog.boschin.it/articles/aspnetadam.aspx


Da un articolo in uscita su MSDN ecco l'aggiornamento del 10 must-have per Visual Studio 2005. Cominque mi sembra che quelli del precedente articolo per VS2003 siano sempre da tenere in considerazione.
Ecco l'elenco tratto dall'articolo:
  1. TestDriven.NET
  2. GhostDoc
  3. Paster
  4. CodeKeep
  5. PInvoke.NET
  6. VSWindowManager PowerToy
  7. WSContractFirst
  8. VSMouseBindings
  9. CopySourceAsHTML
  10. Cache Visualizer

Source: Ten Essential Tools: Visual Studio Add-Ins Every Developer Should Download Now -- MSDN Magazine, December 2005

powered by IMHO 1.3


Ecco un bell'esempio di uso dei generics in una architettura a plugins. Pubblicato dal neo-padre Abhinaba, che si sta scrivendo una piccola applicazione per monitorare la figlioletta in culla. Curioso anche il primo commento, che fa giustamente notare che se un bimbo si muove in culla non c'è nulla di strano ed è inutile sollevare un'alert. Ve lo conferma un ormai consumato (dalle nottate in bianco) padre...

I know the answer (its 42) : C# 2.0: Loading plugins at run-time using late binding

powered by IMHO 1.3


Lasciatemi spendere due parole per qualche istante su una gradita scoperta che ho fatto in questi giorni usando Visual Studio 2005. Mi riferisco a GenerateMember, una utile proprietà che decora tutti i controls del Framework 2.0. Forse la sua piccolezza non meritava un post nel mio blog, ma per chi ama la pulizia del codice bisogna dire che la sua funzione è irrinunciabile. GenerateMember semplicemente indica con un booleano a Visual Studio se usare una variabile membro della classe per referenziare il controllo oppure se semplicemente utilizzare una variabile locale al metodo InitializeComponents(). Appunto, una piccolezza, ma quante e quante volte mi sono chiesto chi me lo faceva fare ad avere tutti quei membri riferiti magari alle singole colonne di una datagrid che mai avrei usato? Complimenti, sono i dettagli che fanno la qualità.

powered by IMHO 1.3


Torno sull'argomento degli eventi FormClosed e FormClosing perchè approfondendo la documentazione ho scoperto che essi non sostituiscono Closed e Closing, ma li affiancano fornendo in più la ragione dalla chiusura per mezzo dell'enumeratore CloseReason (utilissimo) che si trova negli argomenti dell'event-handler. Il mio abbaglio è dovuto al fatto che l'intellisense di Visual Studio 2005 non mostra più i vecchi eventi. Questo mi insegna due cose:

1) Non usare Visual Studio per apprendere l'uso del framework

2) Il team di sviluppo sa il fatto suo...

Però devo dire che questo fatto che Visual Studio mi nasconda qualcosa mi piace poco. Al limite dovrebbe mettere un'icona che indica che il membro è obsoleto, ma se uso una classe debbo poterla vedere da capo a piedi. O no?

powered by IMHO 1.3


Sempre lavorando con il framework 2.0 ho qualche minuto a scovare due eventi di uso comune che a quanto pare hanno cambiato nome. Si tratta degli utilissimi Closing e Closed della classe System.Windows.Forms.Form che sono stati inopinatamente rinominati in FormClosing l'uno e in FormClosed l'altro.

Chissà poi perchè questo cambiamento potenzialmente foriero di noie??

powered by IMHO 1.3


Chi come me ama sfrenatamente l'operatore ternario ? .. : che C# eredita dal C, sarà lieto di sapere che un nuovo operatore è entrato a far parte del linguaggio C# 2.0. Si tratta dell'operatore ?? che si comporta ne più ne meno che come l'ISNULL del T-SQL oppure come l'equivalente nvl() del cugino Oracle. Ecco un esempio:

string a = null;
string b = "andrea";

Console.WriteLine( a ?? b );

Questo breve esempio scrive "andrea" dato che la variabile a al primo termine è settata a null. Non mi viene in mente ora un valido esempio d'uso pratico, ma considerata l'utilità dell'ISNULL in T-SQL e l'introduzione dei tipi nullabili in C# 2.0, credo proprio che non tarderò a trovare l'occasione per usarlo.

powered by IMHO 1.3