Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serializing Enum as string using attribute in Azure Functions 3.0

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

Additional information

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.

like image 549
CHochlin Avatar asked Dec 17 '19 09:12

CHochlin


2 Answers

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.

like image 170
CJ van der Smissen Avatar answered Nov 17 '22 21:11

CJ van der Smissen


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.

like image 42
Marc Avatar answered Nov 17 '22 23:11

Marc