2013-04-05 51 views
0

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

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

你可以做到这一点,但它不是简单的,你必须稍微改变设计,这样的逻辑“决定,并设置凭据”被拉出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); 
} 

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

设计变化:

  • 凭证的 “设置” 操作得到移出消费代码。如果您不想在消费代码中强制转换到基类,那么您将无法选择,而只需将凭据“set”操作取出。这个逻辑在lambda中是正确的;或者你可以把它放在一个单独的类中,在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服务的调用,并在允许调用继续之前设置凭据...我可能会集体讨论其他方式,但您明白了。 我在这里发布的是我如何做到这一点,希望它至少能指引你朝着一个方向去帮助你到达需要去的地方。

+0

的Bleh,这是我很担心。这不是我最终采取的方法,但我很欣赏这些信息,因为它可能适用于其他人。 – Nick 2013-04-16 16:15:15

0

我们最终采取的办法是投ISomeOtherServiceClientBase

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

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

所以可以铸造到ClientBase,但仍然设置为ISomeOtherService

+0

这不符合你所要求的吗? “不必投射到已知类型或ChannelBase?” – 2013-04-16 20:47:12

+0

稍微,我想避免演员,因为这将打破我们的单元测试中的嘲笑,并引入对具体类的依赖。因为您可以在ClientBase 上设置凭据,所以您仍然只能引用已知的接口,并且更容易切换模拟。 – Nick 2013-04-17 20:04:16