Post-Build Event: Copy files and TFS Build Service
Today I’ve got a problem with a Silverlight project and a custom post-build event. I have a Silverlight project that, on post-build event, must copy the xap inside another project in my solution. Easy I have to add a post build copy command as follow….
… ehm not so easy… It works on my local Visual Studio but when I commit this changes on TFS and TFS Build Service starts, a surprise…. error copy return errors… My aim is to make a copy locally on post build but not make this copy during TFS Build Service compile process. After a lot of searches on web and a lot of tries… a solution ![]()
- Open .csproj with an xml editor manually or with context menu (VSCommands 2010 Add-In for Visual Studio 2010 http://bit.ly/gnEAgb)
- Add <Target> Section as follow
<Target Name="AfterBuild" Condition="'$(BuildingInsideVisualStudio)' == 'true'"> <Copy SourceFile="$(Target)*.xap" DestinationFolder="$(SolutionDir)MyProject\MyFolder"/> </Target>
This works because variable BuildingInsideVisualStudio is true only in Visual Studio Environment.
Utilizzare librerie .NET 2.0 in applicazioni .NET 4.0
In alcune situazioni capita di dover utilizzare librerie già compilate con framework 2.0 (magari perchè non abbiamo a disposizione i sorgenti) all’interno di un’applicazione realizzata con il framework .NET 4.0. A me è appena successo con la libreria System.Data.SqlLite di cui non volevo rincompilare i sorgenti per .NET 4.0.
Affinchè tutto funzioni è necessario indicare nel file app.config (o web.config) quanto segue:
<?xml version="1.0"?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0"/>
</startup>
</configuration>
Collezione di supporto alla Paginazione
La paginazione dei dati è uno dei problemi classici ricorrente, soprattutto in ambito web, ma non solo. Immaginiamo di voler visualizzare una lista di 10000 elementi, ovviamente non potranno essere visualizzati tutti in una sola pagina o finestra. Per questa ragione si ricorre alla paginazione, in modo da visualizzare solo un numero limitato di elementi per pagina fornendo la possibilità di scorrere le pagine e quindi i dati, evitandone il caricamento massivo. Nel caso in cui i dati siano recuperati da database (come al 99% dei casi) dovranno essere effettuate query top k dove k è il numero massimo di elementi ritornati dalla query (grandezza della pagina) con indice in modo da avere gli x-esimi k elementi. (Es. Voglio i primi 10 elementi, per la prima pagina, poi i secondi 10 ecc.).
Prima di tutto è necessario quindi definire i parametri di paginazione, tramite la classe PagingSettings che definisce tutti le informazioni sui dati recuperati. I dati possono essere visualizzati da due punti di vista, assolutamente analoghi:
- Numero di Elementi per pagina – Indice di Pagina
- Indice di Scostamento – Numero massimo di elementi visualizzati
Chiramente è sempre possibile tramite calcolo passare da una visualizzazione all’altra, a patto di sapere se l’indice della prima pagina (che potrebbe essere 0 o 1).
public class PagingSettings
{
private readonly int _firstPage;
private readonly int _pageIndex;
private readonly int _pageSize;
}
La scelta del criterio da utilizzare per la memorizzazione interna dello stato è assolutamente arbitraria, io ho scelto indice di pagine e grandezza della pagina, ma l’altro sarebbe assolutamente equivalente. La conversione nell’altra visualizzalizzazione è possibile tramite proprietà di solo get come segue:
public long StartIndex
{
get { return (_pageIndex – _firstPage) * _pageSize; }
}public int PageIndex
{
get { return _pageIndex; }
}public int PageSize
{
get { return _pageSize; }
}
In entrambe le viste il numero è ovviamente presente il PageSize definibile anche come MaxRowCount (sinonimi). La creazione di questa classe di impostazioni e stato è possibile sia da entrambi i punti di vista (due valori interi con diversa semantica), per questa ragione ho scelto di realizzare il costruttore privato e due metodi factory per i due differenti punti di vista:
public static PagingSettings CreateByPageData(int pageSize, int pageIndex)
{
return new PagingSettings(pageSize, pageIndex, 1);
}public static PagingSettings CreateByPageData(int pageSize, int pageIndex, int firstPage)
{
return new PagingSettings(pageSize, pageIndex, firstPage);
}public static PagingSettings CreateByIndexData(long startIndex, int maxRowCount)
{
return CreateByIndexData(startIndex, maxRowCount, 1);
}public static PagingSettings CreateByIndexData(long startIndex, int maxRowCount, int firstPage)
{
if (startIndex == 0 && maxRowCount == 0)
return new PagingSettings(0, 0, 0);
return new PagingSettings(maxRowCount, (int)Math.Ceiling((double)startIndex / maxRowCount), firstPage);
}
questi si appoggiano ad altri due metodi statici di creazione a cui è possibile passare anche l’indice della prima pagina (il default è ovviamente 1)
realizzato la classe PagingSettings manca ora la collezione vera e propria che dovrà mantenere internamente lo stato (classe PagingSettings) gli elementi della pagina corrente e il numero totale di elementi. Di seguito l’interfaccia della collezione IPaginatedList<T>.
public interface IPaginatedList<T> : IList<T>
{
bool HasNextPage { get; }
bool HasPreviousPage { get; }
int PageIndex { get; }
int PageSize { get; }
long TotalDomainObjectCount { get; set; }
long TotalPages { get; }
}
per I dettagli dell’implementazione si rimanda al codice sorgente allegato..
Regular Expressions e C#
La classe base utilizzata per lavorare con le regular expression è Regex, il cui costruttore richiede la stringa di regular expression da utilizzare per l’elaborazione di stringhe.
Questa classe mette a disposizione una serie di metodi che permettono di:
- Ricercare un particolare pattern all’interno di una stringa
- Effettuare delle sosituzioni testuali
- Isolare alcuni gruppi di caratteri all’interno di una stringa
Sintassi delle Regular Expression
La stringa che definisce una regular expression utilizza una sintassi particolare che verrà descritti di seguito, basata su caratteri speciali a cui è associato uno specifico significato.
Nel caso in cui non siano specificati caratteri speciali, la stringa inserita come regular expression farà match con qualsiasi stringa contenente quel testo, in sostanza si avrà un comportamento del tutto analogo al metodo Contains() di string.
Es. “aaa” farà match con “aaabbb”, “bbaaacc”, “bbbbaaa” ecc.
Inizio e Fine
Per limitare il match a inizio o fine string è possibile utilizzare i caratteri speciali ^ per indicare l’inizio stringa e $ per indicare il fine stringa. Nel caso in cui la stringa sia composta da più righe e sia impostata l’opzione Multiline il match farà riferimento a qualsiasi riga, ovvero basterà una riga che soddisfi il criterio per avere il match della stringa nel suo complesso.
Es. “^aaa” fa match con “aaabbb”, “aaabbb(New Line)bbbccc”, “bbbaaa(New Line)aaabbb” (quest’ultima solo nel caso in cui sia specificato l’opzione multiline altrimenti non farà match)
Nel caso in cui si voglia il comportamento analogo di ^ e $ anche nel caso Multiline è possibile usare rispettivamente \A e \Z per inizio e fine di stringa, indipendentemente dalle righe
Es. “\Aaaa” fa match con “aaabbb”, “aaabbb(New Line)bbbccc”, mentre NON fa match “bbbaaa(New Line)aaabbb”
Nel caso in cui si vogliano considerare le singole parole (separate da spazi) è possibile utilizzare \b per indicare che il match è sui confini di una parola e \B per indicare che è all’interno della parola.
Es. “aa\b” fa match con “aabb cc”, “bbaa cc” ma non con “bbaabb” e “aa\B” fa match con “baab cc”, “cc caac”.
Molteplicità e Occorrenze
Nel caso in cui si vogliano ripetere alcuni caratteri 0 o più volte è possibile usare il carattere *,per una o più volte +, per un numero n specificato di volte {n} (con n numero di volte), n o più di n volte {n,}, da n a m volte {n,m}. Nel caso in cui si vogliano ripetere gruppi di caratteri è possibile utilizzare le parantesi tonde.
Es. “^a*$” farà match con “”, “a”, “aa” ecc. “^a+$” farà match con “a”, “aa” ecc ma non con “”. “^a{2}$” farà match con “aa”, “^(ab){2,3}$” farà match con “abab” e “ababab”.
Gruppi di Caratteri e Wildcards
Nel caso in cui si voglia far match con un carattere qualsiasi opzionale si può usare il carattere ?, mentre con un carattere qualsiasi obbligatorio . è possibile definire un gruppo di caratteri ammissibile tramite semplice elenco [abc] o [a|b|c] (farà match con il carattere a o b o c), un range [a-c] (farà match con i caratteri a o b o c)
Es. “^a?c$” farà match con “ac”,”aac”,”abc”, “a.c$” farà match con “abc”,”aac” ma non con “ac”, “^[ab]c$” farà match con “ac” e “bc”, “^[a-c]d$” farà match con “ad”, “bd”, “cd”
Di seguito altre wildcards utilizzabili
| \d | qualsiasi numero equivalente a [0-9] |
| \D | qualsiasi carattere NON numerico equivalente a [^0-9] |
| \s | qualsiasi carattere equivalente allo spazio |
| \S | qualsiasi carattere NON equivalente ad uno spazio |
| \w | qualsiasi carattere [a-zA-Z0-9] |
| \W | l’opposto del precedente [^a-zA-Z0-9] |
Gruppi
E’ possibile definire dei gruppi che è possibile ripetere e richiamare all’interno di una espressione regolare. La definizione di un gruppo avviene tramite caratteri ^(?<groupname>regex) in cui groupname è il nome del gruppo e regex è la regular expression che definisce quel gruppo. Per richiamare il gruppo è necessario usare \k<groupname>, in alternativa è possibile richiamare i gruppi con \n dove n è l’indice del gruppo in ordine di apparizione (da sinistra verso destra). I gruppi sono numerati a partire da 1 poichè il gruppo 0 è sempre il match completo dell’intera regular expression
Es."^(?<first>aa)bb\k<first>$” fa match con “aabbaa” così come "^(?<first>aa)(?<second>bb)\1$"”
WCF Sicurezza con Doppio Certificato
Vediamo un po’ come configurare un servizio WCF (Web Service) con sicurezza a livello di messagio: solo il cotenuto dell’envelope soap sarà cifrato. Con questo sistema varrà garantita oltre alla sicurezza anche l’autenticità di client e server: il client avendo a disposizione il certificato pubblico del server potrà verificarne l’autenticità oltre ad utilizzarlo per cifrare i messaggi verso il servizio. In modo duale anche il servizio potrà controllare autenticità del client e cifrare il conenuto a lui destinato.
Tralasciamo la creazione del servizio e veniamo subito alla configurazione….
Creazione dei Certificati
Iniziamo con la generazione dei certificati che ci serviranno per cifrare il contenuto dei messaggi e autenticare client e server. Per la generazione utilizziamo l’utility da linea di comando disponibile con visual studio makecert per la generazione del certificato e chiave private e pvk2pfx per l’esportazione in formato pfx (in cui è possibile esportare anche la chiave privata per il deploy). Tali utility a linea di comando sono disponibili utilizzando il prompt dei comandi di visual studio (che ha già il path correttamente configurato).
Generazione Certificato Server
makecert.exe -n "CN=Service" -sky exchange -sv Service.pvk Service.cer
pvk2pfx.exe -pvk Service.pvk -spc Service.cer
Generazione Certificato Client
makecert.exe -n "CN=Client" -sky exchange -sv Client.pvk Client.cer
pvk2pfx.exe -pvk Client.pvk -spc Client.cer
Configurazione dei Servizi
Generati i certificato procediamo con la configurazione del nostro servizio WCF e del client in modo da utilizzare i certificati per autenticare mutuamente client e server e cifrare i messaggi.
Configurazione del Servizio
<system.serviceModel> <services> <service name="ServiceName" behaviorConfiguration="CustomServiceBehaviour"> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="CustomBinding" contract="MyNamespace.IServiceContract"> <identity> <certificateReference storeName="My" storeLocation="CurrentUser" x509FindType="FindBySubjectName" findValue="Service" /> </identity> </endpoint> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> </service> </services> <bindings> <wsHttpBinding> <binding name="CustomBinding"> <security mode ="Message"> <message clientCredentialType="Certificate" /> </security> </binding> </wsHttpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name="CustomBehaviour"> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="False" /> <serviceCredentials> <serviceCertificate findValue="AuthService" storeLocation="CurrentUser" storeName="My" x509FindType="FindBySubjectName" /> <clientCertificate> <authentication certificateValidationMode="PeerOrChainTrust" /> </clientCertificate> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
Configurazione Del Client
<system.serviceModel> <client> <endpoint address="http://address/MyService.svc" behaviorConfiguration="CustomBehaviour" binding="wsHttpBinding" bindingConfiguration="ClientBinding" contract="AdhocWs.IAdhocServices" name=""> <identity> <certificateReference storeName="TrustedPeople" storeLocation="CurrentUser" x509FindType="FindBySubjectName" findValue="AdhocBridgeService" /> </identity> </endpoint> </client> <bindings> <wsHttpBinding> <binding name="ClientBinding"> <security mode="Message" > <message clientCredentialType="Certificate" /> </security> </binding> </wsHttpBinding> </bindings> <behaviors> <endpointBehaviors> <behavior name="CustomBehaviour"> <dataContractSerializer maxItemsInObjectGraph="2147483646" /> <clientCredentials> <clientCertificate findValue="Client" storeLocation="CurrentUser" storeName="My" x509FindType="FindBySubjectName" /> <serviceCertificate> <authentication certificateValidationMode="PeerOrChainTrust" /> </serviceCertificate> </clientCredentials> </behavior> </endpointBehaviors> </behaviors> </system.serviceModel>
Con questa configurazione i certificati dovranno trovarsi nel Certificate Store dell’utente che esegue l’applicazione in particolare il Client deve avere il certificato Client con chiave privata e il certificato del server senza chiave privata. L’utente che esegue il Servizio dovrà avere il certificato Service con chiave privata e il certificato client senza privata. I certificati con chiave privata dovranno trovarsi in Personal mentre i certificati senza chiave privata in Trusted People.
Configurazione con IIS
Nel caso in cui il server, ma anche il client, siano ospitati da IIS l’application Pool dovrà essere configurato in modo da utilizzare un utente con i corretti certificati nel relativo Certificate Store.
Attenzione! Affinche il certificate store sia caricato e accessibile per l’utente dovrà essere caricato il profilo dell’utente.
Creazione di una Certification Authority con OpenSSL
Per prima cosa è necessario scaricare OpenSSL Toolkit, qui trovate la versione per Windows e qui le versioni per linux.
Una volta completata l’installazione consiglio di aggiungere il percorso [OpenSSLFolder]\bin\ all’elenco dei PATH, in modo da porte richiamare le utility da linea di comando in ogni posizione dal prompt dei comandi.
Di seguito verranno descritte le procedure di generazione dei certificati passo a passo tramite linea di comando (prompt dei comandi).
In alternativa è possibile utilizzare un tool che si appoggia ad OpenSSL chiamato XCA, che permette di effettuare tutte le operazioni tramite GUI.
Generarazione della Certification Authority (CA)
Come prima cosa è necessario generare le chiavi per la Certification Authority, ovvero le chiavi che verrano usate per generare firmare tutte le chiavi di cui abbiamo bisogno. Siccome queste chiavi non verrano firmate da una Authority riconosciuta (Verisign, Thawte ecc.) ma da noi stessi, molte applicazioni visualizzeranno degli avvisi per notificare la cosa.
Vediamo ora come procedere alla generazione di una CA:
- Create una cartella in cui posizionare tutte le informazioni della CA (Es. \MyCA)
- Al suo interno consiglio di create tre cartelle (come viene consigliato anche dal questo tutorial) Keys (per le chiavi pubbliche e private generate) Requests (per le richieste di emissione dei certificati) e Certs (per i certificati veri e propri).
- Come prima cosa è necessario generare una chiave con algoritmo RSA e cifratura Triple DES (in alternativa è possibile utilizzare DES o IDEA). La chiave privata (da conservare in luogo sicuro e protetto) verrà salvata all’interno del file ca.key e sarà di 1024 bit (in alternativa è possibile utilizzare 512, 2048, 4076). Il comando (per maggiori informazioni consultare qui) da eseguire è:
openssl genrsa -des3 -out keys/ca.key 1024
- Verrà richiesta una passphrase con cui proteggere la chiave privata da generare. Tale passphrase verrà richiesta tutte le volte che si utilizzata la chiave generata per firmare o cifrare. Lasciandola bianca non verrà richiesta durante l’utilizzo. Si consiglia di utilizzare una passphrase per garantire la sicurezza della propria CA, che sfrutti tutti i canoni di sicurezza per le password. Il file ottenuto avrà una forma simile alla seguente (chiave espressa in base64):
—–BEGIN RSA PRIVATE KEY—–
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,09C9FA8C5DDAE12DI29lb68jzuR0FFbdQRS6HKaQ7NEJauH689YFRpKKmqvYCCIhDxARRIRkskI4yp9B
IHEGyICcBnEvNiw+Jgm+4nmJsaoJuWFYSVb4qfcrq/7MXUa7hPVHF+hFxuU/uS4q
/INqbcVeH74ivI+rMZr0p4KcXll8fFH/vh+JultcR/tlsN8tc6RtGmE/zrVmubrU
Ufxpt+XgwESnyQqOhXcFxQDwcakQ3lSBll2SQaYBLvCgblJqPGFxfFb7QVyeoQJ0
yaHnzm2aun+gXCuU83BXfgHQUv9uWugsVMModgO/cyDiP9xwfasiYIOdGRCkZFXV
pOskmdtk6s+uYQBXU+TvnUeiYir5J5Ci+yuTOqx/dxQSFvcxjhX5d53+oWlLg+2K
0PHU5M3rzc5ybLREoqX31MOd0f6IFGfV8C8fsyuQoKYFVeeKAUWB0Br9Sg8eBVnA
GyQG4NQlpI+fKrcPLV6x8G+bx8Cns2Zht0TZpMRXT0ZbaEmLRhcK718SroUx5lCo
SdjvLvHD3saUsdNq28Zk0wh8MWXyW60uJmgQPP7dtzMpq5VKXQVAgh8FY+z2VumM
5oZgQXje7YnjVN+esB1NYoMUuXbPZfYmFPTAZEEP5aueRnntjsMa8IildZz5jhWN
c5Yb0L0/OobZfTXa8lVEc2HfJ7dh3mimtVCF6tQzwiUecZmxXaeLmpjbshE8JLPv
tsxPmCPwy1r+eqLlb8nz9SkhhUZoj7wS4BIhtUXdWILWmz8lp/dHdMug/kh9MkoY
fswyGGbI9Z1B6t22ZsjVuAQgmPQ9Sz3Sqr/bhRJnhRtE6/i17opotQ==
—–END RSA PRIVATE KEY—–
- Il passo successivo è di generareil certificato della nostra Certification Authority sfruttando proprio quella chiave privata generata al passo precedente per la firma. Il comando per effettuare l’operazione è
openssl req -new -x509 -days 1001 -key keys/ca.key -out certs/ca.cer
- Verrà creato in nuovo certificato X509 con validità 1001 giorni (modificabile a piacere) sfruttando la chiave ca.key memorizzandolo sul file ca.cer.Durante la fase di generazione del certificato verranno richieste alcune informazioni come Stato, Provincia, Città, Nome, Mail che verranno memorizzate all’interno del certificato.
- L’ultima operazione consiste nel’estrazione del certificato della CA in formato PKCS12, in modo da poter distribuire il certificato e corrispondete chiave pubblica agli utilizzatore dei certificati generati dalla nostra nuova CA. Per evitare che alcuni software segnalino i futuri certificati da noi generati come non affidabili è necessario aggiungere tale PKCS12 alla liste delle Trusted Root Store. Il comando da eseguire sarà:
openssl x509 -in ca.cer -outform DER -out ca.der
Nel caso lo si voglia in formato DER.
Configurazione di NHibernate 1.2
La configurazione di NHibernate può essere fatta principalmente in tre modi; vediamo un attimo come realizzarli con alcuni esempi:
- Tramite .Config:
La configurazione è mantenuata all’interno di un una Section custom del file di configurazione dell’applicazione. Prima di tutto bisogna definire la section, che verrà poi utilizzata da NHibernate in questo modo:
<configSections> <section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </configSections>
Una volta impostata la definizione per la configSection è necessario inserire la sezione di configurazione vera e propria. Di seguito riporto un esempio classico di configurazione.
<nhibernate> <add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" /> <add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver" /> <add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2005Dialect" /> <add key="hibernate.connection.connection_string" value="Server=.\SQLEXPRESS; Integrated Security=True; Database=AdventureWorks;" /> <add key="hibernate.default_schema" value="dbo" /> </nhibernate>
I primi tre parametri riguardano la tipologia di connessione, utilizzando un DriverConnectionProvieder e il Client per Sql Server (ne esistono un po’ per tutti i principali motori DBMS). Il Dialect indica il particolare dialetto SQL (dal momento che SQL non è proprio identico per ogni DBMS), in questo caso per Sql Server 2005. La ConnectionString anch’essa legata al tipo di DBMS al quale ci si collega (Consultare la documentazione del Driver di connessione specifico). Ed infine nel caso si voglia definire lo schema di default da utilizzare per l’accesso al DB, in questo caso lo standard di SQL server “dbo”.
A livello di codice è necessario, all’avvio di NHibernate, avviare la configurazione, che recupera dal file appena descritto tutti i parametri di cui ha bisogno per effettuare le connessioni al DB.
Configuration configuration = new Configuration(); configuration.AddAssembly("Assembly.Name");
Il metodo AddAssembly non fa altro che indicare l’assembly da cui ricavare le Entity per effettuare il mapping (classi e file .hbm.xml).
- Tramite File hibernate.cfg.xml
In alternativa è possibile utilizzare un file di configurazione specifico di nhibernate chiamato hibernate.cfg.xml; tale file va copiato nella cartella di output dei file compilati e non incluso come risorsa embedded. La struttura di questo file rispecchia le caratteristiche già evidenziate in precedenza, ma con una sintassi leggermente differente.
<?xml version="1.0" encoding="utf-8" ?> <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> <session-factory> <property name="dialect"> NHibernate.Dialect.MsSql2005Dialect</property> <property name="connection.provider"> NHibernate.Connection.DriverConnectionProvider</property> <property name="hibernate.connection.driver_class"> NHibernate.Driver.SqlClientDriver</property> <property name="connection.connection_string"> Server=.\SQLEXPRESS; Integrated Security=True; Database=AdventureWorks;</property> <mapping assembly="Assembly.Name" /> </session-factory> </hibernate-configuration>
In questo caso il mapping con assembly contenente le entità è stato specificato direttamente all’interno del file di configurazione; ciò eviterà di dover utilizzare il metodo AddAssembly().
Con questo tipo di soluzione si ha il vantaggio di poter utilizzare l’Xsd (nhibernate-configuration-2.2) fornito con la distribuzione di nhibernate e conseguentemente l’intellisense funzionante durante la scrittura del file. Affinchè venga individuato il file XSD è necessario copiarlo all’interno della cartella [VisualStudioFolder]\Xml\Schemas.
A Livello di implementazione è necessario aggiungere una riga di codice, così come mostrato dall’esempo sottostante:
Configuration configuration = new Configuration(); configuration.Configure();
- Tramite file xml
L’ultima possibilità è quella di utilizzare un file xml, con nome e percorso qualsiasi, con la stessa sintassi già vista per il file hibernate.cfg.xml. Ciò che cambia a livello di codice è che al metodo Configure in questo caso è necessario passare percorso e nome del file di configurazione utilizzato.
Configuration configuration = new Configuration(); configuration.Configure(@"path\filename.xml");