Monday, February 15, 2021

Custom Binding for Azure Functions

Azure Functions are a great development tool, allowing us to create serverless software. However, one detail was bothering me when I create a function: The HTTP Trigger receives a HTTPRequest object and we need to extract the parameters from the request. This becomes kind of a low-level code mixed with our business code. It’s like going back in time some years.

The solution is simple: we can create a custom binding to extract objects from the HTTPrequest. I’m surprised I haven’t found many references to this online. The most complete reference I found was a reply on stackoverflow: https://stackoverflow.com/questions/45183820/how-to-pass-parameters-by-post-to-an-azure-function . However, we can still improve the code.

Let’s see how we can implement this custom binding and how it can improve the development of Azure Functions. We will analyze the classes needed to build the custom binding.

BindingExtensionProvider

This class is responsible for adding a binding rule to the azure function configuration. While adding the binding rule, a link is stablished between a custom attribute for the binding rule (FromBodyAttribute in our example) and the binding rule factory (FromBodyBindingProvider). This is the only purpose of the attribute, stablish this link.

    public class BindingExtensionProvider : IExtensionConfigProvider
    {
        private readonly ILogger logger;
        public BindingExtensionProvider(ILogger<Startup> logger)
        {
            this.logger = logger;
        }

        public void Initialize(ExtensionConfigContext context)
        {
            // Creates a rule that links the attribute to the binding
            context.AddBindingRule<FromBodyAttribute>().Bind(new FromBodyBindingProvider(this.logger));
        }
    }


FromBodyAttribute

It’s a simple attribute with no special code, but it’s used by BindingExtensionProvider to link a parameter in the Azure Function with this binding provider. We can apply the attribute to a parameter and the link will be stablished.

 

    [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)]
    [Binding]
    public sealed class FromBodyAttribute : Attribute
    {
    }

 

FromBodyBindingProvider

This class implements the interface IBindingProvider and works like a factory for our custom binding rule. The BindingExtensionProvider inserts an instance of this class in the Azure Function configuration.

This class creates the next one, the binding rule. and here I included one small touch of wisdom: The binding rule is generic, the code captures the data type of the parameter in the context and dynamically creates the binding rule using this data type. By doing so, the data type will be identified from the parameter declaration, no additional clutter in the middle.

 

    public class FromBodyBindingProvider : IBindingProvider
    {
        private readonly ILogger logger;
        public FromBodyBindingProvider(ILogger logger)
        {
            this.logger = logger;
        }

        public Task<IBinding> TryCreateAsync(BindingProviderContext context)
        {
            IBinding binding = CreateBodyBinding(logger, context.Parameter.ParameterType);
            return Task.FromResult(binding);
        }

        private IBinding CreateBodyBinding(ILogger log,Type T)
        {
            var type = typeof(FromBodyBinding<>).MakeGenericType(T);
            var a_Context = Activator.CreateInstance(type, new object[] { log });
            return (IBinding)a_Context;
        }
    }

 

FromBodyBinding

This class implements the IBinding interface. This is the class injected inside Azure Function configuration. It provides an instance of IValueProvider, responsible to extract the value from the HTTPRequest.

 

    public class FromBodyBinding<T> : IBinding
    {
        private readonly ILogger logger;
        public FromBodyBinding(ILogger logger)
        {
            this.logger = logger;
        }
        public Task<IValueProvider> BindAsync(BindingContext context)
        {
            // Get the HTTP request
            var request = context.BindingData["req"] as HttpRequest;
            return Task.FromResult<IValueProvider>(new FromBodyValueProvider<T>(request, logger));
        }

        public bool FromAttribute => true;


        public Task<IValueProvider> BindAsync(object value, ValueBindingContext context)
        {
            return null;
        }

        public ParameterDescriptor ToParameterDescriptor() => new ParameterDescriptor();
    }

 

FromBodyValueProvider

This class implements the IValueProvider interface and is the responsible for extracting the object from the HTTPRequest. This is where we will use the type we got from the original parameter and used as a generic type for the FromBodyBinding class.

 

    public class FromBodyValueProvider<T> : IValueProvider
    {
        private HttpRequest request;
        private ILogger logger;

        public FromBodyValueProvider(HttpRequest request, ILogger logger)
        {
            this.request = request;
            this.logger = logger;
        }

        public async Task<object> GetValueAsync()
        {
            try
            {
                string requestBody = await new StreamReader(this.request.Body).ReadToEndAsync();
                T result = JsonConvert.DeserializeObject<T>(requestBody);
                return result;
            }
            catch (System.Exception ex)
            {
                this.logger.LogCritical(ex, "Error deserializing object from body");

                throw ex;
            }
        }

        public Type Type => typeof(object);
        public string ToInvokeString() => string.Empty;
    }

 

Linking Everything

The Azure Functions have evolved along the time. Some time ago, Azure functions used the interface IWebJobsHostBuilder on their startup function. Nowadays this interface was replaced by IFunctionsHostBuilder.

The old IWebJobsHostBuilder interface had a direct method to add the binding extension. On the other hand, the IFunctionsHostBuilder doesn’t. In order to solve this problem we need to use a strange workaround.

Once again, stackoverflow comes to the rescue. This link explains the workaround: https://stackoverflow.com/questions/57564396/how-do-i-mix-custom-parameter-binding-with-dependency-injection-in-azure-functio

 

        public override void Configure(IFunctionsHostBuilder builder)
        {
            string SqlConnection = Environment.GetEnvironmentVariable("SqlConnectionString");
            builder.Services.AddDbContext<VotingContext>(x => x.UseSqlServer(SqlConnection));


            // IWebJobsBuilders instance
            var wbBuilder = builder.Services.AddWebJobs(x => { return; });

            // And now you can use AddExtension
            wbBuilder.AddExtension<BindingExtensionProvider>();
        }

 

Using the Binding in a Function

The code below is an example of how to use the binding we created in a function.

 

        [FunctionName("Voting")]
        public async Task<IActionResult> RegisterVote(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = "Sessions/Vote")]
            HttpRequest req,
            ILogger log, [libTools.Bindings.FromBody] VotesRecord votes)
        {

        }

 

Conclusion

It’s always a good practice to create and re-use tools to make our code more high-level, I hope this will help to improve your code. I published this custom binding on GitHub, feel free to improve it: https://github.com/DennesTorres/HttpBinding

The post Custom Binding for Azure Functions appeared first on Simple Talk.



from Simple Talk https://ift.tt/3s0Ayfl
via

No comments:

Post a Comment