Questo sito utilizza i cookie per migliorare servizi ed esperienza del lettore. Se decidi di continuare la navigazione consideriamo che accetti il loro uso . Per e informazioni sulla Privacy leggere la Google Policies Privacy

Se continui nella navigazione accetti il loro uso. OK

martedì 24 agosto 2010

WPFNotes (Parte 2): Un po' di API Win32

Vediamo ora come aggiungere due funzionalità essenziali per l'applicazione:
  • Filtrare le note in base alle finestre che sono aperte in windows
  • Filtrare le note in base alla finestra attiva per dare maggiore priorità alle note contestuali
Per svolgere queste due operazioni dobbiamo utilizzare alcune funzioni di windows perchè il .NET non ci mette a disposizione questo genere di funzionalità. Partiamo con la più facile: trovare la finestra attiva e leggere la sua Caption. Le funzioni api che ci servono sono due:
GetForegroundWindow e GetWindowText. La prima ci permette di reperire la finestra attiva su cui sta lavorando l'utente (in pratica la funzione cerca la finestra attiva che ha la priorità leggermente più alta, cosa che viene fatat da windows per le finestre in Foreground).
La seconda invece invia un messaggio WM_GETTEXT a un determinato handle di finestra (Nota: non funziona con i controlli ospitati in altre finestre, ma con i caption delle window sì, cosa che per noi va benissimo!).
Vediamo di scrivere le due firme delle funzioni (copiate da msdn):

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetForegroundWindow();
Quello che ora dobbiamo scrivere è una funzione che, eseguita in polling con un timer ci permetta di sapere sempre quel'è la finestra attiva:
/// 
/// Restituisce il caption della finestra specificata.
/// 
const int MAXTITLE = 255;
public static string GetWindowText(IntPtr hWnd) {
    StringBuilder titolo = new StringBuilder(MAXTITLE);
    int titleLength = GetWindowText(hWnd, titolo, titolo.Capacity + 1);
    titolo.Length = titleLength;

    return titolo.ToString();
}

//Reperisce la finestra attiva e ne legge la caption
IntPtr ptr = EnumerateWindow.GetForegroundWindow();
if (ptr!=IntPtr.Zero)
    CurrentWindowText = EnumerateWindow.GetWindowText(ptr);
Fin qui tutto semplice. Vediamo adesso di risolvere un problema più complesso: la lettura di tutti i caption delle finestre che sono aperte.
Per svolgere questa operazione di avvaliamo della funzione EnumDesktopWindows. La funzione accetta 3 parametri. Il primo è il desktop su cui eseguire la ricerca (NULL per eseguire la ricerca nel desktop corrente), una funzione call back (EnumWindowsProc) che verrà richiamata ad ogni nuova finestra trovata e un lParam che verrà passato alla funzione di callback. Vediamo le dichiarazioni:
private delegate bool EnumDelegate(IntPtr hWnd, int lParam);

[DllImport("user32.dll", ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumDelegate lpEnumCallbackFunction, IntPtr lParam);

private static ArrayList listaTitoli;
/// 
/// Restituisce i caption di tutte le finestre nel desktop di windows
/// 
public static string[] GetDesktopWindowsCaptions() {
    listaTitoli = new ArrayList();
    EnumDelegate delegateFunction = new EnumDelegate(EnumWindowsProc);
    bool success = EnumDesktopWindows(IntPtr.Zero, delegateFunction, IntPtr.Zero);

    if (success) {
        // Copia il risultato in un'array di stringhe
        string[] titoli = new string[listaTitoli.Count];
        listaTitoli.CopyTo(titoli);
        return titoli;
    }
    else {
        // ERRORE
        int errore = Marshal.GetLastWin32Error();
        string errorMessage = String.Format("EnumDesktopWindows ha fallito con errore: {0}.", errore);
        throw new Exception(errorMessage);
    }
}
private static bool EnumWindowsProc(IntPtr hWnd, int lParam) {
    string titolo = GetWindowText(hWnd);
    if (titolo != "")
        listaTitoli.Add(titolo);
    return true;
}
Chiaramente la funzione di ricerca può essere resa più efficiente andando a cercare solo caption con determinate caratteristiche. Ma questo lo lascio alla vostra fantasia.

WPFNotes (Parte 1): Progettazione applicazione e Database

WPFNotes (Parte 1): Progettazione applicazione e Database

Scommetto che capita un po' a tutti di dover gestire una gran quantità di post-it e di liste di TODO che crescono inesorabilmente. Per non parlare poi di tutti gli username-password da ricordare oppure della vostra lista di HowTo che è diventata talmente grande da essere ormai inutile perchè non riuscite più a trovare quello che vi serve.
Ebbene, ho sempre avuto questa idea per la mente, delle note che apparissero in base alla finestra che state visualizzando.
Così se per caso dovete ricordarvi una determinata cosa all'apertura della finestra di Excel, ecco che quella nota appare. Oppure non ricordate mai la password quando dovete loggarvi in un sito che usate una volta ogni diversi eoni e dovete sempre cercarla dentro a un'enormità di mail, ecco che in base al titolo della finestra del browser la nota viene visualizzata.
Ho deciso quindi di creare una bella applicazione che facesse al caso mio.
Vediamo allora di crearci un'elenco di funzionalità che vogliamo che la nostra applicazione abbia:
  • Ricerca fra tutte le finestre aperte in windows per controllare se ci sono note contestuali che combaciano
  • Ricerca nella finestra attiva per rendere più visibile la notifica di note
  • Possibilità di aggiungere delle note in cui specificare la finestra a cui 'agganciarle' usando regular expression in modo da rendere più flessibile la ricerca.
  • Visualizzazione, modifica ed eliminazione di note già scritte.
  • Associare Tag a ogni singola nota in modo da poter visualizzare anche note correlate.
Inanzi tutto vediamo di creare un database per salvarci le nostre note. Useremo SQLServer Express 2008.
Andiamo a creare la tabella per il salvataggio delle note:

USE [bsd_Notes]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[T_NOTE](
[NOT_KEY] [int] IDENTITY(1,1) NOT NULL,
[NOT_NOTA] [text] NULL,
[NOT_WINDOW_NAME] [nvarchar](4000) NULL,
CONSTRAINT [PK_T_NOTE] PRIMARY KEY CLUSTERED 
(
[NOT_KEY] ASC
)WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
a questo punto creiamo una anagrafe per la memorizzazione dei tag da associare (così potranno essere condivisi e più facilmente utilizzati per correlare fra loro le note)
USE [bsd_Notes]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[T_TAG](
 [TAG_KEY] [int] IDENTITY(1,1) NOT NULL,
 [TAG_NOME] [nvarchar](100) NULL,
 CONSTRAINT [PK_T_TAG] PRIMARY KEY CLUSTERED 
(
 [TAG_KEY] ASC
)WITH (PAD_INDEX  = OFF,
STATISTICS_NORECOMPUTE  = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS  = ON,
ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
Quindi creiamo la tabella per la correlare le note con i tag:
USE [bsd_Notes]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[T_NOTE_TAG](
 [TNO_NOT_KEY_NOTE] [int] NOT NULL,
 [TNO_TAG_KEY_TAG] [int] NOT NULL,
 CONSTRAINT [PK_T_NOTE_TAG] PRIMARY KEY CLUSTERED 
(
 [TNO_NOT_KEY_NOTE] ASC,
 [TNO_TAG_KEY_TAG] ASC
)WITH (PAD_INDEX  = OFF,
STATISTICS_NORECOMPUTE  = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS  = ON,
ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[T_NOTE_TAG]  WITH CHECK 
ADD  CONSTRAINT [FK_T_NOTE_TAG_T_NOTE] 
FOREIGN KEY([TNO_NOT_KEY_NOTE])
REFERENCES [dbo].[T_NOTE] ([NOT_KEY])
GO
ALTER TABLE [dbo].[T_NOTE_TAG] CHECK CONSTRAINT [FK_T_NOTE_TAG_T_NOTE]
GO
ALTER TABLE [dbo].[T_NOTE_TAG]  WITH CHECK 
ADD  CONSTRAINT [FK_T_NOTE_TAG_T_TAG] 
FOREIGN KEY([TNO_TAG_KEY_TAG])
REFERENCES [dbo].[T_TAG] ([TAG_KEY])
GO
ALTER TABLE [dbo].[T_NOTE_TAG] CHECK CONSTRAINT [FK_T_NOTE_TAG_T_TAG]
GO
Il diagramma che ci troviamo è il seguente:
Diagramma del database

WPFNotes (Parte 2): Un po' di API Win32

mercoledì 18 agosto 2010

Creare un pdf da codice HTML

Parliamo sempre della libreria iTextSharp. Oggi vediamo come usarla per creare un pdf da del codice HTML e permettere all'utente di scaricarlo. Lo scenario è il seguente:
  • Abbiamo una pagina con dei post salvati su db (come questo forum per esempio)
  • Vogliamo aggiungere un pulsante su ogni post che permette di scaricarlo in formato pdf
  • Andremo a creare un HTTPHandler che crea il db e restituisce lo stream del file
Partiamo con la creazione dell'HTTPHandler. Intanto creiamo un file .ashx chiamato GetPDF.ashx:
//Codice del file GetPDF.ashx
<%@ WebHandler Language="C#" Class="GetPDF" %>

//Codice del file GetPDF.ashx.cs
public class GetPDF : IHttpHandler{
    public void ProcessRequest(HttpContext context){
    ...
    }

    public bool IsReusable { get { return false; } }
}
Ora andiamo a impostare, nella procedura ProcessRequest che verrà chiamata quando richiediamo il modulo GetPDF.ashx, gli header HTTP per indicare al browser che gli stiamo spedendo uno stream pdf e che deve permetterci il download del file:
context.Response.ContentType = "application/pdf";
context.Response.AddHeader("content-disposition", "attachment;filename=FileName.pdf");
//Toglie la cache al file da parte del browser
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
Per convertire un testo HTML in PDF utilizzaremo la classe HTMLWorker che permette il parsing di un semplice codice HTML e di qualche style (naturamente non copre completamente lo standard HTML, ma come vedremo in un'altro post, è possibile estendere questa classe per renderla più potente). I tag HTML supportati sono:
ol ul li a pre font span br p div body table td th tr i b u sub sup em strong s strike h1 h2 h3 h4 h5 h6 img hr.
e gli style css che vengono considerati sono (in parte):
align, width, height, size.
Prima di usarla però dobbiamo creare un TextReader che viene preso in pasto dalla funzione Parse della classe HTMLWorker.
System.IO.StringWriter stringWrite = new StringWriter();
//Legge il contento HTML a partire dalla query string
stringWrite.Write(GetContenuto(context.Request.QueryString["id"]));
StringReader reader = new StringReader(stringWrite.ToString());
A questo punto non ci serve altro che andare a creare un documento pdf, associarlo al parser HTML, creare un PDFWriter e indirizzare il suo output sullo stream della response, quindi scrivere il pdf.
Document doc = new Document(PageSize.A4);
HTMLWorker parser = new HTMLWorker(doc);
PdfWriter.GetInstance(doc, context.Response.OutputStream);
doc.Open();
try {
    parser.Parse(reader);
}
catch (Exception ex) {
    Paragraph paragraph = new Paragraph("Errore nella generazione del pdf: " + ex.Message);
    Chunk text = paragraph.Chunks[0] as Chunk;
    if (text != null)
        text.Font.Color = BaseColor.RED;
    paragraph.SetAlignment("center");
    doc.Add(paragraph);
}
finally {
    doc.Close();
}
L'utilizzo è semplicissimo, basta creare un link al file ashx e passargli come query string l'id dell'elemento che volgiamo trasformare in pdf:
/GetPDF.ashx?id=27

martedì 10 agosto 2010

Aggiungere un watermark trasparente a un PDF

Torniamo a parlare di PDF e della libreria iTextSharp che mi sta esaltando sempre di più.
Vedremo come applicare un watermark trasparente con un testo a piacere su tutte le pagine di un pdf esistente.
Iniziamo a vedere come aprire un pdf:
MemoryStream m = new MemoryStream();
PdfReader read = new PdfReader(@"c:\sample.pdf");
PdfStamper st = new PdfStamper(read, m);
Il MemoryStream conterrà in nuovo pdf modificato, il PdfReader si occuperà di aprere il flusso e il PdfStamper si occuparà di modificare il pdf e salvarlo nello strem.
Vediamo ora di analizzare il problema della stampa del testo che ovviamente vorremo vederlo ruotato lungo la diagonale del foglio. Quindi dobbiamo intanto calcolare l'angolo di rotazione del testo:
iTextSharp.text.Rectangle r = read.GetPageSize(1);
double ipo = Math.Sqrt(r.Width * r.Width + r.Height * r.Height);
double angolo = Math.Acos(r.Width / ipo) * 180 / Math.PI;
Premetto che la trigonometria non è il mio forte, ma avendo la base e l'altezza del triangolo, quella dovrebbe essere la formula corretta per calcolare l'angolo.
Ora vediamo di creare un font con cui scrivere nel nostro pdf e di impostare una dimensione e un colore con cui scrivere:
//Font standard (HELVETICA)
BaseFont bf = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.EMBEDDED);

//Acquisisco un 'livello' sopra al contenuto del pdf
PdfContentByte content = st.GetOverContent(1);

//Inizio a scrivere un testo, imposto size e colore
content.BeginText();
content.SetFontAndSize(bf, 120);
content.SetRGBColorFill(100, 100, 100);

//Disegno un testoa allineato al centro e ruotato
content.ShowTextAligned(Element.ALIGN_CENTER, "RESERVED", r.Width / 2, r.Height / 2, (float)angolo);
content.EndText();
Aggiungiamo una finezza ulteriore: la trasparenza del testo. La otteniamo creando uno GraphicState con:
PdfGState trasparenza = new PdfGState();
trasparenza.FillOpacity = 0.3f;
Dobbiamo ricordarci di impostare anche lo stato della grafica al contenuto.
Vediamo il codice completo (dovevamo anche scrivere su tutte le pagine!):
MemoryStream m = new MemoryStream();
PdfReader read = new PdfReader(@"c:\sample.pdf");
PdfStamper st = new PdfStamper(read, m);


PdfGState trasparenza = new PdfGState();
trasparenza.FillOpacity = 0.3f;

BaseFont bf = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.EMBEDDED);

//Le pagine vengono conteggiate a partire da 1
for (int i = 1; i <= read.NumberOfPages; i++) {
    iTextSharp.text.Rectangle r = read.GetPageSize(i);
    double ipo = Math.Sqrt(r.Width * r.Width + r.Height * r.Height);
    double angolo = Math.Acos(r.Width / ipo) * 180 / Math.PI;

    PdfContentByte content = st.GetOverContent(i);

    content.SaveState();
    content.SetGState(trasparenza);
    content.BeginText();
    content.SetFontAndSize(bf, 120);
    content.SetRGBColorFill(100, 100, 100);


    content.ShowTextAligned(Element.ALIGN_CENTER, "RESERVED", r.Width / 2, r.Height / 2, (float)angolo);
    content.EndText();
    content.RestoreState();
}

st.Close();
File.WriteAllBytes(@"c:\result.pdf", m.ToArray());

lunedì 9 agosto 2010

Inserimento di un codice a barre in un report RDLC

I report RDLC sono un potente mezzo messo a disposizione agli sviluppatori .NET per la creazione di report, potrebbero non avere l'eleganza di strumenti professionali, ma si distinguono per semplicità d'uso e facilità di installazione. Spesso ci si trova a dover inserire immagini al suo interno e ci si accorge che in fase di generazione di un PDF l'immagine stessa è inserita come parte dello stream del file. Fantastico penso io, questo vuol dire che se per esempio devo creare un bel report con un codice a barre stampigliato sopra mi basta indicarlo come font da usare per un'etichetta di testo e il gioco è fatto, durante il packing del file verrà inserita la risorsa del font (cosa che il formato pdf permette perfettamente) e anche se porto il mio pdf su un computer in cui non c'è il font indicato (o, ancor meglio, lo renderizzo da un'applicazione ASP.NET e lo faccio scaricare a chiunque), esso verrà comunque renderizzato senza problemi. E qui il mio sogno si infrange. Il font NON viene inserito come risorsa ma solo come riferimento, di conseguenza se nel mio sistema non c'è installato quel determinato font leggerò una serie senza senso di numeri e lettere.
A questo punto ho cominciato a cercare una libreria che mi permettesse di aprire un file pdf esistente (il file generato dal mio motore di reportistica) e modificarlo per inserire 'a mano' il codice a barre.
Nei miei pellegrinaggi mi sono imbattuto in una libreria opensource iTextSharp che è il porting .NET della potente libreria iText di java.
La mia idea iniziale era quella di andare ad includere il file ttf come risorsa all'interno del pdf, idea che poi è naufragata per la mia scarsa conoscenza della libreria (se qualcuno è riuscito a fare questo, può postare un commento sul blog nel quale può spiegare la tecnica a tutti o deridermi!).
Alla fine mi sono arreso a una soluzione paliativa, ma comunque funzionante. La libreria iTextSharp contiene svariate classi per la generazione di codici a barre, quindi non mi serviva altro che utilizzarle per modificare il mio file.
Partiamo con i passi base: prendere lo stream del nostro file pdf e renderlo modificabile con iTextSharp:
MemoryStream m = new MemoryStream();
PdfReader r = new PdfReader(pdfContent);
PdfStamper pdf = new PdfStamper(r, m);
Lo strem iniziale conterrà il file finale con la modifica, il PdfReader permette di leggere il flusso stream del file (pdfContent è un byte[]) e PdfStamper permette di prendere il file letto dal reader, eseguire delle modifice e scrivere il nuovo contenuto nel MemoryStream.
A questo punto dobbiamo andare a aprire una pagina del pdf (alla fine inseriremo il codice a barre su ogni pagina), creare una classe che ci permetterà di controllare il codice a barre, stampigliarlo sopra e salvare il tutto.
for (int i = 1; i <= r.NumberOfPages; i++){
   //Craezione della classe per la generazione del codice a barre
   Barcode128 b = new Barcode128();
   b.Code = '1234656789ABCDE'
   b.StartStopText = true;
   b.GenerateChecksum = false;
   b.Extended = true;
   b.BarHeight = 30;

   //Apertura della pagina
   PdfContentByte cb = pdf.GetOverContent(i);
   iTextSharp.text.Image img = b.CreateImageWithBarcode(cb, null, null);

   //Aggiunta in una posizione fissa del codice a barre
   img.SetAbsolutePosition(200, 20);
   cb.AddImage(img);
}
pdf.Close();

Response.Clear();
Response.AddHeader("content-disposition", "attachment;filename=" + "PDF.pdf");
Response.Charset = "";
Response.ContentType = "application/pdf";
Response.BinaryWrite(m.ToArray());
Response.Flush();
Response.End();
L'unico difetto di questa tecnica è la posizione assoluta del codice a barre. Ma con un pochino di fantasia possiamo andare a cercare qualche elemento sempre presente nel pdf e a leggere la sua posizione, quindi impostare il left e il top in base a questo valore. Ma questa è un'altra storia.

Inviare posta certificata di Aruba con C#

La posta certificata sta entrando a far parte della vita di tutti i giorni, con l'obbligo introdotto per legge di utilizzo per gli enti pubblici e il prossimo obbligo di utilizzo per tutte le aziende private.
Quindi, come non esimersi da creare delle procedure per l'invio di posta certificata.
In questo post ci soffermiamo sull'uso della posta certificata offerta da Aruba, ma i metodi qui descritti sono validi anche per altri provider di posta elettronica.
Prima di iniziare lo sviluppo controlliamo quello che ci viene detto da aruba per la configurazione di un client (alla fine ci serviranno proprio quei parametri per configurare il nostro client): http://assistenzaold.aruba.it/kb/idx/51/981/article/Parametri-per-la-configurazione-delle-caselle-PEC-sul-Client-di-Posta.html
Dato che vogliamo inviare la posta, i parametri che ci interessano sono quelli della posta in uscita, e quindi smtp. Andando a leggere la guida già ci accorgiamo di due cose, l'uso di una connessione autenticata SSL (Secure Socket Layer) impostata in maniera implicita (e cioè la connessione deve essere già in modalità SSL e quindi non impostata da un comando AUTH TLS). E qui cominciano già i primi problemi, infatti la classe System.Net.Mail supporta solo l'SSL impostato in modo esplicito. Di conseguenza una sequenza di comandi del genere:
Connect port 25 -> Start TLS -> Send
non viene recepita da un server che si attende già una connessione autenticata.
Ma attenzione, non disperate, in quanto il framework nasconde un segreto o più precisamente ha introdotto un segreto, infatti il vecchio namespace System.Web.Mail che dal complatore viene marcato come deprecato con messaggi di sfida ad usarlo, può operare in maniera SSL implicita. Tutto questo, ovviamente, non senza qualche difficoltà...
System.Web.Mail.MailMessage newMail = new System.Web.Mail.MailMessage();
newMail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserver", "smtps.pec.aruba.it");
newMail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserverport", "465");
newMail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusing", "2");
newMail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", "1");
newMail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername", "miamail@pec.it");
newMail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword", "password");
newMail.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpusessl", "true");

newMail.From = "miamail@pec.it";
newMail.To = "[dest]";
newMail.Subject = "[object]";
newMail.BodyFormat = System.Web.Mail.MailFormat.Text;
newMail.Body = "[body]";
newMail.Priority = System.Web.Mail.MailPriority.High;

System.Web.Mail.SmtpMail.SmtpServer = "smtps.pec.aruba.it:465";
System.Web.Mail.SmtpMail.Send(newMail);
il parametro sendusing permette di impostare il meccanismo di invio (in questo caso utilizzando la rete (SMTP over the network)).

Estensione della classe double

Sin dalla versione 1.0 del framework .NET gli sviluppatori avevano a disposizione il metodo Parse per convertire una stringa composta da numeri in un double, la limitazione di questo metodo era evidente: se il testo non poteva essere convertito in un numero scatenava un'eccezione, con l'abbligo da parte dello sviluppatore di dover gestire con un blocco try...catch... il fallimeto di una conversione. Con la versione 2.0 finalmente viene introdotto (per la maggior parte dei tipi base, ma non per tutti (?)) il metodo TryParse che si occupa di gestire eventuali eccezioni in fase di parsing (chiaramente senza utilizzare lenti costrutti try...catch... necessari prima). Esso restituisce true se la conversione  va a buon fine o false altrimenti.
Sempre con l'introduzione del framework 2.0 sono stati introdotti anche i tipi nullable che permettevano di valorizzare a null un value type (come un double o una data), con grande sollievo di tutte quelle persone che avevano bisogno di database nelle proprie applicazioni! Purtroppo ci è accorti di una profonda mancanza (che sussiste tuttora, con la versione 4.0 del framework). La possibilità di fare il parsing utilizzando la capacità dei nullable di impostarsi a null (!).
public static double? ParseDoubleNullable(object source) {
   double d;
   if (double.TryParse(source.ToString(), out d))
      return d;
   else
      if (double.TryParse(source.ToString(), System.Globalization.NumberStyles.Currency, null, out d))
      return d;
   else
      return null;
}
questo metodo è funzionale, ma scomodo, perchè necessita di ricordarsi il namespace dove risiede l'estensione e rende faragginosa la lettura del codice.
Per risolvere questi piccoli problemi possiamo spingerci oltre e andare a creare un metodo di estensione che si applichi a stringhe (o più in generale a un'object) e che permetta di restituirci un double?.
public static double? ParseDoubleNullable(this object source) {
   double d;
   if (double.TryParse(source.ToString(), out d))
      return d;
   else
   if (double.TryParse(source.ToString(), System.Globalization.NumberStyles.Currency, null, out d))
      return d;
   else
      return null;        
}
Questo, chiaramente, apre tutta una serie di possibili metodi di parsing!

Chiamare un metodo di una pagina ASP.NET da jQuery

Tramite ASP.NET abbiamo la possibilità di chiamare un metodo statico, passargli dei parametri e farci restituire dei dati come se fosse un servizio web. I web service di ASP.NET sono basati sul protocolo SOAP, ma dalla versione 3.5 il protocollo di default è JSON, che è perfettamente compatibile con javascript. Ora vediamo come creare un metodo ASP.NET e, utilizzando jQuery, richiamarlo dal client senza eseguire alcun PostBack. L'unica limitazione (anche se non è così grave) che si ha è l'utilizzo di metodi statici. Per iniziare vediamo come creare un metodo server che ci permetta di formattare una data utilizzando le potenti funzioni di formattazione del .NET:
[WebMethod]
public static string FormatDateTime(DateTime data, string format) {
   return data.ToString(format);
}
a questo punto il metodo (essendo statico e marcato come WebMethod) è richiamabile attraverso una chiamata POST all'url http://[sito]/[paginaaspx]/FormatDateTime e inviando nello stream della chiamata post, codificata in JSON la sequenza dei parametri da invocare. Infatti se andiamo a leggere lo stream di input della Request ci troveremo:
byte[] s=new byte[HttpContext.Current.Request.InputStream.Length];
HttpContext.Current.Request.InputStream.Seek(0, System.IO.SeekOrigin.Begin);
HttpContext.Current.Request.InputStream.Read(s, 0, (int)HttpContext.Current.Request.InputStream.Length);
Avremo come input stream della richiesta una stringa del genere:
{"data":"2010-08-09T09:44:48.882Z","format":"dd MMMM yyyy"}.
Bene, una volta che abbiamo capito come funziona la 'parte server' vediamo di andare a fare questa chiamata POST utilizzando jQuery come framework client.
JQuery mette a disposizione un comodo metodo per eseguire chiamate AJAX: $.ajax al quale si possono passare dei parametri per controllare la chiamata, quali l'url, il metodo da utilizzare, l'eventuale stream di dati. Vediamo di analizzare quali parametri dobbiamo andare a impostare perché la nostra chiamata abbia successo:
  • l'url che deve essere il nome della pagina seguita dal nome del metodo, come abbiamo visto prima.
  • la tipologia di invio dati, che nel nostro caso deve essere di tipo POST
  • il content-type (inviato come header http) che deve essere 'application/json; charset=utf-8' per consentire ad ASP.NET di utilizzare il giusto formatter.
  • i parametri da inviare alla richiesta, sotto forma di stringa JSON (useremo JSON.stringify)
$.ajax({
   url: aspx + "/" + method,
   type: "POST",
   contentType: "application/json; charset=utf-8",
   data: pars == null ? null : JSON.stringify(pars),
   dataType: "json"
});
il parametro dataType indica a jQuery che i dati che verranno restituiti saranno in formato JSON (internamente jQuery andrà ad eseguire un eval sui dati restituiti per poterci fornire un'oggetto già completo di proprietà).
Ora vediamo di creare un bel wrapper alla funzione che ci permetterà di richiamarla in maniera agevole e di fare la nostra chiamata AJAX in tutta semplicità:
function callASPX(aspx, method, asyncMethod, pars, success, errorFunc) {
    $.ajax({
        url: aspx + "/" + method,
        type: "POST",
        contentType: "application/json; charset=utf-8",
        data: pars == null ? null : JSON.stringify(pars),
        dataType: "json",
        async: asyncMethod,
        success: function (msg) {
            if (msg != null) {
                if (success != undefined)
                    success(msg.d);
            }
            else
                if (success != undefined)
                    success(null);
        },
        error: function (XMLHttpRequest, textStatus, errorThrown) {
            if (errorFunc == undefined)
                alert("Errore: " + XMLHttpRequest.responseText);
            else {
                errorFunc(XMLHttpRequest);
            }
        }
    });
}
In questa funzione abbiamo aggiunto la possibilità di richiamare il metodo in maniera sincrona o asincrona, di controllare eventuali errori e di richiamare una funzione una volta che i dati ritornano al client. La nostra chiamata sarà quindi:
callASPX("/Playsport/Default.aspx", "FormatDateTime", true, {data:new Date(), format:'dd MMMM yyyy'}, function (ret) {
   alert(ret);
});