Argument as interface types

I’m migrating from using a custom task runner for our app to Hangfire. The current implementation has the concept of tasks as a message and handler (ITask and ITaskHandler).

To keep this concept and avoid reworking a lot of code I have a small snippet to integrate Hangfire with Hangfire:

public void Execute(ITask task)
    BackgroundJob.Enqueue<ImmediateTaskExecutor>(e => e.Execute(task));

The problem is one execution of the background task the argument is not actually created (passed to the ImmediateTaskExecutor as null). I believe this is because the runtime type is not stored with the argument, only the static type (e.g. ITask is stored in the database not TheTaskThatImplementsITask).

Is there a way around this for now and should this be considered a bug?


I would be very surprised if the runtime type was not properly serialized. I don’t recall if we have a similar case in our project (and can’t check now), but you can inspect the scheduled task in the dashboard and see the serialized argument - maybe that will give you some insight as to what’s going on.

Thanks for the reply. In the dashboard I can see the generated code:

using Blueprint.Core.Tasks;

ImmediateTaskExecutor immediateTaskExecutor = Activate<ImmediateTaskExecutor>();

I take it from that code that the task that would be created would be of type ITask, which of course will not be correct as that is an interface type with no parameters (just a marker interface)

Looking in Hangfire.Job SQL job table I see, in InvocationData:

{"Type":"Blueprint.Core.Tasks.ImmediateTaskExecutor, Blueprint.Core, Version=, Culture=neutral, PublicKeyToken=null","Method":"Execute","ParameterTypes":"[\"Blueprint.Core.Tasks.ITask, Blueprint.Core, Version=, Culture=neutral, PublicKeyToken=null\"]","Arguments":"[\"{\\\"CreatedAt\\\":\\\"2015-08-02T18:29:24.9869395+00:00\\\",\\\"ProjectId\\\":80456868}\"]"}

I have created an example that shows the problem I am running in to, based off of the minimal Hangfire sample console app:

Hopefully that shows the problem better than just a text description

@AdamBarclay, serialized string of a job argument does not include the type name by default, and thus it can’t be deserialized. However, you can change this by passing your own serialization options:

    new JsonSerializerSettings {  TypeNameHandling = TypeNameHandling.All }); 

This will enable Json.NET to serialize your arguments with preserving the type information. Please try this.

@odinserj, I’ve just tried that but it results in an exception when the job is being run:


Type specified in JSON ‘System.Linq.Enumerable+WhereSelectArrayIterator`2[[System.Reflection.ParameterInfo, mscorlib, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Type, mscorlib, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089]], System.Core, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089’ is not compatible with ‘System.Type[], mscorlib, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089’. Path ‘$type’, line 1, position 142.

at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
at Hangfire.Common.JobHelper.FromJson[T](String value)
at Hangfire.Storage.InvocationData.Deserialize()

The Arguments column in Job table now contains:

{"$type":"System.String[], mscorlib","$values":["{\"$type\":\"Hangfire.ConsoleApplication.GreetingTask, Hangfire.ConsoleApplication\",\"Greeting\":\"Hello world\"}"]}

@AdamBarclay, what Hangfire version are you using? This PR attempts to fix this in 1.4.3.

@odinserj I was using version 1.4.1 (the default version from Hangfire.Sample github, which I had assumed was the latest version).

Having upgraded to 1.4.5 that now fixes the exception posted. Perhaps the default should be to include the type names in the JSON being serialised? It pushes Hangfire a step further towards ‘just working’ without requiring any extra configuration. I don’t think my usage is particularly abnormal.

Or at a minimum perhaps somewhere in the documentation?

Many thanks for all your work! Looking forward to replacing our home-grown solution with something that actually works real nice :smile:

This is a breaking change, since objects serialized without TypeNameHandling.All should be de-serialized without this setting either. Need to think how to handle this.

I moved 2 posts to a new topic: Use TypeNameHandling.All when serializing arguments by default