In questa sesta puntata dedicata alla libreria di base degli Helper, discuteremo una classe che si occupa di fornire delle scorciatoie ad alcune operazioni sui files, che quotidianamente faremo nelle nostre applicazioni.
Introduzione
Quest’ultima piccola classe, raccoglie una serie di metodi per produrre, verificare generare nomi di files e cartelle, FileHelper utilizza le classi di System.IO per il suo lavoro, permettendoci di generare file di log su una cartella su cui siamo certi l’utente ha diritto di scrivere, ci permette di leggere files sulla cartella di installazione del programma e di generare cartelle se ne abbiamo necessità. Inoltre, ci permette di creare il nome dei files per la memorizzazione dei settings utente e dei settings applicazione personalizzati.
La struttura base della classe
using System; using System.Collections.Generic; using System.Text; using System.IO; using Dnw.Base.Logger; namespace Dnw.Base { public static class FileHelper { private readonly static string mClassName = System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name; } }
Imports System Imports System.Collections.Generic Imports System.Text Imports System.IO Imports Dnw.Base.Logger Public NotInheritable Class FileHelper Private Shared ReadOnly mClassName As String = _ System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name Private Sub New() End Sub End Class
Nulla cambia rispetto a tutte le altre classi helper, anche questa è una classe statica in C# traslata come classe NotInheritable con costruttore privato in VB.
Le costanti
private const string FMP_TmpDateTime = "yyyymmddHHmmssff"; private const string TXT_APPDATA = "APPDATA"; private const string TXT_AssemblyVuoto = "Il Nome dell'assembly non può essere vuoto"; private const string TXT_Backspace = "\\"; private const string TXT_ConfigExtension = ".dnwcfg"; private const string TXT_Dot = "."; private const string TXT_FilenameVuoto = "Il nome file non può essere vuoto"; private const string TXT_FileNonEsiste = "Il file {0} non esiste"; private const string TXT_FirmBaseDirectory = "DotNetWork"; private const string TXT_LogExtension = ".log"; private const string TXT_PathVuota = "La stringa Path non può essere vuota"; private const string TXT_ProductNameVuoto = "Il Product Name non può essere vuoto"; private const string TXT_USERNAME = "USERNAME";
Private Const FMP_TmpDateTime As String = "yyyymmddHHmmssff" Private Const TXT_APPDATA As String = "APPDATA" Private Const TXT_AssemblyVuoto As String = "Il Nome dell'assembly non può essere vuoto" Private Const TXT_Backspace As String = "\" Private Const TXT_ConfigExtension As String = ".dnwcfg" Private Const TXT_Dot As String = "." Private Const TXT_FilenameVuoto As String = "Il nome file non può essere vuoto" Private Const TXT_FileNonEsiste As String = "Il file {0} non esiste" Private Const TXT_FirmBaseDirectory As String = "DotNetWork" Private Const TXT_LogExtension As String = ".log" Private Const TXT_PathVuota As String = "La stringa Path non può essere vuota" Private Const TXT_ProductNameVuoto As String = "Il Product Name non può essere vuoto" Private Const TXT_USERNAME As String = "USERNAME"
Abbiamo utilizzato le costanti sia per i messaggi di errore sia per definire formattazioni e stringhe di base, queste stringhe possono essere trasferite nel file delle risorse della libreria se pensiamo di aver bisogno di tradurle in più lingue.
Usare le costanti è una questione di disciplina, non sempre rende il codice più leggibile per quanto lo renda più pulito, è certo comunque che rende semplice il passaggio a un file di risorse internazionalizzato e soprattutto usandole sui nomi di controlli e nomi di campi delle datatable è utile a evitarci errori di nomenclatura che generano eccezioni.
Il metodo CheckCreateDir
public static void CheckCreateDir(string pDir) { try { if (!System.IO.Directory.Exists(pDir)) { System.IO.Directory.CreateDirectory(pDir); } } catch (Exception ex) { EventLogger.SendMsg(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), MessageType.Error); throw new ApplicationException(" " + mClassName + TXT_Dot + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }
Public Shared Sub CheckCreateDir(ByVal pDir As String) Try If Not System.IO.Directory.Exists(pDir) Then System.IO.Directory.CreateDirectory(pDir) End If Catch ex As Exception EventLogger.SendMsg(mClassName, _ System.Reflection.MethodBase.GetCurrentMethod(), MessageType.[Error]) Throw New ApplicationException(" " _ + mClassName + TXT_Dot + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End Sub
Questo metodo, data una stringa contenente il nome di una cartella, utilizza le classi di System.IO per verificarne l’esistenza ed eventualmente generarla. Può tornare utile usarla prima di generare un file di output di qualsiasi tipo.
Il metodo CheckNoFile
public static void CheckNoFile(string pPath) { if (!System.IO.File.Exists(pPath)) { throw new NoFileException(string.Format(TXT_FileNonEsiste, pPath)); } }
Public Shared Sub CheckNoFile(ByVal pPath As String) If Not System.IO.File.Exists(pPath) Then Throw New NoFileException(String.Format(TXT_FileNonEsiste, pPath)) End If End Sub
Un metodo molto semplice per accertarsi se un file esiste, per esempio quando dobbiamo leggere dei dati dal file system. In questo metodo utilizziamo l’eccezione personalizzata NoFileException che abbiamo visto nella puntata precedente di questa serie.
Il Metodo CreateAppConfigFleName
public static string CreateAppConfigFileName(string pAssemblyName) { try { if (StringHelper.IsNullOrTrimEmpty( pAssemblyName) ) { throw new EmptyParameterException(TXT_AssemblyVuoto); } return GetAppDir(String.Concat(pAssemblyName, TXT_ConfigExtension)); } catch (Exception ex) { EventLogger.SendMsg(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), MessageType.Error); throw new ApplicationException(" " + mClassName + TXT_Dot + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }
Public Shared Function CreateAppConfigFileName(ByVal pAssemblyName As String) As String Try If (StringHelper.IsNullOrTrimEmpty( pAssemblyName)) Then Throw New EmptyParameterException(TXT_AssemblyVuoto) End If Return GetAppDir([String].Concat(pAssemblyName, TXT_ConfigExtension)) Catch ex As Exception EventLogger.SendMsg(mClassName, _ System.Reflection.MethodBase.GetCurrentMethod(), MessageType.[Error]) Throw New ApplicationException(" " + mClassName _ + TXT_Dot + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End Function
Questo è un metodo da utilizzarsi nella configurazione delle applicazioni, se come accade nelle mie applicazioni, viene utilizzato un file non standard per la memorizzazione di settaggi e di parametri applicativi, è opportuno che il suo nome sia costruito in modo standard. Questo metodo genera un nome di file costruito con la forma:
PathDiInstallazione\Nomeassembly.dnwcfg ad esempio: c:\Programmi\Dotnetwork\HelloWorld.dnwcfg
Ovviamente il modo di costruire questo tipo di nome è ancora una volta una decisione arbitraria, una regola di business che io ho stabilito, non un assoluto, ciascuno può costruire le proprie regole in merito in base alle proprie esigenze.
Il metodo CreateUserLogFileName
public static string CreateUserLogFileName(string pProductName, string pFileName) { string ret = ""; try { if (StringHelper.IsNullOrTrimEmpty( pProductName)) { throw new EmptyParameterException(TXT_ProductNameVuoto); } if (StringHelper.IsNullOrTrimEmpty( pFileName)) { throw new EmptyParameterException(TXT_FilenameVuoto); } StringBuilder filename = new StringBuilder(Environment.GetEnvironmentVariable(TXT_APPDATA)); if (!(filename.ToString()).EndsWith(TXT_Backspace)) { filename.Append(TXT_Backspace); } filename.Append(TXT_FirmBaseDirectory); CheckCreateDir(filename.ToString()); filename.Append(TXT_Backspace); filename.Append(pProductName); // Determine whether the directory exists. CheckCreateDir(filename.ToString()); filename.Append(TXT_Backspace + pFileName); filename.Append(TXT_LogExtension); ret = filename.ToString(); } catch (Exception ex) { EventLogger.SendMsg(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), MessageType.Error); throw new ApplicationException(" " + mClassName + TXT_Dot + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } return (ret); }
Public Shared Function CreateUserLogFileName(ByVal pProductName _ As String, ByVal pFileName As String) As String Dim ret As String = "" Try If (StringHelper.IsNullOrTrimEmpty( pProductName)) Then Throw New EmptyParameterException(TXT_ProductNameVuoto) End If If (StringHelper.IsNullOrTrimEmpty( pFileName )) Then Throw New EmptyParameterException(TXT_FilenameVuoto) End If Dim filename As New StringBuilder(Environment.GetEnvironmentVariable(TXT_APPDATA)) If Not (filename.ToString()).EndsWith(TXT_Backspace) Then filename.Append(TXT_Backspace) End If filename.Append(TXT_FirmBaseDirectory) CheckCreateDir(filename.ToString()) filename.Append(TXT_Backspace) filename.Append(pProductName) ' Determine whether the directory exists. CheckCreateDir(filename.ToString()) filename.Append(TXT_Backspace + pFileName) filename.Append(TXT_LogExtension) ret = filename.ToString() Catch ex As Exception EventLogger.SendMsg(mClassName, _ System.Reflection.MethodBase.GetCurrentMethod(), MessageType.[Error]) Throw New ApplicationException(" " + mClassName _ + TXT_Dot + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try Return (ret) End Function
Questo metodo, genera il nome del file di Log per un utente, viene costruito per essere posizionato nella cartella Application Data (Dati Applicazioni) dell’utente, ove siamo certi di avere il permesso di scrittura e lettura. la stringa viene composta nel seguente modo:
UserApplicationDataDir\CompanyBaseDirName\ProductName\Filename.LogExtension
nel nostro caso, posto che il ProductName sia HelloWorld e il FileName sia HelloLog il risultato sarà:
C:\Documents and settings\username\Dati Applicazioni\DotNetWork\HelloWorld\HelloLog.log (su Win XP)
C:\Users\username\AppData\Roaming\DotnetWork\HelloWorld\HelloLog.log (su Windows Vista)
Non ho ancora esplorato tutte le differenze fra XP e Vista, ma credo potremo verificarle testando le classi con NUnit nel prossimo articolo.
Come per il metodo precedente anche questo usa regole di business arbitrarie, per quanto, l’uso della cartella Application Data per i files di configurazione utente e per gli eventuali file accessori ad una applicazione sia fra le linee guida del team di sviluppo di Microsoft.
Il metodo CreateUserTempFileName
public static string CreateUserTempFileName(string pFolder, string pExtension) { string ret = String.Empty; try { StringBuilder filename = new StringBuilder(); if (StringHelper.IsNullOrTrimEmpty( pFolder)) { pFolder = Path.GetTempPath(); } filename.Append(TXT_Backspace); filename.Append(Environment.GetEnvironmentVariable(TXT_USERNAME)); filename.Append(DateTime.Now.ToString(FMP_TmpDateTime)); if (!pExtension.StartsWith(TXT_Dot)) { filename.Append(TXT_Dot); } filename.Append(pExtension); ret = filename.ToString(); } catch (Exception ex) { EventLogger.SendMsg(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), MessageType.Error); throw new ApplicationException(" " + mClassName + TXT_Dot + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } return ret; }
Public Shared Function CreateUserTempFileName(_ ByVal pFolder As String, ByVal pExtension As String) As String Dim ret As String = [String].Empty Try Dim filename As New StringBuilder() If (StringHelper.IsNullOrTrimEmpty( pFolder)) Then pFolder = Path.GetTempPath() End If filename.Append(TXT_Backspace) filename.Append(Environment.GetEnvironmentVariable(TXT_USERNAME)) filename.Append(DateTime.Now.ToString(FMP_TmpDateTime)) If Not pExtension.StartsWith(TXT_Dot) Then filename.Append(TXT_Dot) End If filename.Append(pExtension) ret = filename.ToString() Catch ex As Exception EventLogger.SendMsg(mClassName, _ System.Reflection.MethodBase.GetCurrentMethod(), MessageType.[Error]) Throw New ApplicationException(" " + mClassName _ + TXT_Dot + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try Return ret End Function
In questo caso generiamo un file temporaneo con nome univoco che può essere necessario per qualsiasi esigenza, non partiamo da una cartella precisa, perché il file potrebbe essere necessario posizionarlo in una cartella specifica, ad esempio se si tratta di un file dati usato per comunicare in modo asincrono con un servizio di elaborazione a cui semplicemente poniamo il file con i dati da elaborare in una cartella senza comunicazione diretta. L’univocità del nome del file la generiamo assegnando al file dei dati relativi all’utente e un timestamp rilevato dalla macchina su cui il file è generato. Non è ovviamente a prova di bomba, ma ci sono ottime probabilità che non esistano files simili.
Il file generato è nella forma: NomeCartellaDestinazione\NomeutenteAAAAmmgghhmmssff.estensione
ove AAAAmmgg ecc. è il valore di data ed ora corrente al centesimo di secondo.
Se necessitassimo di certezza, possiamo sempre utilizzare un GUID per il nome file, ma questo metodo, ci permette di capire chi e quando a colpo d’occhio e potrebbe tornarci utile in alcuni casi.
Come per i metodi precedenti, qui ciascuno può decidere le proprie regole in base a dove vuole arrivare.
Il Metodo CreateUserConfigFileName
public static string CreateUsrConfigFileName(string pProductName, string pAssemblyName) { try { if (StringHelper.IsNullOrTrimEmpty( pProductName)) { throw new EmptyParameterException(TXT_ProductNameVuoto); } if (StringHelper.IsNullOrTrimEmpty( pAssemblyName)) { throw new EmptyParameterException(TXT_AssemblyVuoto); } StringBuilder sb = new StringBuilder(Environment.GetEnvironmentVariable(TXT_APPDATA)); if (!(sb.ToString()).EndsWith(TXT_Backspace)) { sb.Append(TXT_Backspace); } sb.Append(TXT_FirmBaseDirectory); CheckCreateDir(sb.ToString()); sb.Append(TXT_Backspace); sb.Append(pProductName); // Determine whether the directory exists. CheckCreateDir(sb.ToString()); sb.Append(TXT_Backspace + pAssemblyName); sb.Append(TXT_ConfigExtension); return (sb.ToString()); } catch (Exception ex) { EventLogger.SendMsg(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), MessageType.Error); throw new ApplicationException(" " + mClassName + TXT_Dot + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }
Public Shared Function CreateUsrConfigFileName(ByVal pProductName As String, _ ByVal pAssemblyName As String) As String Try If(StringHelper.IsNullOrTrimEmpty( pProductName)) Then Throw New EmptyParameterException(TXT_ProductNameVuoto) End If If (StringHelper.IsNullOrTrimEmpty( pAssemblyName)) Then Throw New EmptyParameterException(TXT_AssemblyVuoto) End If Dim sb As New StringBuilder(Environment.GetEnvironmentVariable(TXT_APPDATA)) If Not (sb.ToString()).EndsWith(TXT_Backspace) Then sb.Append(TXT_Backspace) End If sb.Append(TXT_FirmBaseDirectory) CheckCreateDir(sb.ToString()) sb.Append(TXT_Backspace) sb.Append(pProductName) ' Determine whether the directory exists. CheckCreateDir(sb.ToString()) sb.Append(TXT_Backspace + pAssemblyName) sb.Append(TXT_ConfigExtension) Return (sb.ToString()) Catch ex As Exception EventLogger.SendMsg(mClassName, _ System.Reflection.MethodBase.GetCurrentMethod(), MessageType.[Error]) Throw New ApplicationException(" " + mClassName + _ TXT_Dot + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End Function
Questo metodo procede in modo simile a quello che genera il file di Log per generare il nome di un file personalizzato per la memorizzazione di parametri di configurazione per uno specifico utente. Anche in questo caso viene utilizzato il percorso Application Data (Dati Applicazioni) per generare un file specifico in una cartella ove l’utente ha certamente i permessi di lettura e scrittura. La forma del nome file è la seguente:
UserApplicationDataDir\CompanyBaseDirName\ProductName\Filename.ConfigExtension
Quindi usando lo stesso esempio che abbiamo utilizzato per il log, otteniamo:
C:\Documents and settings\username\Dati Applicazioni\DotNetWork\HelloWorld\HelloLog.dnwcfg (su Win XP)
C:\Users\username\AppData\Roaming\DotnetWork\HelloWorld\HelloLog.dnwcfg (su Windows Vista)
Il Metodo GetAppDir
public static string GetAppDir() { return AppDomain.CurrentDomain.SetupInformation.ApplicationBase; }
Public Shared Function GetAppDir() As String Return My.Application.Info.DirectoryPath 'Return AppDomain.CurrentDomain.SetupInformation.ApplicationBase End Function
Questo metodo minuscolo, l’ho generato perché in C# non ho trovato un metodo più semplice per ottenere la cartella di installazione di un programma e ricordarlo a memoria è complicato. Invece in VB come potete vedere c’è un semplice parametro su My. L’uso di questa versione del metodo è più raro, più facilmente si utilizza il suo Overload, per comporre il nome di un file o una sottocartella ove ad esempio abbiamo installato dei file di risorsa che la nostra applicazione utilizza ma non possono essere inclusi direttamente come risorse compilate.
public static string GetAppDir(string pPath) { if (pPath == null) pPath = String.Empty; return Path.Combine(GetAppDir(), pPath); }
Public Shared Function GetAppDir(ByVal pPath As String) As String If pPath Is Nothing Then pPath = [String].Empty End If Return Path.Combine(GetAppDir(), pPath) End Function
Questo secondo overload, permette di comporre il nome di un file o di una sottocartella che si trova sotto alla cartella di installazione del programma.
Conclusioni
Anche questa classe Helper è terminata, sicuramente in futuro potremo modificarla aggiungendovi nuovi metodi, ma con essa a disposizione avremo il necessario per iniziare a pensare ad una semplice applicazione funzionante. Nel codice dei metodi di questa classe potete notare che abbiamo utilizzato altre classi spiegate nelle puntate precedenti, quindi abbiamo iniziato ad assemblare fra loro i mattoni di base che costituiscono i pezzi riutilizzabili che OOP ci permette di sviluppare.
Concluderò la presentazione di queste mini classi con un ulteriore articolo in cui utilizzeremo NUnit per testare il funzionamento delle classi sviluppate a cui allegherò il progetto completo delle classi.
Per qualsiasi Feedback, Ulteriore domanda, Chiarimento, oppure se trovate qualche errore usate direttamente il form di contatto con un click sulla bustina in cima alla pagina.
Potete scaricare il progetto esempio dedicato alle classi di uso comune dal link qui sotto indicato.