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?
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
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();
}
}
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