How to parse form data using Azure Functions

Viewed 4635

I am trying to get form data within an Azure function.

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info("C# HTTP trigger function processed a request.");
    NameValueCollection col = req.Content.ReadAsFormDataAsync().Result;   
    return req.CreateResponse(HttpStatusCode.OK, "OK");
}

I am getting the following error:

Exception while executing function: System.Net.Http.Formatting: No MediaTypeFormatter is available to read an object of type 'FormDataCollection' from content with media type 'multipart/form-data'.

I am trying to parse inbound emails via SendGrid as described here. https://sendgrid.com/docs/Classroom/Basics/Inbound_Parse_Webhook/setting_up_the_inbound_parse_webhook.html

The incoming request looks correct.

--xYzZY Content-Disposition: form-data; name="attachments"

0 --xYzZY Content-Disposition: form-data; name="text"

Hello world --xYzZY Content-Disposition: form-data; name="subject"

Subject --xYzZY Content-Disposition: form-data; name="to"

3 Answers

Since there doesn't appear to be a good way to convert an IFormCollection to a custom model / viewmodel type, I wrote an extension method for this.

It's a shame that Azure Functions v2/v3 doesn't support this out of the box yet.

public static class FormCollectionExtensions
{
    /// <summary>
    /// Attempts to bind a form collection to a model of type <typeparamref name="T" />.
    /// </summary>
    /// <typeparam name="T">The model type. Must have a public parameterless constructor.</typeparam>
    /// <param name="form">The form data to bind.</param>
    /// <returns>A new instance of type <typeparamref name="T" /> containing the form data.</returns>
    public static T BindToModel<T>(this IFormCollection form) where T : new()
    {
        var props = typeof(T).GetProperties();
        var instance = Activator.CreateInstance<T>();
        var formKeyMap = form.Keys.ToDictionary(k => k.ToUpper(), k => k);

        foreach (var p in props)
        {
            if (p.CanWrite && formKeyMap.ContainsKey(p.Name.ToUpper()))
            {
                p.SetValue(instance, form[formKeyMap[p.Name.ToUpper()]].FirstOrDefault());
            }
        }

        return instance;
    }
}

This will attempt to bind an IFormCollection to whatever model type you pass in. Property names are case insensitive (i.e. you can map firstname=Bob to public string FirstName { get; set; }.

Usage:

var myModel = (await httpReq.ReadFormAsync()).BindToModel<MyModel>();

To read form data, you should use the method ReadAsMultipartAsync:

data = await req.Content.ReadAsMultipartAsync().ConfigureAwait(false);
Related