Technology Experience
Software

Se avete una scheda Creative ed usate Windows Vista…

…sedetevi, allacciatevi le cinture e leggete bene.

Due giorni fa – era il 2 Giugno 2008 – Creative ha rilasciato drivers aggiornati per le sue schede audio. Questi drivers sono comunque in beta, ma a vedere le release notes sistemano un sacco di problemi sotto Windows Vista. Dopo averli scaricati, li ho ovviamente installati subito e tutto è filato liscio come l’olio. A differenza dei drivers per XP, quelli per Vista sono un po’ più scarni, ma fanno il loro dovere. Tutto quello che c’è è un tool che si chiama Audio Console, dal quale potete configurare gli altoparlanti, il CMSS e tutte le solite impostazioni. La qualità sembra un po’ migliore rispetto alla versione precedente, ma magari è solo un’impressione.

L’altra grande novità è che finalmente Creative ha rilasciato Creative Entertainment Center per Windows Vista, il software media-center che è compatibile con le schede audio e – soprattutto – con il telecomando in dotazione. Era ora!!! Ero stufo di dover usare Windows XP quando volevo guardarmi qualche bel film su PC!

Io ho provato ad installare sia i drivers che l’Entertainment Center e non ho avuto alcun problema.
Solo un po’ di riavvii, le solite conferme della UAC e nulla di più.
Perciò…fatelo anche voi!

Send to Kindle
.NET World

Ottenere l’ID dell’ultimo file uploadato – optimized version

Introduzione
Lunedì pomeriggio ho scritto un post in cui facevo vedere come usare la chiamata al metodo GetListItems del web-service /_vti_bin/lists.asmx di Sharepoint per ottenere l’elenco dei files pubblicati in una document library. Lo scopo era quello di reperire l’ID univoco dell’ultimo file uploadato – file di cui vogliamo aggiornare i metadati. Il mio ragionamento era il seguente:

  1. ottengo l’elenco completo di tutti i files pubblicati
  2. faccio il parsing dell’XML restituito dal web-service
  3. ciclo su tutti i files restituiti, fino a quando non trovo quello che mi interessa.

Controindicazioni
Questo approccio – fondamentalmente corretto – presenta però qualche problema, come giustamente ha osservato mio fratello nel commento che mi ha lasciato nel suddetto post. Per prima cosa, genero più traffico http del dovuto: io ho bisogno di sapere solo l’ID di un solo file, mentre così facendo l’XML contiene tutto l’immaginabile relativo ad una document library.

CAML: cosa diavolo è?
Con la sua osservazione, Omar mi ha fatto conoscere un nuovo acronimo: CAML. L’acronimo sta per Collaborative Application Markup Language, e – come dice il nome – è un XML particolare da “dare in pasto” al web-service. Tramite CAML possiamo filtrare ed ordinare sui campi di una document library. Va da sè quindi che possiamo ottimizzare molto la ricerca che facciamo, dicendo per esempio di escludere le directory e di ordinare in base al campo ID in modo decrescente. L’ultimo file uploadato otterrà sempre l’ID più alto, e quindi…

Ok. Dopo averci litigato un po’, ieri sera ho raggiunto la soluzione. Il codice CAML che dobbiamo scrivere per trovare l’ID del file è la seguente:

<OrderBy> <FieldRef Name="ID" Ascending="False" /> </OrderBy> <Where> <Neq> <FieldRef Name="ContentType" /> <Value Type="Text">Folder</Value> </Neq> </Where>

Il blocco <OrderBy></OrderBy> esprime ovviamente l’ordinamento, che avviene per ID in modo decrescente (grande –> piccolo). Il blocco <Where></Where> esprime eventuali filtri (inclusivi od esclusivi) da applicare alla ricerca. In questo caso, diciamo che il campo ContentType NON deve essere uguale (operatore Neq) a Folder.

Non di solo CAML vivrà la mia applicazione!

CAML deve essere eseguito sul server WSS per fornire un risultato. Io lo eseguo con C#, voi fate un po’ come volete.

Ho riscritto praticamente da zero il metodo ReadFileID che avevo pubblicato lunedì. La versione attuale è la seguente:

1 public void ReadFileID(string listName, string fileName) 2 { 3 Lists l = new Lists(); 4 l.Credentials = new NetworkCredential(this.Username, this.Password, this.Domain); 5 6 XmlDocument doc = new XmlDocument(); 7 XmlNode query = doc.CreateNode(XmlNodeType.Element, "Query", string.Empty); 8 9 string xml = ResourceHelper.GetResourceContent("UploaderMOSS.XML.ListQuery.xml"); 10 11 XmlNode node = l.GetListItems(listName, null, query, null, "1", null, null); 12 13 node = node.FirstChild.NextSibling.FirstChild.NextSibling; 14 15 this.CacheFilesID.Add(fileName, node.Attributes["ows_ID"].Value); 16 }

Riga (3) e (4): istanzio il web-service con le credenziali corrette.

Riga (6) e (7): creo un XmlDocument e creo il nodo <Query></Query>. All’interno di questi tag viene messo il blocco CAML visto sopra, riga (9). Ecco perchè non ho messo <Query></Query> direttamente nel CAML: preferisco farlo via C# ed innestare il CAML nel nodo creato a riga (7).

Riga (11): lancio la chiamata al metodo GetListItems. I parametri sono: il nome della lista (in chiaro), il nome della view (opzionale, in chiaro), l’oggetto query, un blocco XAML per i fields da vedere, il row-count, un blocco XAML per le query options, ed il WebID (opzionale?). Notare che – al contrario di quanto facevo con la vecchia versione – qui il row-count è messo a “1”.

Il resto non è nulla di che: navigo nell’XMLNode restituito dal web-service ed aggiungo alla cache privata della classe l’ID che abbiamo chiesto. Il metodo potrebbe aggiungerlo alla cache e restituirlo al chiamante, che forse sarebbe più comodo. Sono scelte: io l’ho fatto così.

Eventuali problemi

Nei giorni scorsi io ho avuto parecchi problemi ad utilizzare questa tecnica. Secondo me, dovete tener ben presente le seguenti cose:

  1. occhio a come create il CAML. Se non state attenti, potrà capitarvi di annidare un nodo <Query> dentro un nodo <Query>, e questo ovviamente al run-time di WSS non piace molto. Lo stesso vale per tutto il resto della query.
  2. il campo ID deve essere numerico, altrimenti – lo sapete meglio di me – l’ordinamento va a pallino. Sembra uno scherzo, ma io ho una document library con un campo ID che non viene ordinato nel modo corretto. Deduco che non sia numerico, ma non riesco a capirlo. Se non ci credete, eccola qua…

Send to Kindle
.NET World

Ottenere l’ID univoco di un file in una document library di Sharepoint

Ho scritto il metodo seguente, che dato un nome di una lista di Sharepoint ed un nome di file pubblicato, restituisce il suo ID univoco. Questo ID può essere utile durante la chiamata ai web-services di Sharepoint. Vediamo insieme come funziona.

1 public void ReadFileID(string listName, string fileName) 2 { 3 string name; 4 Lists l = new Lists(); 5 l.Credentials = new NetworkCredential(this.Username, this.Password, this.Domain); 6 7 XmlDocument doc = new XmlDocument(); 8 doc.LoadXml("<Document><Query /><ViewFields /><QueryOptions /></Document>"); 9 XmlNode listQuery = doc.SelectSingleNode("//Query"); 10 XmlNode listViewFields = doc.SelectSingleNode("//ViewFields"); 11 XmlNode listQueryOptions = doc.SelectSingleNode("//QueryOptions"); 12 XmlNode node = l.GetListItems(listName, string.Empty, listQuery, 13 listViewFields, 14 "100", listQueryOptions, null).FirstChild.NextSibling.FirstChild.NextSibling; 15 16 do 17 { 18 if (node.NodeType != XmlNodeType.Whitespace) 19 { 20 if (node.Name.Contains("row")) 21 { 22 name = node.Attributes["ows_FileRef"].Value.Split(';')[1].Replace("#", ""); 23 if (name.Equals(fileName)) 24 { 25 this.CacheFilesID.Add(fileName, node.Attributes["ows_ID"].Value); 26 break; 27 } 28 } 29 } 30 31 node = node.NextSibling; 32 } while (node != null); 33 }

Riga (4) e (5): creo un’istanza della classe wrapper per chiamare il web-services e ne imposto le credenziali per avere l’accesso.

Alla riga (12) avviene la chiamata vera e propria al metodo GetListItems. Esso restituisce un XML che contiene tutti gli elementi pubblicati nella document library, per cui dopo dovremo parsare l’XML per recuperare il file voluto. I parametri nella chiamata non sono pochi. Essi sono rispettivamente: il nome della lista, un eventuali nome della view, l’eventuale query, l’elenco dei campi da vedere, il row limit (messo a 100 in questo caso), eventuali query options ed il Web ID (opzionale).

Questa chiamata restituisce un XmlNode piuttosto lungo e complesso. Il codice non fa altro che saltare tutti i nodi di tipo Whitespace, recupera solo quelli il cui tag è “row”. Quando ne trova uno (riga 22), prendo l’attributo “ows_FileRef” e lo parserizza in modo brutale, splittando sul carattere ‘;’ ed eliminando la presenza del carattere ‘#’. Quando finalmente viene trovato il file voluto, lo inserisce in una cache (Dictionary<string, string>), in modo tale che se viene chiesto in un secondo momento l’ID dello stesso file, posso recuperarlo più velocemente dalla cache.

Technorati Tags:   

Send to Kindle
.NET World

Upload di un file in una document library di WSS: ed i metadati?

Di per sè non è una cosa estremamente complicata. E’ possibile accedere ad una document library di Sharepoint come se fosse un normale path di rete. Detto questo, potete per esempio uploadare un file locale su un server con una banalissima File.Copy – pratica che a me personalmente non piace perchè non è asincrona e soprattutto blocca il thread corrente fino a quando l’operazione non è completata (con successo o meno).

Per l’upload ho preferito usare questo blocco di codice…

public void UploadDocument(string localFilename, string remoteFilename) { FileStream strm = new FileStream(localFilename, FileMode.Open, FileAccess.Read); BinaryReader reader = new BinaryReader(strm); byte[] fileContents = reader.ReadBytes((int)strm.Length); reader.Close(); strm.Close(); string sRemoteFileURL = Settings.Default.ServerWS + "/" + remoteFilename.Trim(); sRemoteFileURL.Replace(@" ", @"%20"); sRemoteFileURL.Replace(@"", @"/"); WebClient client = new WebClient(); client.Credentials = new NetworkCredential(this.Username, this.Password, this.Domain); client.UploadData(sRemoteFileURL, "PUT", fileContents); client.Dispose(); }

…che utilizza la classe WebClient per inviare uno stream di bytes sul server WSS. Il metodo UploadData – che io ho utilizzato – è comunque sincrono, ma ricordo che ci sono le varianti asincrone che seguono il pattern APM. Con questa tecnica, non dovete utilizzare alcun web-service, il che rende il tutto estremamente comodo e semplice da utilizzare.

La parte più complicata, se così vogliamo definirla, è aggiornare eventuali metadati previsti dalla document library nella quale abbiamo copiato il file. Per dirla breve, i metadati sono le colonne aggiuntive che noi abbiamo creato nella document library. Facciamo un piccolo esempio. Supponiamo di aver creato una document library sul nostro server WSS chiamata “Download”: lo scopo è quello di pubblicare su questa lista tutti i files che noi vogliamo far scaricare agli utenti. Oltre alle classiche informazioni di base (Nome del file, Dimensione, Ultima modifica) è possibile creare campi aggiuntivi, così da rendere più fruibile il contenuto della lista (Descrizione, Versione, Requisiti, Tipo di file, etc.). Come è possibile valorizzare questi metadati per un determinato file già uploadato sul server?

Qui le cose si fanno complicate, e bisogna necessariamente fare uso dei web-services di Sharepoint. Per questo tipo di operazione dobbiamo utilizzare il web-service con un url simile a questo:

http://nome_server_WSS/nome_document_library/_vti_bin/lists.asmx

Ecco il metodo che io ho scritto:

1 public void UpdateDocumentMetadata(string listName, string remoteFilename, Dictionary<string, string> values) 2 { 3 string finalFilePath = Settings.Default.ServerWS + "/" + remoteFilename; 4 this.ReadFileID(listName, remoteFilename); 5 string fileID = this.CacheFilesID[remoteFilename]; 6 7 #region Building command using XML 8 StringBuilder strBuilder = new StringBuilder(); 9 strBuilder.AppendLine("<Method ID='1' Cmd='Update'>"); 10 strBuilder.AppendFormat("<Field Name = 'ID'>{0}</Field>", fileID); 11 strBuilder.AppendFormat("<Field Name = 'FileRef'>{0}</Field>", finalFilePath); 12 13 Dictionary<string, string>.Enumerator enumerator = values.GetEnumerator(); 14 enumerator.MoveNext(); 15 16 do 17 { 18 strBuilder.AppendFormat("<Field Name = '{0}'>{1}</Field>", enumerator.Current.Key, enumerator.Current.Value); 19 } while (enumerator.MoveNext()); 20 21 strBuilder.AppendLine("</Method>"); 22 #endregion 23 24 XmlDocument doc = new XmlDocument(); 25 XmlElement elBatch = doc.CreateElement("Batch"); 26 elBatch.InnerXml = strBuilder.ToString(); 27 28 Lists l = new Lists(); 29 l.Credentials = new NetworkCredential(this.Username, this.Password, this.Domain); 30 l.UpdateListItems(listName, elBatch); 31 }

Al metodo arriva in input il nome della document library, il nome del file ed un Dictionary<string, string>. Quest’ultimo contiene coppie di key/value per i metadati che vogliamo aggiornare. Alla riga (3) ottengo il path completo del file sul server WSS (esempio: http://nome_server_wss/nome_document_library/nome_file). Alla riga (5) ottengo un ID univoco per il file: per farlo faccio uso di un altro metodo, ReadFileID, che trova il file e riempie una cache, in modo tale che se chiedo in un secondo momento l’ID dello stesso file uso la cache e non più il web-service che adesso voi vedete nascosto nel metodo ReadFileID().

Dalla riga (7) alla riga (26) costruisco un blocco XML che contiene un comando formattato da inviare all’engine di Sharepoint. L’XML contiene l’ID del file e contiene i metadati da aggiornare (ciclo do…while), formati da tanti <Field></Field>, dove l’attributo Name è il nome della colonna nella lista, ed il contenuto del tag è il valore che il metadato corrente deve assumere.

Dalla riga (28) alla riga (30) costruisco un’istanza del web-services ed eseguo una chiamata al metodo UpdateListItems(). Se tutto è andato a buon fine, potete navigare nella document library e controllare il file uploadato: esso avrà i metadati valorizzati così come abbiamo popolato il Dictionary!

Technorati Tags:    

Send to Kindle
My personal life

Partenza della Nove Colli 2008

Ho pubblicato su YouTube un filmato di poco meno di 8 minuti con la partenza della Nove Colli 2008, la cicloturistica internazionale che parte ed arriva a Cesenatico (FC). I numeri di questa cicloturistica – alla quale partecipa ogni anno mio padre – sono i seguenti:

  1. 2 percorsi: uno “corto” da 130 chilometri, ed uno lungo da 200 chilometri
  2. quello corto prevede 4 colli, tra cui il mitico Barbotto (5,5km di salita, pendenza media del 7%, punta massina del 18%)
  3. quello lungo prevede invece 9 colli: da qui il nome della cicloturistica
  4. più di 11.000 partecipanti da tutto il mondo
  5. partenza alle 6:00 del mattino
  6. la carovana dei corridori ci mette circa 45 minuti per partire (dal primo all’ultimo, chiaramente)

E – aggiungo io oggi – 1 pazzo come me che si è svegliato quella domenica mattina alle 5:20 per andare alla partenza a filmare tutto! Buona visione!

Link : http://it.youtube.com/watch?v=Q1at2edRPnQ

Technorati Tags:    

Send to Kindle
Software

Tool per uploadare video su YouTube

Allora. Il link più utilizzato per inviare e pubblicare un video su YouTube è questo: http://it.youtube.com/my_videos_upload. Se non siete registrati, il link vi porta alla pagina di login. La fase di upload è divisa in due step: nel primo decidete il titolo da dare al video, la descrizione, i tag(s), data di registrazione, luogo della registrazione e via dicendo. Il secondo step prevede l’upload vero e proprio di un file video (preferibilmente AVI). Peccato però che già da un bel po’ di tempo durante l’upload non vi sia alcuna ProgressBar che vi possa minimamente dire a che punto siete. Pochi minuti fa ho terminato la pubblicazione di un file video da 8 minuti circa (118Mb) e ci ha messo più di un’ora: non avevo alcun indizio se tutto stava andando bene oppure no.

Ho trovato questo tool che si raggiunge sempre attraverso una pagina Web (Link : http://it.youtube.com/my_videos_multiupload), ma è un po’ più sofisticato. Avete una ProgressBar, potete uploadare più video allo stesso tempo, anche qui potete impostare titolo, tags, etc. etc.

Assolutamente consigliato!

Technorati Tags:    

Send to Kindle
My personal life

Icarus AeroExpo Athens 2008

Dal 12 al 15 Giugno prossimi io e mio fratello saremo ad Atene per partecipare (da spettatori) all’Icarus AeroExpo, una manifestazione interamente dedicata al volo di tutti i possibili velivoli: aerei militari, aerei civili, business jet, paracadutisti, modellismo, etc. Ci saranno seminari per piloti veri e virtuali, visita ai musei che verranno allestiti per l’occasione e molti altri eventi.

Un’altra occasione per scattare fotografie e per girare video con la mia piccola attrezzatura. E speriamo che ci sarà anche un po’ di tempo per girare la città! E speriamo che il tempo sia clemente, che in questi giorni non c’è troppo da star tranquilli!

Technorati Tags:   

Send to Kindle
.NET World

Evento Validating. Ok per Windows Forms. Ma per Windows Mobile?

Introduzione per chi non sa di cosa si sta parlando
Facciamola breve. L’evento Validating è il metodo più vecchio per validare il contenuto di un controllo in un’applicazione Windows Forms. L’evento Validating si scatena un attimo prima che un determinato controllo perda il focus: nell’event handler implementate il controllo che volete, e se le cose non vanno come dovrebbero, impostate a true il valore della proprietà Cancel del parametro di tipo CancelEventArgs. Quindi, supponiamo:

1 private void textBox1_Validating(object sender, CancelEventArgs e) 2 { 3 if(string.IsNullOrEmpty(this.textBox1.Text)) 4 e.Cancel = true; 5 }

Il codice qui sopra valida il contenuto di una TextBox su una Windows Forms. Se la TextBox è vuota, non potete andarvene: dovete per forza digitare qualcosa.

Il discorso fatto sinora è valido fintantochè la proprietà CausesValidation della TextBox (più in generale…del controllo) è impostata a true. Se questa proprietà è impostata a false, l’evento Validating non viene più scatenato, e quindi i vostri controlli di validazione non vengono più eseguiti.

Per la serie…non si finisce mai di imparare

Ma non è finita qua. Oltre a CausesValidation, è possibile lavorare con la proprietà AutoValidate del form. Tecnicamente parlando, questa proprietà esiste per tutti i controlli che ereditano da ControlContainer (Panel, GroupBox, TabControl, etc.). Il valore di default è EnablePreventFocusChange: evita che il controllo perda il focus se la validazione non avviene con successo. Qui ci sono maggiori informazioni su questo argomento.

E’ possibile impostare AutoValidate a Disabled. Questo impedisce lo scatenarsi dell’evento Validating, anche se avete impostato CausesValidation a true per un determinato controllo. A cosa potrebbe servire? Gli scenari che mi sono venuti in mente sono fondamentalmente due:

  • Non posso validare un controllo alla volta. Mi spiego meglio. Non sempre posso validare – per esempio – la TextBox su cui mi trovo senza tenere in considerazione altri controlli sulla Windows Form: controlli su cui magari devo ancora passare. Piccolo esempio: per validare una CodiceFiscaleTextBox sulla lunghezza (11 per partita iva o 16 per codice fiscale), devo anche controllare quale degli OptionButton l’utente ha selezionato, per indicare se è un privato o un utente aziendale.
  • Voglio validare esplicitamente quando lo dico io.

Mettendo AutoValidate a Disabled, è sempre e comunque possibile validare il contenuto della Windows Form chiamando esplicitamente il metodo ValidateChildren(), che restituisce un booleano. Questo metodo itera su tutti i controlli della Windows Form e scatena l’evento Validating solo su quelli che hanno CausesValidation a true. La chiamata al metodo restituisce true se tutte le validazioni sono avvenute con successo; restituisce false se almeno una delle validazioni è fallita.

E su Mobile?

Il Compact Framework prevede l’evento Validating sui controlli. Ma non c’è modo di utilizzare AutoValidate sulla Form, e neppure di usare CausesValidation. A quest’ultima cosa è naturalmente possibile sopperire in modo abbastanza semplice, creando una classe che erediti da TextBox e che aggiunga questa proprietà booleana che esegua la validazione solo quando vale true.

Technorati Tags:   

Send to Kindle