1

我有一个 WCF 服务库,其中包含从 DefaultServiceHostFactory 派生的自定义 ServiceHostFactory。我无法让测试客户使用这个工厂。我只是得到“没有找到无参数构造函数”错误。

这是我的托管环境配置:

<serviceHostingEnvironment>
  <serviceActivations>
    <add service="TestService.WcfLibrary.TestService"
         relativeAddress="/TestService.svc"
         factory="TestService.WcfLibrary.TestServiceHostFactory, TestService.WcfLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  </serviceActivations>
</serviceHostingEnvironment>

请注意,我实际上没有 .svc 文件。我正在尝试使用无文件激活。

这是我的自定义 ServiceHostFactory:

public class TestServiceHostFactory : DefaultServiceHostFactory
{
    public TestServiceHostFactory() : base(CreateKernel()) { }

    private static IKernel CreateKernel()
    {
        WindsorContainer container = new WindsorContainer();

        container.AddFacility<WcfFacility>();

        container.Register(Component.For<TestService>());
        container.Register(Component.For<IServiceManager>().ImplementedBy<ServiceManager>());

        return container.Kernel;
    }
}

看起来这条路径永远不会执行。如何让 WCF 测试客户端使用我的自定义实现?

4

2 回答 2

3

好吧,这是可能的,但它并不漂亮......

首先,我们需要一种在程序集加载时挂接的方法,因为在这种情况下,它们不是“主”或“应用程序启动”或任何东西。AppDomain 上有一个名为AssemblyLoaded的有趣事件,看起来它可以解决问题,嗯,但是如何挂钩它,我认为这样做的一种方法是定义一个自定义应用程序域管理器,所以......

创建一个全新的程序集并在其中定义一个接口,该接口可以由某些客户端和AppDomainManager实现,如下所示:

public interface IAssemblyLoaded
{
    void AssemblyLoaded();
}

public class CustomManager : AppDomainManager
{
    public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
    {
        base.InitializeNewDomain(appDomainInfo);

        // Look for any classes that implement IAssemblyLoaded,
        // constuct them (will only work if they have a default constructor)
        // and call the AssemblyLoaded method
        var loaded = typeof (IAssemblyLoaded);
        AppDomain
            .CurrentDomain
            .AssemblyLoad += (s, a) =>
                                 {
                                     var handlers = a
                                         .LoadedAssembly
                                         .GetTypes()
                                         .Where(loaded.IsAssignableFrom);

                                     foreach (var h in handlers)
                                     {
                                         ((IAssemblyLoaded)
                                          Activator.CreateInstance(h))
                                             .AssemblyLoaded();
                                     }
                                 };
    }
}

确保程序集已签名,然后将其添加到 GAC。假设我调用了程序集 AssemblyInitCustomDomainManager 我可以像这样添加它(我会立即返回有关它的详细信息,因为我需要它们):

gacutil /i AssemblyInitCustomDomainManager.dll
gacutil /l AssemblyInitCustomDomainManager

现在编辑WcfServiceHost.exe.config(通常位于:C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE 或 64 位系统上的 x86 版本)并在运行时元素中添加以下内容(有关信息,请参见此处此设置):

<appDomainManagerType value="AssemblyInitCustomDomainManager.CustomManager" />
<appDomainManagerAssembly value="AssemblyInitCustomDomainManager, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c841b3549556e52a, processorArchitecture=MSIL" />

注意:您将需要更改至少一项(可能全部取决于您上面所说的内容):类型名称、命名空间、程序集名称、公钥、版本号。我想你应该能够弄清楚他们在你的情况下需要什么。

好的,这很容易,现在我们将在 Visual Studio 中创建一个新的“WCF 服务库”项目,它将为我们创建一个 app.config 和一个服务(这是你想要测试的项目类型,哦,我希望如此!)。

首先,从 app.config 中删除system.servicemodel部分,因为我们不希望服务主机应用程序读取它,然后删除 Service1.cs 和 IService1.cs(因为我将自己制作一点之后)。确保您引用了您之前创建的应用程序域管理器程序集,因为您需要实现该接口。

现在,创建一个新文件并粘贴以下代码(我们只是有一个简单的服务,它具有由 Castle WCF 设施托管的依赖项):

using System;
using System.ServiceModel;

using AssemblyInitCustomDomainManager;

using Castle.Facilities.WcfIntegration;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using Castle.Windsor.Installer;

namespace TestWcfServiceLibrary
{
    public class AssemblyInitializedHandler : IAssemblyLoaded
    {
        // This method is called when the assembly loads so we will create the
        // windsor container and run all the installers we find
        public void AssemblyLoaded()
        {            
            new WindsorContainer().Install(FromAssembly.This());            
        }        
    }

    // This installer will set up the services
    public class ServicesInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, 
                            IConfigurationStore store)
        {
            container
                .AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero)
                .Register(Component
                              .For<ITestDependency>()
                              .ImplementedBy<TestDependency>(),
                          Component
                              .For<IService1>()
                              .ImplementedBy<Service1>()
                              .AsWcfService(new DefaultServiceModel(WcfEndpoint
                                                                        .BoundTo(new WSHttpBinding()))
                                                .AddBaseAddresses("http://localhost:9777/TestWcfServiceLibrary/Service1")
                                                .PublishMetadata(m => m.EnableHttpGet())));
        }
    }

    // This is the contract of something we want to make sure is loaded
    // by Windsor
    public interface ITestDependency
    {
        int DoSomething(int value);
    }

    public class TestDependency : ITestDependency
    {
        public int DoSomething(int value)
        {
            return value;
        }
    }

    // Regular WCF service contract
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        string GetData(int value);
    }

    // Service implementation - notice it does not have a default 
    // constructor
    public class Service1 : IService1
    {
        private readonly ITestDependency _td;

        public Service1(ITestDependency td)
        {
            _td = td;
        }

        public string GetData(int value)
        {
            int v = _td.DoSomething(value);
            return string.Format(
                "According to our dependency you entered: {0}", v);
        }
    }
}

单击运行,您将收到一条错误消息,内容为:

WCF 服务主机找不到任何服务元数据。这可能会导致客户端应用程序运行不正常。请检查是否启用了元数据。你想离开吗?

不用担心,只需单击no

测试客户端启动,但遗憾的是它没有您的服务。不用担心,只需右键单击 add service... 并输入服务的 URL(它位于代码的安装程序中 - http://localhost:9777/TestWcfServiceLibrary/Service1)。

你去 - WCF 服务托管在 WCF 测试客户端内。不信 - 测试一下,调用 GetData 操作,您应该会看到结果。

你去吧。现在,如果您问这是否是个好主意...我不知道,但它有效,我认为这是您所要求的...

于 2012-04-13T07:02:35.333 回答
1

我记得在某处读过这个!关联

“WcfSvcHost 的最大缺点是它仅适用于在打开主机实例之前不需要对主机实例进行编程访问或在打开后对其事件模型进行编程访问的简单场景。与使用 IIS 或 Windows 激活服务 (WAS ), 没有等效的服务主机工厂支持。因此,无法动态添加基址、配置端点、限制调用、在主机级别配置自定义行为等。我对 WCF 的经验是,除了在最简单的情况下,最终您将需要对主机实例进行编程访问,因此我不认为 WcfSvcHost 是一个成熟的、可用于生产的主机,就像我认为 WAS 或专用的自主机一样。”

于 2012-04-13T05:13:30.960 回答