I'm using Azure Durable Functions and have an activity that returns a list of objects of my custom class.When I check the result in the Activity Function before it's returned, everything seems correct.
However, in the orchestrator, I only get an initialized list with the correct number of elements, but the objects themselves are empty—none of their properties have values.
Has anyone encountered this issue before, or does anyone know why the objects might be getting "zeroed out" when passed from the activity to the orchestrator?
My Code:
Orchestrator
public static class OrchestratorFunc
{
[Function(nameof(OrchestratorFunc))]
public static async Task RunOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
{
ILogger logger = context.CreateReplaySafeLogger(nameof(OrchestratorFunc));
using (logger.BeginScope(new Dictionary<string, object> { ["OrchestrationInstanceId"] = context.InstanceId }))
{
logger.LogInformation($"OrchestratorFunc Executed. InstanceId={context.InstanceId}");
var accessToken = await context.CallActivityAsync<string>(nameof(RetrieveAccessTokenActivity));
var companies = await context.CallActivityAsync<IEnumerable<CompanyModel>>(nameof(GetCompaniesActivity), accessToken);
logger.LogInformation($"OrchestratorFunc completed");
}
}
}
Activity Function (GetCompaniesActivity)
internal class GetCompaniesActivity
{
private readonly HttpClient _httpClient;
public GetCompaniesActivity(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient();
}
[Function(nameof(GetCompaniesActivity))]
public async Task<IEnumerable<CompanyModel>> Run([ActivityTrigger] string accessToken, string instanceId, FunctionContext executionContext)
{
ILogger logger = executionContext.GetLogger(nameof(GetCompaniesActivity));
var result = new List<CompanyModel>();
using (logger.BeginScope(new Dictionary<string, object> { ["OrchestrationInstanceId"] = instanceId }))
{
using var request = new HttpRequestMessage(HttpMethod.Get, "http://");
request.Headers.Add("Authorization", $"Bearer Authorization: {accessToken}");
var response = await _httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var jsonResponse = await response.Content.ReadAsStringAsync();
var companiesResponse = JsonConvert.DeserializeObject<CompanyResponse>(jsonResponse);
if (companiesResponse != null && companiesResponse.Companies != null)
{
result.AddRange(companiesResponse.Companies);
}
}
else
{
throw new TaskFailedException($"{response.StatusCode} {response.ReasonPhrase}");
}
return result.AsEnumerable<CompanyModel>();
}
}
}
CompanyModel:
[JsonObject(MemberSerialization.OptIn)]
public class CompanyResponse
{
[JsonProperty("results")]
public required List<CompanyModel> Companies;
}
[JsonObject(MemberSerialization.OptIn)]
public class CompanyModel
{
[JsonProperty("id")]
public Guid Id;
[JsonProperty("companyName")]
public required string CompanyName;
[JsonProperty("street")]
public required string Street;
[JsonProperty("zip")]
public required string Zip;
[JsonProperty("city")]
public required string City;
[JsonProperty("country")]
public required string Country;
[JsonProperty("vat")]
public required string VAT;
[JsonProperty("sys")]
public int? Sys;
[JsonProperty("idExternal")]
public required string IdExternal;
public override string ToString()
{
return $"{CompanyName} - {Id} - {IdExternal}";
}
}
The return value from my Activity Function:
The returned list in Orchestrator
I tried both IEnumerable
and List
—no difference. I also experimented with the JSON properties, but without success. The custom CompanyModel
is correctly deserialized in the activity function, but in the orchestrator, all values are missing, even though the object is initialized.
Add a JsonInclude
attribute to your fields, like this:
[JsonObject(MemberSerialization.OptIn)]
public class CompanyModel
{
[JsonInclude] // This
[JsonProperty("id")]
public Guid Id;
[JsonInclude] // This
[JsonProperty("companyName")]
public required string CompanyName;
// etc.
You might need to add using System.Text.Json.Serialization
as well.
It looks like you're using Newtonsoft's JSON serialiser for your models, which is fine when you're doing explicit conversion - like when you process the HTTP response in your Activity Function.
However. When you return the objects at the end of the Activity, the Orchestration framework uses the System.Text.Json
serialiser instead of Newtonsoft.
System.Text.Json
doesn't serialise the fields of an object, by default. It only serialises properties (i.e. get
/set
methods)
So to make it serialise your fields, add [JsonInclude]
attribute on those fields.
See here for more details: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/fields