Inversifyjs: Allow access to currentRequest from onActivation & toDynamicValue

Created on 6 Jun 2017  路  15Comments  路  Source: inversify/InversifyJS

From SO: https://stackoverflow.com/questions/44312091/inversify-contextual-injection-with-class-name

We need a change in the APIs of onActivation so the users can access the currentRequest:

import "reflect-metadata";
import { injectable, inject, Container, interfaces } from "inversify";

@injectable()
class Logger {
    public className: string;
    public log(txt: string) {
        console.log(`This ${this.className} says => ${txt}`);
    }
}

@injectable()
class SomeClass {
    private logger: Logger;
    public constructor(
        @inject("Logger") logger: Logger
    ) {
        this.logger = logger;
    }
    public doSomething() {
        this.logger.log("I'm doing something!");
    }
}

const container = new Container();
container.bind<SomeClass>("SomeClass").to(SomeClass);

container.bind<Logger>("Logger").to(Logger).onActivation((context, currentRequest: interfaces.Request, loggerInstance) => {
    if (currentRequest.bindings[0].implementationType) {
        const constructorName = (currentRequest.bindings[0].implementationType as Function).name;
        loggerInstance.className = constructorName;
        return loggerInstance;
    } else {
        throw new Error();
    }
});

const someClass = container.get<SomeClass>("SomeClass");
someClass.doSomething(); // This SomeClass says => I'm doing something!

We need a change in the APIs of toDynamicValue so the users can access the currentRequest:

import "reflect-metadata";
import { injectable, inject, Container, interfaces, unmanaged } from "inversify";

@injectable()
class Logger {
    public className: string;
    public constructor(
        @unmanaged() className: string
    ) {
        this.className = className;
    }
    public log(txt: string) {
        console.log(`This ${this.className} says => ${txt}`);
    }
}

@injectable()
class SomeClass {
    private logger: Logger;
    public constructor(
        @inject("Logger") logger: Logger
    ) {
        this.logger = logger;
    }
    public doSomething() {
        this.logger.log("I'm doing something!");
    }
}

const container = new Container();
container.bind<SomeClass>("SomeClass").to(SomeClass);

container.bind<Logger>("Logger").toDynamicValue((context, currentRequest: interfaces.Request) => {
    const constructorName = (currentRequest.bindings[0].implementationType as Function).name;
    const logger = new Logger(constructorName);
    return logger;
});

const someClass = container.get<SomeClass>("SomeClass");
someClass.doSomething(); // This SomeClass says => I'm doing something!

Most helpful comment

Hi @guscastro @mtfranchetto @Aboisier boisier

This is now implemented by https://github.com/inversify/InversifyJS/pull/719 and available in [email protected] 馃帀

You can now access the current request via context.currentRequest. The following code snippet is an example:

@injectable()
class Logger {
    public named: string;
    public constructor(named: string) {
        this.named = named;
    }
}

const container = new Container();

const TYPE = {
    Logger: Symbol.for("Logger")
};

container.bind<Logger>(TYPE.Logger).toDynamicValue((context) => {
    const namedMetadata = context.currentRequest.target.getNamedTag();
    const named = namedMetadata ? namedMetadata.value : "default";
    return new Logger(named);
});

const logger1 = container.getNamed<Logger>(TYPE.Logger, "Name1");
const logger2 = container.getNamed<Logger>(TYPE.Logger, "Name2");

expect(logger1.named).to.eq("Name1");
expect(logger2.named).to.eq("Name2");

All 15 comments

Will it be possible to access the class name even when the code is minified? If not, would it be possible to use something like the @targetName decorator?

In the example:

const constructorName = (currentRequest.bindings[0].implementationType as Function).name;

If the code is minified, constructorName will be the minified name. If you are not comparing the constructorName value against a hard coded string, then it is OK, you won't need the @targetname decorator.

If you want to do some conditional you could always use the values instead of hard coded strings:

container.bind<Logger>("Logger").toDynamicValue((context, currentRequest: interfaces.Request) => {
    const constructorName = (currentRequest.bindings[0].implementationType as Function).name;
    const someClassName = (SomeClass as any).name; // "SomeClass" or minified name
    if (constructorName === someClassName) {
        // do something
    }
    const logger = new Logger(constructorName);
    return logger;
});

Ok, good to know! And if we wanted the non-minified name, would we use the @targetName decorator? I feel like this not how targetName was idiomatically designed, so please correct me if I'm wrong!

@injectable()
class SomeClass {
    private logger: Logger;
    public constructor(
        @inject("Logger") @targetName("SomeClass") logger: Logger
    ) {
        this.logger = logger;
    }
    public doSomething() {
        this.logger.log("I'm doing something!");
    }
}

[...]

container.bind<Logger>("Logger").to(Logger).onActivation((context, currentRequest: interfaces.Request, loggerInstance) => {
        const targetName = request.target.name;
        loggerInstance.className = targetName ;
        return loggerInstance;
});

You could do that but if you have two classes like in the example you have two requests with two targets:

  • SomeClass
  • Logger

In your example the annotation is applied to the target Logger so it should be @targetName("Logger") to access SomeClass you will need to use context.plan.rootRequest (because in this case the root request is SomeClass) after fixing this issue you will be able to use currentRequest.parentRequest instead but the name will be compressed sometimes. So I guess at that point we might need to add support for the @targetName decorator for classes. At the moment it only works in arguments.

Hi,

We're quite keen on this feature. Is this something you're working on/accepting PRs for?

Thanks!

Any update on this feature?

@guscastro Please feel free to make a PR

@AltekkeE @remojansen and other maintainers - I'm working on a PR to allow receiving the context into the binding methods mentioned. It looks simple enough.

I have two questions, though:
1) The change to onActivation is a breaking change in the way it's proposed. Are you comfortable proceeding with the breaking change? And would the breaking change affect how soon we are able to have this feature released?
2) Given interfaces.Request already has context, do we still require context to be passed on to onActivation (the alternative is e.g.: onActivation((currentRequest, loggerInstance))? The same applies to toDynamicValue, except that that API wouldn't have a breaking change otherwise.

Thanks

Hi @guscastro @mtfranchetto @Aboisier boisier

This is now implemented by https://github.com/inversify/InversifyJS/pull/719 and available in [email protected] 馃帀

You can now access the current request via context.currentRequest. The following code snippet is an example:

@injectable()
class Logger {
    public named: string;
    public constructor(named: string) {
        this.named = named;
    }
}

const container = new Container();

const TYPE = {
    Logger: Symbol.for("Logger")
};

container.bind<Logger>(TYPE.Logger).toDynamicValue((context) => {
    const namedMetadata = context.currentRequest.target.getNamedTag();
    const named = namedMetadata ? namedMetadata.value : "default";
    return new Logger(named);
});

const logger1 = container.getNamed<Logger>(TYPE.Logger, "Name1");
const logger2 = container.getNamed<Logger>(TYPE.Logger, "Name2");

expect(logger1.named).to.eq("Name1");
expect(logger2.named).to.eq("Name2");

@Aboisier

Will it be possible to access the class name even when the code is minified? If not, would it be possible to use something like the @targetname decorator?

You can access the class name via context.currentRequest.bindings[0].implementationType.name but the name will change if the class is minified. This can be solved if you compare agains the class though.

class Ninja {
}

// ...

container.bind<Warrior>(TYPE.Warrior)to(Ninja).inActivation((context) => {
    const currentClass = context.currentRequest.bindings[0].implementationType;
    if (currentClass) {
         if (currentClass.name === Ninja.name) {
            // ...
        }
    }

However, in this case, would be better to check for the constructor:

container.bind<Warrior>(TYPE.Warrior)to(Ninja).inActivation((context) => {
    const currentClass = context.currentRequest.bindings[0].implementationType;
    if (currentClass) {
         if (currentClass === Ninja) {
            // ...
        }
    }

I haven't try this but I think it should work.

For factories (auto-factory, factory, dynamic value and provider bindings) the implementationType will be null because the constructor is not the class (is the factory).

Awesome! Shout out to your responsiveness @remojansen and to the contributors who worked on this feature, it is much appreciated! 馃挴 馃

@remojansen is there a good way to access parent request? In my case context.currentRequest.parent it's just circular reference oO

context.currentRequest.bindings[0].implementationType.name points me just to logger symbol and thus is null. I tried: context.currentRequest.parentContext.plan.rootRequest.bindings[0].implementationType.name but this gets only root request obiously.

@krzkaczor by parent request do you mean root request? It should be accessible via context.plan.rootRequest.

I think @krzkaczor meant the same case as I also need, which is referencing the immediately injecting class. Use case: a Logger tagged with the name of the class it's injected into.
I was able to get the parent name with the following, which is a bit cumbersome...

context.currentRequest.parentRequest &&
          context.currentRequest.parentRequest.bindings.length &&
          context.currentRequest.parentRequest.bindings[0].implementationType &&
          (context.currentRequest.parentRequest.bindings[0].implementationType as any).name

@guscastro thanks! It works!

Was this page helpful?
0 / 5 - 0 ratings