使用autofac动态解析WCF端点地址

时间:2014-02-14 11:13:56

标签: wcf autofac

我有一个在MVC应用程序中使用的WCF客户端,它可以从多个WCF服务获取数据,服务以相同的方式配置并实现相同的接口,唯一的区别是暴露端点的地址。

这就是我的尝试:

  
  builder.Register(c => new ChannelFactory<IService>(
                     new BasicHttpBinding(),
                     new EndpointAddress("http://service.com/Service")))
                     .InstancePerHttpRequest();

  builder.Register(c => c.Resolve<ChannelFactory<IService>>().CreateChannel())
      .UseWcfSafeRelease();

这里的事情是IService将始终从http://service.com/Service获取数据,因为地址是在MVC应用程序的Application_Start方法中的某处硬编码。

然后我尝试使用元数据:

  
    builder.Register(c => new ChannelFactory<IService>(
                    new BasicHttpBinding(),
                    new EndpointAddress("http://foo.com/Service")))
                    .SingleInstance().WithMetadata("name", "fooservice");

    builder.Register(c => new ChannelFactory<IService>(
                     new BasicHttpBinding(),
                     new EndpointAddress("http://bar.com/Service")))
                     .SingleInstance().WithMetadata("name", "barservice");

    builder.Register(c => c.Resolve<ChannelFactory<IService>>().CreateChannel())
      .UseWcfSafeRelease();

但是这样我每次想要添加相同的WCF服务时都必须编辑代码 在不同的服务器上实现。相反,我想从数据库中获取地址。

有什么方法可以更改每次服务调用的地址,或者至少在创建客户端实例时。

补充说明:

让我们说我有一个网站的五个精确副本,每个网站都有自己的域名和数据库,我希望能够做到以下几点:

  
foreach(Provider provider in providers)
{
    SetServiceAddress(provider.Address);//how can i do that
    _service.GetData()
}

4 个答案:

答案 0 :(得分:5)

根据以下假设:

  • 当地址发生变化时,绑定不会发生变化(例如,它不会从HTTP切换到HTTPS)
  • 地址可能会根据请求进行更改

然后我可能会用lambda和一个小接口来解决它。

首先,您需要从数据存储中检索地址的内容:

public interface IAddressReader
{
  Uri GetAddress();
}

它的实现将从数据库(或环境,或XML配置,或其他)读取。

然后我会在我的注册中使用它:

builder
  .RegisterType<MyDatabaseAddressReader>()
  .As<IAddressReader>();
builder
  .Register(c => new ChannelFactory<IService>(new BasicHttpBinding()))
  .SingleInstance();
builder
  .Register(c =>
    {
      var reader = c.Resolve<IAddressReader>();
      var factory = c.Resolve<ChannelFactory<IService>();
      var endpoint = new EndpointAddress(reader.GetAddress());
      return factory.CreateChannel(endpoint);
    })
  .As<IService>()
  .UseWcfSafeRelease();

这样您就可以使用IService(或Func<IService>)作为构造函数参数,而您的调用类将不会了解Autofac,服务位置或端点。

如果绑定也发生变化,则会变得更复杂。您可能不希望为每个频道启动一个全新的频道工厂,所以您希望有一些你在哪里的缓存机制:

  1. 从配置源获取设置。
  2. 将这些设置与当前使用的设置进行比较。
  3. 如果设置不匹配...
    1. 废弃上一个渠道工厂。
    2. 使用新设置创建新的渠道工厂。
    3. 缓存频道工厂以供日后重用。
  4. 返回当前渠道工厂。
  5. 如果您可以在设置上使用缓存依赖项,那么更好,但不是每个配置源都支持,所以YMMV。我可能会实现一个自定义模块来封装逻辑,但我不会在这里写出所有这些。

答案 1 :(得分:1)

如果您想在每次呼叫之前设置端点,可以执行以下操作:

containerBuilder
    .Register(c => new ChannelFactory<IService>(new BasicHttpBinding()))
    .SingleInstance();

containerBuilder.Register((c, p) =>
{
    var factory = c.Resolve<ChannelFactory<IService>>();
    var endpointAddress = p.TypedAs<string>();
    return factory.CreateChannel(new EndpointAddress(endpointAddress));
})
    .As<IService>()
    .UseWcfSafeRelease();

然后你注入这个:

Func<string, IService> getService

然后这样称呼:

string endpoint = getDataDependentEndpointFromSomewhere();
var service = getService(endpoint);

答案 2 :(得分:0)

我有一个在多个站点上运行的服务,在启动时,应用程序需要确定它在哪个站点上运行。它使用启动参数来实现,并且基于此,可以在属性或方法中动态设置端点地址,如GetEndPointAddressForService()。

在您的情况下,您似乎需要连续在不同的站点呼叫n个服务。您绝对可以在数据库或磁盘上的简单配置文件中配置它们,在启动时加载服务定义(包括其端点地址),将它们保存在列表中,并在从所有现有服务器收集数据时执行操作。

逻辑的关键部分在代码的以下部分:

new EndpointAddress("http://bar.com/Service")

做一个

foreach (ServiceDefinition sd in ServiceDefinitions)
{
    builder.Register(c => new ChannelFactory<IService>(
        new BasicHttpBinding(),
        new EndpointAddress(sd.EndPointAddress)))
        .InstancePerHttpRequest();

    builder.Register(c => c.Resolve<ChannelFactory<IService>>().CreateChannel())
        .UseWcfSafeRelease();

    GoGetTheData();
}

答案 3 :(得分:0)

最后我使用了以下实现:

在应用程序启动时,我注册没有端点地址的ChannelFactory类型。 我使用命名参数来注册客户端,以便我能够在以后实际调用服务时分配地址。

  
builder.RegisterType<ChannelFactory<IService>>(new BasicHttpBinding())
                     .SingleInstance();
builder.Register((c, p) => c.Resolve<ChannelFactory<IService>>().CreateChannel(p.Named<EndpointAddress>("address")))
                        .UseWcfSafeRelease();

然后我在运行时使用服务客户端,如下所示:

  
public Data GetData(string url)
{
     EndpointAddress address = new EndpointAddress(url);
     NamedParameter parameter = new NamedParameter("address", address);

     var service = _autofacContainer.Resolve<IService>(parameter);//this is what I have been looking for

     Response response = service.GetData();
     return CreateDataFromResponse(response);
}

这样我可以为数据库中的每个地址调用GetData方法。而且我将能够在运行时添加更多地址而无需编写代码或配置。