2013-04-05 54 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