使用Autofac动态解析WCF终结点地址

7
我是一名有帮助的助手,以下是您需要翻译的内容:

我有一个WCF客户端在MVC应用程序中使用,可以从多个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()
}

所以,只是为了明确起见,地址不会因每个请求而改变,只是在应用程序启动时动态选择一次?您可以使用XML配置代替数据库吗? - Travis Illig
我已经添加了更多的解释。 - Yavor Dimitrov
再次强调一下,应用程序启动后地址不会改变吗?XML配置是一个选项吗?附加的解释并不清楚地址集是否在应用程序运行时不断发生变化,还是只需要在应用程序启动时发生一次。我只是想确保我正确回答了这个问题。 - Travis Illig
地址在运行时会发生变化,而且在应用程序生命周期中还可以添加更多的地址。如果我不必手动编辑XML配置来添加新地址,则XML配置只是一个选项。 - Yavor Dimitrov
4个回答

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. 返回当前的通道工厂。

如果您可以在设置上使用缓存依赖项,那就更好了,但不是每个配置源都支持,所以效果可能因人而异。我可能会实现一个自定义模块来封装逻辑,但我不会在此处编写所有内容。


有没有使用Autofac解析两个命名工厂实例的好主意,并使用IAddressReader接口和额外的方法获取Uri.Scheme来解析它们,例如var factory = c.ResolveNamed<ChannelFactory<IService>(reader.GetUriScheme());。由于所有服务的配置都相同,唯一的区别是某些服务可能是http,某些服务可能是https。 - Yavor Dimitrov
我不会这样做。如果一次只需要一个通道工厂,那么为每个绑定分配内存“以防万一”就没有意义了。也许可以使用IBindingFactory接口来处理基于方案的绑定创建? - Travis Illig

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

0

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

在您的情况下,似乎您需要连续调用不同站点上的n个服务。您可以在数据库或磁盘上的简单配置文件中配置这些服务,包括它们的终结点地址,在启动时加载服务定义,将它们保存在列表中,并在从所有现有服务器收集数据时进行foreach操作。

您代码中关键部分的逻辑如下:

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

我考虑过这个问题,但这样做会导致多个ChannelFactory实例的出现,这并不是一个很优雅的解决方案。我能否将注册语句移出循环,并仅在使用相同的ChanelFactory实例时分配绑定和端点?类似于container.Resolve<ChannelFactory<IService>>(bindingParameter, endpointParameter);container.Resolve<IService>(bindingParameter, endpointParameter);这样,我可以获得相同的实例并进行配置,而不是创建新的实例。 - Yavor Dimitrov
当然,上面的建议只是伪代码,并不应该被过于字面理解。我只是使用了你自己代码中的片段来表达这个想法。 - N. Jensen

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方法。而且我还能够在运行时添加更多的地址,而无需进行代码或配置编辑。

4
这将从控制反转(IoC)转为服务定位,并将该类与“了解”Autofac绑定。 - Travis Illig

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接