J'essaie d'implémenter une section de configuration personnalisée dans un projet et je continue à me heurter à des exceptions que je ne comprends pas. J'espère que quelqu'un pourra remplir les blancs ici.
J'ai App.config
qui ressemble à ceci:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="ServicesSection" type="RT.Core.Config.ServicesConfigurationSectionHandler, RT.Core"/>
</configSections>
<ServicesSection type="RT.Core.Config.ServicesSection, RT.Core">
<Services>
<AddService Port="6996" ReportType="File" />
<AddService Port="7001" ReportType="Other" />
</Services>
</ServicesSection>
</configuration>
J'ai un élément ServiceConfig
défini comme suit:
public class ServiceConfig : ConfigurationElement
{
public ServiceConfig() {}
public ServiceConfig(int port, string reportType)
{
Port = port;
ReportType = reportType;
}
[ConfigurationProperty("Port", DefaultValue = 0, IsRequired = true, IsKey = true)]
public int Port
{
get { return (int) this["Port"]; }
set { this["Port"] = value; }
}
[ConfigurationProperty("ReportType", DefaultValue = "File", IsRequired = true, IsKey = false)]
public string ReportType
{
get { return (string) this["ReportType"]; }
set { this["ReportType"] = value; }
}
}
Et j'ai un ServiceCollection
défini comme suit:
public class ServiceCollection : ConfigurationElementCollection
{
public ServiceCollection()
{
Console.WriteLine("ServiceCollection Constructor");
}
public ServiceConfig this[int index]
{
get { return (ServiceConfig)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
public void Add(ServiceConfig serviceConfig)
{
BaseAdd(serviceConfig);
}
public void Clear()
{
BaseClear();
}
protected override ConfigurationElement CreateNewElement()
{
return new ServiceConfig();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((ServiceConfig) element).Port;
}
public void Remove(ServiceConfig serviceConfig)
{
BaseRemove(serviceConfig.Port);
}
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
public void Remove(string name)
{
BaseRemove(name);
}
}
La partie qui me manque, c'est ce qu'il faut faire pour le gestionnaire. À l’origine, j’ai essayé de mettre en œuvre une IConfigurationSectionHandler
mais j’ai trouvé deux choses:
Je ne sais plus quoi faire pour pouvoir lire mes données dans config. Toute aide s'il vous plaît!
La réponse précédente est correcte, mais je vais également vous donner tout le code.
Votre app.config devrait ressembler à ceci:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="ServicesSection" type="RT.Core.Config.ServiceConfigurationSection, RT.Core"/>
</configSections>
<ServicesSection>
<Services>
<add Port="6996" ReportType="File" />
<add Port="7001" ReportType="Other" />
</Services>
</ServicesSection>
</configuration>
Vos classes ServiceConfig
et ServiceCollection
restent inchangées.
Vous avez besoin d'une nouvelle classe:
public class ServiceConfigurationSection : ConfigurationSection
{
[ConfigurationProperty("Services", IsDefaultCollection = false)]
[ConfigurationCollection(typeof(ServiceCollection),
AddItemName = "add",
ClearItemsName = "clear",
RemoveItemName = "remove")]
public ServiceCollection Services
{
get
{
return (ServiceCollection)base["Services"];
}
}
}
Et cela devrait faire l'affaire. Pour le consommer, vous pouvez utiliser:
ServiceConfigurationSection serviceConfigSection =
ConfigurationManager.GetSection("ServicesSection") as ServiceConfigurationSection;
ServiceConfig serviceConfig = serviceConfigSection.Services[0];
Si vous recherchez une section de configuration personnalisée comme suit
<CustomApplicationConfig>
<Credentials Username="itsme" Password="mypassword"/>
<PrimaryAgent Address="10.5.64.26" Port="3560"/>
<SecondaryAgent Address="10.5.64.7" Port="3570"/>
<Site Id="123" />
<Lanes>
<Lane Id="1" PointId="north" Direction="Entry"/>
<Lane Id="2" PointId="south" Direction="Exit"/>
</Lanes>
</CustomApplicationConfig>
alors vous pouvez utiliser mon implémentation de la section de configuration afin de commencer, ajoutez System.Configuration
Référence d'assemblage à votre projet
Regardez chacun des éléments imbriqués que j'ai utilisés, le premier est des informations d'identification avec deux attributs, ajoutons-les d'abord
Elément d'identification
public class CredentialsConfigElement : System.Configuration.ConfigurationElement
{
[ConfigurationProperty("Username")]
public string Username
{
get
{
return base["Username"] as string;
}
}
[ConfigurationProperty("Password")]
public string Password
{
get
{
return base["Password"] as string;
}
}
}
PrimaryAgent et SecondaryAgent
Les deux ont les mêmes attributs et ressemblent à une adresse vers un ensemble de serveurs pour un serveur principal et un basculement. Il vous suffit donc de créer une classe d'élément pour les deux éléments suivants:
public class ServerInfoConfigElement : ConfigurationElement
{
[ConfigurationProperty("Address")]
public string Address
{
get
{
return base["Address"] as string;
}
}
[ConfigurationProperty("Port")]
public int? Port
{
get
{
return base["Port"] as int?;
}
}
}
J'expliquerai comment utiliser deux éléments différents avec une classe plus tard dans ce post. Laissons de côté le SiteId car il n'y a aucune différence. Il vous suffit de créer une classe identique à celle ci-dessus avec une seule propriété. Voyons comment implémenter la collection Lanes
il est divisé en deux parties, vous devez d'abord créer une classe d'implémentation d'élément, puis vous devez créer une classe d'élément de collection
LaneConfigElement
public class LaneConfigElement : ConfigurationElement
{
[ConfigurationProperty("Id")]
public string Id
{
get
{
return base["Id"] as string;
}
}
[ConfigurationProperty("PointId")]
public string PointId
{
get
{
return base["PointId"] as string;
}
}
[ConfigurationProperty("Direction")]
public Direction? Direction
{
get
{
return base["Direction"] as Direction?;
}
}
}
public enum Direction
{
Entry,
Exit
}
vous pouvez remarquer qu'un attribut de LanElement
est une énumération et si vous essayez d'utiliser une autre valeur dans la configuration qui n'est pas définie dans l'application Énumération, un System.Configuration.ConfigurationErrorsException
sera lancé au démarrage. Ok permet de passer à la définition de collection
[ConfigurationCollection(typeof(LaneConfigElement), AddItemName = "Lane", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class LaneConfigCollection : ConfigurationElementCollection
{
public LaneConfigElement this[int index]
{
get { return (LaneConfigElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
public void Add(LaneConfigElement serviceConfig)
{
BaseAdd(serviceConfig);
}
public void Clear()
{
BaseClear();
}
protected override ConfigurationElement CreateNewElement()
{
return new LaneConfigElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((LaneConfigElement)element).Id;
}
public void Remove(LaneConfigElement serviceConfig)
{
BaseRemove(serviceConfig.Id);
}
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
public void Remove(String name)
{
BaseRemove(name);
}
}
vous pouvez remarquer que j’ai réglé le AddItemName = "Lane"
vous pouvez choisir ce que vous voulez pour votre élément d’entrée de collection, je préfère utiliser "ajouter" celui par défaut, mais je l’ai modifié uniquement pour les besoins de cet article.
Maintenant que tous nos éléments imbriqués ont été implémentés, nous devrions maintenant regrouper tous ceux-ci dans une classe devant implémenter System.Configuration.ConfigurationSection
CustomApplicationConfigSection
public class CustomApplicationConfigSection : System.Configuration.ConfigurationSection
{
private static readonly ILog log = LogManager.GetLogger(typeof(CustomApplicationConfigSection));
public const string SECTION_NAME = "CustomApplicationConfig";
[ConfigurationProperty("Credentials")]
public CredentialsConfigElement Credentials
{
get
{
return base["Credentials"] as CredentialsConfigElement;
}
}
[ConfigurationProperty("PrimaryAgent")]
public ServerInfoConfigElement PrimaryAgent
{
get
{
return base["PrimaryAgent"] as ServerInfoConfigElement;
}
}
[ConfigurationProperty("SecondaryAgent")]
public ServerInfoConfigElement SecondaryAgent
{
get
{
return base["SecondaryAgent"] as ServerInfoConfigElement;
}
}
[ConfigurationProperty("Site")]
public SiteConfigElement Site
{
get
{
return base["Site"] as SiteConfigElement;
}
}
[ConfigurationProperty("Lanes")]
public LaneConfigCollection Lanes
{
get { return base["Lanes"] as LaneConfigCollection; }
}
}
Maintenant, vous pouvez voir que nous avons deux propriétés portant les noms PrimaryAgent
et SecondaryAgent
ayant le même type. Vous pouvez donc facilement comprendre pourquoi nous n'avions qu'une seule classe d'implémentation pour ces deux éléments.
Avant de pouvoir utiliser cette section de configuration nouvellement inventée dans votre app.config (ou web.config), il vous suffit de dire à votre application que vous avez inventé votre propre section de configuration et de la respecter, pour ce faire, vous devez ajouter les lignes suivantes dans app.config (peut être juste après le début de la balise racine).
<configSections>
<section name="CustomApplicationConfig" type="MyNameSpace.CustomApplicationConfigSection, MyAssemblyName" />
</configSections>
REMARQUE: MyAssemblyName doit être sans .dll, par exemple. Si votre nom de fichier d'assemblage est myDll.dll, utilisez myDll au lieu de myDll.dll
pour récupérer cette configuration, utilisez la ligne de code suivante n'importe où dans votre application
CustomApplicationConfigSection config = System.Configuration.ConfigurationManager.GetSection(CustomApplicationConfigSection.SECTION_NAME) as CustomApplicationConfigSection;
J'espère que le message ci-dessus vous aiderait à démarrer avec un type de configuration personnalisée un peu compliqué.
Bonne codage :)
**** Edit **** Pour activer LINQ sur LaneConfigCollection
vous devez implémenter IEnumerable<LaneConfigElement>
Et ajouter après la mise en oeuvre de GetEnumerator
public new IEnumerator<LaneConfigElement> GetEnumerator()
{
int count = base.Count;
for (int i = 0; i < count; i++)
{
yield return base.BaseGet(i) as LaneConfigElement;
}
}
pour les personnes qui ne savent toujours pas comment fonctionne réellement le rendement, lisez cet article de Nice
Deux points clés tirés de l'article ci-dessus sont
cela ne met pas vraiment fin à l’exécution de la méthode. return return met en pause l'exécution de la méthode et la prochaine fois que vous l'appelez (pour la valeur d'énumération suivante), la méthode continuera à s'exécuter à partir du dernier appel de return return. Cela semble un peu déroutant, je pense… (Shay Friedman)
Le rendement n'est pas une fonctionnalité du runtime .Net. C'est simplement une fonctionnalité du langage C # qui est compilée en code IL simple par le compilateur C #. (Lars Corneliussen)
C'est un code générique pour la collecte de configuration:
public class GenericConfigurationElementCollection<T> : ConfigurationElementCollection, IEnumerable<T> where T : ConfigurationElement, new()
{
List<T> _elements = new List<T>();
protected override ConfigurationElement CreateNewElement()
{
T newElement = new T();
_elements.Add(newElement);
return newElement;
}
protected override object GetElementKey(ConfigurationElement element)
{
return _elements.Find(e => e.Equals(element));
}
public new IEnumerator<T> GetEnumerator()
{
return _elements.GetEnumerator();
}
}
Une fois que vous avez GenericConfigurationElementCollection
, vous pouvez l’utiliser simplement dans la section config (il s’agit d’un exemple de mon répartiteur):
public class DispatcherConfigurationSection: ConfigurationSection
{
[ConfigurationProperty("maxRetry", IsRequired = false, DefaultValue = 5)]
public int MaxRetry
{
get
{
return (int)this["maxRetry"];
}
set
{
this["maxRetry"] = value;
}
}
[ConfigurationProperty("eventsDispatches", IsRequired = true)]
[ConfigurationCollection(typeof(EventsDispatchConfigurationElement), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")]
public GenericConfigurationElementCollection<EventsDispatchConfigurationElement> EventsDispatches
{
get { return (GenericConfigurationElementCollection<EventsDispatchConfigurationElement>)this["eventsDispatches"]; }
}
}
L'élément de configuration est config ici:
public class EventsDispatchConfigurationElement : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true)]
public string Name
{
get
{
return (string) this["name"];
}
set
{
this["name"] = value;
}
}
}
Le fichier de configuration ressemblerait à ceci:
<?xml version="1.0" encoding="utf-8" ?>
<dispatcherConfigurationSection>
<eventsDispatches>
<add name="Log" ></add>
<add name="Notification" ></add>
<add name="tester" ></add>
</eventsDispatches>
</dispatcherConfigurationSection>
espérons que ça aide!
Une alternative plus facile pour ceux qui préfèrent ne pas écrire toute cette configuration passe-partout manuellement ...
1) Installez Nerdle.AutoConfig à partir de NuGet
2) Définissez votre type ServiceConfig (soit une classe concrète, soit simplement une interface, cela suffira)
public interface IServiceConfiguration
{
int Port { get; }
ReportType ReportType { get; }
}
3) Vous aurez besoin d’un type pour contenir la collection, par exemple.
public interface IServiceCollectionConfiguration
{
IEnumerable<IServiceConfiguration> Services { get; }
}
4) Ajoutez la section de configuration comme suit (notez le nom de camelCase)
<configSections>
<section name="serviceCollection" type="Nerdle.AutoConfig.Section, Nerdle.AutoConfig"/>
</configSections>
<serviceCollection>
<services>
<service port="6996" reportType="File" />
<service port="7001" reportType="Other" />
</services>
</serviceCollection>
5) Carte avec AutoConfig
var services = AutoConfig.Map<IServiceCollectionConfiguration>();
Essayez d’hériter de ConfigurationSection . This blog post par Phil Haack a un exemple.
Confirmé, conformément à la documentation de IConfigurationSectionHandler :
Dans .NET Framework version 2.0 et ultérieure, vous devez plutôt dériver de la classe ConfigurationSection pour implémenter le gestionnaire de section de configuration associé.