azure-webjobs-sdk-extensions icon indicating copy to clipboard operation
azure-webjobs-sdk-extensions copied to clipboard

HttpTrigger binding to POCO fails on Enums

Open dlalex83 opened this issue 5 years ago • 28 comments

I've recently updated my Azure Functions app to v2.0.12050.0 which upgrade the webjobs-sdk-extensions from 3.0.0-beta5 to 3.0.0-beta8.

I have several functions using HttpTrigger in POST binding to a POCO object. The POCO has an enum property and before the upgrade I was able to post content into it and the sdk was correctly binding an int value to the enum. See below for more details.

POCO:

public class Message
{
     public Region RegionId { get; set; }
     public string Content { get; set; }
}

public enum Region
{
     UK = 1
}

Request payload:

{
   RegionId: 1,
   Content: "test"
}

Function declaration: public static async Task ProcessMessage([HttpTrigger(AuthorizationLevel.Anonymous, "POST")]Message message);

Expected behavior

The message property should be populated from the payload and the enum property Region correctly set.

Actual behavior

Invocation breaks with the error:

System.Private.CoreLib: Exception while executing function: AddRequestTweakingWebsiteRule. Microsoft.Azure.WebJobs.Host: Exception binding parameter 'req'. System.Private.CoreLib: Invalid cast from 'System.String' to 'LoveTheSales.Shared.Enums.Region'.

Stacktrace:

 System.InvalidOperationException: Exception binding parameter 'req' ---> System.InvalidCastException: Invalid cast from 'System.String' to 'Shared.Enums.Region'.
   at System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)
   at System.String.System.IConvertible.ToType(Type type, IFormatProvider provider)
   at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
   at System.Convert.ChangeType(Object value, Type conversionType)
   at Microsoft.Azure.WebJobs.Extensions.Http.HttpTriggerAttributeBindingProvider.HttpTriggerBinding.ConvertValueIfNecessary(Object value, Type targetType) in C:\azure-webjobs-sdk-extensions\src\WebJobs.Extensions.Http\HttpTriggerAttributeBindingProvider.cs:line 413
   at Microsoft.Azure.WebJobs.Extensions.Http.HttpTriggerAttributeBindingProvider.HttpTriggerBinding.ApplyBindingData(Object target, IDictionary`2 bindingData) in C:\azure-webjobs-sdk-extensions\src\WebJobs.Extensions.Http\HttpTriggerAttributeBindingProvider.cs:line 228
   at Microsoft.Azure.WebJobs.Extensions.Http.HttpTriggerAttributeBindingProvider.HttpTriggerBinding.BindAsync(Object value, ValueBindingContext context) in C:\azure-webjobs-sdk-extensions\src\WebJobs.Extensions.Http\HttpTriggerAttributeBindingProvider.cs:line 174
   at Microsoft.Azure.WebJobs.Host.Triggers.TriggeredFunctionBinding`1.BindCoreAsync(ValueBindingContext context, Object value, IDictionary`2 parameters) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Triggers\TriggeredFunctionBinding.cs:line 57
   --- End of inner exception stack trace ---
   at Microsoft.Azure.WebJobs.Host.Executors.DelayedException.Throw() in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\DelayedException.cs:line 27
   at Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithWatchersAsync(IFunctionInstance instance, ParameterHelper parameterHelper, ILogger logger, CancellationTokenSource functionCancellationTokenSource) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs:line 478
   at Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(IFunctionInstance instance, ParameterHelper parameterHelper, IFunctionOutputDefinition outputDefinition, ILogger logger, CancellationTokenSource functionCancellationTokenSource) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs:line 444
   at Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(IFunctionInstance instance, FunctionStartedMessage message, FunctionInstanceLogEntry instanceLogEntry, ParameterHelper parameterHelper, ILogger logger, CancellationToken cancellationToken) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs:line 249
   --- End of inner exception stack trace ---
   at Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(IFunctionInstance instance, FunctionStartedMessage message, FunctionInstanceLogEntry instanceLogEntry, ParameterHelper parameterHelper, ILogger logger, CancellationToken cancellationToken) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs:line 293

Known workarounds

I can change the HttpTrigger to bind to HttpRequestMessage and deserialize the content manually into my POCO but I was working fine before the upgrade.

Related information

This started to happen when we upgraded from 3.0.0-beta5 to 3.0.0-beta8.

dlalex83 avatar Sep 11 '18 16:09 dlalex83

Seems guys messed things a lil bit in 3.0.0-beta8 :) https://github.com/Azure/azure-webjobs-sdk-extensions/issues/485

Badabum avatar Sep 12 '18 09:09 Badabum

Having the same issue. Is this possible to solve in the startup configurations? Or do I have to deserialize it within the azure function?

System.Private.CoreLib: Exception while executing function: PostSomething. 
Microsoft.Azure.WebJobs.Host: One or more errors occurred. (Exception binding parameter 'something') (Exception binding parameter 'req'). 
Exception binding parameter 'something'. System.Private.CoreLib: 
Invalid cast from 'System.String' to 'My.SomethingEnum'.

jansoren avatar Oct 19 '18 07:10 jansoren

This is still an issue in the release version 3.0.0 and is a real big showstopper for our project. @mathewc or @brettsam, could you please comment on this issue?

jansoren avatar Oct 24 '18 08:10 jansoren

I see this is a P1 issue now. Any updates on this? We had a clean api which is now cluttered with workarounds to address this.

rrrodzilla avatar Jun 05 '19 05:06 rrrodzilla

I will take a shot at this in this Sprint.

ankitkumarr avatar Jun 13 '19 23:06 ankitkumarr

We looked at this, and it's an issue with our config for serialization not being propagated. It will need some design changes (possibly in the next sprint). In the meanwhile, @fabiocav, would you mind sharing the work-around here?

ankitkumarr avatar Jun 25 '19 22:06 ankitkumarr

As @ankitkumarr mentioned, this is a configuration setup issue, but the goo news is that there's a clear workaround. While the problem is not resolved, please add the following to your function project (in addition to a reference to Microsoft.Azure.Functions.Extensions:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

[assembly: FunctionsStartup(typeof(MyFunctionApp.FunctionAppStartup))]

namespace MyFunctionApp
{
    public class FunctionAppStartup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
           builder.Services.AddTransient<IConfigureOptions<MvcOptions>, MvcJsonMvcOptionsSetup>();
        }
    }
}

fabiocav avatar Jun 25 '19 23:06 fabiocav

Just adding comment here - still a valid issue but fixing this outright would be a breaking change as it would impact the default serialization logic. We have some thoughts on how we may be able to make this easier, but for now following the recommendation from @fabiocav above and adding some MvcJsonMvcOptions in the DI pipeline is best way to get the expected POCO serialization. Removing P1 label, but still tracking and leaving open, so feel free to keep feedback / comments / gaps on this issue.

jeffhollan avatar Jun 26 '19 20:06 jeffhollan

To follow on from @fabiocav's comment above, that works perfectly but you may also need a reference to Microsoft.AspNetCore.Mvc.Formatters.Json.

WiredUK avatar Jul 29 '19 10:07 WiredUK

Even with the work around, I can't get my function to run, I currently have a model that holds a list of strings. i have a second input binding, when the second input binding try to access the {Ids} it's empty.

It seems like the dictionary holding the values is not getting populate, even with the work around. If I access the RequestQuery object within the function it's correctly populated.

Changing the RequestQuery class to a hold string instead of a list, it works fine.

is there any timeline for when this is getting fixed @jeffhollan ???

[FunctionName("SelectCustomerSample6")]
public IActionResult SelectCustomerSample6(
            [HttpTrigger()] RequestQuery requestQuery,
            [Dapper(Sql = "Select6.sql", SqlConnection = "SqlConnection", 
                          Parameters = "Ids:{Ids}")] IList<Customer> customers,
            ILogger log)
 {
            return new OkObjectResult(customers);
}

public class RequestQuery 
{
   public List<string> Ids{ get; set; }
}

kristianGNI avatar Oct 18 '19 09:10 kristianGNI

To follow on from @fabiocav's comment above, that works perfectly but you may also need a reference to Microsoft.AspNetCore.Mvc.Formatters.Json.

doesn't exist for core 3.1?

gerab avatar Jan 20 '20 13:01 gerab

@fabiocav I can't get the work-around to run on Azure Functions V2:

[11-02-2020 12:50:13] A host error has occurred during startup operation 'e5061744-cc42-4f93-991a-71a3a52eb7e0'.
[11-02-2020 12:50:13] Hemonto.Asset: Could not load type 'Microsoft.AspNetCore.Mvc.Formatters.Json.Internal.MvcJsonMvcOptio
nsSetup' from assembly 'Microsoft.AspNetCore.Mvc.Formatters.Json, Version=3.1.0.0, Culture=neutral, PublicKeyToken=adb97938
29ddae60'.

I tried adding a reference to NuGet package Microsoft.AspNetCore.Mvc.Formatters.Json (ver. 2.2.0) but still no luck. Using:

Azure Functions Core Tools (3.0.2009 Commit hash: 77395527a4e9c28da8400dcfd1a450f4e0d0c36c)
Function Runtime Version: 3.0.12930.0

CasperWSchmidt avatar Feb 11 '20 12:02 CasperWSchmidt

@CasperWSchmidt I'm assuming you mean Functions V3 above, correct? Have you tried to move to 3.0 and remove the workaround?

fabiocav avatar Feb 12 '20 17:02 fabiocav

@fabiocav no I mean V2 as in. Net core 2.2. My colleague tried migrating one of our services to V3 but it didn't work out-of-the-box and we're on a tight schedule so we won't be able to try that right now

CasperWSchmidt avatar Feb 13 '20 15:02 CasperWSchmidt

@fabiocav @CasperWSchmidt I get the same error on Functions V3 and net core 3.1:

Could not load type 'Microsoft.AspNetCore.Mvc.Formatters.Json.Internal.MvcJsonMvcOptionsSetup' from assembly 'Microsoft.AspNetCore.Mv
c.Formatters.Json, Version=3.1.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.

maxbog avatar Apr 15 '20 19:04 maxbog

I'm getting the same error as @maxbog. Could not load type 'Microsoft.AspNetCore.Mvc.Formatters.Json.Internal.MvcJsonMvcOptionsSetup' from assembly 'Microsoft.AspNetCore.Mvc.Formatters.Json, Version=3.1.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'

evotch avatar Jul 23 '20 18:07 evotch

The problem persists?

fabioleardini avatar Mar 19 '21 18:03 fabioleardini

@fabioleardini this is an old issue and I want to make sure what's described here applies to you.

Would you be able to share your setup details and, ideally, a link to a repro so we can look at how you have this setup? Also, which of the issues described above are you experiencing? The originally described problem, or the load issue with the workaround?

Thanks!

fabiocav avatar Mar 19 '21 18:03 fabiocav

@fabiocav I have done some quick testing and I can confirm that on AZ V3 there are some issues binding querystring parameters to enum and lists (IEnumerable<string>) (using a POST request to test also combination of querystring and body). It works just fine posting it all in the body. Should I create a new issue for this?

CasperWSchmidt avatar Mar 26 '21 13:03 CasperWSchmidt

FunctionAppHttpParameterTesting.zip Here's a small example. I have attached three requests in a Postman collection as well. Enabling and disabling the querystring parameters that maps to enum or IEnumerable<string> will either result in 500 - Internal server error (if any is enabled) or 200 - Ok with the rest of the request serialized as the output.

CasperWSchmidt avatar Apr 06 '21 12:04 CasperWSchmidt

@fabiocav any news here?

CasperWSchmidt avatar May 10 '21 07:05 CasperWSchmidt

Still nothing? I'm amazed that a bug/severe limitation like this get so little attention :(

CasperWSchmidt avatar Jun 16 '21 08:06 CasperWSchmidt

@CasperWSchmidt did you find any workaround for this? I'm also facing the same issue with enum. @fabiocav can we expect any help on this please?

After adding the line what @ankitkumarr mentioned, getting following error -

System.TypeLoadException: 'Could not load type 'Microsoft.AspNetCore.Mvc.Formatters.Json.Internal.MvcJsonMvcOptionsSetup' from assembly 'Microsoft.AspNetCore.Mvc.Formatters.Json, Version=3.1.14.0

rahul230691 avatar Jun 24 '21 05:06 rahul230691

@rahul230691 the only workaround I know of is to do the deserialization yourself using Newtonsoft/Json.Net but this is not easy if you have values in both querystring and body.

CasperWSchmidt avatar Jun 24 '21 10:06 CasperWSchmidt

@fabiocav @jeffhollan could you at least give some kind of response?

CasperWSchmidt avatar Jan 07 '22 09:01 CasperWSchmidt

It appears to also be a problem for net6.0 on Functions V4 (referencing Microsoft.NET.Sdk.Functions 4.1.1) @fabiocav @jeffhollan

CasperWSchmidt avatar Aug 02 '22 12:08 CasperWSchmidt

Any plans on resolving this? Still an issue for .net6.0 and Microsoft.Net.Sdk.Functions 4.0.1

Seems like lots of people are having this issue and are forced to manually bind the models with enums that comes from querystring.

BullFrog13 avatar Nov 28 '22 23:11 BullFrog13

@BullFrog13 In my experience this repo is DEAD. I guess all their focus is on the isolated model (the only model going forward now) so this regression (from V2) is just ignored 👎 Also from what I can see on Twitter, @jeffhollan is no longer part of the Azure Functions team. @fabiocav should be part of it still though...

CasperWSchmidt avatar Dec 02 '22 08:12 CasperWSchmidt