Como faço para passar valores para o construtor em meu serviço wcf?

103

Eu gostaria de passar valores para o construtor da classe que implementa meu serviço.

No entanto, o ServiceHost permite apenas que eu passe o nome do tipo a ser criado, não quais argumentos passar ao seu construtor.

Gostaria de poder passar em uma fábrica que crie meu objeto de serviço.

O que descobri até agora:

Ian Ringrose
fonte
6
Temo que a complexidade seja inerente ao WCF e não há muito que você possa fazer para aliviá-la, a não ser não usar o WCF ou escondê-la atrás de uma fachada mais amigável, como o Windsor's WCF Facility se você estiver usando o Windsor
Krzysztof Kozmic

Respostas:

122

Você precisará implementar uma combinação de costume ServiceHostFactory, ServiceHoste IInstanceProvider.

Dado um serviço com esta assinatura de construtor:

public MyService(IDependency dep)

Aqui está um exemplo que pode ativar o 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
}

Registre MyServiceHostFactory em seu arquivo MyService.svc ou use MyServiceHost diretamente no código para cenários de auto-hospedagem.

Você pode facilmente generalizar essa abordagem e, na verdade, alguns DI Containers já fizeram isso para você (sugestão: Windsor's WCF Facility).

Mark Seemann
fonte
+1 (Mas eca, #regions , embora seja o caso menos grave da ofensa, eu converto para a interface explícita de mim mesmo: P)
Ruben Bartelink
5
Como posso usá-lo para auto-hospedagem? Recebo uma exceção depois de ligar para CreateServiceHost. Só posso chamar o método protegido de substituição pública ServiceHostBase CreateServiceHost (string constructorString, Uri [] baseAddresses); A exceção é A mensagem de exceção era: 'ServiceHostFactory.CreateServiceHost' não pode ser chamado no ambiente de hospedagem atual. Esta API requer que o aplicativo de chamada seja hospedado no IIS ou WAS.
Guy
2
@Guy Estou tendo o problema da amostra. Porque a função é: protectedNão consigo chamá-la de Main ()
Andriy Drozdyuk
1
Há um problema inerente a essa abordagem, que é sua dependência, na verdade, criada apenas uma vez em um ambiente hospedado IIS. O ServiceHostFactory, ServiceHost e InstanceProvider são todos criados apenas uma vez até que o pool de aplicativos seja reciclado, o que significa que sua dependência não pode realmente ser atualizada por chamada (DbContext por exemplo), o que introduz o armazenamento não intencional de valores e vida útil mais longa da dependência que é não queria. Não tenho certeza de como resolver isso, alguma ideia?
David Anderson,
2
@MarkSeemann Só estou me perguntando, por que você injetou depem cada InstanceProvider do contrato . Você poderia fazer: ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));ondeIMyService está uma interface de contrato seu MyService(IDependency dep). Portanto, injete IDependencyapenas no InstanceProvider que realmente precisa dele.
voytek
14

Você pode simplesmente criar uma instância de seu Servicee passar essa instância para o ServiceHostobjeto. A única coisa que você precisa fazer é adicionar um [ServiceBehaviour]atributo para o seu serviço e marcar todos os objetos retornados com o [DataContract]atributo.

Aqui está um 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 o uso:

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

host.Open();

Espero que isso facilite a vida de alguém.

Kerim
fonte
5
Isso só funciona para singletons (conforme indicado por InstanceContextMode.Single).
John Reynolds,
11

A resposta de Mark com o IInstanceProvider está correta.

Em vez de usar o ServiceHostFactory personalizado, você também pode usar um atributo personalizado (digamos MyInstanceProviderBehaviorAttribute). Derive-o Attribute, faça-o implementar IServiceBehaviore implementar o IServiceBehavior.ApplyDispatchBehaviormétodo como

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

Em seguida, aplique o atributo à sua classe de implementação de serviço

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

A terceira opção: você também pode aplicar um comportamento de serviço usando o arquivo de configuração.

dalo
fonte
2
Tecnicamente, isso também parece uma solução, mas com essa abordagem, você acopla fortemente o IInstanceProvider ao serviço.
Mark Seemann,
2
Apenas uma segunda opção, sem avaliação do que é melhor. Usei o ServiceHostFactory personalizado algumas vezes (especialmente quando você deseja registrar vários comportamentos).
dalo
1
O problema é que você pode iniciar, por exemplo, DI container apenas no construtor de atributo .. você não pode enviar dados existentes.
Guy
5

Trabalhei com a resposta de Mark, mas (pelo menos para meu cenário), era desnecessariamente complexo. Um dos ServiceHostconstrutores aceita uma instância do serviço, que você pode passar diretamente doServiceHostFactory implementação.

Para pegar carona no exemplo de Mark, seria assim:

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
fonte
12
Isso funcionará se o seu serviço e todas as dependências injetadas forem thread-safe. Essa sobrecarga específica do construtor ServiceHost desabilita essencialmente o gerenciamento do ciclo de vida do WCF. Em vez disso, você está dizendo que todas as solicitações simultâneas serão tratadas por instance. Isso pode ou não afetar o desempenho. Se você deseja ser capaz de lidar com solicitações simultâneas, todo o gráfico de objeto deve ser thread-safe, ou você obterá um comportamento incorreto e não determinístico. Se você pode garantir a segurança do thread, minha solução é, de fato, desnecessariamente complexa. Se você não pode garantir isso, minha solução é necessária.
Mark Seemann
3

Dane-se… Eu combinei os padrões de injeção de dependência e localizador de serviço (mas na maioria das vezes ainda é injeção de dependência e até ocorre no construtor, o que significa que você pode ter estado somente leitura).

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

As dependências do serviço são claramente especificadas no contrato de sua Dependenciesclasse aninhada . Se você estiver usando um contêiner IoC (um que ainda não conserte a bagunça do WCF para você), pode configurá-lo para criar a Dependenciesinstância em vez do serviço. Dessa forma, você obtém a sensação aconchegante de seu contêiner, ao mesmo tempo em que não precisa passar por muitos obstáculos impostos pelo WCF.

Não vou perder o sono com essa abordagem. Nem deveria ninguém mais. Afinal, seu contêiner IoC é uma grande coleção estática de delegados que cria coisas para você. O que está adicionando mais um?

Ronnie Overby
fonte
Parte do problema era que eu queria que a empresa usasse injeção de dependência e, se não parecesse limpa e simples para um programador que nunca havia usado injeção de dependência, então a injeção de dependência nunca seria usada por outro programador. No entanto, há muitos anos não uso o WCF e não sinto falta!
Ian Ringrose
Esta é minha abordagem para uma propriedade de gravação única stackoverflow.com/questions/839788/…
Ronnie Overby
0

Estávamos enfrentando esse mesmo problema e o resolvemos da seguinte maneira. É uma solução simples.

No Visual Studio, basta criar um aplicativo de serviço WCF normal e remover sua interface. Deixe o arquivo .cs no lugar (apenas renomeie-o) e abra esse arquivo cs e substitua o nome da interface pelo seu nome de classe original que implementa a lógica de serviço (desta forma, a classe de serviço usa herança e substitui sua implementação real). Adicione um construtor padrão que chame os construtores da classe base, como este:

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

A classe base MyService é a implementação real do serviço. Esta classe base não deve ter um construtor sem parâmetros, mas apenas construtores com parâmetros que aceitam as dependências.

O serviço deve usar essa classe em vez do MyService original.

É uma solução simples e funciona perfeitamente :-D

Ron Deijkers
fonte
4
Você não desvinculou Service1 de suas dependências, o que era bem o ponto. Você acabou de instanciar as dependências no construtor para Service1, o que pode ser feito sem a classe base.
Saille
0

Essa foi uma solução muito útil - especialmente para alguém que é um programador de WCF novato. Eu queria postar uma pequena dica para qualquer usuário que possa estar usando isso para um serviço hospedado no IIS. MyServiceHost precisa herdar WebServiceHost , não apenas ServiceHost.

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

Isso criará todas as ligações necessárias, etc. para seus pontos de extremidade no IIS.

Eric Dieckman
fonte
-2

Eu uso variáveis ​​estáticas do meu tipo. Não tenho certeza se esta é a melhor maneira, mas funciona para mim:

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

Ao instanciar o host de serviço, faço o seguinte:

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

    ...

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

    ....
}
Boris
fonte
5
Static / Singletons são maus! - consulte stackoverflow.com/questions/137975/…
Immortal Blue