Statiq.Framework icon indicating copy to clipboard operation
Statiq.Framework copied to clipboard

Consider "injected" services, settings, etc. in modules

Open daveaglick opened this issue 3 years ago • 5 comments

Thinking about something like LazyService<TService>(bool required). Could also introduce a few others like LazySetting<TType>(string key, TType defaultValue = default), etc. Essentially a series of lazy types that will do a gate check to make sure you're somewhere where a context is available, refresh the value if/when the context changes (to account for the same module instance being used in different pipelines), handle thread synchronization with the appropriate LazyThreadSafetyMode, and generally take care of the details.

That would get you (I'd probably implement an implicit operator to TService):

protected LazyService<AzureService> Azure { get; } => new LazyService<AzureService>(true);

Still debating whether I'd want to call this LazyService, ContextService, or something else.

daveaglick avatar Sep 30 '20 14:09 daveaglick

Been a minute since you created this issue, but I'm thinking I could use this for my little social image generator. Right now, every run I need to create a full ASPNET web app on each run when in preview mode.

Be nice if I could inject a singleton into the module with that webapp and save five or six seconds per run.

phil-scott-78 avatar Jul 13 '21 01:07 phil-scott-78

You can do that today (at least I think you can do what you're looking for). This issue was mainly for some sugar that'll abstract the service container out and make it seem like properties are injected since we can't actually inject dependencies into a module given how/when they're instantiated.

But if you don't mind talking directly to the container, you can register a service like this:

await Bootstrapper
    .Factory
    .CreateWeb(args)
    .ConfigureServices(services => services.AddSingleton<IFoo, Foo>())
    // ...

And then access it from within the module like this:

public class MyModule : Module
{
    protected override Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context)
    {
        IFoo foo = context.Services.GetRequiredService<IFoo>();
        // ...
    }
}

Did I get the gist of what you'd want to do?

daveaglick avatar Jul 13 '21 01:07 daveaglick

I guess the service container and how they flow through the execution context, lifetime, etc. is another thing I need to document 😂😭

daveaglick avatar Jul 13 '21 01:07 daveaglick

I figure if I procrastinate on documentation long enough that Codepilot will write it for us.

But in the meantime I'll give the ConfigureServices a go!

phil-scott-78 avatar Jul 13 '21 01:07 phil-scott-78

got it working, but alas it seems like some weirdness with disposing of the injected service isn't working right. And with this hosting ASPNET and all that magic it does not end well when that goes sideways. image

The good news is that while working on this I moved the processing to a ParallelModule and this brought speed up to the same of the slowest other pipeline (archive) so as of right now making this faster isn't going to make much of a difference anyway. I'm sure I'll revisit later, just wanted to follow up with confirmation that I did try your advice :-)

phil-scott-78 avatar Jul 15 '21 02:07 phil-scott-78