I have tried getting an HTTP-trigger in an Azure Functions 3.0/3.1 app to return the string-representation of enums without any luck. I have tried both Core 3.0 and Core 3.1.
Given this class:
public enum TestEnum
{
TestValue
}
public class TestClass
{
public TestEnum Test { get; set; }
}
I expect this HTTP-trigger:
[FunctionName("MyHttpTrigger")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
{
return new OkObjectResult(new TestClass { Test = TestEnum.TestValue });
}
to return { "test": "TestValue" }
. Instead it returns { "test": 0 }
.
In .Net Core 2, I could decorate the enum using [JsonConverter(typeof(StringEnumConverter))]
from the namespace Newtonsoft.Json
to make this happen. This does not work now.
I know .Net Core 3 switched to the converter in the System.Text.Json
-namespace, so I tried decorating the same enum with [JsonConverter(typeof(JsonStringEnumConverter))]
from the namespace System.Text.Json.Serialization
. This did not work either.
All of the other similar issues either says that the above should work, or, like this, says to solve it by configuring JsonOptions in the .AddControllers()
or .AddMvc()
-chain, but for a function app that won't work. The function runtime is responsible for setting up the controllers, so we don't have direct access to further configuration AFAIK.
The issue should be reproducible with a barebones Azure Functions project using the core tools:
func init MyFunctionProj
cd MyFunctionProj
func new --name MyHttpTrigger --template "HttpTrigger"
Then change the http trigger to return an object with an enum inside.
My versions:
> func --version
3.0.1975
> dotnet --version
3.1.100
I have tried multiple workarounds, so here is some of my observations.
OkObjectResult
is handled by ObjectResultExecutor. I tried to copy the implementation into my solution and adding it to the DI container so I could debug:
builder.Services.AddSingleton<IActionResultExecutor<ObjectResult>, TestExecutor>();
At line 101 in the linked source, the formatter is chosen. Interestingly, when I break at this point, the formatter chosen is NewtonsoftJsonOutputFormatter! I would think the first JsonConverter
-attribute should work with this, but it doesn't.
I figured it would be worth a shot to modify the list of available output formatters. The ActionResultExecutor uses DefaultOutputFormatterSelector to make its selection, which in turn injects IOptions<MvcOptions>
to get output formatters.
To force the DefaultOutputFormatterSelector
to choose differently I tried this:
class MvcOptionsConfiguration : IPostConfigureOptions<MvcOptions>
{
...
}
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.ConfigureOptions<MvcOptionsConfiguration>();
}
}
Note that the regular IConfigureOptions<T>
couldn't be used as the options.Outputformatters
-collection was missing all other formatters than Microsoft.AspNetCore.Mvc.WebApiCompatShim.HttpResponseMessageOutputFormatter
, which is strange in itself (why is a brand new 3.0 app using a compatibility shim?).
I tried to remove the type NewtonsoftJsonOutputFormatter
from the collection using:
options.OutputFormatters.RemoveType<NewtonsoftJsonOutputFormatter>();
To do that I had to reference the nuget package Microsoft.AspNetCore.Mvc.NewtonsoftJson
to access the type, but the type when I referenced it was different from the one used by the runtime. type.Assembly.CodeBase
for the type used by the runtime was located in %USERPROFILE%/AppData/Local/AzureFunctionsTools/
but the one from nuget was located in Project root/bin/Debug
. I have never encountered this before. Usually, the package will be a transient dependency so I can reference it without explicitly depending on the package myself, so I'm not really sure what's going on here. Is this normal, or is there something wrong in my environment?
When I removed it in another manner it didn't matter and it got selected anyway, so it seems like the MvcOptions
injected in DefaultOutputFormatterSelector
is not the same MvcOptions
we can configure in the startup.
At this point, I ran out of leads and turned to you guys. I hope there is someone who can point me in the right direction.
I was able to get this working using the following code
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Converters;
[assembly: FunctionsStartup(typeof(Configs.Startup))]
namespace Configs
{
class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddMvcCore().AddNewtonsoftJson(x =>
{
x.SerializerSettings.Converters.Add(new StringEnumConverter());
});
}
}
}
This was in a netcoreapp3.1 on Azure Functions Core Tools (3.0.2534 Commit hash: bc1e9efa8fa78dd1a138dd1ac1ebef97aac8d78e) and Function Runtime Version: 3.0.13353.0 with the following packages:
<PackageReference Include="AsyncEnumerator" Version="4.0.2" />
<PackageReference Include="AzureFunctions.Autofac" Version="4.0.0" />
<PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="Dapper" Version="2.0.35" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.0.0" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.7" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" />
Hopefully this helps someone.
I pushed up an example repo here: https://github.com/rawrspace/string-enum-example
EDIT: I was using this again today with the same setup and using [JsonConverter(typeof(StringEnumConverter))] worked perfectly fine. I am not sure if an update recently happened but I will leave the above solution just in case.
I also ran into serialization issues with a .NET Core 3.1 Function App. I was recommended to use this application setting as a temporary workaround:
"FUNCTIONS_V2_COMPATIBILITY_MODE": true
This solved my issue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With