¿Cómo paso valores al constructor en mi servicio wcf?

103

Me gustaría pasar valores al constructor en la clase que implementa mi servicio.

Sin embargo, ServiceHost solo me permite pasar el nombre del tipo a crear, no qué argumentos pasar a su contrstructor.

Me gustaría poder pasar en una fábrica que crea mi objeto de servicio.

Lo que he encontrado hasta ahora:

Ian Ringrose
fuente
6
Me temo que la complejidad es inherente a WCF y no hay mucho que pueda hacer para aliviarlo, aparte de no usar WCF u ocultarlo detrás de una fachada más fácil de usar, como WCF Facility de Windsor si está usando Windsor
Krzysztof Kozmic

Respuestas:

122

Tendrá que aplicar una combinación de costumbre ServiceHostFactory, ServiceHosty IInstanceProvider.

Dado un servicio con esta firma de constructor:

public MyService(IDependency dep)

Aquí hay un ejemplo que puede activar 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 en su archivo MyService.svc o use MyServiceHost directamente en el código para escenarios de autohospedaje.

Puede generalizar fácilmente este enfoque y, de hecho, algunos DI Containers ya lo han hecho por usted (señal: WCF Facility de Windsor).

Mark Seemann
fuente
+1 (Pero qué asco, #regiones , aunque es el caso menos grave de la infracción, me convierto en una interfaz explícita implícita: P)
Ruben Bartelink
5
¿Cómo puedo usarlo para autohospedaje? Recibo una excepción después de llamar a CreateServiceHost. Solo puedo llamar al método protegido public override ServiceHostBase CreateServiceHost (string constructorString, Uri [] baseAddresses); La excepción es El mensaje de excepción fue: 'ServiceHostFactory.CreateServiceHost' no se puede invocar dentro del entorno de alojamiento actual. Esta API requiere que la aplicación de llamada esté alojada en IIS o WAS.
Guy
2
@ Guy, tengo el problema de la muestra. Porque la función es protectedque no puedo llamarla yo mismo desde Main ()
Andriy Drozdyuk
1
Hay un problema inherente con este enfoque, y es que su dependencia realmente solo se crea una vez en un entorno alojado en IIS. ServiceHostFactory, ServiceHost e InstanceProvider solo se crean una vez hasta que se recicla el grupo de aplicaciones, lo que significa que su dependencia no se puede actualizar realmente por llamada (DbContext, por ejemplo), lo que introduce el almacenamiento en caché no intencionado de valores y una vida útil más larga de la dependencia que es no deseado. No estoy realmente seguro de cómo resolver esto, ¿alguna idea?
David Anderson
2
@MarkSeemann Me pregunto, ¿por qué inyectó depen el InstanceProvider de cada contrato ? Podría hacer: ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));dónde IMyService está una interfaz de contrato de su MyService(IDependency dep). Así que inyecte IDependencysolo en InstanceProvider que realmente lo necesita.
voytek
14

Simplemente puede crear una instancia de su Servicey pasar esa instancia al ServiceHostobjeto. Lo único que tiene que hacer es agregar un [ServiceBehaviour]atributo para su servicio y marcar todos los objetos devueltos con [DataContract]atributo.

Aquí hay una maqueta:

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

y el uso:

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

host.Open();

Espero que esto le facilite la vida a alguien.

kerim
fuente
5
Eso solo funciona para singletons (como lo indica InstanceContextMode.Single).
John Reynolds
11

La respuesta de Mark con la IInstanceProvideres correcta.

En lugar de utilizar ServiceHostFactory personalizado, también puede utilizar un atributo personalizado (por ejemplo MyInstanceProviderBehaviorAttribute). Derivarlo Attribute, hacer que implemente IServiceBehaviore implementar el 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;
    }
}

Luego, aplique el atributo a su clase de implementación de servicio

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

La tercera opción: también puede aplicar un comportamiento de servicio utilizando el archivo de configuración.

dalo
fuente
2
Técnicamente, esto también parece una solución, pero con ese enfoque, acopla firmemente el IInstanceProvider con el servicio.
Mark Seemann
2
Solo una segunda opción, sin evaluar qué es mejor. He usado el ServiceHostFactory personalizado un par de veces (especialmente cuando desea registrar varios comportamientos).
dalo
1
El problema es que puede iniciar, por ejemplo, el contenedor DI solo en el constructor de atributos ... no puede enviar datos existentes.
Guy
5

Trabajé a partir de la respuesta de Mark, pero (al menos para mi escenario), era innecesariamente complejo. Uno de los ServiceHostconstructores acepta una instancia del servicio, que puede pasar directamente desde la ServiceHostFactoryimplementación.

Para aprovechar el ejemplo de Mark, se vería así:

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
fuente
12
Esto funcionará si su servicio y todas las dependencias inyectadas son seguras para subprocesos. Esa sobrecarga particular del constructor ServiceHost esencialmente deshabilita la administración del ciclo de vida de WCF. En cambio, está diciendo que todas las solicitudes simultáneas serán manejadas por instance. Eso puede afectar o no al rendimiento. Si desea poder manejar solicitudes simultáneas, todo el gráfico de objeto debe ser seguro para subprocesos, o obtendrá un comportamiento incorrecto no determinista. Si puede garantizar la seguridad de los subprocesos, mi solución es, de hecho, innecesariamente compleja. Si no puede garantizar eso, se requiere mi solución.
Mark Seemann
3

Al diablo ... Combiné la inyección de dependencia y los patrones del localizador de servicios (pero en su mayoría sigue siendo inyección de dependencia e incluso tiene lugar en el constructor, lo que significa que puede tener un estado de solo lectura).

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

Las dependencias del servicio están claramente especificadas en el contrato de su Dependenciesclase anidada . Si está utilizando un contenedor de IoC (uno que aún no soluciona el problema de WCF), puede configurarlo para crear la Dependenciesinstancia en lugar del servicio. De esta manera, obtiene la cálida sensación difusa que le da su contenedor sin tener que pasar por demasiados aros impuestos por WCF.

No voy a perder el sueño con este enfoque. Nadie más debería hacerlo. Después de todo, su contenedor de IoC es una colección grande, gruesa y estática de delegados que crea cosas para usted. ¿Qué agrega uno más?

Ronnie Overby
fuente
Parte del problema era que deseaba que la empresa usara la inyección de dependencias, y si no se veía limpio y simple para un programador que nunca había usado la inyección de dependencias, ningún otro programador usaría la inyección de dependencias. Sin embargo, no he usado WCF durante muchos años, ¡y no me lo pierdo!
Ian Ringrose
Aquí está mi enfoque de una propiedad de escritura única stackoverflow.com/questions/839788/…
Ronnie Overby
0

Estábamos enfrentando este mismo problema y lo hemos resuelto de la siguiente manera. Es una solucion sencilla.

En Visual Studio, simplemente cree una aplicación de servicio WCF normal y elimine su interfaz. Deje el archivo .cs en su lugar (simplemente cámbiele el nombre) y abra ese archivo cs y reemplace el nombre de la interfaz con su nombre de clase original que implementa la lógica del servicio (de esta manera, la clase de servicio usa la herencia y reemplaza su implementación real). Agregue un constructor predeterminado que llame a los constructores de la clase base, como este:

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

La clase base MyService es la implementación real del servicio. Esta clase base no debe tener un constructor sin parámetros, sino solo constructores con parámetros que acepten las dependencias.

El servicio debe usar esta clase en lugar del MyService original.

Es una solución simple y funciona de maravilla :-D

Ron Deijkers
fuente
4
No ha desacoplado Service1 de sus dependencias, que era el punto. Acaba de crear una instancia de las dependencias en el constructor de Service1, lo que puede hacer sin la clase base.
Saille
0

Esta fue una solución muy útil, especialmente para alguien que es un codificador WCF novato. Quería publicar un pequeño consejo para cualquier usuario que pudiera estar usando esto para un servicio alojado en IIS. MyServiceHost necesita heredar WebServiceHost , no solo ServiceHost.

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

Esto creará todos los enlaces necesarios, etc. para sus puntos finales en IIS.

Eric Dieckman
fuente
-2

Utilizo variables estáticas de mi tipo. No estoy seguro de si esta es la mejor manera, pero me funciona:

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

Cuando creo una instancia del host de servicio, hago lo siguiente:

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

    ...

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

    ....
}
Boris
fuente
5
Estático / Singletons son malvados! - ver stackoverflow.com/questions/137975/…
Immortal Blue