Come si passano i valori al costruttore sul mio servizio wcf?


103

Vorrei passare i valori nel costruttore sulla classe che implementa il mio servizio.

Tuttavia ServiceHost mi lascia passare solo il nome del tipo da creare, non gli argomenti da passare al suo costruttore.

Vorrei poter passare in una fabbrica che crea il mio oggetto di servizio.

Quello che ho trovato finora:


6
Temo che la complessità sia inerente a WCF e non c'è molto che puoi fare per alleviarlo, a parte non usare WCF o nasconderlo dietro una facciata più user friendly, come la struttura WCF di Windsor se stai usando Windsor
Krzysztof Kozmic

Risposte:


122

Avrai bisogno di implementare una combinazione di costume ServiceHostFactory, ServiceHoste IInstanceProvider.

Dato un servizio con questa firma del costruttore:

public MyService(IDependency dep)

Ecco un esempio che può far girare 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
}

Registra MyServiceHostFactory nel tuo file MyService.svc o usa MyServiceHost direttamente nel codice per scenari di hosting autonomo.

Puoi facilmente generalizzare questo approccio, e in effetti alcuni contenitori DI l'hanno già fatto per te (spunto: WCF Facility di Windsor).


+1 (Ma bleah, #regions anche se è il caso meno grave del reato, mi converto all'interfaccia esplicita impl me stesso: P)
Ruben Bartelink

5
Come posso usarlo per il self hosting? Ricevo un'eccezione dopo aver chiamato CreateServiceHost. Posso solo chiamare il metodo protetto public override ServiceHostBase CreateServiceHost (string constructorString, Uri [] baseAddresses); L'eccezione è Il messaggio di eccezione era: "ServiceHostFactory.CreateServiceHost" non può essere richiamato nell'ambiente di hosting corrente. Questa API richiede che l'applicazione chiamante sia ospitata in IIS o WAS.
Guy,

2
@ Guy Sto riscontrando il problema del campione. Perché la funzione è protectedche non posso chiamarla io stesso da Main ()
Andriy Drozdyuk

1
C'è un problema intrinseco con questo approccio, e cioè la tua dipendenza in realtà viene creata solo una volta in un ambiente ospitato da IIS. ServiceHostFactory, ServiceHost e InstanceProvider vengono tutti creati solo una volta fino a quando il pool di applicazioni non viene riciclato, il che significa che la tua dipendenza non può essere realmente aggiornata per chiamata (DbContext ad esempio), che introduce la memorizzazione nella cache non intenzionale dei valori e una durata maggiore della dipendenza che è non voluto. Non sono davvero sicuro di come risolvere questo problema, qualche pensiero?
David Anderson

2
@MarkSeemann Mi chiedo solo perché hai iniettato depin InstanceProvider di ogni contratto . Potresti fare: ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));dov'è IMyService un'interfaccia del contratto del tuo MyService(IDependency dep). Quindi iniettare IDependencysolo in InstanceProvider che ne ha effettivamente bisogno.
voytek

14

Puoi semplicemente creare un'istanza del tuo Servicee passare quell'istanza ServiceHostall'oggetto. L'unica cosa che devi fare è aggiungere un [ServiceBehaviour]attributo per il tuo servizio e contrassegnare tutti gli oggetti restituiti con [DataContract]attributo.

Ecco un mock up:

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();
    }
}

e l'utilizzo:

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

host.Open();

Spero che questo renda la vita più facile a qualcuno.


5
Funziona solo per i singleton (come indicato da InstanceContextMode.Single).
John Reynolds

11

La risposta di Marco con la IInstanceProviderè corretta.

Invece di utilizzare il ServiceHostFactory personalizzato, potresti anche utilizzare un attributo personalizzato (ad esempio MyInstanceProviderBehaviorAttribute). Derivalo da Attribute, fallo implementare IServiceBehaviore implementare il IServiceBehavior.ApplyDispatchBehaviormetodo come

// 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;
    }
}

Quindi, applica l'attributo alla classe di implementazione del servizio

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

La terza opzione: puoi anche applicare un comportamento del servizio utilizzando il file di configurazione.


2
Tecnicamente, anche questa sembra una soluzione, ma con quell'approccio, accoppi strettamente IInstanceProvider con il servizio.
Mark Seemann

2
Solo una seconda opzione, nessuna valutazione su cosa sia meglio. Ho utilizzato la ServiceHostFactory personalizzata un paio di volte (soprattutto quando si desidera registrare diversi comportamenti).
dalo

1
Il problema è che puoi avviare per esempio il contenitore DI solo nel costruttore dell'attributo .. non puoi inviare dati esistenti.
Guy,

5

Ho lavorato sulla risposta di Mark, ma (almeno per il mio scenario) era inutilmente complesso. Uno dei ServiceHostcostruttori accetta un'istanza del servizio, che puoi passare direttamente dall'implementazione ServiceHostFactory.

Per riprendere l'esempio di Mark, sarebbe simile a questo:

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)
    {
    }
}

12
Questo funzionerà se il tuo servizio e tutte le dipendenze inserite sono thread-safe. Quel particolare sovraccarico del costruttore ServiceHost disabilita essenzialmente la gestione del ciclo di vita di WCF. Invece, stai dicendo che tutte le richieste simultanee saranno gestite da instance. Ciò può influire o meno sulle prestazioni. Se vuoi essere in grado di gestire richieste simultanee, l' intero oggetto grafico deve essere thread-safe, altrimenti otterrai un comportamento non deterministico e non corretto. Se puoi garantire la sicurezza dei thread, la mia soluzione è, in effetti, inutilmente complessa. Se non puoi garantirlo, è necessaria la mia soluzione.
Mark Seemann

3

Fanculo ... Ho combinato i modelli di inserimento delle dipendenze e di localizzazione dei servizi (ma per lo più è ancora iniezione di dipendenze e avviene anche nel costruttore, il che significa che puoi avere lo stato di sola lettura).

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
    }
}

Le dipendenze del servizio sono chiaramente specificate nel contratto della sua Dependenciesclasse annidata . Se stai usando un contenitore IoC (uno che non risolve già il problema WCF per te), puoi configurarlo per creare l' Dependenciesistanza invece del servizio. In questo modo ottieni la calda sensazione sfocata che ti dà il tuo contenitore senza dover saltare i troppi cerchi imposti da WCF.

Non perderò il sonno con questo approccio. Nemmeno qualcun altro. Dopo tutto, il tuo contenitore IoC è una grande raccolta statica di delegati che crea cose per te. Cosa ne aggiunge uno in più?


Parte del problema era che desideravo che la società usasse l'inserimento delle dipendenze e, se non fosse sembrato pulito e semplice a un programmatore che non aveva mai usato l'inserimento delle dipendenze, l'iniezione delle dipendenze non sarebbe mai stata usata da nessun altro programmatore. Tuttavia non uso WCF da molti anni e non mi manca!
Ian Ringrose

Ecco il mio approccio a una proprietà write-once stackoverflow.com/questions/839788/…
Ronnie Overby

0

Stavamo affrontando lo stesso problema e l'abbiamo risolto nel modo seguente. È una soluzione semplice.

In Visual Studio è sufficiente creare una normale applicazione di servizio WCF e rimuovere la sua interfaccia. Lascia il file .cs in posizione (rinominalo semplicemente) e apri quel file cs e sostituisci il nome dell'interfaccia con il nome della tua classe originale che implementa la logica del servizio (in questo modo la classe del servizio utilizza l'ereditarietà e sostituisce la tua implementazione effettiva). Aggiungi un costruttore predefinito che chiama i costruttori della classe base, in questo modo:

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

La classe base MyService è l'effettiva implementazione del servizio. Questa classe base non dovrebbe avere un costruttore senza parametri, ma solo costruttori con parametri che accettano le dipendenze.

Il servizio dovrebbe utilizzare questa classe invece dell'originale MyService.

È una soluzione semplice e funziona a meraviglia MrGreen


4
Non hai disaccoppiato Service1 dalle sue dipendenze, il che era più o meno importante. Hai appena istanziato le dipendenze nel costruttore per Service1, cosa che puoi fare senza la classe base.
salpano il

0

Questa è stata una soluzione molto utile, soprattutto per chi è un programmatore WCF alle prime armi. Volevo pubblicare un piccolo suggerimento per tutti gli utenti che potrebbero utilizzarlo per un servizio ospitato da IIS. MyServiceHost deve ereditare WebServiceHost , non solo ServiceHost.

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

Questo creerà tutti i collegamenti necessari, ecc. Per i tuoi endpoint in IIS.


-2

Uso variabili statiche del mio tipo. Non sono sicuro che sia il modo migliore, ma per me funziona:

public class MyServer
{   
    public static string CustomerDisplayName;
    ...
}

Quando creo un'istanza dell'host del servizio, faccio quanto segue:

protected override void OnStart(string[] args)
{
    MyServer.CustomerDisplayName = "Test customer";

    ...

    selfHost = new ServiceHost(typeof(MyServer), baseAddress);

    ....
}

5
Statico / Singleton sono il male! - vedi stackoverflow.com/questions/137975/…
Immortal Blue
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.