Wie übergebe ich Werte an den Konstruktor meines wcf-Dienstes?

103

Ich möchte Werte an den Konstruktor der Klasse übergeben, die meinen Service implementiert.

Mit ServiceHost kann ich jedoch nur den Namen des zu erstellenden Typs übergeben, nicht die Argumente, die an den Konstruktor übergeben werden sollen.

Ich möchte in der Lage sein, eine Fabrik zu übergeben, die mein Serviceobjekt erstellt.

Was ich bisher gefunden habe:

Ian Ringrose
quelle
6
Ich fürchte, die Komplexität ist WCF inhärent und es gibt nicht viel, was Sie tun können, um es zu lindern, außer WCF nicht zu verwenden oder es hinter einer benutzerfreundlicheren Fassade zu verstecken, wie Windsors WCF-Einrichtung, wenn Sie Windsor
Krzysztof Kozmic verwenden

Antworten:

122

Sie müssen eine Kombination von benutzerdefinierten implementieren ServiceHostFactory, ServiceHostund IInstanceProvider.

Bei einem Dienst mit dieser Konstruktorsignatur:

public MyService(IDependency dep)

Hier ist ein Beispiel, das MyService starten kann:

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
}

Registrieren Sie MyServiceHostFactory in Ihrer MyService.svc-Datei oder verwenden Sie MyServiceHost direkt im Code für Self-Hosting-Szenarien.

Sie können diesen Ansatz leicht verallgemeinern, und tatsächlich haben einige DI-Container dies bereits für Sie getan (Stichwort: WCF-Einrichtung von Windsor).

Mark Seemann
quelle
+1 (Aber yuck, #regions , obwohl es der am wenigsten schwere Fall der Straftat ist, konvertiere ich in explizite Schnittstelle impl selbst: P)
Ruben Bartelink
5
Wie kann ich es für das Selbsthosting verwenden? Ich erhalte eine Ausnahme, nachdem ich CreateServiceHost aufgerufen habe. Ich kann nur die geschützte Methode public override ServiceHostBase CreateServiceHost aufrufen (Zeichenfolge constructorString, Uri [] baseAddresses); Die Ausnahme lautet: Die Ausnahmemeldung lautete: 'ServiceHostFactory.CreateServiceHost' kann in der aktuellen Hosting-Umgebung nicht aufgerufen werden. Diese API erfordert, dass die aufrufende Anwendung in IIS oder WAS gehostet wird.
Guy
2
@ Guy Ich habe das Beispielproblem. Weil die Funktion ist, protectedkann ich sie nicht selbst von Main ()
Andriy Drozdyuk
1
Dieser Ansatz weist ein inhärentes Problem auf, und das heißt, Ihre Abhängigkeit wird in einer von IIS gehosteten Umgebung nur einmal erstellt. ServiceHostFactory, ServiceHost und InstanceProvider werden nur einmal erstellt, bis der Anwendungspool wiederverwendet wird. Dies bedeutet, dass Ihre Abhängigkeit nicht wirklich pro Aufruf aktualisiert werden kann (z. B. DbContext), was zu einem unbeabsichtigten Zwischenspeichern von Werten und einer längeren Lebensdauer der Abhängigkeit führt nicht gewollt. Ich bin mir nicht sicher, wie ich das lösen soll.
David Anderson
2
@ MarkSeemann Ich frage mich nur, warum Sie depin den InstanceProvider jedes Vertrags injiziert haben . Sie könnten tun: ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));Wo IMyService ist eine Vertragsschnittstelle von Ihnen MyService(IDependency dep). Spritzen Sie also IDependencynur in InstanceProvider, die es tatsächlich benötigen.
Voytek
14

Sie können einfach eine Instanz von erstellen und Servicediese Instanz an das ServiceHostObjekt übergeben. Sie müssen lediglich ein [ServiceBehaviour]Attribut für Ihren Dienst hinzufügen und alle zurückgegebenen Objekte mit [DataContract]Attribut markieren .

Hier ist ein Modell:

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

und die Verwendung:

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

host.Open();

Ich hoffe, das wird jemandem das Leben leichter machen.

Kerim
quelle
5
Das funktioniert nur für Singletons (wie durch angegeben InstanceContextMode.Single).
John Reynolds
11

Marks Antwort mit dem IInstanceProviderist richtig.

Anstatt die benutzerdefinierte ServiceHostFactory zu verwenden, können Sie auch ein benutzerdefiniertes Attribut verwenden (z MyInstanceProviderBehaviorAttribute. B. ). Leiten Sie es ab Attribute, lassen Sie es implementieren IServiceBehaviorund implementieren Sie die IServiceBehavior.ApplyDispatchBehaviorMethode wie

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

Wenden Sie dann das Attribut auf Ihre Service-Implementierungsklasse an

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

Die dritte Option: Sie können ein Dienstverhalten auch mithilfe der Konfigurationsdatei anwenden.

Dalo
quelle
2
Technisch gesehen sieht dies auch nach einer Lösung aus, aber mit diesem Ansatz koppeln Sie den IInstanceProvider eng mit dem Service.
Mark Seemann
2
Nur eine zweite Option, keine Einschätzung, was besser ist. Ich habe die benutzerdefinierte ServiceHostFactory einige Male verwendet (insbesondere, wenn Sie mehrere Verhaltensweisen registrieren möchten).
Dalo
1
Das Problem ist, dass Sie beispielsweise DI-Container nur im Attributkonstruktor initiieren können. Sie können keine vorhandenen Daten senden.
Guy
5

Ich habe nach Marks Antwort gearbeitet, aber (zumindest für mein Szenario) war es unnötig komplex. Einer der ServiceHostKonstruktoren akzeptiert eine Instanz des Dienstes, die Sie direkt von der ServiceHostFactoryImplementierung übergeben können.

Um Marks Beispiel zu huckepack zu nehmen, würde es so aussehen:

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)
    {
    }
}
McGarnagle
quelle
12
Dies funktioniert, wenn Ihr Dienst und alle injizierten Abhängigkeiten threadsicher sind. Diese besondere Überlastung des ServiceHost-Konstruktors deaktiviert im Wesentlichen das Lebenszyklusmanagement von WCF. Stattdessen sagen Sie, dass alle gleichzeitigen Anforderungen von bearbeitet werden instance. Dies kann die Leistung beeinträchtigen oder nicht. Wenn Sie in der Lage sein möchten, gleichzeitige Anforderungen zu verarbeiten, muss das gesamte Objektdiagramm threadsicher sein. Andernfalls wird ein nicht deterministisches, falsches Verhalten angezeigt. Wenn Sie die Thread-Sicherheit gewährleisten können, ist meine Lösung in der Tat unnötig komplex. Wenn Sie das nicht garantieren können, ist meine Lösung erforderlich.
Mark Seemann
3

Scheiß drauf ... Ich habe die Muster der Abhängigkeitsinjektion und des Service-Locators gemischt (aber meistens ist es immer noch die Abhängigkeitsinjektion und sie findet sogar im Konstruktor statt, was bedeutet, dass Sie einen schreibgeschützten Status haben können).

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

Die Abhängigkeiten des Dienstes sind im Vertrag seiner verschachtelten DependenciesKlasse klar angegeben . Wenn Sie einen IoC-Container verwenden (der das WCF-Chaos noch nicht für Sie behebt), können Sie ihn so konfigurieren, dass die DependenciesInstanz anstelle des Dienstes erstellt wird. Auf diese Weise erhalten Sie das warme, unscharfe Gefühl, das Ihr Container Ihnen vermittelt, während Sie nicht durch zu viele von WCF auferlegte Reifen springen müssen.

Ich werde bei diesem Ansatz keinen Schlaf verlieren. Weder sollte jemand anderes. Schließlich ist Ihr IoC-Container eine große, fette, statische Sammlung von Delegierten, die Dinge für Sie erstellt. Was fügt noch einen hinzu?

Ronnie Overby
quelle
Ein Teil des Problems bestand darin, dass ich das Unternehmen dazu bringen möchte, die Abhängigkeitsinjektion zu verwenden, und wenn es für einen Programmierer, der noch nie die Abhängigkeitsinjektion verwendet hatte, nicht sauber und einfach aussah, würde die Abhängigkeitsinjektion von keinem anderen Programmierer verwendet werden. Allerdings habe ich WCF seit vielen Jahren nicht mehr benutzt und ich vermisse es nicht!
Ian Ringrose
Hier ist mein Ansatz für eine einmalige Eigenschaft stackoverflow.com/questions/839788/…
Ronnie Overby
0

Wir standen vor dem gleichen Problem und haben es auf folgende Weise gelöst. Es ist eine einfache Lösung.

Erstellen Sie in Visual Studio einfach eine normale WCF-Dienstanwendung und entfernen Sie die Schnittstelle. Lassen Sie die CS-Datei an Ort und Stelle (benennen Sie sie einfach um) und öffnen Sie diese CS-Datei. Ersetzen Sie den Namen der Schnittstelle durch Ihren ursprünglichen Klassennamen, der die Servicelogik implementiert (auf diese Weise verwendet die Serviceklasse die Vererbung und ersetzt Ihre tatsächliche Implementierung). Fügen Sie einen Standardkonstruktor hinzu, der die Konstruktoren der Basisklasse wie folgt aufruft:

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

Die MyService-Basisklasse ist die eigentliche Implementierung des Dienstes. Diese Basisklasse sollte keinen parameterlosen Konstruktor haben, sondern nur Konstruktoren mit Parametern, die die Abhängigkeiten akzeptieren.

Der Dienst sollte diese Klasse anstelle des ursprünglichen MyService verwenden.

Es ist eine einfache Lösung und funktioniert wie ein Zauber :-D

Ron Deijkers
quelle
4
Sie haben Service1 nicht von seinen Abhängigkeiten entkoppelt, was irgendwie der Punkt war. Sie haben gerade die Abhängigkeiten im Konstruktor für Service1 instanziiert, was Sie ohne die Basisklasse tun können.
Saille
0

Dies war eine sehr hilfreiche Lösung - insbesondere für jemanden, der ein unerfahrener WCF-Codierer ist. Ich wollte einen kleinen Tipp für alle Benutzer veröffentlichen, die diesen für einen von IIS gehosteten Dienst verwenden. MyServiceHost muss WebServiceHost erben , nicht nur ServiceHost.

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

Dadurch werden alle erforderlichen Bindungen usw. für Ihre Endpunkte in IIS erstellt.

Eric Dieckman
quelle
-2

Ich benutze statische Variablen meines Typs. Ich bin mir nicht sicher, ob dies der beste Weg ist, aber es funktioniert bei mir:

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

Wenn ich den Service-Host instanziiere, gehe ich wie folgt vor:

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

    ...

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

    ....
}
Boris
quelle
5
Static / Singletons sind böse! - siehe stackoverflow.com/questions/137975/…
Immortal Blue