0
votes

MS DI Comment configurer des services en utilisant des informations connues uniquement au moment de l'exécution

J'utilise microsoft.extensions.dependencendencendencyInjection 2.1.1 et ont des services qui utilisent le modèle d'options pour obtenir leur configuration. Je veux pouvoir sélectionner une implémentation concrète d'un service à l'aide d'informations connues uniquement au moment de l'exécution (par exemple, lire à partir de la configuration).

Si je connais toutes les implémentations de service concret possibles et leurs options à la compilation, je peux faire quelque chose Comme ce qui suit pour sélectionner et configurer une implémentation: p>

Key="MyServiceConcreteType" Value="MyServiceImplementation2,OtherAssembly"
Key="MyServiceOptionsConcreteType" Value="MyServiceImplementation2Options,OtherAssembly" 
Key="MyServiceOptionsConfigSection" Value="MyServiceImplementation2"

Key="MyServiceImplementation2:ConnectionString" Value="Server=...etc..."
Key="MyServiceImplementation2:ProviderName" Value="System.Data.SqlClient"


9 commentaires

Ok j'ai supprimé ma réponse pour réduire la confusion. Ma question ici: Avez-vous besoin de plus d'une implémentation dans votre application que vous pourriez choisir? Ou vous avez seulement besoin de configurer votre application pour utiliser une implémentation?


Comme il est actuellement écrit, il n'est pas clair exactement ce que vous demandez. Pouvez-vous reformater la question afin que nous obtenons une image plus claire du problème actuel et de ce que vous êtes en fait essayer de faire? Votre exemple de code d'exécution sudo-code est incomplet et donc pas clair.


@weichch - Je n'ai pas besoin d'avoir plus d'une implémentation: je veux juste pouvoir configurer mon application pour utiliser une implémentation qui n'est pas connue à l'heure de la compilation. J'ai mis à jour la question de la rendre plus claire.


@Joe C'est le fichier de configuration qui me confond. Utilisez-vous la configuration .NET-CORE (c'est-à-dire Appseinttings.json )? Il a l'air que vous devrez utiliser une certaine mesure de réflexion pour configurer manuellement la collection de services en fonction de ce qui est extrait de la configuration.


Cette configuration est-elle connue au démarrage et restera-t-elle la même pour la durée de l'application en cours d'exécution, ou est-ce une exigence de pouvoir modifier la configuration lorsque l'application continue d'exécuter?


@Nikosi, j'utilise la configuration .Net-Core aka microsoft.extensions.configuration. Pourrait être appSettings.json (auquel cas la hiérarchie est représentée par la nidification) ou autre chose (hiérarchie représentée par le séparateur de colon). Pour une brièveté, j'ai montré ce dernier dans mon exemple.


@Steven, je n'ai aucune obligation de modifier la configuration pendant laquelle l'application est en cours d'exécution - donc aucun ioptionsMonitor : juste des options définies une fois au démarrage de l'application.


Dans ce cas, pourquoi ne pas lire la configuration au démarrage et utiliser le code que vous avez affiché dans votre premier code?


@Steven, mon premier exemple de code nécessite que les assemblages avec des implémentations du service soient disponibles au moment de la compilation. Je souhaite pouvoir sélectionner une implémentation concrète d'un service à l'aide d'informations connues uniquement au moment de l'exécution.


3 Réponses :


2
votes

OK, maintenant je vois où se trouve la confusion. Étant donné que la méthode configure ne dispose pas de versions non génériques, vous ne saviez donc pas comment passer du type connu au moment de l'exécution à la méthode?

Dans ce cas, j'utiliserais ConfigureOptions Méthode qui vous permet de passer un type de configurateur en tant que paramètre. Le type doit mettre en œuvre iconfigureOptions qui définit configure (t) méthode pour configurer une option de t.

par exemple, Ce type configure myserviceImplementatation1Options à l'aide de iconfiguration : xxx

myserviceImplementaser1option.configure est invoqué pendant que Résolution ioptions , et vous pouvez injecter iconfiguration sur le type pour lire la configuration de la section spécifiée.

et vous pouvez utiliser le type comme celui-ci. Dans Startup : xxx

en termes d'enregistrement de service, il existe des versions non génériques de Add * méthodes. Par exemple, le code ci-dessous enregistre le type connu à l'exécution comme imyservice : xxx


8 commentaires

Je ne comprends pas comment cela vous aide. Votre ServiceFactory semble avoir besoin de connaître les implémentations concrètes (Service1 et Service2) lors de la compilation. De plus, dans mon cas, le service1 et le service2 ont des options de configuration différentes.


@Joe ok maintenant votre commentaire ici me fait me demander si je comprends vraiment votre question. N'as-tu pas savoir ce que vous avez des types concrets? Quelle est la relation entre les sections de configuration, les options et vos services?


Par exemple: supposez imyservice est un service d'accès aux données. J'ai une implémentation concrète aujourd'hui qui obtient les données de, par exemple, un fichier XML. Demain, j'ai peut-être une mise en œuvre alternative qui reçoit les données de, par exemple, une base de données SQL. Ou d'une API Web. À ce stade, je ne sais pas quelles implémentations existent parce qu'elles n'ont pas encore été écrites. Chaque mise en œuvre possible aurait sa propre section de configuration (le chemin d'accès à un fichier XML, la chaîne de connexion pour une base de données SQL ou l'URL d'une API Web) et aurait sa propre classe d'options avec les options pertinentes.


Après avoir essayé, je ne suis toujours pas entièrement content de cette solution. L'endroit évident pour le iconfigureOptions est dans l'ensemble de mise en œuvre du service correspondant. Mais pour implémenter cela, je dois appeler config, getection (section) .bind (options) ce qui nécessite de prendre une dépendance sur microsoft.extensions.configuration.binder et microsoft.extensions.configuration, alors que précédemment Je ne dépendai que sur microsoft.extensions.Options. Je dois dire que je trouve ce modèle d'options compliqué et mal documenté :(


... suite. Je pourrais obtenir cette dépendance en ayant une interface (E.G. ioptionsBinder ) avec une méthode par ex. Bind (configuration Iconfiguration, Options d'objet) , puis injectant cela dans mon iconfigureOptions implémentation. Mais s'il s'agit d'un modèle utile / recommandé, je suis surpris que l'abstraction n'est pas disponible hors de la boîte.


Je suppose que c'est pourquoi ce paquet existe Microsoft.extensions.option.configurationExtensions . Configurer (iconfiguration) La méthode provient de ce package. Il est par défaut dans l'applique SDK Package. Mais oui, si votre implémentation doit lire la configuration, vous devez inclure des dépendances de configuration.


Et vous pouvez également configurer une instance d'options avec plusieurs configurations. C'est-à-dire que vous pouvez appeler configurer et configureOptions plusieurs fois avec différentes configurations. Et chaque configuration fonctionnera dans la commande d'enregistrement. Donc, si la dépendance est un problème ici, vous pouvez implémenter une configuration dépendante de la configuration dans votre application principale où la configuration est référencée et l'enregistrer avant l'enregistrement de la configuration du montage de la mise en œuvre. Fondamentalement, on agissant comme une usine / lecteur.


Je ne comprends pas comment microsoft.extensions.option.configurationextextextextexs aide dans mon scénario de dépendances d'exécution. Et cela dépend de microsoft.extensions.configuration.binder et donc microsoft.extensions.configuration donc j'ai toujours des dépendances indésirables. En pensant plus à ce sujet, j'aimerais voir l'interface ioptionsbinder avec une méthode unique liaison (options d'objet) , que je peux injecter dans mon iconfigureOptions instance, et conservez-le indépendamment du système de configuration.



1
votes

Choisir une implémentation concrète basée sur des options

Vous pouvez sélectionner une implémentation concrète d'une interface basée sur un type d'options à l'aide de AddSingleton (implementationFactory) code> surcharge. En utilisant cette surcharge, vous pouvez retarder la résolution du type de béton à utiliser jusqu'à ce que vous puissiez accéder à un ioptionsSnapshot code>. En fonction de vos besoins et des implémentations de l'interface que vous utilisez, vous pouvez changer de type de type de manière dynamique à l'aide d'un ioptionsMonitor code> à la place. P>

Si cela aide, vous pouvez Pensez au modèle d'usine comme un moyen de Curry Création d'objet lors de l'utilisation du conteneur DI. p>

// when configuring services, add a call to AddAutoRegisterTypes()

// ...
            .ConfigureServices((hostContext, services) =>
            {
                services
                    // Finds and registers config & the type for all types with [AutoRegister]
                    .AddAutoRegisterTypes(hostContext.Configuration)
                    // ...
            });

// ...

// The first concrete implementation. See below for how AutoRegister
// is used & implemented.
[AutoRegister(optionsType: typeof(FooFirstOptions), configSection: "Foo1")]
public class FooFirst : IFoo
{
    public FooFirst(IOptionsSnapshot<FooFirstOptions> options)
    {
        Value = $"{options.Value.ValuePrefix}First";
    }

    public string Value { get; }
}

public class FooFirstOptions
{
    public string ValuePrefix { get; set; } = string.Empty;
}

// The second concrete implementation. See below for how AutoRegister
// is used & implemented.
[AutoRegister(optionsType: typeof(FooSecondOptions), configSection: "Foo2")]
public class FooSecond : IFoo
{
    public FooSecond(IOptionsSnapshot<FooSecondOptions> options)
    {
        Value = $"Second{options.Value.ValueSuffix}";
    }

    public string Value { get; }
}

public class FooSecondOptions
{
    public string ValueSuffix { get; set; } = string.Empty;
}

// Attribute used to annotate a type that should be:
// 1. automatically added to a service collection and
// 2. have its corresponding options type configured to bind against
//    the specificed config section.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class AutoRegisterAttribute : Attribute
{
    public AutoRegisterAttribute(Type optionsType, string configSection)
    {
        OptionsType = optionsType;
        ConfigSection = configSection;
    }

    public Type OptionsType { get; }

    public string ConfigSection { get; }
}

public static class AutoRegisterServiceCollectionExtensions
{
    // Helper to call Configure<T> given a Type argument. See below for more details.
    private static readonly Action<Type, IServiceCollection, IConfiguration> s_configureType
        = MakeConfigureOfTypeConfig();

    // Automatically finds all types with [AutoRegister] and adds
    // them to the service collection and configures their options
    // type against the provided config.
    public static IServiceCollection AddAutoRegisterTypes(
        this IServiceCollection services,
        IConfiguration config)
    {
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            foreach (Type type in assembly.GetTypes())
            {
                var autoRegAttribute = (AutoRegisterAttribute?)Attribute
                    .GetCustomAttributes(type)
                    .SingleOrDefault(attr => attr is AutoRegisterAttribute);

                if (autoRegAttribute != null)
                {
                    IConfiguration configForType = config.GetSection(
                        autoRegAttribute.ConfigSection);
                    s_configureType(
                        autoRegAttribute.OptionsType,
                        services,
                        configForType);
                    services.AddSingleton(type);
                }
            }
        }

        return services;
    }

    // There is no non-generic analog to
    // OptionsConfigurationServiceCollectionExtensions.Configure<T>(IServiceCollection, IConfiguration)
    //
    // Therefore, this finds the generic method via reflection and
    // creates a wrapper that invokes it given a Type parameter.
    private static Action<Type, IServiceCollection, IConfiguration> MakeConfigureOfTypeConfig()
    {
        const string FullMethodName = nameof(OptionsConfigurationServiceCollectionExtensions) + "." + nameof(OptionsConfigurationServiceCollectionExtensions.Configure);

        MethodInfo? configureMethodInfo = typeof(OptionsConfigurationServiceCollectionExtensions)
            .GetMethod(
                nameof(OptionsConfigurationServiceCollectionExtensions.Configure),
                new[] { typeof(IServiceCollection), typeof(IConfiguration) });

        if (configureMethodInfo == null)
        {
            var msg = $"Cannot find expected {FullMethodName} overload. Has the contract changed?";
            throw new Exception(msg);
        }

        if (   !configureMethodInfo.IsGenericMethod
            || configureMethodInfo.GetGenericArguments().Length != 1)
        {
            var msg = $"{FullMethodName} does not have the expected generic arguments.";
            throw new Exception(msg);
        }

        return (Type typeToConfigure, IServiceCollection services, IConfiguration configuration) =>
        {
            configureMethodInfo
                .MakeGenericMethod(typeToConfigure)
                .Invoke(null, new object[] { services, configuration });
        };
    }
}


0 commentaires

1
votes

Comme @weichch mentionné, le principal point de douleur ici est le manque d'existence d'une surcharge non générique de configuration . Je pense que cela est peut être considéré comme une omission de la part de Microsoft (mais la création d'une demande de fonctionnalité pour cela serait une bonne idée).

En outre, la solution de Weichch, vous pouvez également utiliser la réflexion pour appeler le Configurer la méthode de votre choix. Cela ressemblerait à ceci: xxx

où la configuration peut être la suivante: xxx


1 commentaires

Merci pour cela. Dans mon cas, les services seront développés en interne, afin que je puisse avoir besoin d'inclure une implémentation iconfigureOptions et @ Weichch's Solution fonctionnera.