具有更改客户端凭据的AutoFac WCF代理

具有更改客户端凭据的AutoFac WCF代理,wcf,autofac,Wcf,Autofac,我正在编写一个WCF服务,并将AutoFac WCF集成用于DI。我有一个稍微奇怪的情况,我有一个代理到另一个需要凭据的服务。凭据将根据传入的某些参数进行更改,因此我不能在设置容器并完成操作时仅设置值 public class MyService : IMyService { private ISomeOtherService _client; public MyService(ISomeOtherService client) { _client = c

我正在编写一个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的最佳方法是什么。我试图避免这种情况,因为在我的单元测试中,我模拟了代理接口,所以将其转换回其中一种类型将失败


有什么想法吗?

您可以这样做,但这并不简单,,您必须稍微更改设计,以便将“决定并设置凭据”的逻辑从
MyService
类中拉出

首先,让我们定义场景中的其余类,这样您就可以看到所有这些类组合在一起

我们有
isomootherservice
接口,我稍微修改了一下,这样您就可以看到最后设置了哪些凭据。我让它返回一个字符串,而不是一个空格。我还有一个
SomeOtherService
的实现,它有一个凭证get/set(在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
操作的相关请求/响应对象。(在这个问题中,您有一些数据,但这是相同的想法,我只是使用了一个稍微不同的命名约定来帮助我保持输入与输出的一致性。)

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

这是它开始变得更复杂的地方。首先,您确实需要熟悉Autofac的两个方面:

  • -我们可以参考
    Func
    而不是
    T
    ,以获得“创建
    T
    实例的工厂”
  • -我们可以获取一些输入,并使用这些输入来通知解析操作的输出
我们将在这里使用这两个概念

MyService的实现被切换到一个工厂,该工厂将接收一个
int
并返回一个
IsoOtherService
的实例。当您想要获取对另一个服务的引用时,您可以执行该函数并传入将确定客户端凭据的编号

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 };
  }
}
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);
}
使用(var scope=container.BeginLifetimeScope())
{
var myService=scope.Resolve();
var request=newsomecallrequest{Number=2};
var response=myService.SomeCall(请求);
//这将在控制台上写入“凭证=偶数”
//因为我们通过了一个偶数和注册
//执行lambda以正确设置凭据。
WriteLine(“Credentials={0}”,response.CredentialsUsed);
}
轰!在不必强制转换到基类的情况下设置了凭据

设计变更:

  • 凭证“设置”操作已从消费代码中移出。如果您不想强制转换到消费代码中的基类,您将别无选择,只能将凭证“设置”操作移出。这种逻辑在lambda中可能是正确的;或者你可以把它放在一个单独的类中,在lambda中使用;或者你也可以在那里施展一点魔法(我没有展示——练习留给读者)。但是“将所有内容绑定在一起”位必须位于组件注册中的某个位置(lambda、事件处理程序等),因为这是您仍然拥有具体类型的唯一点
  • 凭据是为代理的生存期设置的。如果您的消费代码中只有一个代理,并且在执行每个操作之前设置了不同的凭据,则可能不太好。我不能从你的问题判断你是不是这样的,但是。。。如果是这种情况,您将需要为每个调用使用不同的代理。这可能意味着您实际上希望在处理完代理后处理它(这将使工厂
    Func
    ),或者如果服务像单例一样长期存在,您可能会遇到内存泄漏

可能还有其他方法可以做到这一点。您可以创建自己的定制工厂;您可以处理我提到的
OnActivated
事件;您可以使用
Autofac.Extras.dynamicProxy 2
库创建一个动态代理,该代理在允许调用继续之前拦截对WCF服务的调用并设置凭据。。。我可能会想出其他办法,但你明白了。我在这里发布的是我将如何做到这一点,希望它至少能为您指明一个方向,帮助您达到您需要的目标。

我们最终采取的方法是将IsoOtherService转换为ClientBase

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

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

所以它可以转换到ClientBase,但仍然设置为ISomeOtherService

Bleh,这是我担心的。这不是我最终采取的方法,但我很感激这些信息,因为它可能会对其他人有用。这不正是你所要求的吗?“不必强制转换到已知类型或通道库?”
var client = new Mock<ClientBase<ISomeOtherService>>().As<ISomeOtherService>();