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

How can I decouple my logging interface from PerformContext? #90

Open
nicholasyin opened this issue Oct 23, 2019 · 5 comments
Open

How can I decouple my logging interface from PerformContext? #90

nicholasyin opened this issue Oct 23, 2019 · 5 comments

Comments

@nicholasyin
Copy link

Can I write logs to dashboard without going through the PerformContext, which is quite obscure to resolve? Can I just call Hangfire.Console code to write logs?

@danielmeza
Copy link

Related to #58

@pieceofsummer
Copy link
Owner

You can create a custom JobActivator implementation and inject whatever you want in a job constructor.

For example, you can create the ILogger implementation that wraps PerformContext instance and forwards console writes, so you’re not referencing any Hangfire-specific classes directly. Of course, you may lose some features (e.g. progress bars) if using the standard logger interface.

Here’s an example of a custom JobActivator: #80 (comment). It injects PerformContext, but you can extend it to inject any other types as well.

@JonnyBooker
Copy link

I'm just looking to implement this at the moment. Can you please elaborate a little bit? I'm a bit confused how exactly how the custom JobActivator is then used to inject the context?

I have implemented the outlined JobActivator from your comment and then done the following implementation so far:

//My Job
public class JobProcessor
{
    private readonly PerformContext _performContext;

    public JobProcessor(PerformContext performContext)
    {
        _performContext = performContext;
    }

    public void Do()
    {
        _performContext.WriteLine("Log Value 1");
        //...Logic
        _performContext.WriteLine("Log Value 2");
    }
}

//Controller
public class HomeController : Controller
{
    private readonly IBackgroundJobClient _backgroundJobClient;
    private readonly JobProcessor _jobProcessor;

    public HomeController(IBackgroundJobClient backgroundJobClient, JobProcessor jobProcessor)
    {
        _backgroundJobClient = backgroundJobClient;
        _jobProcessor = jobProcessor;
    }

    public IActionResult Index()
    {
        _backgroundJobClient.Enqueue(() => _jobProcessor.Do());
        return View();
    }
}

I'm not sure what I then register my job as scope wise without a error presenting itself.

@pieceofsummer
Copy link
Owner

@JonnyBooker I'm not sure which error you're getting.

Also, is there a reason you're injecting JobProcessor in your controller's constructor instead of just using _backgroundJobClient.Enqueue<JobProcessor>(x => x.Do())? It doesn't look like you're using it somewhere else.

Usually there's no reason to register your job classes as scoped. You may even not register them at all, so they're always instantiated instead of being resolved from container.

@JonnyBooker
Copy link

The injection of the JobProcessor was just as an example but in hindsight, that isn't the ideal implementation.

Following your suggestion using Enqueue<JobProcessor> does work, however is it possible to use an interface in this Enqueue? Using an interface seems to cause the following exception:

System.AggregateException
  Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Test.Startup+IJobProcessor Lifetime: Transient ImplementationType: Test.Startup+JobProcessor': Unable to resolve service for type 'Hangfire.Server.PerformContext' while attempting to activate 'Test.Startup+JobProcessor'.)
  Source=Microsoft.Extensions.DependencyInjection
  StackTrace:
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
   at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
   at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
   at Microsoft.Extensions.Hosting.HostBuilder.Build()
   at Test.Program.Main(String[] args) in C:\src\Test\Program.cs:line 10

  This exception was originally thrown at this call stack:
	Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(System.Type, System.Type, Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteChain, System.Reflection.ParameterInfo[], bool)
	Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Microsoft.Extensions.DependencyInjection.ServiceLookup.ResultCache, System.Type, System.Type, Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteChain)
	Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Microsoft.Extensions.DependencyInjection.ServiceDescriptor, System.Type, Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteChain, int)
	Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Microsoft.Extensions.DependencyInjection.ServiceDescriptor, Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteChain)
	Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.ValidateService(Microsoft.Extensions.DependencyInjection.ServiceDescriptor)

Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: Test.Startup+IJobProcessor Lifetime: Transient ImplementationType: Test.Startup+JobProcessor': Unable to resolve service for type 'Hangfire.Server.PerformContext' while attempting to activate 'Test.Startup+JobProcessor'.

Inner Exception 2:
InvalidOperationException: Unable to resolve service for type 'Hangfire.Server.PerformContext' while attempting to activate 'Test.Startup+JobProcessor'.

The reason for wanting to use an interface is being able the usage of separate Hangfire servers and clients via a shared assembly interface to split running of the jobs into a separate process.

In its simplest form, this code below will yield the exception above all within one application.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        services.AddSingleton<JobActivator, InjectContextJobActivator>();
        services.AddTransient<IJobProcessor, JobProcessor>();

        services.AddHangfire(configuration => configuration
            .UseSimpleAssemblyNameTypeSerializer()
            .UseRecommendedSerializerSettings()
            .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
            .UseConsole()
            .UseMemoryStorage()
        );

        services.AddHangfireServer();
    }

    public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobClient)
    {
        app.UseRouting();
        app.UseHangfireDashboard();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });

        backgroundJobClient.Enqueue<IJobProcessor>(x => x.Output("Sample Output"));
    }
    
    //Processor
    public interface IJobProcessor
    {
        void Output(string text);
    }

    public class JobProcessor : IJobProcessor
    {
        private readonly PerformContext _performContext;

        public JobProcessor(PerformContext performContext)
        {
            _performContext = performContext;
        }

        public void Output(string text)
        {
            _performContext.WriteLine(text);
            Console.WriteLine(text);
        }
    }
}

Not registering the job and using the concrete implementation will work as described.

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

No branches or pull requests

4 participants