简体   繁体   中英

Simple Injector change registration at runtime

I'm using Simple Injector with an ASP.NET Web API project, and I want to use different implementations for one interface, depending on the version of REST endpoint used. For example, if v1 of an endpoint is used, IPaymentData should be instantiated with a class called PaymentData , but if v2 is used IPaymentData should be implemented by a class called PaymentDataNew . This is turning out to be problematic!

I don't want to use a composite class like a part of Simple Injector documentation suggests since that means that my code needs to be aware of and take into account the injection framework I'm using (bad).

I noticed in the latest version (3.0) that something called "Context based injection" is a feature. Using the container.RegisterConditional function it should be possible to run a delegate each time a type is resolved.

container.RegisterConditional(typeof(IPaymentData),
    c => ((HttpContext.Current.Items["version"] as string ?? "1") == "2") 
        ? typeof(PaymentDataNew) 
        : typeof(PaymentData),
    Lifestyle.Scoped,
    c => true);

This doesn't seem to work, though, since even though the lifetime is scoped, and the default lifestyle is WebApiRequestLifestyle the delegate which returns the implementation depending on version is only called for the first request that comes in. Subsequent requests skip this (they seem to be using a cached implementation).

Is there something I'm missing? How can I make sure the delegate is called each time a request comes in??

since that means that my code needs to be aware of and take into account the injection framework I'm using

Not exactly. The composite pattern is a well known and commonly used pattern, so defining it in your code doesn't make your code dependent on the DI library. And if you don't want to define the composite in your application code, you can always specify it inside your Composition Root . The Composition Root already has a very strong dependency on the DI library, so any library-specific stuff should be placed there.

Using the container.RegisterConditional function it should be possible to run a delegate each time a type is resolved.

Simple Injector v3's RegisterConditional method allows applying registrations conditionally based on static information. This means that the supplied predicate is only called a finite amount of time (usually once per consuming component). The methods (IntelliSense/XML) documentation states :

The predicate will only be evaluated a finite number of times; the predicate is unsuited for making decisions based on runtime conditions.

It will not be called every time you resolve it. The reason for this is two-fold:

  1. This optimizes performance, because the decision made in the predicate can be burned into the compiled expression, but more importantly,
  2. This prevents you from making runtime decisions during the building of the object graph.

The shape of your object graph should not be dependent on runtime parameters (such as the version in your HttpContext ). Doing this complicates the object graph and makes it very hard to verify and diagnose the object graph for correctness.

The solution I would advice is to implement a proxy implementation for IPaymentData that allows making the switch at runtime. This is not a Simple Injector specific implementation; you should strive to have simple, static, and verifiable object graphs, independently of the DI library you use.

This is how such proxy might look like:

public sealed class HttpRequestVersionSelectorPaymentData : IPaymentData
{
    private readonly PaymentData paymentOld;
    private readonly PaymentDataNew paymentNew;
    public VersionSelectorPaymentData(PaymentData old, PaymentDataNew paymentNew) { ... }

    private bool New => HttpContext.Current.Items["version"] as string ?? "1") == "2";
    private IPaymentData PaymentData => this.New ? paymentNew : paymentOld;

    // IPaymentData method(s)
    public Payment GetData(int id) => this.PaymentData.GetData(id);
}

Although it is absolutely possible to make runtime decisions during the building of the object graph (by registering a delegate), I strongly advice against doing this, for the reasons expressed above.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM