【问题标题】:How do I pass values to the constructor on my wcf service?如何将值传递给我的 wcf 服务的构造函数?
【发布时间】:2011-01-28 03:09:03
【问题描述】:

我想将值传递给实现我的服务的类的构造函数。

但是 ServiceHost 只允许我传递要创建的类型的名称,而不是传递给它的构造函数的参数。

我希望能够传入一个创建我的服务对象的工厂。

到目前为止我发现了什么:

【问题讨论】:

  • 恐怕复杂性是 WCF 固有的,除了不使用 WCF 或将其隐藏在更用户友好的外观之后,您无能为力,例如 Windsor 的 WCF 设施,如果你正在使用温莎

标签: wcf dependency-injection factory-pattern


【解决方案1】:

您需要实现自定义ServiceHostFactoryServiceHostIInstanceProvider 的组合。

给定具有此构造函数签名的服务:

public MyService(IDependency dep)

这是一个可以启动 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
}

在您的 MyService.svc 文件中注册 MyServiceHostFactory,或直接在代码中使用 MyServiceHost 进行自托管方案。

您可以轻松概括这种方法,实际上一些 DI 容器已经为您完成了此操作(提示:Windsor 的 WCF 设施)。

【讨论】:

  • +1(但yuck, #regions 尽管这是最不严重的犯罪案例,我自己转换为显式接口 impl :P)
  • 我如何将它用于自托管?调用 CreateServiceHost 后收到异常。我只能调用受保护的方法 public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses);异常是异常消息是:无法在当前托管环境中调用“ServiceHostFactory.CreateServiceHost”。此 API 要求调用应用程序托管在 IIS 或 WAS 中。
  • @Guy 我遇到了示例问题。因为函数是 protected 我不能自己从 Main() 调用它
  • 这种方法存在一个固有问题,那就是您的依赖项实际上只在 IIS 托管环境中创建一次。 ServiceHostFactory、ServiceHost 和 InstanceProvider 都只创建一次,直到应用程序池被回收,这意味着您的依赖项不能在每次调用时真正刷新(例如 DbContext),这会引入意外的值缓存和更长的依赖项生命周期,即不想要。我不确定如何解决这个问题,有什么想法吗?
  • @MarkSeemann 我只是想知道,你为什么将dep 注入到每个Contract 的 InstanceProvider 中。你可以这样做:ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep)); 其中IMyService 是你的MyService(IDependency dep) 的合约接口。所以只将IDependency 注入到实际需要它的 InstanceProvider 中。
【解决方案2】:

您可以简单地创建Service 的实例并将该实例传递给ServiceHost 对象。您唯一需要做的就是为您的服务添加一个[ServiceBehaviour] 属性,并用[DataContract] 属性标记所有返回的对象。

这是一个模型:

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

及用法:

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

host.Open();

我希望这会让某人的生活更轻松。

【讨论】:

  • 这只适用于单身人士(如InstanceContextMode.Single所示)。
【解决方案3】:

马克用IInstanceProvider 的回答是正确的。

您也可以使用自定义属性(比如MyInstanceProviderBehaviorAttribute),而不是使用自定义ServiceHostFactory。从Attribute 派生,使其实现IServiceBehavior 并实现IServiceBehavior.ApplyDispatchBehavior 方法,如

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

然后,将属性应用到您的服务实现类

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

第三个选项:您也可以使用配置文件应用服务行为。

【讨论】:

  • 从技术上讲,这看起来也是一种解决方案,但通过这种方法,您可以将 IInstanceProvider 与服务紧密耦合。
  • 只是第二种选择,没有评估更好的选择。我已经使用了几次自定义 ServiceHostFactory(尤其是当您想要注册多个行为时)。
  • 问题是你只能在属性构造函数中启动例如DI容器..你不能发送现有数据。
【解决方案4】:

我根据 Mark 的回答工作,但是(至少对于我的场景),它是不必要的复杂。其中一个ServiceHost 构造函数接受一个服务实例,您可以直接从ServiceHostFactory 实现传入该实例。

以 Mark 为例,它看起来像这样:

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

【讨论】:

  • 如果您的服务和所有注入的依赖项都是线程安全的,这将起作用。 ServiceHost 构造函数的特定重载本质上禁用了 WCF 的生命周期管理。相反,您是说 所有 个并发请求将由 instance 处理。这可能会或可能不会影响性能。如果您希望能够处理并发请求,那么 整个 对象图必须是线程安全的,否则您将获得不确定的、不正确的行为。如果你能保证线程安全,我的解决方案确实是不必要的复杂。如果你不能保证,需要我的解决方案。
【解决方案5】:

搞砸了……我混合了依赖注入和服务定位器模式(但大多数情况下它仍然是依赖注入,它甚至发生在构造函数中,这意味着你可以拥有只读状态)。

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

服务的依赖关系在其嵌套的Dependencies 类的契约中明确指定。如果您使用的是 IoC 容器(尚未为您修复 WCF 混乱的容器),您可以将其配置为创建 Dependencies 实例而不是服务。通过这种方式,您可以获得容器给您带来的温暖的模糊感觉,同时也不必跳过 WCF 强加的太多障碍。

我不会因为这种方法而失眠。其他人也不应该。毕竟,你是 IoC 容器是一个庞大的、胖的、静态的代表集合,它为你创建东西。什么又加了一个?

【讨论】:

  • 部分问题是我希望让公司使用依赖注入,如果对于从未使用过依赖注入的程序员来说它看起来不干净和简单,那么永远不会使用依赖注入由任何其他程序员。不过我已经很多年没用过WCF了,也不想错过!
  • 这是我对一次性写入属性的方法stackoverflow.com/questions/839788/…
【解决方案6】:

这是一个非常有用的解决方案 - 特别是对于 WCF 编码新手而言。我确实想为任何可能将其用于 IIS 托管服务的用户发布一个小提示。 MyServiceHost 需要继承 WebServiceHost,而不仅仅是 ServiceHost。

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

这将为您在 IIS 中的端点创建所有必要的绑定等。

【讨论】:

    【解决方案7】:

    我们遇到了同样的问题,并通过以下方式解决了它。这是一个简单的解决方案。

    在 Visual Studio 中只需创建一个普通的 WCF 服务应用程序并删除它的接口。保留 .cs 文件(只需重命名)并打开该 cs 文件并将接口名称替换为实现服务逻辑的原始类名(这样服务类使用继承并替换您的实际实现)。添加一个调用基类构造函数的默认构造函数,如下所示:

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

    MyService 基类是服务的实际实现。这个基类不应该有一个无参数的构造函数,而应该只有带有接受依赖的参数的构造函数。

    服务应该使用这个类而不是原来的 MyService。

    这是一个简单的解决方案,就像一个魅力:-D

    【讨论】:

    • 您还没有将 Service1 与其依赖项解耦,这很重要。您刚刚在 Service1 的构造函数中实例化了依赖项,您可以在没有基类的情况下执行此操作。
    【解决方案8】:

    创建您的实例化服务及其依赖项(我们称之为myService),然后像这样打开您的 ServiceHost:

    var myService = new Service(argumentOne, argumentTwo, . . . etc.);
    var host = new WebServiceHost(myService, new Uri("http://localhost:80"));
    var behavior = host.Description.Behaviors.Find<ServiceBehaviorAttribute>();
    behavior.InstanceContextMode = InstanceContextMode.Single;
    host.Open();
    

    【讨论】:

      【解决方案9】:

      我使用我的类型的静态变量。不确定这是否是最好的方法,但它对我有用:

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

      当我实例化服务主机时,我会执行以下操作:

      protected override void OnStart(string[] args)
      {
          MyServer.CustomerDisplayName = "Test customer";
      
          ...
      
          selfHost = new ServiceHost(typeof(MyServer), baseAddress);
      
          ....
      }
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-14
      • 1970-01-01
      • 2011-05-04
      • 1970-01-01
      • 1970-01-01
      • 2010-12-21
      相关资源
      最近更新 更多