We have been enqueueing and executing jobs on the same .NET Core webapp, but now we want to separate the execution of the jobs to separate servers from the webapp. So I created a new app with the same Hangfire configuration as the original app, copied the UseHangfireServer call and related config to the new app and removed UseHangfireServer from the original app. That change broke the serialization of jobs when enqueuing them, which is really weird. If I run the Hangfire server in the original app, a typical job serializes like this (Hangfire.Job table in SQL Server):
C# code:
_queue.Enqueue(() => _mediator.Send(new SubmissionUploadBackgroundJobCommand(_principal.UserId, _principal.ClientId, submissionUpload.Id, id, false), default));
InvocationData;
{"t":"MediatR.Mediator, MediatR","m":"Send","p":["MediatR.IRequest
1[[MediatR.Unit, MediatR]], MediatR",“System.Threading.CancellationToken, mscorlib”]}`
Arguments:
["{\"$type\":\"Aca.Mr.Commands.SubmissionUploadBackgroundJobCommand, Mr.Core\",\"SubmissionUploadId\":395,\"SubmissionId\":\"35d89c58-7dce-44ed-8523-277580c5b21a\",\"UserInternalId\":208,\"ClientInternalId\":6104}",null]
If I do not UseHangfireServer (but still AddHangfire with same config), then the exact same job gets serialized like this:
InvocationData:
{"Type":"MediatR.Mediator, MediatR, Version=9.0.0.0, Culture=neutral, PublicKeyToken=bb9a41a5e8aaa7e2","Method":"Send","ParameterTypes":"[\"MediatR.IRequest
1[[MediatR.Unit, MediatR, Version=9.0.0.0, Culture=neutral, PublicKeyToken=bb9a41a5e8aaa7e2]], MediatR, Version=9.0.0.0, Culture=neutral, PublicKeyToken=bb9a41a5e8aaa7e2","System.Threading.CancellationToken, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"]",“Arguments”:null}`
Arguments:
["{\"SubmissionUploadId\":396,\"SubmissionId\":\"35d89c58-7dce-44ed-8523-277580c5b21a\",\"IsAutoUploadOnClose\":false,\"UserInternalId\":208,\"ClientInternalId\":6104}",null]
Notice in particular that the Arguments do not contain any Type information. That seems to be particularly what Hangfire complains about when trying to execute that job in another process; it can’t deserialize the Arguments without some Type info.
The mystery is why running Hangfire server makes any difference at all in how Enqueue serializes an Expression. I’ve looked at the Hangfire source code and can’t find anything related to serialization in the code for UseHangfireServer.
As I was starting to create this post, I got a popup window with “Your topic is similar to…” Serializing with type information, suggesting that using JsonSerializerSettings TypeNameHandling.Objects solved it. So I added:
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
});
to the Configuration and that solved the problem. Things don’t serialize exactly the same as before, but at least the necessary Type info is included, so that the deserialization and execution of the jobs (in a separate app) now succeeds.
InvocationData:
{"$type":"Hangfire.Storage.InvocationData, Hangfire.Core","Type":"MediatR.Mediator, MediatR, Version=9.0.0.0, Culture=neutral, PublicKeyToken=bb9a41a5e8aaa7e2","Method":"Send","ParameterTypes":"[\"MediatR.IRequest
1[[MediatR.Unit, MediatR, Version=9.0.0.0, Culture=neutral, PublicKeyToken=bb9a41a5e8aaa7e2]], MediatR, Version=9.0.0.0, Culture=neutral, PublicKeyToken=bb9a41a5e8aaa7e2","System.Threading.CancellationToken, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"]",“Arguments”:null}`
Arguments:
["{\"$type\":\"Aca.Mr.Commands.SubmissionUploadBackgroundJobCommand, Mr.Core\",\"SubmissionUploadId\":398,\"SubmissionId\":\"35d89c58-7dce-44ed-8523-277580c5b21a\",\"IsAutoUploadOnClose\":false,\"UserInternalId\":208,\"ClientInternalId\":6104}",null]
Why would serialization be affected by whether I’m running Hangfire server on the app that is doing the Enqueueing of Jobs? I.e., the mystery isn’t why TypeNameHandling.Objects solved the problem, but why that option wasn’t necessary when the enqueuing and execution of jobs is all within the same process. Anyway, I have a solution to the problem, but I’m still curious about why separating the server from the enqueuing makes a difference for serialization.