2012-11-04 71 views
0

我有許多實現IFilter接口(定義過濾器邏輯)的過濾器類。使用構造函數注入,對於每個過濾器實現,我想傳遞一個定義過濾器設置的接口。請看下面的例子:AutoFac解析參數

interface IFilter 
{ 
    void Filter(DataSource dataSource); 
} 

interface ITimeSpanFilterSettings 
{ 
    DateTime From {get; set; } 
    DateTime To {get; set; } 
} 

public class TimeSpanFilter : IFilter 
{ 
    private ITimeSpanFilterSettings settings; 

    public TimeSpanFilter(ITimeSpanFilterSettings settings) 
    { 
    this.settings = settings; 
    } 
} 

我的ITimeSpanFilterSettings具體實施卻需要settingsKey從數據庫retreive的設置。不過,我無法將我的 ITimeSpanFilterSettings的實施註冊到靜態settingsKey

是否可以解析所有IFilter實現並指定應該用於實例化ITimeSpanFilterSettings實現的settingsKey

+0

在很大程度上取決於其中'settingsKey'的來源。你能再描述一下你的情況嗎? –

回答

8

在我看來,有幾個因素在起作用。這可能是我對這個問題的誤解,也可能是某個問題的「縮寫」,請耐心等待。

首先我們來談談獲取ITimeSpanFilterSettings對象的TimeSpanFilter的分辨率。稍後我們將討論設置對象的參數化,現在讓我們來談談將設置獲取到過濾器

如果您具有所述的設置,我推斷您有一個對應於每個IFilter實現的ISomethingFilterSettings接口。您有TimeSpanFilterITimeSpanFilterSettings;如果你有一個DateTimeFilter你會有一個IDateTimeFilterSettings

鑑於此,沒有什麼特別的,你需要做的。使用ContainerBuilder註冊您的各種類型,併發生魔法。

var builder = new ContainerBuilder(); 
builder.RegisterType<TimeSpanFilter>().As<IFilter>(); 
builder.RegisterType<TimeSpanFilterSettings>().As<ITimeSpanFilterSettings>(); 
var container = builder.Build(); 
// When you resolve, the TimeSpanFilterSettings class gets instantiated 
// and injected into the constructor of the TimeSpanFilter. 
var filter = container.Resolve<IFilter>(); 

即使您有多個過濾器,Autofac也會將所有適當的接口連同構造函數參數一起排列。你不必做任何事情。

var builder = new ContainerBuilder(); 
// Look, Ma! Two filters and settings! :) 
builder.RegisterType<TimeSpanFilter>().As<IFilter>(); 
builder.RegisterType<TimeSpanFilterSettings>().As<ITimeSpanFilterSettings>(); 
builder.RegisterType<DateTimeFilter>().As<IFilter>(); 
builder.RegisterType<DateTimeFilterSettings>().As<IDateTimeFilterSettings>(); 
var container = builder.Build(); 
// You can resolve collections and get all of the registered filters. 
var filterEnumerable = container.Resolve<IEnumerable<IFilter>>(); 

現在讓我們來談談過濾器設置對象的參數化。這聽起來像你需要得到一些配置,所以讓我們說(爲了方便),配置來自AppSettings

隨着Autofac可以寄存器lambda表達式作爲依賴,而不僅僅是一個具體類型,所以你可以做這樣的事情:

var builder = new ContainerBuilder(); 
builder.RegisterType<TimeSpanFilter>().As<IFilter>(); 
builder.Register(
    ctx => 
    { 
    var config = ConfigurationSettings.AppSettings["my-key"]; 
    return new TimeSpanFilterSettings(config); 
    }).As<ITimeSpanFilterSettings>(); 
var container = builder.Build(); 
// When you resolve, the TimeSpanFilterSettings class gets instantiated 
// and injected into the constructor of the TimeSpanFilter. 
var filter = container.Resolve<IFilter>(); 

這種事情是非常方便的,如果你只是有您的設置對象的一個​​傳入參數。如果有多個參數,你可以使用傳入的上下文參數在lambda做一些解析上的蒼蠅,太:

builder.Register(
    ctx => 
    { 
    var config = ConfigurationManager.AppSettings["my-key"]; 
    var other = ctx.Resolve<OtherDependency>(); 
    return new TimeSpanFilterSettings(config, other); 
    }).As<ITimeSpanFilterSettings>(); 

但是,如果你有太多的參數,可以讓有些凌亂,所以你還可以使用參數拉姆達這樣您指定將手動注入只有一個參數,其餘的將被自動完成註冊的依賴:

var builder = new ContainerBuilder(); 
builder.RegisterType<TimeSpanFilterSettings>().WithParameter(
    // Parameter selector determines which parameter this 
    // thing is referring to - here the constructor parameter 
    // is called "config" and has to be a System.String. 
    (pinfo, ctx) => 
    { 
    return 
     pinfo.Name == "config" && 
     pinfo.ParameterType == typeof(string); 
    }, 

    // Value provider gets the value that should be injected 
    // and returns it. 
    (pinfo, ctx) => 
    { 
    return ConfigurationManager.AppSettings["my-key"]; 
    }); 

任何一項都將正常工作,它只是取決於你怎麼想去做吧。

其他複雜性:您在此評論中提到您根據用戶選擇的視圖獲取您的設置信息。您需要更新系統以將該設置密鑰放置在Autofac可以訪問的地方。

鑑於您提到了一個「視圖」,我假設您的意思是ASP.NET MVC或類似的東西。一個放置請求級值的地方是HttpContext.Items。這可能需要在系統中進行一些重新設計。例如,如果依賴項必須作爲控制器上的構造函數/屬性來實現,那麼在控制器實例化之前,您可能需要一些機制來填充HttpContext中的值。也許在你的控制器上有一個屬性,也許有一個IHttpModule位於管道中,並具有URL到設置的映射,也許它是其他內容。這不是我們在這個問題上可以處理的事情(否則我們只是在這裏寫下整個產品,對吧?而且我不是那麼想的......)。

一旦它在中央位置像HttpContext.Items,你可以將其寫入拉姆達登記:

// Need to be able to resolve HttpContext, so... 
builder.RegisterModule<AutofacWebTypesModule>(); 
// Then resolve HttpContext in your registration: 
builder.Register(
    ctx => 
    { 
    var httpCtx = ctx.Resolve<HttpContextBase>(); 
    var configKey = httpCtx.Items["settings-config-key"]; 
    var config = ConfigurationManager.AppSettings[configKey]; 
    return new TimeSpanFilterSettings(config); 
    }).As<ITimeSpanFilterSettings>() 
    .InstancePerHttpRequest(); 

做出一項屬性或IHttpModule將呼叫從自己的控制器內DependencyResolver.Current.GetService<IFilter>()您設置HttpContext.Items後的替代值。儘管我不喜歡服務地點,許多人認爲它是一種「反模式」,所以如果可以的話,請儘量避免。

的注意事項有關緩存:這聽起來像您的配置價值實際上是從什麼地方數據庫未來 - 一個更昂貴的通話相比,閱讀AppSettings。您可以將數據庫調用正確地註冊到註冊中,但是如果解決了其中一些問題,則可能會遇到一些有趣的性能問題。這兩種情況下的lambda表達式都會在每次分辨率發生時執行 - 參數值不會緩存給您,除非您使用生命週期(默認值)以外的其他值註冊對象,否則Autofac不緩存創建的對象。這可能意味着很多意外的數據庫調用。根據需要確定緩存是一個留給讀者的練習。

(注意在最後一個例子我用InstancePerHttpRequest的範圍 - 這意味着你會得到緩存一個Web請求。)

還有一件事對設計:這是一個意見,但一般來說,我嘗試避免「參數化分辨率」,就像它一樣。也就是說,「我想要一個普通的IFilter,但它需要完全適應這種特定情況。」這聽起來就是你在這裏所擁有的。在這些情況下,我發現儘管我可能需要使用像IFilter這樣的通用基本級接口,但我還會嘗試使用特定於我的需求的接口。

public interface ICustomSituationFilter : IFilter 

我會再使用這些自定義接口作爲我的依賴,而不是試圖把每件事做是通用的。它允許我更容易地將控制器中的「配置」概念分開(不應該配置傳入的依賴關係)並將其推送到我的註冊中 - 我不需要將事情彈入HttpContext.Items或任何共享類型位置,因爲唯一知道設置的地方是實際的依賴關係註冊。如果可以的話,您可能需要考慮更改設計以打破「選擇視圖」和「使用哪些配置設置」之間的關係。它會讓你的生活更輕鬆。

有關Autofac的wiki頁面:

+0

感謝您深入解釋註冊lambda作爲'RegisterType <>()'的依賴關係。然而,問題是我不把設置密鑰存儲在AppSettings或其他可以在此lambda表達式內部解決的其他內容中。問題是這個settingKey在運行時被解析,這取決於用戶正在訪問哪個視圖。現在我結束了創建一個新的Lifetimecope,在其中爲我的設置添加新的註冊(例如,我使用具體的設置鍵來解析'ITimeSpanSettings'。在'BeginLifeTimeScope'內部,我添加了以下內容: – Toni

+0

'builder.Register(context => new TimeSpanSettings(「NormallyMySettingsKeyIsAVariable」)。As(ITimeSpanSettings);'也許有一個更優雅的解決方案,它不需要依賴於'ILifetimeScope'。 – Toni

+0

這是你應該把問題放在你的問題上。如果你不分享他們,你係統的複雜性是什麼 –