"ArgumentNullException: Value cannot be null. Parameter name: factory" when trying to AddOrUpdate a recurring task

recurring
logging
Tags: #<Tag:0x00007f499dcb81d8> #<Tag:0x00007f499dcc3718>

#1

Hi,

I’ve just started with Hangfire and I’m planning to use it for a bunch of things, but to start with I want to create a job that will run every day at a set time, and do some database maintenance. I am using .NET Core 2.0.

I’ve got this block in my ConfigureServices method in my Startup class:

    services.AddHangfire(configuration => configuration
        .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
        .UseSimpleAssemblyNameTypeSerializer()
        .UseRecommendedSerializerSettings()
        .UseSqlServerStorage(_configuration.GetConnectionString("ExtranetConnection"), new SqlServerStorageOptions
        {
            CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
            SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
            QueuePollInterval = TimeSpan.Zero,
            UseRecommendedIsolationLevel = true,
            UsePageLocksOnDequeue = true,
            DisableGlobalLocks = true
        })); 
    services.AddHangfireServer();

And I’ve got this in my Configure method in the Startup class:

        app.UseHangfireDashboard();

        app.UseHangfireServer();
       
        // Add MVC to the request execution pipeline.
        app.UseMvc();

        // Enable middleware to serve generated Swagger as a JSON endpoint.
        app.UseSwagger();

        // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
        app.UseSwaggerUI(c =>
        {
            c.RoutePrefix = "api-docs";
            c.InjectStylesheet("/swagger-ui/custom.css");
            c.SwaggerEndpoint("/swagger/v1/swagger.json", $"{_configuration["application.name"]} v1");
        });

        //RecurringJob.RemoveIfExists(nameof(DeleteExpiredUsersJob));
        RecurringJob.AddOrUpdate(nameof(DeleteExpiredUsersJob), ()=> DeleteExpiredUsersJob(userManager), Cron.Daily(14,55));

I have a further method called DeleteExpiredUsersJob which looks like this:

public void DeleteExpiredUsersJob(UserManager userManager)
    {
        var usersToDelete =
            userManager.Users.Where(x => x.LastLogin == null || x.LastLogin < (DateTime.Now - TimeSpan.FromDays(int.Parse(_configuration.GetSection("DeleteOldUsersJob")["InactiveDays"]))));

        foreach (var user in usersToDelete)
        {
            var result = userManager.DeleteAsync(user).Result;
            //if (result.Succeeded)
            //{
            //    logger.LogInformation($"Deleted user [{user.Id}]");
            //}
            //else
            //{
            //    logger.LogWarning($"Failed to delete user [{user.Id}]");
            //}
        }
    }

When I start the application, I get an ArgumentNullException on the line starting with RecurringJob. This is the stack trace:

System.ArgumentNullException: Value cannot be null.
Parameter name: factory
   at Microsoft.Extensions.Logging.Logger`1..ctor(ILoggerFactory factory)
   at lambda_method(Closure , Object[] )
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObjectUsingCreatorWithParameters(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, ObjectConstructor`1 creator, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ResolvePropertyAndCreatorValues(JsonObjectContract contract, JsonProperty containerProperty, JsonReader reader, Type objectType)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObjectUsingCreatorWithParameters(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, ObjectConstructor`1 creator, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
   at Hangfire.Common.SerializationHelper.Deserialize(String value, Type type, SerializationOption option)
   at Hangfire.Storage.InvocationData.DeserializeArgument(String argument, Type type)
--- End of stack trace from previous location where exception was thrown ---
   at Hangfire.Storage.InvocationData.DeserializeArgument(String argument, Type type)
   at Hangfire.Storage.InvocationData.DeserializeArguments(MethodInfo methodInfo, String[] arguments)
   at Hangfire.Storage.InvocationData.DeserializeJob()

From what I can see, the configuration is about as generic as it could possibly be. I can’t see what I’m doing wrong. It evidently relates to logging somehow, but I haven’t a clue why. I commented out any reference to logging in my method and removed the parameter for it, but I still get the same result. Am I missing a step somewhere?

Thanks

Adam


#2

Never mind, I figured it out. It turns out my approach of putting the task itself in the Startup class was causing the problem. I moved that code to a service that I injected logging (and other relevant stuff) into and the error went away. I was aiming for the simplest possible implementation to make sure everything worked, but it looks like you’ve got to do things properly from the start, which is good, really. Anyway, all working. Thanks!