Questo articolo illustra come testare il sistema di Log che abbiamo progettato e realizzato nel post precedente, mostrando come usarlo all’interno di una applicazione WPF.
Introduzione
Nel precedente articolo, disponibile QUI, abbiamo implementato in una delle nostre librerie di uso comune una serie di classi che permettono di creare un sistema di Log in qualsiasi applicazione. Vediamo come testarne l’uso in una applicazione WPF. Premetto che essendo lo scopo dell’applicazione testare le funzionalità di Log, non è da prendersi in considerazione come esempio brillante di applicazione WPF, anche se, spiegheremo alcuni concetti base di WPF per i beginners e per chi sta passando a WPF da Winforms e si trova ad avere nuove modalità funzionali da assorbire e metabolizzare.
La soluzione
Creiamo una nuova soluzione in Visual Studio che chiameremo TestTheLogger, con un progetto di tipo WPF Application.
Visual Studio ci genererà questo progetto.
Quello che vogliamo ottenere noi è qualcosa di un pochino diverso, che ci serve per spiegare alcuni piccoli concetti iniziali che possono dare una mano a chi crea questo tipo di applicazione per la prima volta.
Questa è la soluzione che vogliamo ottenere noi, nel solution explorer, la differenza che si può notare è che MainWindow, la finestra principale (ed unica) di questa applicazione si trova in una sottocartella che si chiama Windows.
Lo spostamento fisico della classe è molto semplice, tasto destro sul progetto e crea nuova cartella per creare la cartella Windows, e poi un Drag and Drop dell’oggetto MainWindow dentro alla cartella.
Modificare il Namespace delle classi in WPF
Ottimo, però, noi che arriviamo da Windows Forms, siamo abituati a identificare le sottocartelle di progetto come dei Namespace, essendo un sistema creato per aiutarci ad organizzare il codice, vediamo come modificare il namespace in una applicazione WPF. Prima di tutto modifichiamo il default namespace a livello di Progetto:
Doppio click sulla cartella Properties del progetto e sostituiamo TestTheLogger con Dnw.TestTheLogger
Per sostituire il namespace nelle classi, in un progetto Winforms faremmo un Trova e Sostituisci, e possiamo farlo anche in WPF, facendo attenzione che le classi WPF hanno il Namespace sia nel file .cs che nel file .Xaml, quindi le sostituzioni da effettuare sono due:
Se non modifichiamo il namespace anche sul file .XAML l’effetto sarà lo stesso che cambiare il namespace ad una Windows Form per la classe .cs e dimenticarlo per la classe .designer.cs, infatti, in WPF, lo XAML da origine ad un File .cs autogenerato dal compilatore.
Per la classe MainWindow.xaml.cs e .xaml, dobbiamo fare lo stesso, ma in questo caso, il namespace sarà: Dnw.TestTheLogger.Windows.
Perché fare questo esercizio inutile in un progetto di Test? Semplicemente per spiegare dei concetti di base che serviranno negli articoli futuri.
Una volta sistemato il progetto nella modalità a noi più consona, iniziamo con la prima domanda.
Dove mettiamo quanto necessario ad attivare il log?
EventLogger.LogType = LogType.ExceptionsOnly; EventLogger.LogNewEntry += new SendMessageEventHandler(EventLogger_LogNewEntry);
Le due righe di codice qui sopra sono le righe sufficienti e necessarie ad attivare il logger, ovvero indichiamo che tipo di log vogliamo sia effettuato e assegnamo un event handler all’evento statico che si occupa di generare il log, il contenuto dell’event handler non è importante in questo momento, è importante invece decidere in quale porzione di codice lo mettiamo.
Se fosse una applicazione Console, lo inseriremo nel metodo Main.
Se fosse una applicazione Windows Forms, lo inseriremo nel metodo Main, che si trova dentro a Program.cs.
Ma in WPF, non c’è il metodo Main. Rimandandovi alla Figura 1 qui sopra, potete notare che App.xaml.cs non contiene alcuna funzione Main. In realtà, il Main esiste anche in WPF, ma si trova nel codice autogenerato,
Questa è la classe autogenerata dal compilatore a partire dal contenuto dello .xaml, non la possiamo eliminare da qui perché verrebbe rigenerata, a meno di cambiare il tipo di classe dell’oggetto App che non è auspicabile se vogliamo sfruttare quanto il framework ci mette a disposizione nella classe Application.
Allora, dove mettiamo il codice di Startup di una applicazione WPF?
La classe Application di WPF contiene una serie di eventi al suo interno, fra questi un evento Startup, che è esattamente quello che ci serve.
Come modifichiamo la classe per poterlo gestire?
<Application x:Class="Dnw.TestTheLogger.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application>
Dallo Xaml della classe Application eliminiamo lo StartupUri, che contiene il nome dell’oggetto di startup dell’applicazione. Nel Template Standard di WPF è la classe MainWindow.
<Application x:Class="Dnw.TestTheLogger.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Application.Resources> </Application.Resources> </Application>
Il codice dell’applicazione
Adesso, andiamo a costruire il codice dell’evento di startup modificato per soddisfare le nostre esigenze:
protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); StartupTheLogger(); OpenMainWindow(); }
All’interno del file App.xaml.cs, facciamo un override dell’evento della classe base, chiamiamo ovviamente l’evento della classe base e poi inseriamo due metodi:
- StartupTheLogger – che inizializza il sistema di Log
- OpenMainWindow – che replica quello che automaticamente faceva il Main prima della nostra modifica ed avvia la finestra principale dell’applicazione.
Vediamo ora cosa contengono i due metodi:
public void StartupTheLogger() { string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), LOG_FOLDER); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } string testLog = string.Format("TestLog{0}_{1}_{2}.log", DateTime.Now.Year, DateTime.Now.Month.ToString("0#"), DateTime.Now.Day.ToString("0#")); mLogFileName = Path.Combine(path, testLog); EventLogger.LogType = LogType.ExceptionsOnly; EventLogger.LogNewEntry += new SendMessageEventHandler(EventLogger_LogNewEntry); }
Lo startup del sistema di log, genera un nome di file ed eventualmente una cartella ove memorizzarlo sulla cartella documenti dell’utente che esegue l’applicazione, il nome del file abbiamo scelto di crearlo con anno, mese e giorno corrente, in modo che venga generato un file di Log per ogni giornata.
Inizializziamo inoltre il tipo di log per loggare solo le eccezioni e aggiungiamo un event handler all’evento LogNewEntry il cui contenuto vedremo fra poco.
private void OpenMainWindow() { this.MainWindow = new MainWindow(mLogFileName); MainWindow.Show(); }
Il metodo qui sopra, fa esattamente quello che avremmo fatto in un progetto Windows Forms, crea un istanza della classe MainWindow e la mostra.
Vediamo ora cosa fa il nostro event handler quando viene loggato un messaggio:
void EventLogger_LogNewEntry(object sender, SendMessageEventArgs e) { try { if (EventLogger.LogType != LogType.None) { StringBuilder message = new StringBuilder(); bool doLog = false; switch (e.MessageType) { case MessageType.Error: { doLog = true; break; } case MessageType.Warning: { if (EventLogger.LogType == LogType.WarningsAndExceptions || EventLogger.LogType == LogType.AllMessages) { doLog = true; } break; } case MessageType.Info: { if (EventLogger.LogType == LogType.AllMessages) { doLog = true; } break; } } if (doLog) { message.AppendLine(new string('*', 80)); message.AppendFormat("[{0} {1}]", DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString()); message.AppendLine(); message.Append(e.MessageToLog); message.AppendLine(); message.AppendLine(new string('*', 80)); message.AppendLine(); using (StreamWriter sw = new StreamWriter(mLogFileName, true, Encoding.UTF8)) { sw.Write(message.ToString()); sw.Flush(); sw.Close(); } } } } catch (Exception) { //Se il logger da un eccezione c'è qualcosa che non va ma non possiamo //gestirla } }
Il metodo è molto semplice, costruisce una stringa messaggio verificando se il LogType prevede sia loggata e accoda la stringa al file di testo che abbiamo predisposto allo startup.
Questo è ovviamente un esempio, infatti il sistema potrebbe inviare il messaggio alla console, potrebbe fare una chiamata HTTP e inoltrarlo ad un webservice, potrebbe mostrarla a video, potrebbe scriverla su un database. Dipende dall’uso che vogliamo fare del sistema di Log. Se come in questo caso è predisposto come sistema per il log degli errori ed il debug, è sempre opportuno restare su una gestione semplice, un file di testo è facile da creare ed aggiornare, anche se si è verificato un errore sul sistema.
La nostra MainWindow, testiamo il sistema
Non vi tedierò con il codice .xaml della finestra di test che troverete nel progetto di esempio a corredo, ne mostrerò unicamente la forma.
La nostra finestra ha i seguenti controlli e relative funzionalità:
- un set di RadioButton che quando modifichiamo quello selezionato cambiano automaticamente il tipo di messaggi che verrà loggato.
- Tre Button, che effettuano il log dei tre diversi tipi di messaggi forniti dal sistema e uno che cancella il file di log.
- Una textbox, txtResult che useremo per visualizzare il contenuto del file di log.
- Un TextBlock per i messaggi di stato.
RadioButton rb = sender as RadioButton; switch (rb.Name) { case "optAll": EventLogger.LogType = LogType.AllMessages; break; case "optExceptions": EventLogger.LogType = LogType.ExceptionsOnly; break; case "optNone": EventLogger.LogType = LogType.None; break; case "optWarnings": EventLogger.LogType = LogType.WarningsAndExceptions; break; } ShowLogTypeStatus();
Il codice eseguito al cambio dell’opzione selezionata, modifica la proprietà statica dell’event logger in modo che il sistema sappia quali messaggi loggare.
//Log di una informazione EventLogger.SendMsg(string.Format("Sono le {0} {1} e tutto va bene", DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString()), MessageType.Info); //Log di un avviso EventLogger.SendMsg(string.Format("Questo è un avviso spedito alle {0} {1} e qualcosa non va bene", DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString()), MessageType.Warning); //Log di una exception try { throw new ApplicationException(string.Format("Test of an application sent by me at {0} {1}", DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString())); } catch (Exception ex) { EventLogger.SendMsg(ex); }
Il codice dei tre bottoni di Log, come potete notare, piuttosto banale, permette di inviare un messaggio che il logger interpreta e salva sul file di testo.
Ho preparato un breve video che mostra il risultato finale della nostra applicazione di test.
Conclusioni
Abbiamo testato la funzionalità del sistema di Log progettato nell’articolo precedente a questo ed abbiamo introdotto un paio di concetti iniziali relativi a WPF.
Il progetto che testa l’uso del logger è disponibile al link seguente:
Per qualsiasi domanda, commento, approfondimento, o per segnalare un problema, potete utilizzare il link alla form di contatto in cima alla pagina.