Hangfire server unable to pick job in case of strategy design pattern
Asked Answered
B

1

2

I am having following application :

1) Mvc app : Hangfire client from where i will just enqueue jobs and host dashboard.This app will contains reference of my class library.

2) Console App : Hangfire server will live in this console application.

3) Class library : Shared library between hangfire server(Console app) and hangfire client(asp.net mvc) where my long running code will reside.Hangfire server will execute code of this library.

I am having structure like below in Class library following strategy design pattern.

Code taken from following : Reference:

Interfaces:

public interface IOperation
{
    Output Process(Input input);
    bool AppliesTo(Type type);
}

public interface IOperationStrategy
{
    Output Process(Type type,Input input);
}

Operations:

public class Add : IOperation
{
    public bool AppliesTo(Type type)
        {
            return typeof(Add).Equals(type);
        }

    public Output Process(Input input)
    {
        // Implementation
        return new Output();
    }
}

public class Multiply : IOperation
{
     public bool AppliesTo(Type type)
        {
            return typeof(Multiply).Equals(type);
        }

    public Output Process(Input input)
    {
        // Implementation
        return new Output();
    }
}

Strategy:

public class OperationStrategy : IOperationStrategy
{
    private readonly IOperation[] operations;

    public OperationStrategy(params IOperation[] operations)
    {
        if (operations == null)
            throw new ArgumentNullException(nameof(operations));
        this.operations = operations;
    }

    public Output Process(Type type, Input input)
    {
        var op = operations.FirstOrDefault(o => o.AppliesTo(type));
        if (op == null)
            throw new InvalidOperationException($"{operation} not registered.");

        return op.Process(input);
    }
}

Usage:

// Do this with your DI container in your composition 
// root, and the instance would be created by injecting 
// it somewhere.
var strategy = new OperationStrategy(
    new Add(), // Inject any dependencies for operation here
    new Multiply()); // Inject any dependencies for operation here

// And then once it is injected, you would simply do this.
var input = new Input { Value1 = 2, Value2 = 3 };
BackgroundJob.Enqueue(() => strategy.Process(typeof(Add), input));

But hangfire server is unable to pick job and when i check state table,i get to see error like below :

"FailedAt": "2018-03-21T13:14:46.0172303Z", "ExceptionType": "System.MissingMethodException", "ExceptionMessage": "No parameterless constructor defined for this object.", "ExceptionDetails": "System.MissingMethodException: No parameterless constructor defined for this object.\r\n at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)\r\n at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)\r\n at System.Activator.CreateInstance(Type type, Boolean nonPublic)\r\n at System.Activator.CreateInstance(Type type)\r\n at Hangfire.JobActivator.SimpleJobActivatorScope.Resolve(Type type)\r\n at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context)\r\n at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_0.b__0()\r\n at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func 1 continuation)\r\n at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable`1 filters)\r\n at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context)\r\n at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection connection, String jobId)"

I am not getting what should be the hangfire set up for this structure as i dont have any IOC container involved here.

Can anybody please help me with this?

Demo project : https://www.dropbox.com/s/bfjr58y6azgmm3w/HFDemo.zip?dl=0

Bullfinch answered 27/3, 2018 at 13:24 Comment(0)
R
1

The answer is right there in the exception

No parameterless constructor defined for this object.

Your OperationStrategy doesn't have a parameterless constructor, so when Hangfire tries to create this object, it can't - it does so through reflection. Hangfire does not have access to the instance that you used when scheduling the job, it attempts to recreate it. Which it can't do.

You could add a parameterless constructor, and make Operations a public collection.

This will enable you to use your ctor exactly as you currently are, but also allow the object to be serialized, stored by Hangfire, then deserialized and created when it tries to run.

public class OperationStrategy : IOperationStrategy
{
    // paramaterless ctor
    public OperationStrategy()
    {
        Operations = new List<Operation>(); 
    }

    public OperationStrategy(params IOperation[] operations)
    {
        if (operations == null)
            throw new ArgumentNullException(nameof(operations));

        Operations = operations;
    }

    public Output Process(Type type, Input input)
    {
        var op = Operations.FirstOrDefault(o => o.AppliesTo(type));

        if (op == null)
            throw new InvalidOperationException($"{operation} not registered.");

        return op.Process(input);
    }

    //property - this can be deserialized by Hangfire
    public List<IOperation> Operations {get; set;}
}
Rice answered 27/3, 2018 at 13:39 Comment(11)
Throwing error on this line :var op = operations.FirstOrDefault(o => o.AppliesTo(type)); Value cannot be null.Operations is coming as nullBullfinch
@User Have you tried with public List<IOperation> Operations {get; set;} = new List<IOperation>(); ?Omaromara
@Omaromara I havent tried this option what you suggested.Let me try it and i will update you.But why list and not array?Bullfinch
@User please see my edits - init Operations in ctor, but importantly use the right case (was operations before) when setting the value of the propertyRice
@Rice Now i am getting Operations count as 0 and because of that i am getting Exception:Type not registeredBullfinch
@Omaromara I am getting type not registered error after doing changes you suggestedBullfinch
@User you seem to be forgetting I can't see your screen. 'type not registered' is not a standard .net exception. Need more detailsRice
Operations.Count is coming as 0 and because of that op becomes null( var op = Operations.FirstOrDefault(o => o.AppliesTo(type));)Hence i get operation not registered exception.Its in your code alsoBullfinch
This is really strange that why Operations.Count becomes 0 even after injecting require dependency in to itBullfinch
when you do var strategy = new OperationStrategy right before you BackgroundJob.Enqueue - is the Operations.count > 0 there?Rice
Let us continue this discussion in chat.Bullfinch

© 2022 - 2024 — McMap. All rights reserved.