web-dev-qa-db-fra.com

Injection d'IOptions

Il me semble que ce serait une mauvaise idée de demander à un service de domaine de disposer d’une instance d’IOptions pour réussir la configuration. J'ai maintenant des dépendances supplémentaires (inutiles?) Dans la bibliothèque. J'ai vu beaucoup d'exemples d'injections d'IOptions sur le Web, mais je ne vois pas le bénéfice supplémentaire que cela apporte.

Pourquoi ne pas simplement injecter ce POCO dans le service?

    services.AddTransient<IConnectionResolver>(x =>
    {
        var appSettings = x.GetService<IOptions<AppSettings>>();

        return new ConnectionResolver(appSettings.Value);
    });

Ou même utiliser ce mécanisme:

        AppSettings appSettings = new AppSettings();

        Configuration.GetSection("AppSettings").Bind(appSettings);

        services.AddTransient<IConnectionResolver>(x =>
        {      
             return new ConnectionResolver(appSettings.SomeValue);
        });

Utilisation des paramètres:

public class MyConnectionResolver 
{
     // Why this?
     public MyConnectionResolver(IOptions<AppSettings> appSettings)
     {
           ... 
     }

     // Why not this?
     public MyConnectionResolver(AppSettings appSettings)
     {
           ... 
     }

     // Or this
     public MyConnectionResolver(IAppSettings appSettings)
     {
           ... 
     }
}

Pourquoi les dépendances supplémentaires? Qu'est-ce que IOptions m'achète au lieu de l'injection à l'ancienne?

18
Darthg8r

Techniquement, rien ne vous empêche d'enregistrer vos classes POCO avec Dependency Injection d'ASP.NET Core ou de créer une classe d'encapsuleur et de renvoyer le IOption<T>.Value à partir de celle-ci. 

Mais vous perdrez les fonctionnalités avancées du paquet Options, à savoir de les mettre à jour automatiquement lorsque la source change, comme vous pouvez le voir dans la source ici .

Comme vous pouvez le voir dans cet exemple de code, si vous enregistrez vos options via services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));, les paramètres de appsettings.json seront lus et liés dans le modèle, puis suivis pour les modifications. Lorsque appsettings.json est édité, le modèle sera lié aux nouvelles valeurs telles que vues ici

Bien sûr, vous devez décider vous-même si vous souhaitez intégrer un peu d’infrastructure dans votre domaine ou transmettre les fonctionnalités supplémentaires offertes par le package Microsoft.Extension.Options. C'est un joli petit paquet qui n'est pas lié à ASP.NET Core, il peut donc être utilisé indépendamment de celui-ci. 

Le package Microsoft.Extension.Options est suffisamment petit pour ne contenir que des abstractions et la surcharge concrète services.Configure qui pour IConfiguration (qui est plus étroitement liée à la manière dont la configuration est obtenue, ligne de commande, json, environnement, emplacement de stockage Azure, etc.) est un package séparé. 

Donc, dans l’ensemble, les dépendances sur «l’infrastructure» sont assez limitées. 

13
Tseng

Alors que IOption est la méthode officielle, je n'arrive pas à oublier le fait que nos bibliothèques externes ne devraient pas avoir besoin de rien savoir du conteneur DI ni de la manière dont il est implémenté. IOption semble violer ce concept puisque nous expliquons maintenant à notre bibliothèque de classes la manière dont le conteneur DI injectera les paramètres - nous devrions simplement injecter un POCO ou une interface définie par cette classe.

Cela m'a assez énervé que j'ai écrit un utilitaire pour injecter un POCO dans ma bibliothèque de classes contenant les valeurs d'une section appSettings.json. Ajoutez la classe suivante à votre projet d'application:

public static class ConfigurationHelper
{
    public static T GetObjectFromConfigSection<T>(
        this IConfigurationRoot configurationRoot,
        string configSection) where T : new()
    {
        var result = new T();

        foreach (var propInfo in typeof(T).GetProperties())
        {
            var propertyType = propInfo.PropertyType;
            if (propInfo?.CanWrite ?? false)
            {
                var value = Convert.ChangeType(configurationRoot.GetValue<string>($"{configSection}:{propInfo.Name}"), propInfo.PropertyType);
                propInfo.SetValue(result, value, null);
            }
        }

        return result;

    }
}

Certaines améliorations pourraient probablement être apportées, mais cela a bien fonctionné lorsque je l'ai testé avec des chaînes simples et des valeurs entières. Voici un exemple d'utilisation de cette méthode dans la méthode Startup.cs -> ConfigureServices du projet d'application pour une classe de paramètres nommée DataStoreConfiguration et une section appSettings.json du même nom:

services.AddSingleton<DataStoreConfiguration>((_) =>
    Configuration.GetObjectFromConfigSection<DataStoreConfiguration>("DataStoreConfiguration"));

La configuration appSettings.json ressemblait à ceci:

{
  "DataStoreConfiguration": {
    "ConnectionString": "Server=Server-goes-here;Database=My-database-name;Trusted_Connection=True;MultipleActiveResultSets=true",
    "MeaningOfLifeInt" : "42"
  },
 "AnotherSection" : {
   "Prop1" : "etc."
  }
}

La classe DataStoreConfiguration a été définie dans mon projet de bibliothèque et se présente comme suit:

namespace MyLibrary.DataAccessors
{
    public class DataStoreConfiguration
    {
        public string ConnectionString { get; set; }
        public int MeaningOfLifeInt { get; set; }
    }
}

Avec cette configuration d'application et de bibliothèques, j'ai pu injecter une instance concrète de DataStoreConfiguration directement dans ma bibliothèque à l'aide de l'injection de constructeur sans le wrapper IOption:

using System.Data.SqlClient;

namespace MyLibrary.DataAccessors
{
    public class DatabaseConnectionFactory : IDatabaseConnectionFactory
    {

        private readonly DataStoreConfiguration dataStoreConfiguration;

        public DatabaseConnectionFactory(
            DataStoreConfiguration dataStoreConfiguration)
        {
            // Here we inject a concrete instance of DataStoreConfiguration
            // without the `IOption` wrapper.
            this.dataStoreConfiguration = dataStoreConfiguration;
        }

        public SqlConnection NewConnection()
        {
            return new SqlConnection(dataStoreConfiguration.ConnectionString);
        }
    }
}

Le découplage est une considération importante pour DI. Par conséquent, je ne sais pas pourquoi Microsoft a orienté les utilisateurs vers le couplage de leurs bibliothèques de classes à une dépendance externe telle que IOptions, peu importe sa pertinence ou les avantages qui en découleraient. Je suggérerais également que certains des avantages de IOptions semblent être une ingénierie excessive. Par exemple, cela me permet de changer dynamiquement de configuration et de suivre les modifications. J'ai utilisé trois autres conteneurs DI contenant cette fonctionnalité et je ne l'ai jamais utilisée une fois ... En attendant, je peux pratiquement vous garantir que les équipes voudront pour injecter des classes ou des interfaces POCO dans des bibliothèques afin que leurs paramètres remplacent ConfigurationManager, et les développeurs chevronnés ne seront pas satisfaits d'une interface wrapper superflue. J'espère qu'un utilitaire similaire à ce que j'ai décrit ici est inclus dans les futures versions d'ASP.NET Core OR et que quelqu'un me fournit un argument convaincant pour expliquer pourquoi je me trompe.

3
Aaron Newton

Je ne supporte pas la recommandation IOptions non plus. C'est un design de merde pour imposer cela aux développeurs. Les options doivent être clairement documentées comme étant optionnelles, oh ironie.

C'est ce que je fais pour mes valeurs de configuration

var mySettings = new MySettings();
Configuration.GetSection("Key").Bind(mySettings);

services.AddTransient(p => new MyService(mySettings));

Vous conservez la dactylographie forte et n'avez pas besoin d'utiliser IOptions dans vos services/bibliothèques.

1
Kugel