如何将值传递给wcf服务上的构造函数?


103

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

但是,ServiceHost仅允许我传递要创建的类型的名称,而不允许将什么参数传递给其构造方法。

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

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


6
恐怕复杂性固有的WCF并没有什么可以做,以减轻它,比不使用WCF,或者如果你正在使用温莎躲在背后更人性化的外观,像温莎的WCF设施等
克日什托夫·Kozmic

Answers:


122

你需要实现自定义的组合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容器已经为您完成了此操作(提示:温莎的WCF设施)。


+1(但是尽管这是最不严重的情况,但#regions,我转换为暗示界面
暗示

5
如何使用它进行自我托管?调用CreateServiceHost后,我收到异常。我只能调用受保护的方法公共重写ServiceHostBase CreateServiceHost(string constructorString,Uri [] baseAddresses); 例外是例外消息是:当前托管环境中无法调用'ServiceHostFactory.CreateServiceHost'。此API要求调用的应用程序托管在IIS或WAS中。
盖伊2012年

2
@Guy我遇到了样本问题。因为函数是protected我自己,所以不能从Main()
Andriy Drozdyuk

1
这种方法存在一个固有的问题,那就是您的依赖关系实际上仅在IIS托管环境中创建一次。ServiceHostFactory,ServiceHost和InstanceProvider只能创建一次,直到回收应用程序池为止,这意味着您的依赖项不能真正通过每次调用刷新(例如DbContext),这会引入意外的值缓存,并且依赖项的生存期更长不想要。我不确定如何解决此问题,有什么想法吗?
David Anderson

2
@MarkSeemann我只是想知道,您为什么要注入dep每个Contract的 InstanceProvider。您可以这样做:您的合约接口ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));在哪里?因此,仅将其注入实际需要它的InstanceProvider中。IMyService MyService(IDependency dep)IDependency
voytek

14

您可以简单地创建您的实例,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();

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


5
仅适用于单例(如所示InstanceContextMode.Single)。
约翰·雷诺兹

11

用标记的答案IInstanceProvider是正确的。

除了使用自定义ServiceHostFactory之外,还可以使用自定义属性(例如MyInstanceProviderBehaviorAttribute)。从中派生出来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

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


2
从技术上讲,这看起来也像是一种解决方案,但是通过这种方法,您可以将IInstanceProvider与服务紧密耦合。
Mark Seemann

2
只是第二种选择,没有关于更好的评估。我已经使用了自定义ServiceHostFactory两次(尤其是当您要注册多个行为时)。
dalo 2010年

1
问题是您只能在属性构造函数中启动例如DI容器。您无法发送现有数据。
盖伊

5

我从马克的答案开始工作,但是(至少就我的情况而言),它不必要地复杂。其中的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)
    {
    }
}

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

3

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

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/…–
罗尼·奥弗比

0

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

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

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

MyService基类是服务的实际实现。此基类不应具有无参数的构造函数,而应仅具有带有接受依赖项的参数的构造函数。

服务应使用此类而不是原始的MyService。

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


4
您尚未将Service1与它的依赖项解耦,这很重要。您刚刚在Service1的构造函数中实例化了依赖项,而无需基类也可以执行此操作。
Saille 2015年

0

这是一个非常有用的解决方案-尤其是对于WCF新手来说。我确实想为可能将其用于IIS托管服务的所有用户发布一些提示。MyServiceHost需要继承WebServiceHost,而不仅仅是ServiceHost。

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

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


-2

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

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

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

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

    ...

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

    ....
}

5
静态/单例是邪恶的!-看到stackoverflow.com/questions/137975/...
不朽的蓝
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.