Jak przekazać wartości do konstruktora w mojej usłudze WCF?

103

Chciałbym przekazać wartości do konstruktora w klasie implementującej moją usługę.

Jednak ServiceHost pozwala mi tylko przekazać nazwę typu do utworzenia, a nie jakie argumenty przekazać do jego kontrstruktora.

Chciałbym móc przejść do fabryki, która tworzy mój obiekt usługowy.

Co znalazłem do tej pory:

Ian Ringrose
źródło
6
Obawiam się, że złożoność jest nieodłączna od WCF i niewiele można zrobić, aby ją złagodzić, poza niestosowaniem WCF lub ukryciem go za bardziej przyjazną dla użytkownika fasadą, jak w Windsorze WCF Facility, jeśli używasz Windsora
Krzysztof Kozmic

Odpowiedzi:

122

Trzeba zaimplementować kombinacji zwyczaju ServiceHostFactory, ServiceHosti IInstanceProvider.

Biorąc pod uwagę usługę z tym podpisem konstruktora:

public MyService(IDependency dep)

Oto przykład, który może uruchomić 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
}

Zarejestruj MyServiceHostFactory w pliku MyService.svc lub użyj MyServiceHost bezpośrednio w kodzie dla scenariuszy samodzielnego hostingu.

Możesz łatwo uogólnić to podejście, aw rzeczywistości niektóre DI Containers już to zrobiły (cue: Windsor's WCF Facility).

Mark Seemann
źródło
+1 (Ale fuj, #regions, mimo że jest to najmniej poważny przypadek przestępstwa, konwertuję na jawny interfejs sam sobie: P)
Ruben Bartelink
5
Jak mogę go używać do samodzielnego hostingu? Otrzymuję wyjątek po wywołaniu CreateServiceHost. Mogę tylko wywołać chronioną metodę publicznego przesłonięcia ServiceHostBase CreateServiceHost (string constructorString, Uri [] baseAddresses); Wyjątkiem jest komunikat o wyjątku: „ServiceHostFactory.CreateServiceHost” nie może zostać wywołany w bieżącym środowisku hostingu. Ten interfejs API wymaga, aby aplikacja wywołująca była hostowana w usługach IIS lub WAS.
Guy,
2
@ Guy Mam problem z próbką. Ponieważ funkcja jest taka, protectedże nie mogę tego nazwać sam z Main ()
Andriy Drozdyuk
1
Z tym podejściem wiąże się nieodłączny problem, a mianowicie zależność jest tworzona tylko raz w środowisku hostowanym przez usługi IIS. ServiceHostFactory, ServiceHost i InstanceProvider są tworzone tylko raz, dopóki pula aplikacji nie zostanie odtworzona, co oznacza, że ​​zależności nie można tak naprawdę odświeżyć na wywołanie (na przykład DbContext), co wprowadza niezamierzone buforowanie wartości i dłuższą żywotność zależności, która jest niechciane. Nie jestem pewien, jak to rozwiązać, jakieś myśli?
David Anderson
2
@MarkSeemann Zastanawiam się tylko, dlaczego wstrzyknąłeś depdo każdego dostawcy InstanceProvider kontraktu . Możesz zrobić: ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));gdzie IMyService jest interfejs kontraktu twojego MyService(IDependency dep). Więc wstrzykuj IDependencytylko do InstanceProvider, który faktycznie tego potrzebuje.
voytek
14

Możesz po prostu utworzyć i umieścić swoją instancję Servicei przekazać tę instancję do ServiceHostobiektu. Jedyne, co musisz zrobić, to dodać [ServiceBehaviour]atrybut dla swojej usługi i oznaczyć wszystkie zwracane obiekty [DataContract]atrybutem.

Oto makieta:

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

i zastosowanie:

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

host.Open();

Mam nadzieję, że komuś to ułatwi życie.

kerim
źródło
5
To działa tylko dla singletonów (jak wskazano przez InstanceContextMode.Single).
John Reynolds,
11

Odpowiedź Marka z znakiem IInstanceProviderjest poprawna.

Zamiast używać niestandardowego ServiceHostFactory, możesz również użyć atrybutu niestandardowego (powiedzmy MyInstanceProviderBehaviorAttribute). Wyprowadź go z Attribute, IServiceBehaviorspraw , aby zaimplementował i zaimplementuj IServiceBehavior.ApplyDispatchBehaviormetodę taką jak

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

Następnie zastosuj atrybut do klasy implementacji usługi

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

Trzecia opcja: możesz również zastosować zachowanie usługi za pomocą pliku konfiguracyjnego.

dalo
źródło
2
Z technicznego punktu widzenia to również wygląda na rozwiązanie, ale przy takim podejściu ściśle łączysz dostawcę IInstanceProvider z usługą.
Mark Seemann
2
Tylko druga opcja, bez oceny tego, co jest lepsze. Kilka razy korzystałem z niestandardowego ServiceHostFactory (zwłaszcza gdy chcesz zarejestrować kilka zachowań).
dalo
1
Problem polega na tym, że możesz zainicjować na przykład kontener DI tylko w konstruktorze atrybutów .. nie możesz wysłać istniejących danych.
Guy,
5

Pracowałem na podstawie odpowiedzi Marka, ale (przynajmniej dla mojego scenariusza) było to niepotrzebnie skomplikowane. Jeden z ServiceHostkonstruktorów akceptuje wystąpienie usługi, które można przekazać bezpośrednio z ServiceHostFactoryimplementacji.

Aby odnieść się do przykładu Marka, wyglądałoby to tak:

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
źródło
12
To zadziała, jeśli usługa i wszystkie wstrzyknięte zależności są bezpieczne dla wątków. To szczególne przeciążenie konstruktora ServiceHost zasadniczo wyłącza zarządzanie cyklem życia programu WCF. Zamiast tego mówisz, że wszystkie równoczesne żądania będą obsługiwane przez instance. To może mieć wpływ na wydajność, ale nie musi. Jeśli chcesz mieć możliwość obsługi współbieżnych żądań, cały graf obiektu musi być bezpieczny dla wątków, w przeciwnym razie uzyskasz niedeterministyczne, niepoprawne zachowanie. Jeśli możesz zagwarantować bezpieczeństwo wątków, moje rozwiązanie jest rzeczywiście niepotrzebnie skomplikowane. Jeśli nie możesz tego zagwarantować, wymagane jest moje rozwiązanie.
Mark Seemann
3

Chrzanić to… Połączyłem wzorce wstrzykiwania zależności i lokalizatora usług (ale przeważnie jest to nadal wstrzykiwanie zależności i odbywa się to nawet w konstruktorze, co oznacza, że ​​możesz mieć stan tylko do odczytu).

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

Zależności usługi są jasno określone w kontrakcie jej zagnieżdżonej Dependenciesklasy. Jeśli używasz kontenera IoC (takiego, który jeszcze nie naprawia bałaganu WCF), możesz skonfigurować go do tworzenia Dependencieswystąpienia zamiast usługi. W ten sposób uzyskujesz ciepłe, rozmyte uczucie, które daje twój kontener, a jednocześnie nie musisz przeskakiwać przez zbyt wiele obręczy narzuconych przez WCF.

Nie zamierzam tracić snu z powodu tego podejścia. Nikt inny też nie powinien. W końcu kontener you IoC to duży, gruby, statyczny zbiór delegatów, który tworzy rzeczy dla Ciebie. Co dodaje jeszcze jednego?

Ronnie Overby
źródło
Częściowo problem polegał na tym, że chciałbym, aby firma korzystała z wstrzykiwania zależności, a jeśli programista, który nigdy nie korzystał z wstrzykiwania zależności, nie wyglądałby na czysty i prosty, to iniekcja zależności nigdy nie byłaby używana przez żadnego innego programistę. Jednak od wielu lat nie korzystam z WCF i nie tęsknię za tym!
Ian Ringrose
Oto moje podejście do jednokrotnego zapisu własności stackoverflow.com/questions/839788/...
Ronnie Overby
0

Mieliśmy ten sam problem i rozwiązaliśmy go w następujący sposób. To proste rozwiązanie.

W programie Visual Studio po prostu utwórz normalną aplikację usługi WCF i usuń jej interfejs. Pozostaw plik .cs na miejscu (po prostu zmień jego nazwę) i otwórz ten plik cs i zastąp nazwę interfejsu oryginalną nazwą klasy, która implementuje logikę usługi (w ten sposób klasa usługi używa dziedziczenia i zastępuje rzeczywistą implementację). Dodaj domyślny konstruktor, który wywołuje konstruktory klasy bazowej, na przykład:

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

Klasa bazowa MyService to rzeczywista implementacja usługi. Ta klasa bazowa nie powinna mieć konstruktora bez parametrów, ale tylko konstruktory z parametrami, które akceptują zależności.

Usługa powinna używać tej klasy zamiast oryginalnej MyService.

To proste rozwiązanie i działa jak marzenie :-D

Ron Deijkers
źródło
4
Nie odłączyłeś usługi Service1 od jej zależności, o co w pewnym sensie chodziło. Właśnie utworzyłeś wystąpienie zależności w konstruktorze dla usługi Service1, co możesz zrobić bez klasy bazowej.
saille
0

To było bardzo pomocne rozwiązanie - szczególnie dla kogoś, kto jest początkującym programistą WCF. Chciałem opublikować małą wskazówkę dla wszystkich użytkowników, którzy mogą używać tego w usłudze hostowanej przez IIS. MyServiceHost musi dziedziczyć WebServiceHost , a nie tylko ServiceHost.

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

Spowoduje to utworzenie wszystkich niezbędnych powiązań itp. Dla punktów końcowych w usługach IIS.

Eric Dieckman
źródło
-2

Używam zmiennych statycznych mojego typu. Nie jestem pewien, czy to najlepszy sposób, ale dla mnie działa:

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

Podczas tworzenia wystąpienia hosta usługi wykonuję następujące czynności:

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

    ...

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

    ....
}
Boris
źródło
5
Statyczne / Singletony są złe! - patrz stackoverflow.com/questions/137975/…
Immortal Blue