我正在编写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的情况下,在代理上设置凭据的最佳方法是什么。我试图避免这种情况,因为在我的单元测试中,我嘲笑代理接口,因此将其转换回其中一种类型会失败。
有什么想法吗?
答案 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的东西:
Func<T>
而不是T
来获取“创建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
。不需要真正的特殊工作......虽然注册有点复杂。
最后一部分是为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);
}
轰!凭证已设置,无需转换为基类。
设计更改:
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