0

IFilterインターフェイスを実装する (フィルター ロジックを定義する)フィルター クラスがいくつかあります。コンストラクター注入を使用して、フィルターの実装ごとに、フィルター設定を定義するインターフェイスを渡したいと思います。次の例を検討してください。

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ただし、私の実装を ITimeSpanFilterSettingsstaticに登録することはできませんsettingsKey

すべての実装を解決し、実装のインスタンス化に使用する必要があるをIFilter指定することは可能ですか?settingsKeyITimeSpanFilterSettings

4

1 に答える 1

8

私には、いくつかの要因が関係しているように見えます。質問の誤解かもしれませんし、「略語」の質問かもしれませんので、ご容赦ください。

まず、オブジェクトTimeSpanFilterを取得する際の解決策について説明しましょう。ITimeSpanFilterSettings後で設定オブジェクトのパラメーター化について説明します。ここでは、フィルターに設定を取得する方法について説明します。

説明した設定があれば、すべての実装ISomethingFilterSettingsに対応するインターフェースがあると推測されます。IFilterあなたが持っている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を使用すると、ラムダ式を単なる具象型ではなく依存関係として登録できるため、次のようなことができます。

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>();

設定オブジェクトの入力パラメータが1つしかない場合は、このようなことが非常に便利です。複数のパラメーターがある場合は、ラムダでその着信コンテキストパラメーターを使用して、その場で解決を行うこともできます。

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

ただし、パラメータが多すぎて少し面倒になる可能性がある場合は、パラメータラムダを使用して依存関係を登録し、指定した1つのパラメータのみが手動で注入され、残りは自動的に実行されるようにすることもできます。

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.NETMVCなどを意味していると思います。そのようなリクエストレベルの値を置く場所の1つはにあり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です。データベース呼び出しを登録に直接入れることができますが、これらの多くを解決すると、いくつかの興味深いパフォーマンス上の課題が発生する可能性があります。どちらの場合も、ラムダは解決が発生するたびに実行されます。パラメーター値はキャッシュされません。オブジェクトをInstancePerDependencyライフタイム(デフォルト)以外のものに登録しない限り、Autofacは作成されたオブジェクトをキャッシュしません。これは、意図しないデータベース呼び出しが多数発生する可能性があることを意味します。(必要に応じて)そのキャッシュを理解することは、読者に任された演習です。

(最後の例ではInstancePerHttpRequest、スコープとして使用したことに注意してください。つまり、1つのWebリクエストに対してキャッシュを取得します。)

そして、デザインについての1つのこと:それは意見ですが、一般的に私は「パラメータ化された解像度」を避けようとします。つまり、「私は将軍IFilterが欲しいのですが、それはこの特定の状況に完全に合わせられる必要があります。」それはあなたがここに持っているもののように聞こえます。そのような状況では、のような一般的なベースレベルのインターフェイスを使用する必要があるかもしれませんがIFilter、自分のニーズに固有のインターフェイスも使用しようとします。

public interface ICustomSituationFilter : IFilter

次に、すべてを一般的なものにプッシュしようとするのではなく、これらのカスタムインターフェイスを依存関係として使用します。これにより、その「構成」の概念をコントローラーからより簡単に分離し(必ずしも着信依存関係を構成する必要はありません)、それを登録にプッシュすることができます-何かをポップしHttpContext.Itemsたり、共有場所に入れたりする必要はありません設定を知っているのは実際の依存関係の登録だけだからです。可能であれば、「ビューの選択」と「使用する構成設定」の間の結びつきを断ち切るように設計を変更することを検討することをお勧めします。それはあなたの人生を楽にするでしょう。

関連するAutofacwikiページ:

于 2012-11-06T01:13:08.517 に答える