【问题标题】:Can I use WcfFacility with WCF Test Client?我可以将 WcfFacility 与 WCF 测试客户端一起使用吗?
【发布时间】:2012-04-07 22:04:47
【问题描述】:

我有一个 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 测试客户端使用我的自定义实现?

【问题讨论】:

  • 如何(或在何处)托管此服务?
  • @PetarVucetin 这只是一个 WCF 服务库,我必须在调试时让它在 WCF 测试客户端上运行。我知道我可以在 svc 文件上设置工厂,如果我将它托管在 ISS 上或作为 Windows 服务,但它仍然必须作为库工作。
  • 好的。我只是不确定 WCF TC 在托管服务时会做什么。
  • 鉴于 Petar 指出的问题,ConsoleApplication 主机是否允许您轻松进行所需的调试访问?对 TC 的具体需求是什么?

标签: .net wcf castle-windsor wcftestclient wcffacility


【解决方案1】:

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

首先,我们需要一种在程序集加载时挂接的方法,因为在这种情况下,它们不是“主”或“应用程序启动”或任何东西。 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 版本)并添加以下内容在 runtime 元素内(有关此设置的信息,请参阅 here):

<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 服务主机找不到任何服务元数据。这可能会导致 客户端应用程序运行不正常。请检查元数据是否 启用。要退出吗?

别担心,只需点击

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

然后就可以了 - WCF 服务托管在 WCF 测试客户端中。不信 - 测试一下,调用 GetData 操作,您应该会看到结果。

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

【讨论】:

  • 哇。非常感谢,很好的答案。我应该通知您,我可以运行它而不会收到有关元数据的任何错误,服务已经添加并且它刚刚运行。
【解决方案2】:

我记得在某处读过这个! link

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-28
    • 1970-01-01
    • 2019-07-25
    • 1970-01-01
    • 2020-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多