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

Azure functions v4 queue trigger - host complaining for invalid model type when message is compressed

Open giorgos07 opened this issue 2 years ago • 1 comments

Hello and thank you in advance for your help.

I am using the following setup:

  • Windows 11
  • .NET 6
  • Azure Functions v4 (out of process)

and i use the following package versions

<PackageReference Include="Azure.Storage.Queues" Version="12.11.1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.8.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues" Version="5.0.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.7.0" OutputItemType="Analyzer" />

I have an HttpTrigger which creates a new instance of a class, serializes it using JsonSerializer and compresses the payload bytes using BrotliStream. Then the queue item is added in an Azure Storage queue, using Base64 as message encoding. The code looks like this.

[Function(HTTP_TRIGGER_FUNCTION_NAME)]
public async static Task<HttpResponseData> AddCompressedContentHttpTrigger(
    [HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "add-compressed-content")] HttpRequestData request,
    FunctionContext functionContext
)
{
    var logger = functionContext.GetLogger(HTTP_TRIGGER_FUNCTION_NAME);
    logger.LogInformation("Function '{FunctionName}' was triggered.", HTTP_TRIGGER_FUNCTION_NAME);
    var queueClient = new QueueClient(Environment.GetEnvironmentVariable("AzureWebJobsStorage"), QUEUE_NAME, new QueueClientOptions
    {
        MessageEncoding = QueueMessageEncoding.Base64
    });
    await queueClient.CreateIfNotExistsAsync();
    var payload = new Country
    {
        Name = "Greece",
        Capital = "Athens",
        Continent = "Europe"
    };
    var jsonPayload = JsonSerializer.Serialize(payload, _jsonSerializerOptions);
    var payloadBytes = Encoding.UTF8.GetBytes(jsonPayload);
    payloadBytes = await Compress(payloadBytes);
    await queueClient.SendMessageAsync(new BinaryData(payloadBytes));
    var response = request.CreateResponse(HttpStatusCode.NoContent);
    return response;
}

In order to consume the compressed content i created a QueueTrigger having a byte[] parameter and everything works as expected.

[Function(QUEUE_TRIGGER_FUNCTION_NAME)]
public async Task DecompressContentQueueTrigger(
    [QueueTrigger(QUEUE_NAME, Connection = "StorageConnection")] byte[] message,
    FunctionContext functionContext
)
{
    var logger = functionContext.GetLogger(QUEUE_TRIGGER_FUNCTION_NAME);
    logger.LogInformation("Function '{FunctionName}' was triggered.", QUEUE_TRIGGER_FUNCTION_NAME);
    var originalMessage = await Decompress(message);
    var country = JsonSerializer.Deserialize<Country>(originalMessage, _jsonSerializerOptions);
}

But if i change the parameter to a POCO class like the following:

[Function(QUEUE_TRIGGER_FUNCTION_NAME)]
public void DecompressContentQueueTrigger(
    [QueueTrigger(QUEUE_NAME, Connection = "StorageConnection")] Country message,
    FunctionContext functionContext
)
{
}

The host desides to fail my function call before even getting a chance to handle any input conversion using the built in InputConverters feature. It does not even try to run the function pipeline. Tried to test with a custom middleware. The exception thrown seems to be coming from the host because it decides that non utf-8 messages (compressed in my case) should not be given a chance to bind to a concrete type. Then why do we have the InputConverters in the first place?

The error is: System.Private.CoreLib: Exception while executing function: Functions.ReadCompressedContentQueueTrigger. Microsoft.Azure.WebJobs.Host: Exception binding parameter 'message'. System.Private.CoreLib: Unable to translate bytes [A7] at index 5 from specified code page to Unicode.

My expectation was to create an InputConverter that takes care of the binding to a concrete type, like the following:

public class CustomInputConverter : IInputConverter
{
    private static JsonSerializerOptions _jsonSerializerOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    };

    public async ValueTask<ConversionResult> ConvertAsync(ConverterContext context)
    {
        if (context.Source is ReadOnlyMemory<byte> messageBytes)
        {
            var originalMessage = await Decompress(messageBytes.ToArray());
            var country = JsonSerializer.Deserialize<Country>(originalMessage, _jsonSerializerOptions);
            return ConversionResult.Success(country);
        }
        return ConversionResult.Unhandled();
    }
}

Sample

I have written a complete sample that you can use to reproduce the behavior i described above. You can download it from here.

giorgos07 avatar Sep 07 '22 17:09 giorgos07

+1 I have run into the same problem but I have been trying with encrypted payloads.

cleftheris avatar Sep 08 '22 08:09 cleftheris