Service Oriented e i tanti file di configurazione

Una delle cose scomode che si ha in un'applicazione service oriented è il deploy e i numerosi file di configurazione. Se penso alla nostra attuale struttura parliamo di circa 20 servizi e di altrettanti file di configurazione.

Molto spesso questi file di configurazione hanno parti in comune tra loro, tipo custom section, behaviors, ecc e ogni qual volta devi cambiare una di queste parti sei costretto a dover modificare decine di file di configurazione.
Per ovviare il problema mi sono venute in mente due soluzione (se ne avete altre sono ben accette :D):

  1. Mettere le configurazioni in comune su file di configurazione esterni;
  2. Creare un file di configurazione comune a tutti i servizi.

Diciamo che mi è piaciuta più la seconda opzione, minor numero di file da gestire e maggior difficoltà nel realizzare la cosa :D (per la serie le cose semplici non ci piacciono).

Detto ciò ne è uscita la seguente classe:

using System; 
using System.Configuration; 
using System.IO; 
using System.ServiceModel; 
using System.ServiceModel.Configuration; 
using System.Web.Hosting; 

public class MyServiceHost : ServiceHost 
{ 
    private string configPath; 

    /// <summary> 
    /// Initializes a new instance of the <see cref="MyServiceHost"> class. 
    /// </see> 
    /// </summary> 
    /// <param name="serviceType" />Type of the service. 
    /// <param name="baseAddresses" />The base addresses. 
    public MyServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="MyServiceHost"> class. 
    /// </see> 
    /// <param name="singletonInstance" />The instance of the hosted service. 
    /// <param name="baseAddresses" />An <see cref="T:System.Array"> of type <see cref="T:System.Uri"> that contains the base addresses for the hosted service. 
    /// <exception cref="T:System.ArgumentNullException"> 
    ///     <paramref name="singletonInstance"> is null.</paramref> 
    public MyServiceHost(object singletonInstance, params Uri[] baseAddresses) 
        : base(singletonInstance, baseAddresses) 
    { 
    } 

    private string ConfigPath 
    { 
        get 
        { 
            if (configPath == null) 
            { 
                // Hostato in IIS 
                configPath = HostingEnvironment.ApplicationPhysicalPath; 

                if (String.IsNullOrEmpty(configPath)) 
                    //Non hostato da IIS 
                    configPath = Directory.GetCurrentDirectory(); 
            } 

            return configPath; 
        } 
    } 

    /// <summary> 
    /// Loads the service description information from the configuration file and applies it to the runtime being constructed. 
    /// </summary> 
    /// <exception cref="T:System.InvalidOperationException">The description of the service hosted is null.</exception> 
    protected override void ApplyConfiguration() 
    { 
        // generate the name of the custom configFile, from the service name: 
        string configFilename = Path.Combine(ConfigPath, 
                                             String.Format("{0}.config", Description.Name)); 

        if (!string.IsNullOrEmpty(configFilename) && File.Exists(configFilename)) 
            base.ApplyConfiguration(); 
        else 
            LoadConfigFromCustomLocation(@"C:\Temp\Services.config"); 
    } 


    /// <summary> 
    /// Load the config file from custom location. 
    /// </summary> 
    /// <param name="configFilename" />The config filename. 
    private void LoadConfigFromCustomLocation(string configFilename) 
    { 
        var filemap = new ExeConfigurationFileMap {ExeConfigFilename = configFilename}; 

        Configuration config = 
            ConfigurationManager.OpenMappedExeConfiguration 
                (filemap, 
                 ConfigurationUserLevel.None); 

        ServiceModelSectionGroup serviceModel = ServiceModelSectionGroup.GetSectionGroup(config); 

        if (serviceModel == null) 
            throw new ConfigurationErrorsException("There are a problem with the configuration file."); 

        bool loaded = false; 
        foreach (ServiceElement se in serviceModel.Services.Services) 
        { 
            if (!loaded) 
                if (se.Name == Description.ConfigurationName) 
                { 
                    LoadConfigurationSection(se); 
                    loaded = true; 
                } 
        } 
        if (!loaded) 
            throw new ArgumentException("ServiceElements not found in the configuration file."); 
    } 
}

Il modo di utilizzarlo rimane lo stesso:

ServiceHost service = new MtvServiceHost(typeof(EmailService)); 
service.Open();

Nel file Services.config possiamo configurare tutti i servizi di cui abbiamo bisogno, ma tutto il resto del file di configurazione è comune a tutti.
Cambiamo un custom behavior comune a tutti, lo facciamo in un unico posto in un unico file!

Per ora sembra funzionare :D

 


Comments