Comment conserver tout le code du fichier de configuration hors de mon code logique à l'aide des paramètres (ApplicationSettingsBase) et de l'injection de dépendances?
Par configuration, je veux dire un fichier de configuration spécifique au client.
Dois-je vraiment injecter une classe de configuration à chaque fois que j'en ai besoin ou y a-t-il un autre modèle?
Ce serait formidable d'obtenir un exemple de code!
Échantillons:
Configuration statique:
public static class StaticConfiguration
{
public static bool ShouldApplySpecialLogic { get; set; }
public static string SupportedFileMask { get; set; }
}
public class ConsumerOfStaticConfiguration
{
public void Process()
{
if (StaticConfiguration.ShouldApplySpecialLogic)
{
var strings = StaticConfiguration.SupportedFileMask.Split(',');
foreach (var @string in strings)
{
}
}
}
}
Configuration non statique:
public interface IConfiguration
{
bool ShouldApplySpecialLogic { get; set; }
string SupportedFileMask { get; set; }
}
public class Configuration : IConfiguration
{
public bool ShouldApplySpecialLogic { get; set; }
public string SupportedFileMask { get; set; }
}
public class Consumer
{
private readonly IConfiguration _configuration;
public Consumer(IConfiguration configuration)
{
_configuration = configuration;
}
public void Process()
{
if (_configuration.ShouldApplySpecialLogic)
{
var strings = _configuration.SupportedFileMask.Split(',');
foreach (var @string in strings)
{
}
}
}
}
Contexte statique avec configuration non statique:
public static class Context
{
public static IConfiguration Configuration { get; set; }
}
public class ConsumerOfStaticContext
{
public void Process()
{
if (Context.Configuration.ShouldApplySpecialLogic)
{
var strings = Context.Configuration.SupportedFileMask.Split(',');
foreach (var @string in strings)
{
}
}
}
}
La partie importante à réaliser est que la configuration n'est qu'une parmi plusieurs sources de valeurs qui déterminent le comportement de votre application.
La deuxième option (configuration non statique) est la meilleure car elle vous permet de complètement découpler le consommateur de la source des valeurs de configuration . Cependant, l'interface n'est pas requise, car les paramètres de configuration sont normalement mieux modélisés en tant qu'objets de valeur .
Si vous souhaitez toujours lire les valeurs à partir d'un fichier de configuration, vous pouvez le faire à partir de Composition Root de l'application. Avec StructureMap, cela pourrait ressembler à ceci:
var config = (MyConfigurationSection)ConfigurationManager.GetSection("myConfig");
container.Configure(r => r
.For<Consumer>()
.Ctor<MyConfigurationSection>()
.Is(config));
Les classes de configuration réduisent la cohension et augmentent le couplage chez les consommateurs. En effet, il peut y avoir de nombreux paramètres qui ne sont pas liés à celui ou aux deux nécessaires à votre classe, mais pour remplir la dépendance, votre implémentation de IConfiguration
doit fournir des valeurs pour tous les accesseurs, même les non pertinents.
Il associe également votre classe à la connaissance de l'infrastructure: des détails tels que "ces valeurs sont configurées ensemble" sortent de la configuration de l'application et se retrouvent dans vos classes, augmentant la surface affectée par les modifications apportées aux systèmes indépendants.
Le moyen le moins complexe et le plus flexible de partager les valeurs de configuration consiste à utiliser l'injection par le constructeur des valeurs elles-mêmes, en externalisant les problèmes d'infrastructure. Cependant, dans un commentaire sur une autre réponse, vous indiquez que vous avez peur d'avoir beaucoup de paramètres de constructeur, ce qui est une préoccupation valable.
Le point clé à reconnaître est qu'il n'y a pas de différence entre les dépendances primitives et complexes. Que vous dépendiez d'un entier ou d'une interface, ce sont les deux choses que vous ne savez pas et doit être dit. De ce point de vue, IConfiguration
a autant de sens que IDependencies
. Les grands constructeurs indiquent qu'une classe a trop de responsabilités, que les paramètres soient primitifs ou complexes.
Pensez à traiter int
, string
et bool
comme vous le feriez pour toute autre dépendance. Cela rendra vos classes plus propres, plus ciblées, plus résistantes au changement et plus faciles à tester unitaire.
Une façon est d'injecter une interface de configuration comme vous publiez. Voici quelques autres moyens.
Exposer un Setter
class Consumer
{
public bool ShouldApplySpecialLogic { get; set; }
...
}
Dans la racine de la composition, vous pouvez lire un fichier de configuration ou le coder en dur. Exemple d'Autofac:
builder.RegisterType<Consumer>().AsSelf()
.OnActivated(e => e.Instance.ShouldApplySpecialLogic = true);
Ceci n'est probablement conseillé que lorsque vous avez un bon défaut
Injection de constructeur
public class Server
{
public Server(int portToListenOn) { ... }
}
Dans la racine de la composition:
builder.Register(c => new Server(12345)).AsSelf();
Dans mes applications, je fais ce que vous avez fait ci-dessus avec IoC. C'est-à-dire que mon conteneur IoC (StructureMap également) injecte un IApplicationSettings
dans mes classes.
Par exemple, dans un projet ASP.NET MVC3, cela peut ressembler à:
Public Class MyController
Inherits Controller
...
Private ReadOnly mApplicationSettings As IApplicationSettings
Public Sub New(..., applicationSettings As IApplicationSettings)
...
Me.mApplicationSettings = applicationSettings
End Sub
Public Function SomeAction(custId As Guid) As ActionResult
...
' Look up setting for custId
' If not found fall back on default like
viewModel.SomeProperty = Me.mApplicationSettings.SomeDefaultValue
Return View("...", viewModel)
End Function
End Class
Mon implémentation de IApplicationSettings
tire la plupart des choses de .config
et contient également quelques valeurs codées en dur.
Mon exemple n'était pas un contrôle de flux logique (comme votre exemple), mais cela aurait fonctionné de la même manière si c'était le cas.
L'autre façon de procéder serait de créer un modèle de type de localisateur de service, dans lequel vous demandez à votre conteneur d'injection de dépendances de vous obtenir une instance de la classe de configuration à la volée. Service-Location est généralement considéré comme un anti-pattern , mais peut encore vous être utile.