Azure Function Json 序列化问题
Azure Function Json Serialization issue
我的自定义序列化程序在 Orchestration Functions 之间传递信息时有时会出现问题,我不知道这是因为对象的嵌套/构造方式,还是与持久化有关功能以及我如何实现序列化程序。大多数情况下,它似乎在持久客户端调用的 Ochestration 中的 Activity 调用失败。
详情如下:
所以我有一个自定义基础 class 本质上是 string Enum
(它是我在 Stack Overflow 上找到的想法的汇编)
public abstract class StringEnum<T>
where T : StringEnum<T>
{
public readonly string Value;
protected StringEnum(string value)
{
Value = value;
}
public override string ToString()
{
return Value;
}
public override bool Equals(object obj)
{
try
{
return (string)obj == Value;
}
catch
{
return false;
}
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public static IEnumerable<T> All
=> typeof(T).GetProperties()
.Where(p => p.PropertyType == typeof(T))
.Select(x => (T)x.GetValue(null, null));
public static implicit operator string(StringEnum<T> enumObject)
{
return enumObject?.Value;
}
public static implicit operator StringEnum<T>(string stringValue)
{
if (All.Any(x => x.Value == stringValue))
{
Type t = typeof(T);
ConstructorInfo ci = t.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(string) }, null);
return (T)ci.Invoke(new object[] { stringValue });
}
return null;
}
public static bool operator ==(StringEnum<T> a, StringEnum<T> b)
{
return a.Value == b.Value;
}
public static bool operator !=(StringEnum<T> a, StringEnum<T> b)
{
return a.Value != b.Value;
}
}
我有两个实现:
public class ReportType : StringEnum<ReportType>, IReportType
{
private ReportType(string value): base(value) { }
public new string Value { get { return base.Value; } }
public static ReportType A_Orders => new ReportType("A_GET_ORDERS");
// ... more types
}
public class ReportStatus : StringEnum<ReportStatus>
{
private ReportStatus(string value): base(value) { }
public new string Value { get { return base.Value; } }
public static ReportStatus New => new ReportStatus("New");
public static ReportStatus Done => new ReportStatus("Done");
// ... more types
}
我写了一个自定义 JsonConverter
来处理这个 class
的 JSON 转换
public class StringEnumJsonConverter<T> : JsonConverter<T>
where T : StringEnum<T>
{
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer)
{
string s = (string)reader.Value;
return (T)s;
}
}
然后我在函数启动中实现了它
[assembly: FunctionsStartup(typeof(Functions.Startup))]
namespace Functions
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<IMessageSerializerSettingsFactory, StringEnumMessageSerializerSettingsFactory>();
}
internal class StringEnumMessageSerializerSettingsFactory : IMessageSerializerSettingsFactory
{
public JsonSerializerSettings CreateJsonSerializerSettings()
{
return new JsonSerializerSettings()
{
Converters = new List<JsonConverter>
{
new StringEnumJsonConverter<ReportType>(),
new StringEnumJsonConverter<ReportStatus>(),
},
ContractResolver = new StringEnumResolver()
};
}
}
internal class StringEnumResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
if (objectType == typeof(ReportType))
{
return GetContract(new StringEnumJsonConverter<ReportType>()), objectType);
}
else if (objectType == typeof(ReportStatus))
{
return GetContract(new StringEnumJsonConverter<ReportStatus>(), objectType);
}
return base.CreateContract(objectType);
}
private JsonContract GetContract(JsonConverter converter, Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.Converter = converter;
return contract;
}
}
}
}
我有一个 class 使用 ReportType
public class ReportsRequestOptions
{
public List<ReportType> ReportTypes { get; set; }
public List<int> Ids { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
}
和 class 同时使用 ReportType
和 ReportStatus
用于 list
另一个 class
public class ReportRequest
{
public ReportType ReportName { get; }
public ReportStatus ReportStatus { get; set; }
// other fields that work
}
internal class ClientReportsRequest
{
public int Id {get; set; }
public List<ReportRequest> Requests { get; set; }
public DateTime To {get; set; }
public DateTime From {get; set; }
}
当我将数据从我的 HttpTrigger
移动到我的主编排函数时我使用 ReportsRequestOptions
但是当我将 ClientReportsRequest
传递到子编排时 JsonConverter 似乎没有工作,这些值只是 Null
而不是它们通常显示的字符串。我可以在转换器中放置一个断点并看到它正在被调用,但由于某种原因,这些值没有出现在我的当地人中,所以我无法检查它以找出发生这种情况的原因。
实施:
[FunctionName(nameof(RunReportsAsync))]
public async Task<IActionResult> RunReportsAsync(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
[DurableClient] IDurableClient client
)
{
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
ReportsRequestOptions requestOptions = JsonConvert.DeserializeObject<ReportsRequestOptions>(requestBody, new StringEnumJsonConverter<ReportType>());
// StringEnum data is correct at this point
if (!requestOptions.ReportTypes.Any())
requestOptions.ReportTypes.AddRange(ReportType.All);
var instanceId = await client.StartNewAsync(nameof(GetReports), requestOptions);
return new OkObjectResult(instanceId);
}
[FunctionName(nameof(GetReports))]
public async Task<RunLog> GetReports(
[OrchestrationTrigger] IDurableOrchestrationContext context
)
{
var requestOptions = context.GetInput<ReportsRequestOptions>();
// string enum data is correct at this point
var clientReportsRequests = GetClientInfo(storeIds)
.Select(x => new ClientReportsRequest()
{
ReportTypes = requestOptions.ReportTypes,
Id = x.Id,
From = requestOptions.From,
To = requestOptions.To
});
// ParallelForEach Async code shouldn't be the issue here.
// it's based on this article: https://dev.to/cgillum/scheduling-tons-of-orchestrator-functions-concurrently-in-c-1ih7
var results = (await clientReportsRequests.ParallelForEachAsync(MaxParallelStoreThreadCount, clientReportsRequest =>
{
return context.CallSubOrchestratorAsync<(int, List<ReportRequest>)>(nameof(GetReportsForClient), clientReportsRequest);
})).ToDictionary(x => x.Item1, x => x.Item2);
return new RunLog(requestOptions, results);
}
[FunctionName(nameof(GetReportsForClient))]
public async Task<(int, List<ReportRequest>)> GetReportsForClient(
[OrchestrationTrigger] IDurableOrchestrationContext context
)
{
var requestOptions = context.GetInput<ClientReportsRequest>();
var completedRequests = new List<ReportRequest>();
foreach (var request in requestOptions.Requests)
{
completedRequests.add(GetReport(request));
// GetReport code has been truncated for brevity but the issue is that neither field in the request
// has it's StringEnum data at this point
}
return (requestOptions.Id, completedRequests);
}
这几天我一直在苦苦思索,找不到答案,有人有什么想法吗?有没有更好的方法来序列化它?
呃,这是 non-issue。我在 ClientReportsRequest
中遗漏了 public get on Requests
字段,很抱歉浪费了任何人的时间。
我的自定义序列化程序在 Orchestration Functions 之间传递信息时有时会出现问题,我不知道这是因为对象的嵌套/构造方式,还是与持久化有关功能以及我如何实现序列化程序。大多数情况下,它似乎在持久客户端调用的 Ochestration 中的 Activity 调用失败。
详情如下:
所以我有一个自定义基础 class 本质上是 string Enum
(它是我在 Stack Overflow 上找到的想法的汇编)
public abstract class StringEnum<T>
where T : StringEnum<T>
{
public readonly string Value;
protected StringEnum(string value)
{
Value = value;
}
public override string ToString()
{
return Value;
}
public override bool Equals(object obj)
{
try
{
return (string)obj == Value;
}
catch
{
return false;
}
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public static IEnumerable<T> All
=> typeof(T).GetProperties()
.Where(p => p.PropertyType == typeof(T))
.Select(x => (T)x.GetValue(null, null));
public static implicit operator string(StringEnum<T> enumObject)
{
return enumObject?.Value;
}
public static implicit operator StringEnum<T>(string stringValue)
{
if (All.Any(x => x.Value == stringValue))
{
Type t = typeof(T);
ConstructorInfo ci = t.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(string) }, null);
return (T)ci.Invoke(new object[] { stringValue });
}
return null;
}
public static bool operator ==(StringEnum<T> a, StringEnum<T> b)
{
return a.Value == b.Value;
}
public static bool operator !=(StringEnum<T> a, StringEnum<T> b)
{
return a.Value != b.Value;
}
}
我有两个实现:
public class ReportType : StringEnum<ReportType>, IReportType
{
private ReportType(string value): base(value) { }
public new string Value { get { return base.Value; } }
public static ReportType A_Orders => new ReportType("A_GET_ORDERS");
// ... more types
}
public class ReportStatus : StringEnum<ReportStatus>
{
private ReportStatus(string value): base(value) { }
public new string Value { get { return base.Value; } }
public static ReportStatus New => new ReportStatus("New");
public static ReportStatus Done => new ReportStatus("Done");
// ... more types
}
我写了一个自定义 JsonConverter
来处理这个 class
public class StringEnumJsonConverter<T> : JsonConverter<T>
where T : StringEnum<T>
{
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer)
{
string s = (string)reader.Value;
return (T)s;
}
}
然后我在函数启动中实现了它
[assembly: FunctionsStartup(typeof(Functions.Startup))]
namespace Functions
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<IMessageSerializerSettingsFactory, StringEnumMessageSerializerSettingsFactory>();
}
internal class StringEnumMessageSerializerSettingsFactory : IMessageSerializerSettingsFactory
{
public JsonSerializerSettings CreateJsonSerializerSettings()
{
return new JsonSerializerSettings()
{
Converters = new List<JsonConverter>
{
new StringEnumJsonConverter<ReportType>(),
new StringEnumJsonConverter<ReportStatus>(),
},
ContractResolver = new StringEnumResolver()
};
}
}
internal class StringEnumResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
if (objectType == typeof(ReportType))
{
return GetContract(new StringEnumJsonConverter<ReportType>()), objectType);
}
else if (objectType == typeof(ReportStatus))
{
return GetContract(new StringEnumJsonConverter<ReportStatus>(), objectType);
}
return base.CreateContract(objectType);
}
private JsonContract GetContract(JsonConverter converter, Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.Converter = converter;
return contract;
}
}
}
}
我有一个 class 使用 ReportType
public class ReportsRequestOptions
{
public List<ReportType> ReportTypes { get; set; }
public List<int> Ids { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
}
和 class 同时使用 ReportType
和 ReportStatus
用于 list
另一个 class
public class ReportRequest
{
public ReportType ReportName { get; }
public ReportStatus ReportStatus { get; set; }
// other fields that work
}
internal class ClientReportsRequest
{
public int Id {get; set; }
public List<ReportRequest> Requests { get; set; }
public DateTime To {get; set; }
public DateTime From {get; set; }
}
当我将数据从我的 HttpTrigger
移动到我的主编排函数时我使用 ReportsRequestOptions
但是当我将 ClientReportsRequest
传递到子编排时 JsonConverter 似乎没有工作,这些值只是 Null
而不是它们通常显示的字符串。我可以在转换器中放置一个断点并看到它正在被调用,但由于某种原因,这些值没有出现在我的当地人中,所以我无法检查它以找出发生这种情况的原因。
实施:
[FunctionName(nameof(RunReportsAsync))]
public async Task<IActionResult> RunReportsAsync(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
[DurableClient] IDurableClient client
)
{
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
ReportsRequestOptions requestOptions = JsonConvert.DeserializeObject<ReportsRequestOptions>(requestBody, new StringEnumJsonConverter<ReportType>());
// StringEnum data is correct at this point
if (!requestOptions.ReportTypes.Any())
requestOptions.ReportTypes.AddRange(ReportType.All);
var instanceId = await client.StartNewAsync(nameof(GetReports), requestOptions);
return new OkObjectResult(instanceId);
}
[FunctionName(nameof(GetReports))]
public async Task<RunLog> GetReports(
[OrchestrationTrigger] IDurableOrchestrationContext context
)
{
var requestOptions = context.GetInput<ReportsRequestOptions>();
// string enum data is correct at this point
var clientReportsRequests = GetClientInfo(storeIds)
.Select(x => new ClientReportsRequest()
{
ReportTypes = requestOptions.ReportTypes,
Id = x.Id,
From = requestOptions.From,
To = requestOptions.To
});
// ParallelForEach Async code shouldn't be the issue here.
// it's based on this article: https://dev.to/cgillum/scheduling-tons-of-orchestrator-functions-concurrently-in-c-1ih7
var results = (await clientReportsRequests.ParallelForEachAsync(MaxParallelStoreThreadCount, clientReportsRequest =>
{
return context.CallSubOrchestratorAsync<(int, List<ReportRequest>)>(nameof(GetReportsForClient), clientReportsRequest);
})).ToDictionary(x => x.Item1, x => x.Item2);
return new RunLog(requestOptions, results);
}
[FunctionName(nameof(GetReportsForClient))]
public async Task<(int, List<ReportRequest>)> GetReportsForClient(
[OrchestrationTrigger] IDurableOrchestrationContext context
)
{
var requestOptions = context.GetInput<ClientReportsRequest>();
var completedRequests = new List<ReportRequest>();
foreach (var request in requestOptions.Requests)
{
completedRequests.add(GetReport(request));
// GetReport code has been truncated for brevity but the issue is that neither field in the request
// has it's StringEnum data at this point
}
return (requestOptions.Id, completedRequests);
}
这几天我一直在苦苦思索,找不到答案,有人有什么想法吗?有没有更好的方法来序列化它?
呃,这是 non-issue。我在 ClientReportsRequest
中遗漏了 public get on Requests
字段,很抱歉浪费了任何人的时间。