-
Notifications
You must be signed in to change notification settings - Fork 280
Description
We are migrating our durable functions from in-process to the isolated worker model. Some of our activities have input parameters that are typed as an interface or abstract class and we need to be able to determine the concrete type in our activities. In the in-process context, we used to customize the serializer settings by implementing our own IMessageSerializerSettingsFactory
and setting TypeNameHandling
to TypeNameHandling.All
. This ensured that type metadata is included in the JSON so that the activity input could be deserialized correctly.
Regardless of whether this is considered good practice or not, it worked for us and we would like to continue this approach in the isolated model. We also opted for Newtonsoft again, which we configure in our DI container via WorkerOptions.Serializer
(of course using TypeNameHandling.All
again). However, when debugging our functions locally in the isolated model, the deserialization of these polymorphic activity inputs fails. For activity inputs typed as abstract classes, the JSON is deserialized into an instance of that abstract class. All information about the concrete type sent from the orchestration seems to have got lost. For interface-typed inputs, we get a JsonSerializationException
saying "Could not create an instance of type <Type>. Type is an interface or abstract class and cannot be instantiated."
After some debugging we found something curious. It turns out that the serializer can handle polymorphism in activity inputs just fine, as long as the immediate input type is a concrete type, and any abstract classes or interfaces only occur in properties of the input type. An example:
We have an activity that has the following signature:
public async Task VehicleActivity([ActivityTrigger] IVehicle vehicle, CancellationToken cancellationToken)
{
...
}
From an orchestration we call this activity and pass an instance of a class Car : IVehicle
. With Newtonsoft set as the serializer and TypeNameHandling
set to TypeNameHandling.All
, we expect the JSON of the the serialized Car
instance to contain the $type
metadata, enabling the worker to deserialize this into the IVehicle
that the activity needs. Instead we get the JsonSerializationException
mentioned before.
Now we modify the signature of the activity as follows (and modify the calling orchestration code accordingly):
public async Task VehicleActivity([ActivityTrigger] (IVehicle Vehicle, string Dummy) input, CancellationToken cancellationToken)
{
...
}
With the interface wrapped in a tuple, suddenly everything works as expected. We get no more exceptions and input.Vehicle
has type Car
.
This is on Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.7.0.
Our situation looks very much like the one described here: #2801 (comment)