web-dev-qa-db-fra.com

Comment passer des valeurs au constructeur sur mon service wcf?

Je voudrais transmettre des valeurs au constructeur de la classe qui implémente mon service.

Cependant ServiceHost ne me permet que de passer le nom du type à créer, pas les arguments à passer à son constructeur.

Je voudrais pouvoir passer dans une usine qui crée mon objet de service.

Ce que j'ai trouvé jusqu'à présent:

102
Ian Ringrose

Vous devrez implémenter une combinaison de ServiceHostFactory, ServiceHost et IInstanceProvider personnalisés.

Étant donné un service avec cette signature de constructeur:

public MyService(IDependency dep)

Voici un exemple qui peut faire tourner MyService:

public class MyServiceHostFactory : ServiceHostFactory
{
    private readonly IDependency dep;

    public MyServiceHostFactory()
    {
        this.dep = new MyClass();
    }

    protected override ServiceHost CreateServiceHost(Type serviceType,
        Uri[] baseAddresses)
    {
        return new MyServiceHost(this.dep, serviceType, baseAddresses);
    }
}

public class MyServiceHost : ServiceHost
{
    public MyServiceHost(IDependency dep, Type serviceType, params Uri[] baseAddresses)
        : base(serviceType, baseAddresses)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        foreach (var cd in this.ImplementedContracts.Values)
        {
            cd.Behaviors.Add(new MyInstanceProvider(dep));
        }
    }
}

public class MyInstanceProvider : IInstanceProvider, IContractBehavior
{
    private readonly IDependency dep;

    public MyInstanceProvider(IDependency dep)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        this.dep = dep;
    }

    #region IInstanceProvider Members

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        return this.GetInstance(instanceContext);
    }

    public object GetInstance(InstanceContext instanceContext)
    {
        return new MyService(this.dep);
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        var disposable = instance as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }

    #endregion

    #region IContractBehavior Members

    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
    }

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        dispatchRuntime.InstanceProvider = this;
    }

    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {
    }

    #endregion
}

Enregistrez MyServiceHostFactory dans votre fichier MyService.svc ou utilisez MyServiceHost directement dans le code pour les scénarios d'auto-hébergement.

Vous pouvez facilement généraliser cette approche, et en fait, certains conteneurs DI ont déjà fait cela pour vous (cue: Windsor WCF Facility).

119
Mark Seemann

Vous pouvez simplement créer et instance de votre Service et passer cette instance à l'objet ServiceHost. La seule chose que vous avez à faire est d'ajouter un [ServiceBehaviour] attribut pour votre service et marquez tous les objets retournés avec [DataContract] attribut.

Voici une maquette:

namespace Service
{
    [ServiceContract]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class MyService
    {
        private readonly IDependency _dep;

        public MyService(IDependency dep)
        {
            _dep = dep;
        }

        public MyDataObject GetData()
        {
            return _dep.GetData();
        }
    }

    [DataContract]
    public class MyDataObject
    {
        public MyDataObject(string name)
        {
            Name = name;
        }

        public string Name { get; private set; }
    }

    public interface IDependency
    {
        MyDataObject GetData();
    }
}

et l'utilisation:

var dep = new Dependecy();
var myService = new MyService(dep);
var Host = new ServiceHost(myService);

Host.Open();

J'espère que cela facilitera la vie de quelqu'un.

13
kerim

La réponse de Mark avec le IInstanceProvider est correcte.

Au lieu d'utiliser le ServiceHostFactory personnalisé, vous pouvez également utiliser un attribut personnalisé (par exemple MyInstanceProviderBehaviorAttribute). Dérivez-le de Attribute, faites-le implémenter IServiceBehavior et implémentez IServiceBehavior.ApplyDispatchBehavior méthode comme

// YourInstanceProvider implements IInstanceProvider
var instanceProvider = new YourInstanceProvider(<yourargs>);

foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
    foreach (var epDispatcher in dispatcher.Endpoints)
    {
        // this registers your custom IInstanceProvider
        epDispatcher.DispatchRuntime.InstanceProvider = instanceProvider;
    }
}

Ensuite, appliquez l'attribut à votre classe d'implémentation de service

[ServiceBehavior]
[MyInstanceProviderBehavior(<params as you want>)]
public class MyService : IMyContract

Troisième option: vous pouvez également appliquer un comportement de service à l'aide du fichier de configuration.

11
dalo

J'ai travaillé à partir de la réponse de Mark, mais (pour mon scénario au moins), c'était inutilement complexe. L'un des constructeurs ServiceHost accepte une instance du service, que vous pouvez transmettre directement à partir de l'implémentation ServiceHostFactory.

Pour reprendre l'exemple de Mark, cela ressemblerait à ceci:

public class MyServiceHostFactory : ServiceHostFactory
{
    private readonly IDependency _dep;

    public MyServiceHostFactory()
    {
        _dep = new MyClass();
    }

    protected override ServiceHost CreateServiceHost(Type serviceType,
        Uri[] baseAddresses)
    {
        var instance = new MyService(_dep);
        return new MyServiceHost(instance, serviceType, baseAddresses);
    }
}

public class MyServiceHost : ServiceHost
{
    public MyServiceHost(MyService instance, Type serviceType, params Uri[] baseAddresses)
        : base(instance, baseAddresses)
    {
    }
}
5
McGarnagle

Vissez-le ... J'ai mélangé l'injection de dépendance et les modèles de localisateur de service (mais surtout c'est toujours l'injection de dépendance et elle a même lieu dans le constructeur, ce qui signifie que vous pouvez avoir un état en lecture seule).

public class MyService : IMyService
{
    private readonly Dependencies _dependencies;

    // set this before creating service Host. this can use your IOC container or whatever.
    // if you don't like the mutability shown here (IoC containers are usually immutable after being configured)
    // you can use some sort of write-once object
    // or more advanced approach like authenticated access
    public static Func<Dependencies> GetDependencies { get; set; }     
    public class Dependencies
    {
        // whatever your service needs here.
        public Thing1 Thing1 {get;}
        public Thing2 Thing2 {get;}

        public Dependencies(Thing1 thing1, Thing2 thing2)
        {
            Thing1 = thing1;
            Thing2 = thing2;
        }
    }

    public MyService ()
    {
        _dependencies = GetDependencies(); // this will blow up at run time in the exact same way your IoC container will if it hasn't been properly configured up front. NO DIFFERENCE
    }
}

Les dépendances du service sont clairement spécifiées dans le contrat de sa classe imbriquée Dependencies. Si vous utilisez un conteneur IoC (qui ne résout pas déjà le désordre WCF pour vous), vous pouvez le configurer pour créer l'instance Dependencies au lieu du service. De cette façon, vous obtenez la sensation floue chaude que votre conteneur vous donne tout en n'ayant pas à sauter à travers trop de cerceaux imposés par WCF.

Je ne vais pas perdre le sommeil sur cette approche. Personne ne devrait non plus. Après tout, votre conteneur IoC est une grande, grosse et statique collection de délégués qui crée des trucs pour vous. Qu'est-ce qui en ajoute un de plus?

3
Ronnie Overby

C'était une solution très utile - en particulier pour quelqu'un qui est un codeur WCF novice. Je voulais publier un petit conseil pour tous les utilisateurs qui pourraient l'utiliser pour un service hébergé par IIS. MyServiceHost doit hériter WebServiceHost, pas seulement ServiceHost.

public class MyServiceHost : WebServiceHost
{
    public MyServiceHost(MyService instance, Type serviceType, params Uri[] baseAddresses)
        : base(instance, baseAddresses)
    {
    }
}

Cela créera toutes les liaisons nécessaires, etc. pour vos points de terminaison dans IIS.

0
Eric Dieckman

Nous étions confrontés à ce même problème et l'avons résolu de la manière suivante. C'est une solution simple.

Dans Visual Studio, créez simplement une application de service WCF normale et supprimez son interface. Laissez le fichier .cs en place (renommez-le simplement) et ouvrez ce fichier cs et remplacez le nom de l'interface par votre nom de classe d'origine qui implémente la logique de service (de cette façon, la classe de service utilise l'héritage et remplace votre implémentation réelle). Ajoutez un constructeur par défaut qui appelle les constructeurs de la classe de base, comme ceci:

public class Service1 : MyLogicNamespace.MyService
{
    public Service1() : base(new MyDependency1(), new MyDependency2()) {}
}

La classe de base MyService est l'implémentation réelle du service. Cette classe de base ne doit pas avoir de constructeur sans paramètre, mais uniquement des constructeurs avec des paramètres qui acceptent les dépendances.

Le service doit utiliser cette classe au lieu du MyService d'origine.

C'est une solution simple et fonctionne comme un charme :-D

0
Ron Deijkers