Qualcosa su di me

Mi chiamo Andrea Moro, sono un appassionato di informatica da quando avevo 8 anni e da quando mio padre mi regalò il C64.

Qualche anno più tardi, il mio primo pc e nel 1994 la prima esperienza con Internet, di cui mi sono subito innamorato e con cui oggi mando avanti la mia attività di Web Designing e posizionamento nei motori di ricerca.

View Andrea Moro's profile on LinkedIn

Calendar

<<  ottobre 2008  >>
lumamegivesado
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar


RSS

Premessa

Nel mio caso io ho lavorato sul web.config, ma dato che non ho fatto nulla di tracendentale e ho utilizzato solo classi del framework e nulla di personalizzato, presumo che se il file di configurazione sia quello di un applicativo, non dovrebbero esserci problemi.

Ricordo che una soluzione per antonomasia non esiste, questo è chiaro. Sulle prima, infatti, non ero nemmeno sicuro che questa fosse quella che faceva al caso mio, e tra l'altro avevo anche dei problemi nel farla funzionare, sicchè mi venne suggerito di usare un semplice schema XML/XSD, che se sulle prime poteva sembrare efficace, immaginando meglio lo scenario finale, risultava poco "sicuro" per via del fatto che chiunque avesse avuto accesso al sorgente del sito avrebbe potuto variare il contenuto del valore da validare. Quindi sono ritornato sui miei passi e ho iniziato a capirci qualcosa dei Validator.

Al lavoro dunque!

La necessità

Poter controllare cosa accidenti scrive l'utente in un file di configurazione.

La soluzione

Sulle prime quando ho cercato di capire come potessi fare a validare, la prima ricerca che ho fatto è stata proprio "Custom Validator" e - benchè mi pare di capire che questo sia il nome di quello di cui sto parlando -, tutti i risultati che venivano fuori (Msdn, google, ecc.) erano riferiti a custom validator che però si associavano esclusivamente a controlli sulle webform. Il che non era esattamente quello che volevo.

Mi sono guardato un attimo intorno, ed in particolare sul fatto di cosa una custom configSections (per crearla bisogna ereditare da ConfigurationSection) e i suoi elementi (creabili da ConfigurationElement) e qui erano saltati fuori StringValidator e compagni.

Erano esattamente quello che mi serviva. Allora, come da ogni buon programmatore (??) guardo da che classe ereditano ... e nel caso dello StringValidator, da StringValidatorAttribute che come unica proprietà da implementare richiede la ValidatorInstance, una proprietà di sola lettura che restituisce un oggetto, la vostra classe, di tipo ConfigurationValidatorBase che si occuperà di fare il lavoro "sporco" e di validare il contenuto dell'attributo del file di configurazione, richiamando dapprima il metodo CanValidate per verificare che il tipo di dati datogli in pasto sia validabile secondo la nostra classe e poi il rispettivo Validate per sapere se il valore è o meno valido.

Reperite queste informazioni, il gioco è stato abbastanza (??) semplice. 

Ma vediamo qualche linea di codice:

    public sealed class NomeValidatorAttribute 
                       : ConfigurationValidatorAttribute
    {
        NomeValidator istance;

        public NomeValidatorAttribute()
        {
        }

        public override ConfigurationValidatorBase
          ValidatorInstance 
        { 
            get
            {
                if (istance == null)
                    istance = new NomeValidator();
                    
                return istance;
            }
        }
    }

 

Con questa classe - non ereditabile (non sia mai qualcuno tocchi il mio codice) - qui sopra non faccio altro che definire un oggetto di tipo attributo (perchè eredita da ConfigurationBaseAttribute che a sua volta eredita da Attribute) che chiamo - guarda caso per ricordarmi - con il nome del mio attributo presente nel file di configurazione con l'aggiunta del suffisso Validator tanto per rimanere in stile Microsoft (La parola Attribute che segue Validator sebbene non strettamente indispensabile e caldamente ignorata dall'IDE ce l'ho aggiunta per la stessa identica ragione - rimanere con una classe stile Microsoft).

All'interno dichiaro una variabile globale di tipo Validatore (che ancora non ho mostrato), il costruttore di default, e l'impementazione della proprietà ValidatorInstance.

Non mi soffermo di più sul fatto che all'interno della classe si potevano creare dei NamedParameter con i quali poter accettare dei valori opzionali in ingresso, o ulteriori costruttori firmati. Ma per lasciare le cose semplici, supponiamo che a noi non interessano parametri.

A questo punto, dobbiamo scrivere la nostra classe che si occupa di validare il contenuto dell'attributo. Ecco qua:

    public class ContentTypeValidator 
             : ConfigurationValidatorBase
    {
        String[] ct = new String[] {"Andrea", "Giuseppe"};

        public ContentTypeValidator()
        {
        }
    
        public override bool CanValidate(Type type)
        {
            return type == typeof(string);
        }
    
        public override void Validate(object value)
        {
             if (Array.IndexOf(ct, value) == -1)
                throw new ConfigurationErrorsException(
         "The name value supplied isn't allowed");
        }
    }

 

Ok, forse non sarà il massimo dell'eleganza dichiarare quell'array lassu, secco con due valori e francamente in un caso dove realmente dovrei validare il nome scegliendolo da una lista di possibili valori, sinceramente ho qualche difficoltà anche io a capire come andarli a leggere da un elenco più nutrito che non mi sia scritto prima a mano. Ma questo è pur sempre un esempio.

Questa classe, come dicevo prima, controlla il contenuto dapprima verificando che il tipo in ingresso sia valido e poi validandolo, e in caso negativo si preoccupa di sollevare un'eccezione al compilatore con relativo messaggio che aiuti a capire cosa è successo.

Come uso il tutto? Intanto prendendo le due classi e mettendole nel codice, poi, sempre premesso che stiate lavorando su di un configuration element, non dovrete far altro che andare al di sotto della vostro elemento,

mettere una parentesi quadra per aggiungere un attributo ed ecco che magicamente compare nella nostra lista il nostro validatore.

Et voilà, il gioco è fatto.


Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Premessa

 

Un altro problema con il quale mi sono scontrato in questi giorni era la possibilità di avere una collection di elementi per una mia custom section, ma qualcosa che fosse relativamente semplice, e basato su elementi personalizzati. Insomma l'esatto contrario dell'esempio fornito con MSDN, che - IMHO - non era così immediato come speravo, fors'anche perchè è stato concepito per più usi ben più complessi rispetto alla mera collection di sola lettura alla quale miravo io.

La necessità

Volevo ottenere una collection di custom elements all'interno di un file di configurazione, in modo da avere più elementi dello stesso tipo.

La soluzione

Di default, quando si crea una ConfigurationElementCollection, di base la mappatura utilizzata con la proprietà ConfigurationElementCollectionType è di tipo AddRemoveClearMap, il che significa poter interagire con gli elementi che appartengono alla collection esclusivamente con i rispettivi tag Add, Remove e Clear (guarda caso il nome).
Visto che a me non piaceva proprio quell'Add, dovevo trovare un sistema che mi desse un pò più di libertà, e guarda caso il BasicMap lascia tutta la libertà di cui hai bisogno, ivi compresa quella di gestire la ConfigurationProperty con la quale specificare il nome della proprietà che deve corrispondere al nome dell'elemento presente all'interno del file di configurazione.

Ad essere onesto ci sono ancora diverse cosette che non ho compreso del meccanismo delle collection riferito a questo contesto. Per esempio alcuni esperimenti che ho fatto cercando di far partire il tutto dalla classe collection da utilizzare immediatamente in un foreach ... next anzichè da quella section, oppure quello di aggiungere una proprietà della Section che mi restituisse un oggetto corrente (con relative proprietà MoveNext() e Reset(), ma sfortunatamente 3 volte 4 il debug mi va in errore restituendomi di fatto un oggetto collection vuoto, quando di fatto se nascondo quella proprietà dal codice e metto un breakpoint in coda all'ultima graffa, la collection count la vedo piena e perfettamente accessibile. Ma vabbè, che volete, sono ancora agli inizi e sbagliando s'impara. Se imparerò come risolvere la situazione, posterò la situazione; altrimenti come dicono a casa mia "ciccia" o "amen" o quella che preferite).

Comunque, vediamo di capire quello che ho capito io da tutto il discorso. Inizio con un estratto del file web.config e la classe che gestisce gli elements che non starò a descrivere analiticamente poichè, benchè facciano parte dell'esempio, sono in questo preciso istante fuorvianti dal concetto collection (e comunque estremamente semplici da comprendere):

 

<configSections>
   
<section name="MyUrls" 
           type="Andrea.ConfigElementCollection.UrlsSection" />
</configSections>
<MyUrls>
   
<urls>
       
<address name="Microsoft" 
                 url="http://www.microsoft.com" 
                 port="0"/
>
                 
       
<address name="Contoso" 
                 url="http://www.contoso.com/" 
                 port="8080"/>
   
</urls>
</MyUrls>


// Define the UrlConfigElement.
public class UrlConfigElement :
        ConfigurationElement
{
    
public UrlConfigElement(){}

    
public UrlConfigElement(String newName, String newUrl,
          Int16 newPort)
    {
        Name = newName;
        Url = newUrl;
        Port = newPort;
    }

    
public UrlConfigElement(String elementName)
    {
        Name = elementName;
    }

    [ConfigurationProperty("name",
            DefaultValue = "Microsoft",
            IsRequired = 
true,
            IsKey = 
true)]
    
public string Name
    {
        
get
        
{
            
return (string)this["name"];
        }
        
set
        
{
            
this["name"] = value;
        }
    }

    [ConfigurationProperty("url",
            DefaultValue = "http://www.microsoft.com",
            IsRequired = 
true)]
    [RegexStringValidator(@"\w+:\/\/[\w.]+\S*")]
    
public string Url
    {
        
get
        
{
            
return (string)this["url"];
        }
        
set
        
{
            
this["url"] = value;
        }
    }

    [ConfigurationProperty("port",
            DefaultValue = (
int)0,
            IsRequired = 
false)]
    [IntegerValidator(MinValue = 0,
            MaxValue = 8080, ExcludeRange = 
false)]
    
public int Port
    {
        
get
        
{
            
return (int)this["port"];
        }
        
set
        
{
            
this["port"] = value;
        }
    }
}

Quindi, un frammento di codice della pagina default.aspx con la quale ho fatto i tests.

Andrea.ConfigElementCollection.UrlsSection config = 
 (Andrea.ConfigElementCollection.UrlsSection)
  WebConfigurationManager.GetSection("MyUrls");

Debug.WriteLine(config.urls["Microsoft"]);

Quando si chiama la GetSection, specificando il nome della sezione che voglio andare a recuperare, la classe WebConfigurationManager, internamente sà già quale è il file di configurazion della mia applicazione e con la specifica chiamata non fa altro che andare a recuperare in una variabile interna tutto il contenuto della section indicata - in questo caso - e - e e passi ogni singolo tag che incontra alla classe UrlsSection che cercherà al suo interno un riscontro per ogni tag incontrato.

Ne consegue che il primo elemento che trova è , quindi ... bisogna che all'interno della classe Section ci sia una proprietà denominata allo stesso identico modo.
Attenzione, quello che è importante non è il nome della proprietà di per se, che può essere quello che vi pare (anche se per questioni pratiche è meglio che coincida - magari ci sarà pure una nota MS su come si programma e sul fatto che debbano invece essere uguali, ma se c'è non l'ho trovata - ancora) ma il nome che si scrive dentro ai marcatori [ConfigurationProperty("urls")].

Sicchè, nella classe Section avremo questo frammento di codice:

[ConfigurationProperty("urls", IsDefaultCollection = true)]
public UrlsCollection urls
{
    
get
    
{
        
return (UrlsCollection)base["urls"];
    }
}

Al suo interno, alla proprietà gli si chiede di ritornare un oggetto di tipo collection specificandogli che gli elementi che ne dovranno far parte sono quelli all'interno dei marcatori . Come vedete fino a qui, la nostra collection non sà ancora quali elementi saranno al suo interno.

Ma andando oltre. Il debug - visto che vi sto raccontando tutto step-by-step (o almeno ci provo), a questo punto passa nella classe UrlsCollection, per il costruttore di default, quindi immediatamente alla proprietà CollectionType per sapere di che tipo è la collezione (vedi la nota all'inizio) e poi, scoperto che è una collection di tipo BasicMap, visto che presente, andrà nella proprietà ElementName dove è specificato il tag di apertura per gli elementi presenti in , in questo caso

.

Tecnicamente tutto questo processo dovrebbe avvenire in sorta di contemporaneità tra le operazioni di lettura e confronto, il fatto di passare per ElementName è una logica conseguenza per il compilatore che trovandosi sulla prima riga
, essendo un elemento nuovo per la Collection, non potrà far altro che passare per il metodo CreateNewElement con il quale verrà generata la nuova istanza di UrlElementCollection (leggendo quindi tutti i tag che fanno parte di quell'elemento address, verificando che gli attributi corrispondano alle effettive proprietà dalla classe, che i valori siano validabili e validati, ecc. ecc.) che sarà inserita nella collezione.
Ecco il blocco di codice della classe UrlCollection appena descritto:

 

// UrlCollection class fragment
protected override String ElementName
{
  
getreturn "address"; }
}
      
public override ConfigurationElementCollectionType CollectionType
{
  
get
  
{
    
return ConfigurationElementCollectionType.BasicMap;
  }
}

protected override ConfigurationElement CreateNewElement()
{
  
return new UrlConfigElement();
}

Una volta creato il nuovo elemento, il codice ritorna di nuovo alla UrlsCollection e passa per la proprietà GetElementKey (che assieme alla CreateNewElement sono gli unici due metodi "must implement" della ConfigurationElementCollection) dove non si fa altro che specificare quale è la chiave dell'oggetto element che - di fatto essendo una chiave è quella che tecnicamente dovrebbe essere univoca e comunque - fornisce l'accesso diretto tramite l'implementazione dell'indexer ai singoli Element della collection (infatti il tipo restituito, molto genericamente, è un object).

protected override Object 
        GetElementKey(ConfigurationElement element)
{
  
return ((UrlConfigElement)element).Name;
}


Tutto questo processo poc'anzi descritto, viene eseguito tante volte quante sono in realtà gli elementi presenti nella nostra sezione personalizzata del web.config.
Testando con uno step-by-step, ho potuto comunque notare che il debug ha modo di passare per la proprietà ConfigurationElementCollectionType più volte di quelle che in realtà sono gli elementi, e per questo onestamente non ho ancora una risposta.

Comunque, l'ultima volta che il compilatore passa per la GetElementKey è anche quella dove di fatto la classe ha finito il suo compito, e può quindi accingersi a restituire il nostro oggetto Section, memorizzato nel mio caso nella variabile config. Da questo momento in poi, con le altre proprietà e metodi della classe si può "navigare" tra gli elementi, richiedendo una IndexOf di un preciso elemento, o un preciso elemento partendo dalla sua chiave.

Ma torniamo, prima di concludere, all'ultima riga del codice della pagina aspx, quella con la Debug.WriteLine(config.urls["Microsoft"]); il nostro oggetto config essendo ora popolato con i dati presenti nel file di configurazione, mi consente di accedere tramite un indexer a disposizione ad un singolo elemento della collezione. In questo caso gli indexer sono due, uno per indice numerico e l'altro per chiave di tipo stringa.
L'indexer, al suo interno richiamando la BaseGet (che non fa altro che fare un ciclo tra gli elementi della collezione recuperata) confronterà il valore passato con la proprietà del ConfigurationElement il cui attributo IsKey è stato impostato su true. In caso positivo restituirà l'oggetto ConfigurationElement, diversamente un oggetto di tipo null.

Concludo con le due classi UrlsCollection e UrlsSection complete:

//Define the UrlsCollection that contains 
// UrlsConfigElement elements.
public class UrlsCollection : ConfigurationElementCollection
{
  
public UrlsCollection()
  {
  }
    
  
protected override String ElementName
  {
    
getreturn "address"; }
  }
      
  
public override ConfigurationElementCollectionType CollectionType
  {
    
get
    
{
      
return ConfigurationElementCollectionType.BasicMap;
    }
  }

  
protected override ConfigurationElement CreateNewElement()
  {
    
return new UrlConfigElement();
  }

  
protected override Object GetElementKey
            (ConfigurationElement element)
  {
    
return ((UrlConfigElement)element).Name;
  }
      
  
public new int Count
  {
    
get return base.Count; }
  }

  
public UrlConfigElement this[int index]
  {
    
get
    
{
      
if (index >= base.Count)
        
return null;
                    
      
return (UrlConfigElement)BaseGet(index);
    }
  }

  
new public UrlConfigElement this[string Name]
  {
    
get
    
{
      
return (UrlConfigElement)BaseGet(Name);
    }
  }

  
public int IndexOf(UrlConfigElement url)
  {
    
return BaseIndexOf(url);
  }
}

// Define a custom section containing 
// a simple element and a collection of 
// the same element. It uses two custom 
// types: UrlsCollection and 
// UrlsConfigElement.
public class UrlsSection : ConfigurationSection
{
  
public UrlsSection() 
  {
  }

  [ConfigurationProperty("urls", IsDefaultCollection = 
true)]
  
public UrlsCollection urls
  {
    
get
    
{
      
return (UrlsCollection)base["urls"];
    }
  }
}

Technorati Tags: , , ,


Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Uno schema certo per i file di configurazione

Pubblicata il 18/01/2006 da in C#

Cosa fare se si ha bisogno di avere uno schema certo che il nostro file di configurazione deve seguire? Che magari ci aiuti con il supporto dell'intellisense (gran bella invenzione davvero!) per mostrarci anche una lista di enumerati?

Semplice, associato al custom element della nostra custom section il parametro xmlns, passandogli un URI contenente lo schema XSD che vogliamo sia interpretato e utilizzato come linea guida. Così:

<UserSection xmlns="http://tempuri.org/PersoneSchema.xsd">

Generare uno schema XSD è semplice con il tool visuale messo a disposizione nell'IDE, basta solo sapere cosa scriverci dentro.

Il nostro pattern deve essere una cosa di questo tipo:

NomeSezione -> NomeElemento -> Attributi [-> Eventuali enumerati]

Che tradotto in xml, per un esempio sul quale stavo lavorando, diventa:

<xs:element name="Utente">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="Persona">
        <xs:complexType>
          <xs:attribute name="nome">
            <xs:simpleType>
              <xs:restriction 
base="xs:string">
                <xs:enumeration 
value="Andrea"/>
                <xs:enumeration 
value="Giuseppe"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
          <xs:attribute name="cognome" type="xs:string"/>
          <xs:attribute name="sposato" type="xs:boolean"/>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
  </xs:complexType>
</xs:element>

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

I named parameters, questi benedetti

Pubblicata il 17/01/2006 da in C#

In questo caso mi sono ritrovato a cercare di capire cosa potessero essere i named parameters, o meglio quello lo avevo capito, ma come, invece, li potevo creare.

Sulle prime pensavo che la descrizione che compariva al di sotto del tooltip box fosse una questione di tag per la commentazione del codice (Recommended Tags for Documentation Comments), però i conti non mi tornavano.

Gira che ti rigira, dopo un pò di analisi di alcune classi che esponevano questi named parameters, finalmente comprendo che altro non sono che delle banali proprietà (get, set), ma applicate a classi che ereditano da Attribute.

Quindi per intenderci


public class 
MyNewAttribute: Attribute {   

public 
MyNewAttribute(string Nome) {      // Positional parameter      
...   
}

public string Cognome {  // Named parameter
get {...}
set {...}
}
   

public string 
Nome {
get {...}
}
Per poter ereditare da Attribute, bisogna ricordarsi di referenziare la System.Configuration.

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Advanced Technology

Abruzzo SEO specialist, .Net programming and computer stuff