具有更改ClientCredentials的AutoFac WCF代理

时间:2013-04-05 13:14:42

标签: wcf autofac

我正在编写WCF服务,并且正在使用AutoFac WCF集成DI。我有一个有点奇怪的情况,我有一个需要凭据的另一个服务的代理。凭据将根据一些参数进行更改,因此我无法在设置容器时设置值并完成设置。

public class MyService : IMyService
{
    private ISomeOtherService _client;
    public MyService(ISomeOtherService client)
    {
        _client = client;
    }

    public Response SomeCall(SomeData data)
    {
        // how do I set ClientCredentials here, without necessarily casting to concrete implementation
        _client.MakeACall();
    }
}

在不必转换为已知类型或ChannelBase的情况下,在代理上设置凭据的最佳方法是什么。我试图避免这种情况,因为在我的单元测试中,我嘲笑代理接口,因此将其转换回其中一种类型会失败。

有什么想法吗?

2 个答案:

答案 0 :(得分:2)

你可以做到,但这不是直截了当的,你必须稍微改变设计,以便“决定和设置凭据”的逻辑被从MyService类中拉出来

首先,让我们定义场景中的其余类,这样你就可以看到它们的结合。

我们有ISomeOtherService接口,我稍微修改了一下,这样你就可以真正看到最后设置的凭据。我让它返回一个字符串而不是一个空白。我还有SomeOtherService的实现,它具有凭证获取/设置(这是WCF中的ClientCredentials)。这一切看起来像这样:

public interface ISomeOtherService
{
  string MakeACall();
}

public class SomeOtherService : ISomeOtherService
{
  // The "Credentials" here is a stand-in for WCF "ClientCredentials."
  public string Credentials { get; set; }

  // This just returns the credentials used so we can validate things
  // are wired up. You don't actually have to do that in "real life."
  public string MakeACall()
  {
    return this.Credentials;
  }
}

请注意,接口不会公开Credentials属性,因此您可以在不将接口强制转换为具体类型的情况下看到它的工作原理。

接下来,我们为您在问题中显示的IMyService操作提供SomeCall接口和关联的请求/响应对象。 (在你有SomeData的问题中,但是它的想法是一样的,我只是采用了略微不同的命名约定来帮助我保持输入与输出的直接对比。)

public class SomeCallRequest
{
  // The Number value is what we'll use to determine
  // the set of client credentials.
  public int Number { get; set; }
}

public class SomeCallResponse
{
  // The response will include the credentials used, passed up
  // from the call to ISomeOtherService.
  public string CredentialsUsed { get; set; }
}

public interface IMyService
{
  SomeCallResponse SomeCall(SomeCallRequest request);
}

有趣的是,我们用来选择凭据集的数据是请求中的Number。它可以是你想要的任何东西,但是在这里使用数字可以使代码更简单。

这里开始变得越来越复杂。首先,你真的需要熟悉两个Autofac的东西:

我们将在这里使用这两个概念。

MyService的实施转为采取工厂,将采用int并返回ISomeOtherService的实例。如果要获取对其他服务的引用,请执行该函数并传入将确定客户端凭据的数字。

public class MyService : IMyService
{
  private Func<int, ISomeOtherService> _clientFactory;

  public MyService(Func<int, ISomeOtherService> clientFactory)
  {
    this._clientFactory = clientFactory;
  }

  public SomeCallResponse SomeCall(SomeCallRequest request)
  {
    var client = this._clientFactory(request.Number);
    var response = client.MakeACall();
    return new SomeCallResponse { CredentialsUsed = response };
  }
}

真正的关键是Func<int, ISomeOtherService>依赖。我们将注册ISomeOtherService,Autofac将自动创建一个接收int的工厂并返回我们ISomeOtherService。不需要真正的特殊工作......虽然注册有点复杂。

最后一部分是为ISomeOtherService 注册lambda而不是更简单的类型/接口映射。 lambda将查找类型为int的参数,我们将使用它来确定/设置客户端凭据。

var builder = new ContainerBuilder();
builder.Register((c, p) =>
  {
    // In WCF, this is more likely going to be a call
    // to ChannelFactory<T>.CreateChannel(), but for ease
    // here we'll just new this up:
    var service = new SomeOtherService();

    // The magic: Get the incoming int parameter - this
    // is what the Func<int, ISomeOtherService> will pass
    // in when called.
    var data = p.TypedAs<int>();

    // Our simple "credentials" will just tell us whether
    // we passed in an even or odd number. Yours could be
    // way more complex, looking something up from config,
    // resolving some sort of "credential factory" from the
    // current context (the "c" parameter in this lambda),
    // or anything else you want.
    if(data % 2 == 0)
    {
      service.Credentials = "Even";
    }
    else
    {
      service.Credentials = "Odd";
    }
    return service;
  })
.As<ISomeOtherService>();

// And the registration of the consuming service here.
builder.RegisterType<MyService>().As<IMyService>();
var container = builder.Build();

好的,现在你已经注册了一个整数并返回服务实例,你可以使用它:

using(var scope = container.BeginLifetimeScope())
{
  var myService = scope.Resolve<IMyService>();
  var request = new SomeCallRequest { Number = 2 };
  var response = myService.SomeCall(request);

  // This will write "Credentials = Even" at the console
  // because we passed in an even number and the registration
  // lambda executed to properly set the credentials.
  Console.WriteLine("Credentials = {0}", response.CredentialsUsed);
}

轰!凭证已设置,无需转换为基类。

设计更改:

  • 凭据“设置”操作已从消耗代码中移出。如果您不想在使用代码中强制转换为基类,则无法选择还将凭证“设置”操作拉出来。这种逻辑可能是正确的;或者你可以把它放在一个在lambda中使用的单独的类中;或者你可以handle the OnActivated event并在那里做一点魔术(我没有表明 - 运动留给读者)。但是“将它们绑在一起”位必须在组件注册(lambda,事件处理程序等)中的某个位置,因为这是您仍然拥有具体类型的唯一点。
  • 在代理的生命周期内设置凭据。如果在使用代码中只有一个代理,在执行每个操作之前设置不同的凭据,则可能不太好。我无法从你的问题中判断出你是如何拥有它的,但是......如果是这样的话,你需要为每个电话使用不同的代理。这可能意味着你实际上想要在完成代理之后处理代理,so you'll need to look at using Owned<T>(这将使工厂Func<int, Owned<T>>)或者如果服务是长寿的,你可能会遇到内存泄漏喜欢单身人士。

也可能有其他方法可以做到这一点。您可以创建自己的自定义工厂;你可以处理我提到的OnActivated事件;您可以使用Autofac.Extras.DynamicProxy2库创建一个动态代理,拦截对您的WCF服务的调用,并在允许调用继续之前设置凭据......我可能会集体讨论其他方式,但您明白了。 我在这里发布的是我是如何做到的,希望它至少会指明你的方向,以帮助你到达你需要去的地方。

答案 1 :(得分:0)

我们最终采取的方法是将 ISomeOtherService 投射到 ClientBase

这可以避免引用代理类型。然后在我们的单元测试中,我们可以像这样设置模拟

var client = new Mock<ClientBase<ISomeOtherService>>().As<ISomeOtherService>();

因此可以将其转换为ClientBase,但仍设置为ISomeOtherService