Function & Handler in my Az Functions

I like to compare this with an orange, the validation is what you see and what you validate before you buy the orange. Then you peel it and eat the goodies.

What I try to do to achieve this I will try to show you in a couple of images and code samples.

Function.cs

Here I will validate my input, is the input empty, does it pass a schema validation (JSON,XML)

You can even write your own custom validation for a csv-file.

Important! I do not process any content here, only validate it (of course there are a few exceptions)

This is for me a typical function.cs, containing some logging and validation including error handling

[FunctionName("INT0001_Message_ReceivingSystem")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "invoice")] HttpRequest req)
    {
        using (_logger.BeginScope("{INTId}", "INT0001"))
        {
            try
            {
                var inputMessage = await new StreamReader(req.Body).ReadToEndAsync();
                _logger.LogInformation("inputMessage-http\r\n{inputMessage-http}", inputMessage); //mandatory output log, important to take GDPR in consideration 

                ValidateQueryParam(req.Query);

                ValidateHeaderParam(req.Headers);

                //schema validate message, if ok result will be deserialized to object<T>
                var result = await _jsonValidator.ValidateAndDeserialize<Models.FunctionTrigger.Invoice>(inputMessage, "Invoice-schema.json"); //throw InvlidCastException on failure

                await _handler.HandleMessage(result); //only process data if inputMessage is verfied and accepted

                return new OkObjectResult("200 OK");
            }
            catch (ValidateException vex)
            {
                _logger.LogWarning(vex.Message, vex);
                return new BadRequestObjectResult(vex.Message); //bad request from schema validation, 4xx response code
            }
            catch (InvalidCastException icex)
            {
                _logger.LogWarning(icex.Message, icex);
                return new BadRequestObjectResult(icex.Message); //bad request from schema validation, 4xx response code
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
                throw; //if InternalServerErrorResult is returned the az func will successfully exit.
                       //Use kusto (requests | where resultCode == 500) to track the 500 response code
                       //If the http-trigger is a backend to a API then the operation in the API will result in a failure if the api is connected to an APPI instance

                //return new InternalServerErrorResult();
            }
        }
    }

Three lines perform the validation, line 12, 14 and 17. Lets look closer on line 12, ValidateQueryParameters.

A few line of codes are replaced with a singel line of code, easy to read and easy to understand. By using a summary you take this up a level. This is for me quality code, easy to read and easy to understand.

/// <summary>
/// Perform a set of validations on request query parameters
/// </summary>
/// <param name="query"></param>
/// <param name="error"></param>
/// <returns>bool, output ref error</returns>
private void ValidateQueryParam(IQueryCollection query)
{
    if (string.IsNullOrWhiteSpace(query["sender"]) ||
        string.IsNullOrWhiteSpace(query["senderId"]))
    {
        throw new ValidateException($"Missing required query parameter key/value (sender, senderId), adjust your parameters and try again.");
    }
}

Handler.cs

If my input is valid it ends up here, from here i can make some assumptions due to my validations.

Her is a simple example of my handler.cs

public async Task HandleMessage(Models.FunctionTrigger.Invoice invoice)
{
    using (_logger.BeginScope("{EntityId}", invoice.InvoiceId.ToString()))
    {
        _logger.LogInformation($"Processing {invoice.Rows.Count} invoice rows"); //Creates a loginformation with no property
       
        var outputMessage = invoice.
                                MapToXMLInvoice().      //map inovice to outputformat (xml)
                                SerializeToXmlUTF8();   //serialize result 
        _logger.LogInformation("outputMessage-servicebus\r\n{outputMessage-servicebus}", outputMessage);

        await _servicebusSender
            .SendMessageAsync(new ServiceBusMessage(outputMessage)); //add outputMessage to servicebus
    }
}

Line 7 and 12 are of importance here, the rest is logging.

The handler process the content a.k.a it transforms the data and send it to the receiving system, in this case a servicebus (this example could also be a binding here it’s enabled using DI)

Some might say I miss use the extension but for the readability it’s outstanding, it makes reading more easy.

Lets look at line 7, we do two things here (indent and row breaks can make your code easier to read)
invoice.MapToXMLInvoice –> the name MapToXMLInvoice is self explanatory here we transform the input to the output

Lets have a look at the transform, easy to read, encapsulated in its own class and easy to change

/// <summary>
/// Map an incoming message to a xml Invoice message
/// </summary>
/// <param name="invoice"></param>
/// <returns>XML Invoice</returns>
public static Models.ReceivingSystem.Invoice MapToXMLInvoice(this Models.FunctionTrigger.Invoice invoice)
    => new()
    {
        InvoiceId = invoice.InvoiceId,
        Rows = invoice.Rows
            .Select(x => new Models.ReceivingSystem.Row
            {
                Amount = x.Amount.ToString(),
                Qty = x.Qty.ToString(),
                Description = x.Description
            }).ToList()
    };

On line 12 we send the output to a servicebus, I use the same pattern if I where to send it to a API or any other type of system.
Create a client.cs and use one line of code from your handler to send it.

Summarize

For me it’s important to have a structured way of working with my azure functions, I never write all my code in the run method. For me its important to avoid scrolling vertically (almost all thinks the same regarding horizontally), a method should be kept small, break it down to methods/classes.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top