Architecture questions

I’ve a solution with 3 projects:

  1. Service Class Library
  2. WebApi (is also the JobServer)
  3. Worker Client (Console Application)

Service class library has 2 classes:
IService.cs and Service.cs

public interface IService
{
    [Queue("my_queue")]
    [AutomaticRetry(Attempts = 0, OnAttemptsExceeded = AttemptsExceededAction.Fail)]
    string Test(string input);
}

and

public class Service : IService, IDisposable
{
    private readonly string _worker;

    public Service(string worker)
    {
        _worker = worker;
    }

    public string Test(string input)
    {
        var message = string.Format("Worker: {0}, Task: {1}", _worker, input);
        Console.WriteLine(message);
        return message;
    }
}

WebApi’s Startup is like:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    var service = new Service("WebApi");
    services.AddInstance<IService>(service);
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseMvc();
    var hostName = Configuration["Data:Hangfire.RabbitMQ:HostName"];
    var dashboardPath = Configuration["Data:Hangfire.RabbitMQ:DashboardPath"];

    var storage = new SqlServerStorage(Configuration["Data:Hangfire.Sql:ConnectionString"], new SqlServerStorageOptions { QueuePollInterval = TimeSpan.FromSeconds(1) });
    storage.UseRabbitMq(conf => conf.HostName = hostName, new string[] { "my_queue" });
    JobStorage.Current = storage;

    var options = new BackgroundJobServerOptions();
    options.ServerName = "WebApi";
    options.Queues = new string[] { "my_queue" };

    app.UseHangfireDashboard(dashboardPath);
    app.UseHangfireServer(options, storage);
}

and I have a simple controller which basically creates 1000 jobs everytime I refresh the page:

[HttpGet]
public string Get()
{
    for (var i = 1; i <= 1000; i++)
    {
        BackgroundJob.Enqueue<IService>(s => s.Test(i.ToString()));
    }

    return "1000 jobs enqueued";
}

and lastly; the worker client (Console application) is like:

static void Main(string[] args)
{
    // Read Settings from App.config
    var hostName = ConfigurationManager.AppSettings["Hangfire.RabbitMQ:HostName"];

    var storage = new SqlServerStorage("Hangfire.Sql", new SqlServerStorageOptions { QueuePollInterval = TimeSpan.FromSeconds(1) });
    storage.UseRabbitMq(conf => conf.HostName = hostName, new string[] { "my_queue" });
    JobStorage.Current = storage;

    var options = new BackgroundJobServerOptions();
    options.ServerName = string.Format("Worker.{0}", args.Length > 0 ? args[0] : "Default");
    options.Queues = new string[] { "my_queue" };

    IService service = new Service(options.ServerName);
            
    using (var server = new BackgroundJobServer(options, storage))
    {
        Console.WriteLine(string.Format("{0} started. Press any key to exit...", options.ServerName));

        Console.ReadKey();
    }

    Environment.Exit(0);
}

Eventually, I run the web application and goto the hangfire subfolder to check the queue progress, and its full of following error;

System.MissingMethodException

Cannot create an instance of an interface.

System.MissingMethodException: Cannot create an instance of an interface.
   at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
   at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(Type type, Boolean nonPublic)
   at System.Activator.CreateInstance(Type type)
   at Hangfire.JobActivator.ActivateJob(Type jobType)
   at Hangfire.Common.Job.Activate(JobActivator activator)

I am new to hangfire so may be missing something very trivial… Any thoughts?

By default, Hangfire uses Activator.CreateInstance method to create background job types on run. Since you pass IService as a background job type, Hangfire invokes Activator.CreateInstance(typeof(IService)) and fails with your exception, as it does not know anything on a relationship between IService and Service types. Please use IoC container support for Hangfire as written in the docs.

Thanks for the advice, I got it done with Autofac, but I realized something weird. The BackgroundJobServer on the WebApi project is fetching jobs at a real slow rate. But when I run a console application for instance, its extremely fast. Is there a specific reason for this?

Nope, I don’t have any specific reason - the code is the same for each application type. Environment is different, but I don’t have any thoughts (maybe connection pool is too small?). Profiling will help you.