Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Azure Functions 3 and [FromBody] modelbinding

I am creating a post endpoint using Azure Functions version 3. In Asp.net it is very convenient to get the post object using the [FromBody] tag and the magic will happen with modelbinding. Is there a way to use the FromBody tag in Azure Functions v3?

like image 649
doorman Avatar asked Jan 30 '20 11:01

doorman


2 Answers

Yes you can do that,

 public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post")][FromBody] User user, ILogger log, ExecutionContext context)

Here is an Example

like image 129
Sajeetharan Avatar answered Nov 08 '22 14:11

Sajeetharan


Microsoft.Azure.Functions.Worker version 1.7.0-preview1 makes custom input conversion possible. The below will convert HttpRequestData.Body to a POCO via converting the stream to a byte array then passing the byte array back into the normal input converter process (where it is convertered by the built-in JsonPocoConverter. It relies on reflection as the services required to delegate the conversion after converting the stream to a byte array are internal, so it may break at some point.

Converter:

internal class FromHttpRequestDataBodyConverter : IInputConverter
{
    public async ValueTask<ConversionResult> ConvertAsync(ConverterContext context)
    {
        if (context.Source is null
            || context.Source is not HttpRequestData req
            || context.TargetType.IsAssignableFrom(typeof(HttpRequestData)))
        {
            return ConversionResult.Unhandled();
        }
        
        var newContext = new MyConverterContext(
            context,
            await ReadStream(req.Body));

        return await ConvertAsync(newContext);
    }

    private static async Task<ReadOnlyMemory<byte>> ReadStream(Stream source)
    {
        var byteArray = new byte[source.Length];

        using (var memStream = new MemoryStream(byteArray))
        {
            await source.CopyToAsync(memStream);
        }

        return byteArray.AsMemory();
    }

    private static ValueTask<ConversionResult> ConvertAsync(MyConverterContext context)
    {
        // find the IInputConversionFeature service
        var feature = context.FunctionContext
            .Features
            .First(f => f.Key == InputConvertionFeatureType)
            .Value;

        // run the default conversion
        return (ValueTask<ConversionResult>)(ConvertAsyncMethodInfo.Invoke(feature, new[] { context })!);
    }

    #region Reflection Helpers

    private static Assembly? _afWorkerCoreAssembly = null;
    private static Assembly AFWorkerCoreAssembly => _afWorkerCoreAssembly
        ??= AssemblyLoadContext.Default
            .LoadFromAssemblyName(
                Assembly.GetExecutingAssembly()
                    .GetReferencedAssemblies()
                    .Single(an => an.Name == "Microsoft.Azure.Functions.Worker.Core"))
        ?? throw new InvalidOperationException();

    private static Type? _inputConversionFeatureType = null;
    private static Type InputConvertionFeatureType => _inputConversionFeatureType
        ??= AFWorkerCoreAssembly
            .GetType("Microsoft.Azure.Functions.Worker.Context.Features.IInputConversionFeature", true)
        ?? throw new InvalidOperationException();

    private static MethodInfo? _convertAsyncMethodInfo = null;
    private static MethodInfo ConvertAsyncMethodInfo => _convertAsyncMethodInfo
        ??= InputConvertionFeatureType.GetMethod("ConvertAsync")
        ?? throw new InvalidOperationException();

    #endregion
}

Concrete ConverterContext class:

internal sealed class MyConverterContext : ConverterContext
{
    public MyConverterContext(Type targetType, object? source, FunctionContext context, IReadOnlyDictionary<string, object> properties)
    {
        TargetType = targetType ?? throw new ArgumentNullException(nameof(context));
        Source = source;
        FunctionContext = context ?? throw new ArgumentNullException(nameof(context));
        Properties = properties ?? throw new ArgumentNullException(nameof(properties));
    }

    public MyConverterContext(ConverterContext context, object? source = null)
    {
        TargetType = context.TargetType;
        Source = source ?? context.Source;
        FunctionContext = context.FunctionContext;
        Properties = context.Properties;
    }

    public override Type TargetType { get; }

    public override object? Source { get; }

    public override FunctionContext FunctionContext { get; }

    public override IReadOnlyDictionary<string, object> Properties { get; }
}

Service configuration:

public class Program
{
    public static void Main()
    {
        var host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults()
            .ConfigureServices(services =>
            {
                services.Configure<WorkerOptions>((workerOptions) =>
                {
                    workerOptions.InputConverters.Register<Converters.FromHttpRequestDataBodyConverter>();
                });
            })
            .Build();

        host.Run();
    }
}
like image 1
Moho Avatar answered Nov 08 '22 14:11

Moho