0

我正在编写 WCF 服务,并且正在为 DI 使用 AutoFac WCF 集成。我有一个稍微奇怪的情况,我有另一个需要凭据的服务的代理。凭据将根据传入的一些参数而改变,因此我不能只在设置容器并完成它时设置值。

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

有什么想法吗?

4

2 回答 2

2

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

首先,让我们定义场景中的其余类,以便您可以看到它们组合在一起。

我们有ISomeOtherService界面,我稍微修改了一下,这样您就可以实际看到最后设置了哪些凭据。我让它返回一个字符串而不是一个void。我还有一个实现,SomeOtherService它有一个凭证获取/设置(这是你ClientCredentials在 WCF 中的)。这一切看起来像这样:

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 的东西:

  • 隐式关系——我们可以在 a 上引用Func<T>而不是 aT来获得“创建T实例的工厂”。
  • 使用注册代表的参数——我们可以获取一些输入并使用它来通知解析操作的输出。

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

的实现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。不需要真正的特殊工作......虽然注册有点复杂。

最后一部分是为您注册一个 lambda,ISomeOtherService而不是一个更简单的类型/接口映射。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 中可能是正确的;或者你可以把它放在一个单独的类中,在那个 lambda 中使用;或者你可以处理 OnActivated 事件并在那里做一点魔法(我没有展示 - 练习留给读者)。但是“将它捆绑在一起”位必须在组件注册(lambda、事件处理程序等)中的某个位置,因为这是您仍然拥有具体类型的唯一点。
  • 凭据是为代理的生命周期设置的。如果您在使用代码中只有一个代理,在执行每个操作之前设置不同的凭据,这可能不太好。我无法从您的问题中判断您是否拥有它,但是......如果是这种情况,您将需要为每个呼叫使用不同的代理。这可能意味着您实际上想要在完成代理后处理它,因此您需要查看 usingOwned<T>(这将使 factory Func<int, Owned<T>>),或者如果服务长期存在,您可能会遇到内存泄漏单身人士。

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

于 2013-04-08T16:44:21.930 回答
0

我们最终采用的方法是将ISomeOtherService 转换ClientBase

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

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

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

于 2013-04-16T16:18:30.100 回答