Oggi è l'ultimo giorno del 2005, ed assieme agli auguri
a tutti è giunto il momento di fare un piccolo riepilogo su cosa ha significato
per me quest'anno. Spronato dall'ultimo post di Lorenzo, ecco quindi anche i miei hotspot sul
2005:
- E' nato il mio blog personale sul quale posso postare su ogni
argomento
- Il trend dei miei post è molto diminuito, ma credo di poter dire che
nel frattempo è aumentata la qualità
- Ho contribuito attivamente al wiki di UgiDotNet e al blog
- Mi hanno chiamato "guru" più volte e hanno affiancato il mio nome a quello
dei "guru" veri...
- Il mio IMHO è stato scaricato e installato da più di 800 persone
- Ho iniziato a lavorare su IMHO 2.0 e ho pubblicato la prima preview
- Ho scritto un bel numero di articoli su ASP.NET 2.0 (ah... il prossimo
esce a gennaio...)
- Mi hanno offerto due volte di scrivere un libro ma per ora ho
rifiutato
- Ho cambiato lavoro e sono tornato a lavorare con il mio amato web, anche
grazie alla mia partecipazione ad UgiDotNet...
- ...Perdipiù sono riuscito a lavorare da subito con ASP.NET 2.0...
- ...Inoltre lavoro in un team guidato da Davide Vernole e...
- ...Il mio stipendio ne ha tratto beneficio...
- Mi sono comprato una splendida D70s con due obbiettivi
- Ho inaugurato imagic
- Nelle ultime settimane ho iniziato a perdere peso e ho alleviato la
bilancia di ~5 Kg.. nonostante il Natale
- Ho messo in cantiere un progettone che prendera corpo (spero) in gennaio
del 2006... i Veneti sanno a cosa mi riferisco...
- Ho portato avanti i lavori della mia nuova casa a tempo di record...
- Ho parlato in pubblico per la prima volta... e ho fatto una promessa che
devo mantenere
Beh, forse non è molto, ma dal mio punto di vista è stato un ottimo lavoro.
Ed ora lasciatemi augurare a tutti uno splendido
2006!!!
powered by IMHO 1.3
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
Ricevo da una mailing list una offerta di lavoro che giro volentieri ai miei lettori. Si tratta di una affermata azienda che richiede 2 programmatori Junior in .NET. Ecco i requisiti:
Il candidato è un neolaureato o diplomato con esperienza anche scolastica nella programmazione in ambienti Microsoft .net
Conoscenze richieste:
- Microsoft Visual Basic.NET
- Microsoft (C#)
- SQL SERVER
- Sistemi Operativi Microsoft XP / Server 2003
E' gradita l'esperienza nei titoli di seguito riportati:
- programmazione ad oggetti
- C#
- Asp.net
- Java
- piattaforma Microsoft .NET
- sviluppo di portali WEB
- sviluppo di sistemi distribuiti e centralizzati
- sviluppo di Web Services
Completano il profilo capacità relazionali ed una spiccata attitudine al lavoro per obiettivi.
Chi volesse l'indirizzo email non ha che da chiederlo via form contatti.
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
All'Agile Day di oggi mi ero iscritto ma anche questa volta
sono stato costretto a rinunciare. Il motivo è presto detto, anzi credo ormai
sia giunto il momento di metterlo per iscritto ora che si va verso un
consolidamento della situazione. All'inizio di questo mese ho deciso di
lasciare il mio impiego per approdare ad un'altra società dove mi occuperò di
sviluppare con ASP.NET 2.0. La decisione, a dirla tutta è venuta giù come il
proverbiale fulmine a ciel sereno, in un periodo in cui tipicamente di certe
rivoluzioni non se ne vorrebbe nemmeno sentir parlare, ma ora che ho cominciato
a rodare con il nuovo team sono sempre più convinto di aver fatto la scelta
giusta. La transizione non è ancora del tutto conclusa, tanto che sono costretto
a suddividere il mio tempo tra due società, ma ormai stiamo andando verso la
sospirata conclusione di questo acrobatico periodo.
Il nuovo impiego come dicevo mi consentirà di lavorare da
subito con le nuove tecnologie del Framework 2.0, anzi segna per me un ritorno
sospirato ad occuparmi per la maggior parte del tempo di ASP.NET piuttosto che
di Windows Forms come ho fatto negli ultimi due anni. Si tratta di una grande
occasione anche perchè in questi giorni ho potuto lavorare fianco a fianco con
Luca
Minudel che devo ringraziare per aver pensato a me quando si è trattato di
cercare una nuova risorsa, e con Davide
"forza della natura" Vernole, come ho pensato di soprannominarlo
dopo averlo visto al lavoro.
Il nuovo lavoro, spero ve ne siate accorti, è stato anche
l'occasione per cominciare una serie di post su argomenti che ho affrontato in
questi primi giorni di lavoro con ASP.NET. Le problematiche da affrontare sono
molte e sono certo che avrò l'occasione per postare su questa splendida
tecnologia molto più di quello che ho fatto finora.
Detto questo credo che Marco Abis
troverà il modo per perdonarmi per essermi iscritto e poi aver lasciato libera
la poltrona. Ma sono certo che l'interesse dell'argomento l'avrà di certo
riempita. Prometto che alla prossima ci sarò anche io.
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:
- Scaricate il Windows 2000 Authorization Manager
Runtime
- Lanciate l'eseguibile che creerà due directory delle quali una denominata
\pia
- Nella directory \pia\1.2 troverete l'assembly
Microsoft.Interop.Security.AzRoles.dll
- 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
Ho messo in linea la seconda puntata della serie di
articoli sulla creazioen di un web server in C#. Per chi fosse interessato in
questa puntata si affronta il funzionamento del thread principale che riceve le
richieste e ai metodi per avviare e interrompere il servizio. Buona
lettura
Link: http://blog.boschin.it/articles/webserver2.aspx
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
"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
per leggere il post originale o inviare un commento visita il seguente indirizzo:
A tu per tu con ADAM
Lavorando su IMHO 2.0 mi sono trovato a
dover scrivere un piccolo web server per visualizzare i post nel preview
pane. Così, dato il notevole l'interesse del lavoro ho deciso di scrivere qualche articolo per spiegare
nel dettaglio come sono arrivato ad una buona soluzione usando gli strumenti del framework
2.0. E' utile precisare che in questo esempio non è utilizzata la classe HttpListener
perchè essa non è supportata da tutti i sistemi operativi Microsoft.
Buona lettura: http://blog.boschin.it/articles/webserver1.aspx
keywords: http protocol webserver
powered by IMHO
1.3
Ieri sera, preso da un wikiraptus ho aggiornato l'argomento Refactoring aggiungendo l'elenco delle Code
Smells. Trovare l'elenco non è stato difficile, ma descriverle è
sicuramente una altro paio di maniche, soprattutto se si vuole che la
descrizione abbia una utilità pratica. Non sarebbe male se qualche buonanima mi
desse una mano. L'articolo sul Refactoring in realtà apre molte strade, ed è
un'occasione favolosa per approfondire l'argomento. Oltre alle CodeSmells, c'è
tutta la serie dei PatternDiRefactoring che attende di essere esplosa. La mia
idea è di normalizzare la tabella delle Code Smells al pari di quella dei
Pattern e poi uno per uno analizzarli tutti, magari decidendo un template comune
per mantenere leggibilità ed efficacia. I punti per ogni argomento sono:
Descrizione, Esempi pratici, Approfondimenti, Suggerimenti, Letture. E poi chi
più ne ha più ne metta. Chi si fa avanti? Non occorre essere Martin Fowler,
basta solo un po' di buona volontà e voglia di imparare.
powered by IMHO 1.3