Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Dependency Injection from Scheduler #3602

Closed
bdukes opened this issue Mar 2, 2020 · 4 comments · Fixed by #3696
Closed

Support Dependency Injection from Scheduler #3602

bdukes opened this issue Mar 2, 2020 · 4 comments · Fixed by #3696

Comments

@bdukes
Copy link
Contributor

bdukes commented Mar 2, 2020

Description of problem

There is no way to access services in the Dependency Injection container from a scheduler client.

Description of solution

When DNN creates a scheduler client, it needs to use the DI container.

Additional context

Based in a discussion at https://dnncommunity.org/forums/aft/890 and via https://dnntag.slack.com/

@valadas
Copy link
Contributor

valadas commented Mar 3, 2020

I am not an expert at DI and I did not build code samples to test this so just asking questions :)

I was under the impression that we use Microsoft one unaltered so that something like:

public class MyScheduler : SchedulerClient
{
    protected IPortalController PortalController { get; }

    public MyScheduler(IPortalController portalController)
    {
        this.PortalController = portalController;
    }
}

would just work out of the box... Right?

Now for the forum post at hand, the major issue (which is not new) is that the scheduler has no HTTP Context and one needs to use another way to obtain the portalId (configuration, settings, etc.) to get a PortalInfo object manually from code.

@bdukes
Copy link
Contributor Author

bdukes commented Mar 3, 2020

DNN currently supports constructor injection in 1️⃣ Web API controllers, 2️⃣ MVC controllers, 3️⃣ Razor models, and it supports "service location" in WebForms via PortalModuleBase.DependencyProvider.

Anywhere else that DNN is creating new instances of classes, dependency injection (neither constructor injection nor service location) is not yet supported. In this particular case, creating a SchedulerClient involves explicitly calling the constructor which takes a single ScheduleHistoryItem

private SchedulerClient GetSchedulerClient(string strProcess, ScheduleHistoryItem objScheduleHistoryItem)
{
//This is a method to encapsulate returning
//an object whose class inherits SchedulerClient.
Type t = BuildManager.GetType(strProcess, true, true);
var param = new ScheduleHistoryItem[1];
param[0] = objScheduleHistoryItem;
var types = new Type[1];
//Get the constructor for the Class
types[0] = typeof (ScheduleHistoryItem);
ConstructorInfo objConstructor;
objConstructor = t.GetConstructor(types);
//Return an instance of the class as an object
return (SchedulerClient) objConstructor.Invoke(param);
}

@mitchelsellers
Copy link
Contributor

This one could be a bit interesting since we have an architecture requirement with the first parameter being the ScheduleHistoryItem. It is possible that we might have to do something different for this to support DI.

@dimarobert
Copy link
Contributor

dimarobert commented Mar 5, 2020

You could use ActivatorUtilities.CreateInstance(IServiceProvider, Type, Object[]), ActivatorUtilities.CreateInstance<T>(IServiceProvider, Object[]) or even ActivatorUtilities.CreateFactory(Type, Type[]) for repeated resolutions - it returns a reusable delegate object ObjectFactory(IServiceProvider serviceProvider, object[] arguments). The Object[] parameter is for additional constructor parameters that cannot be resolved from the ServiceProvider. In your case the ScheduleHistoryItem.

Another note, to preserve the lifetime of scoped services resolved in this manner (see details about the pitfalls of MS DI) you should open a scope from the Globals.DependencyProvider for each execution of the scheduler and resolve from it. You will also need to dispose it accordingly to avoid memory leaks. The safest way would be to wrap the creation and execution of a job in a using (var scope = sp.CreateScope()) block.
A good example of a scoped service would be the DbContext of EntityFramework.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants