2009-10-18 10 views
5

Ecco la domanda prima:Generazione automatica di un'AppSettings fortemente tipizzato classe

è possibile? Mi sto ispirando a Joe Wrobel's work (un richiamo del dimenticato Codeplex project). Qui, tu fai il tuo lavoro sulla creazione del tuo profilo per il provider, e fa il gioco di parole per creare una forte tipizzazione, creando in modo efficace una facciata per la classe Profile.

E ora la storia precedente!

Davvero non mi piace magic strings. Sono piuttosto cattivi e possono causare seri problemi quando si tratta di aggiornare la tua applicazione. Avendo lavorato in linguaggi come PHP e ColdFusion, so che è facile inserirli nella tua applicazione e dimenticarli finché non ne hai bisogno. E poi devi cacciare ogni variazione di loro e modificarli di conseguenza.

. NET non è molto meglio se si seguono i modelli di applicazione 'out of the box'. Un sacco di esempi là fuori usano le impostazioni dell'app in web.config per memorizzare varie impostazioni. Questo è davvero un bel posto dove riporre ed è perfetto per la maggior parte delle applicazioni. Tuttavia, i problemi cominciano a sorgere quando si inizia a chiamarli direttamente, ad esempio ConfigurationManager.AppSettings["MyAppSetting"]. Allora non sei molto più bravo di un utente di PHP quando torni ad usare le stringhe magiche.

Questo è il punto in cui entra facades. Le facciate offrono un modo di creare un oggetto fortemente tipizzato da una stringa magica in un unico punto, e facendo riferimento allo sviluppatore dal resto dell'applicazione.

Ora, invece di utilizzare un web.config per contenere le mie app, utilizzo un database per conservarle tutte. All'avvio dell'applicazione, le combinazioni nome/valore vengono recuperate e quindi aggiunte in sequenza allo ConfigurationManager.AppSettings tramite Set. No biggie (a parte lo problem avevo in precedenza!).

Questa 'facciata dell'applicazione' è accessibile dal livello dati, livello servizio e livello presentazione e contiene elementi come la modalità applicazione, che l'endpoint del servizio utilizza yada yada yada e limita la necessità di dover cercare per molte stringhe magiche, fino a due corde magiche - una (il nome) nella facciata e l'altra (il nome e il valore) nel punto di creazione (che, per me è il db).

Questa classe di facciata alla fine diventerà piuttosto grande e alla fine mi stancherò di dover aggiornare entrambi.

Quindi quello che mi piacerebbe fare è avere una classe ApplicationFacade che si auto-genera ogni volta che viene eseguita una compilazione. E ora di nuovo all'inizio ... È possibile?

risposta

7

È inoltre possibile utilizzare i modelli CodeSmith per questo scopo.Vantaggio è che è possibile impostare in proprietà del file template da rigenerare in ogni generazione (set BuildAction = "Complile")

Redatta ho anche guardato per tale soluzione. Dopo aver cercato su google ho trovato il modello base T4 per generare una classe del genere. L'ho ridisegnato e lo puoi trovare qui sotto.

Template sta generando classe wrapper per la sezione appSetting dal web.config/file app.config

Supponiamo di avere le seguenti linee di impostazioni nel file di configurazione

<appSettings> 
    <add key="PageSize" value="20" /> 
    <add key="CurrentTheme" value="MyFavouriteTheme" /> 
    <add key="IsShowSomething" value="True" /> 
    </appSettings> 

Dopo l'elaborazione del modello si otterrà dopo classe

namespace MyProject.Core 
{ 
    /// <remarks> 
    /// You can create partial class with the same name in another file to add custom properties 
    /// </remarks> 
    public static partial class SiteSettings 
    { 
     /// <summary> 
     /// Static constructor to initialize properties 
     /// </summary> 
     static SiteSettings() 
     { 
      var settings = System.Configuration.ConfigurationManager.AppSettings; 
      PageSize = Convert.ToInt32(settings["PageSize"]); 
      CurrentTheme = (settings["CurrentTheme"]); 
      IsShowSomething = Convert.ToBoolean(settings["IsShowSomething"]); 
     } 

     /// <summary> 
     /// PageSize configuration value 
     /// </summary> 
     public static readonly int PageSize; 

     /// <summary> 
     /// CurrentTheme configuration value 
     /// </summary> 
     public static readonly string CurrentTheme; 

     /// <summary> 
     /// IsShowSomething configuration value 
     /// </summary> 
     public static readonly bool IsShowSomething; 

    } 
} 

Salva seguente codice al file * .tt e comprendono a yo il tuo progetto dove vuoi mettere il file generato. Per rigenerare classe su ogni costruire see my answer here Template riconoscere tipi di stringa, datetime, int e bool dai valori

<#@ assembly name="System.Core" #> 
<#@ assembly name="System.Xml" #> 
<#@ assembly name="System.Xml.Linq" #> 
<#@ import namespace="System" #> 
<#@ import namespace="System.Text" #> 
<#@ import namespace="System.IO" #> 
<#@ import namespace="System.Linq" #> 
<#@ import namespace="System.Xml.Linq" #> 
<#@ import namespace="Microsoft.VisualBasic" #> 
<#@ template language="VB" debug="True" hostspecific="True" #> 
<#@ output extension=".Generated.cs" #> 
<# 
    Dim projectNamespace as String = "MyProject.Core" 
    Dim className as String = "SiteSettings" 
    Dim fileName as String = "..\..\MyProject.Web\web.config" 

    Init(fileName) 

#> 
//------------------------------------------------------------------------------ 
// FileName = <#= path #> 
// Generated at <#= Now.ToLocaltime() #> 
// 
// <auto-generated> 
//  This code was generated by a tool. 
// 
//  Changes to this file may cause incorrect behavior and will be lost if 
//  the code is regenerated. 
//  
// NOTE: Please use the Add a Reference to System.Configuration assembly if 
//   you get compile errors with ConfigurationManager 
// </auto-generated> 
//------------------------------------------------------------------------------ 

using System; 
using System.Configuration; 

namespace <#= projectNamespace #> 
{ 
    /// <remarks> 
    /// You can create partial class with the same name in another file to add custom properties 
    /// </remarks> 
    public static partial class <#= className #> 
    { 
     /// <summary> 
     /// Static constructor to initialize properties 
     /// </summary> 
     static <#= className #>() 
     { 
      var settings = System.Configuration.ConfigurationManager.AppSettings; 
<#= AddToCostructor(path) #>  } 

<#= RenderApplicationSettings(path) #> } 
} 

<#+ 
    Dim path as String = "" 
    Dim doc as XDocument = Nothing 

    Public Sub Init(fileName as String) 
     Try 
      path = Host.ResolvePath(fileName) 
      If File.Exists(path) Then 
       doc = XDocument.Load(path) 
      End If 
     Catch 
      path = "<< App.config or Web.config not found within the project >>" 
     End Try  
    End Sub 

    Public Function AddToCostructor(ByVal path as String) as String     
     If doc Is Nothing Then Return "" 

     Dim sb as New StringBuilder() 

     For Each result as XElement in doc...<appSettings>.<add>    
      sb.Append(vbTab).Append(vbTab).Append(vbTab) 
      sb.AppendFormat("{0} = {1}(settings[""{0}""]);", [email protected], GetConverter([email protected])) 
      sb.AppendLine() 
     Next 

     Return sb.ToString() 

    End Function 

    Public Function RenderApplicationSettings(ByVal path as String) as String 
     If doc Is Nothing Then Return "" 

     Dim sb as New StringBuilder()  

     For Each result as XElement in doc...<appSettings>.<add>  
      dim key = [email protected] 
      sb.Append(vbTab).Append(vbTab) 
      sb.Append("/// <summary>").AppendLine() 
      sb.Append(vbTab).Append(vbTab) 
      sb.AppendFormat("/// {0} configuration value", key).AppendLine()    
      sb.Append(vbTab).Append(vbTab) 
      sb.Append("/// </summary>").AppendLine() 
      sb.Append(vbTab).Append(vbTab) 
      sb.AppendFormat("public static readonly {0} {1}; ", GetPropertyType([email protected]), key)  
      sb.AppendLine().AppendLine() 
     Next 

     Return sb.ToString() 

    End Function 

    Public Shared Function GetConverter(ByVal prop as String) as String  
     If IsNumeric(prop) Then Return "Convert.ToInt32" 
     If IsDate(prop) Then Return "Convert.ToDateTime" 
     dim b as Boolean 
     If Boolean.TryParse(prop, b) Then Return "Convert.ToBoolean"   
     Return "" 
    End Function 

    Public Shared Function GetPropertyType(ByVal prop as String) as String 
     If IsNumeric(prop) Then Return "int" 
     If IsDate(prop) Then Return "DateTime" 
     dim b as Boolean 
     If Boolean.TryParse(prop, b) Then Return "bool" 
     Return "string" 
    End Function 

#> 
+0

Questa è un'idea interessante, ma non sono un grande fan di CodeSmith per essere onesto. Alla fine, scrissi una mia classe che era necessaria in ogni caso, perché non c'era modo per la mia applicazione di dedurre che tipo erano le mie appsettings. –

+1

Ho aggiunto un po 'di codice al mio post. Spero che possa aiutarti. – Cheburek

+0

Questa è una soluzione davvero interessante! Una cosa che sembrava avere problemi con (e questo è VBs IsNumeric) è "0,5,0" che VB crede sia un valore numerico, anche se non sono sicuro di come! –

1

È possibile eseguire questa operazione con una fase di pre-costruzione. Questo è ragionevolmente facile da fare: basta scrivere un programma o uno script o un modello che rigenera la classe e chiamarla nel tuo evento pre-build, ma questo ti darà wigglies rossi e nessun intellisense sui nuovi membri fino a quando la classe non diventerà rigenerato.

Un approccio leggermente più manuale, ma probabilmente più conveniente, sarebbe quello di creare un T4 template e includerlo nel progetto. Dovresti comunque ricordarti di trasformare nuovamente il modello ogni volta che hai aggiunto una nuova impostazione. Sarebbe troppo oneroso?

+0

Grazie per la risposta! Realisticamente preferirei che qualunque cosa facessi, l'intero processo di compilazione della classe di facciata è automatico e non è richiesto allo sviluppatore alcun lavoro. Ci sono risorse preesistenti che sai di ciò che puoi fare? –

+0

Hmm, dal momento che ci si trova in ASP.NET, si potrebbe essere in grado di farlo utilizzando lo spazio dei nomi System.Web.Compilation, che penso sia quello che i progetti del sito Web di ASP.NET utilizzano per creare le proprie classi del profilo. Sfortunatamente, questo è molto al di fuori della mia area di conoscenza, e non sono nemmeno sicuro che funzioni in progetti Web (basati su .csproj). Scusate. L'altra opzione è di guardare gli strumenti personalizzati VS (IVsSingleFileGenerator) ma sono progettati per uno scenario diverso e funzioneranno solo se le definizioni delle impostazioni sono conservate nel loro file speciale. – itowlson

+0

Questo - http://blog.jqweb.ca/?p=44 - sembra qualcosa che potrei usare, ma dovrò adattarmi se estrarre dal db, piuttosto che dal web.config ... I ' Aggiornerò se/quando trovo una soluzione. –

Problemi correlati