2010-03-16 17 views
92

Me gustaría pasar valores al constructor en la clase que implementa mi servicio.¿Cómo paso valores al constructor en mi servicio wcf?

Sin embargo, ServiceHost solo me permite pasar el nombre del tipo para crear, no los argumentos para 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:

+6

Me temo que la complejidad es inherente a WCF y no hay mucho que pueda hacer para aliviarla, aparte de no usar WCF o esconderlo detrás de una fachada más fácil de usar, como el WCF Facility de Windsor si está usando Windsor –

Respuesta

105

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

dado un servicio con este constructor firma:

public MyService(IDependency dep) 

Aquí hay un ejemplo que puede girar 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 
} 

Registro MyServiceHostFactory en su archivo MyService.svc, o utilizar directamente en el código MyServiceHost para escenarios de autohospedaje.

Puede generalizar fácilmente este enfoque, y de hecho algunos Contenedores DI ya lo han hecho por usted (cue: Facilidad WCF de Windsor).

+0

+1 (Pero [yuck, #regions] (http://www.richard-banks.org/2011/02/anti-region-campaign).html) a pesar de que es el caso menos grave de la ofensa, me convierto a la interfaz explícita impl yo mismo: P) –

+5

¿Cómo puedo usarlo para alojamiento web? Recibo una excepción después de llamar a CreateServiceHost. Solo puedo llamar al método protegido anulación pública 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 muestra. Como la función está 'protegida ', no puedo llamarla desde Main() – drozzy

10

La respuesta de Mark con el IInstanceProvider es correcta.

En lugar de utilizar la función personalizada ServiceHostFactory, también podría usar un atributo personalizado (digamos MyInstanceProviderBehaviorAttribute). Derivarla de Attribute, lo convierten en implementar IServiceBehavior y poner en práctica el método IServiceBehavior.ApplyDispatchBehavior 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; 
    } 
} 

A continuación, aplicar el atributo a la clase de implementación del servicio

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

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

+2

Técnicamente, esto también parece una solución, pero con ese enfoque, acoplas estrechamente el IInstanceProvider con el servicio. –

+2

Solo una segunda opción, no hay evaluación de lo que es mejor. He usado ServiceHostFactory personalizado un par de veces (especialmente cuando quieres 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

He trabajado a partir de la respuesta de Mark, pero (para mi escenario al menos), fue innecesariamente complejo. Uno de los constructores ServiceHost acepta una instancia del servicio, que puede pasar directamente desde la implementación ServiceHostFactory.

llevar a cuestas fuera ejemplo de Marcos, que 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) 
    { 
    } 
} 
+10

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ás diciendo que * todas las solicitudes concurrentes serán manejadas por 'instancia'. Eso puede o no afectar el rendimiento. Si desea poder gestionar solicitudes simultáneas, ese gráfico de objetos * enteros debe ser seguro para subprocesos, o obtendrá un comportamiento no determinista e incorrecto. Si puede garantizar la seguridad del hilo, mi solución es, de hecho, innecesariamente compleja. Si no puede garantizar eso, mi solución es necesaria. –

-1

Estábamos frente a este mismo problema y lo han resuelto de la siguiente manera. Es una solución simple.

En Visual Studio simplemente cree una aplicación de servicio WCF normal y elimine su interfaz. Deje el archivo .cs en su lugar (simplemente renómbrelo) 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 forma, la clase de servicio usa la herencia y reemplaza su implementación real).Añadir un constructor predeterminado que llama a los constructores de la clase base, como esto: la clase base

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

El 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 utilizar esta clase en lugar del MyService original.

Es una solución simple y funciona como un encanto: -D

+3

No ha desacoplado Service1 de sus dependencias, que fue una especie de punto. Acaba de crear una instancia de las dependencias en el constructor para Service1, que puede hacer sin la clase base. – saille

-1

utilizo variables estáticas de mi tipo. No estoy seguro si esta es la mejor manera, pero funciona para mí:

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

Cuando crear una instancia de host de servicio hago lo siguiente:

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

    ... 

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

    .... 
} 
+4

Static/Singletons son malvados - ver http://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons –

12

Usted puede simplemente crear y instancia de su Service y pase esa instancia al objeto ServiceHost. Lo único que debe hacer es agregar un atributo [ServiceBehaviour] para su servicio y marcar todos los objetos devueltos con el atributo [DataContract].

Aquí es 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 hará más fácil la vida a alguien.

0

Esta fue una solución muy útil, especialmente para alguien que es un programador novato de WCF. Quise publicar un pequeño consejo para los usuarios que podrían estar usando esto para un servicio hospedado por 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.

3

Tornillo ... Mezclé los patrones de inyección de dependencia y localizador de servicio (pero sobre todo sigue siendo inyección de dependencia e incluso tiene lugar en el constructor, lo que significa que puede tener 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, claramente especificados en el contrato de está anidado Dependencies clase. Si está utilizando un contenedor IoC (uno que aún no soluciona el desastre de WCF), puede configurarlo para crear la instancia Dependencies en lugar del servicio. De esta forma obtienes la cálida sensación difusa que te proporciona tu contenedor sin tener que saltar demasiados aros impuestos por WCF.

No voy a perder el sueño con este enfoque. Tampoco nadie más. Después de todo, tu contenedor de IoC es una gran colección de delegados estáticos que crean cosas para ti. ¿Qué está agregando uno más?

+0

Parte del problema es que deseo que la empresa use la inyección de dependencia, y si no se veía limpia y simple para un programador que nunca usó la inyección de dependencia, entonces la inyección de dependencia nunca sería utilizada por ningún otro programador. Sin embargo, no he usado WCF durante muchos años y ¡no me lo pierdo! –

+0

Aquí está mi enfoque para una propiedad de escritura única http://stackoverflow.com/questions/839788/is-there-a-way-of-setting-a-property-once-only-in-c-sharp/40209216# 40209216 –

Cuestiones relacionadas