Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to parse form data using Azure Functions

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"

like image 245
mercer Avatar asked Jul 28 '17 02:07

mercer


2 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>();
like image 172
qJake Avatar answered Oct 17 '22 11:10

qJake


Based on the error message, you are using multipart/form-data as your request content type. But you haven't post any media type data to server.

If you just want to send some plain data to server, you could change the content type to application/x-www-form-urlencoded and modify your request body as following format.

name=attachments&anothername=anothervalue

If you want to get the form data from multi part post, you could using MultipartFormDataStreamProvider.

string filePath = "set a temp path to store the uploaded file";
var provider = new MultipartFormDataStreamProvider(filePath);
var multipartProvider = await req.Content.ReadAsMultipartAsync(provider);
var formData = multipartProvider.FormData;

Manually parse the content of request body.

string content = await req.Content.ReadAsStringAsync();
string formdata = content.Split(';')[1];
string[] namevalues = formdata.Split('&');
NameValueCollection col = new NameValueCollection();
foreach (string item in namevalues)
{
    string[] nameValueItem = item.Split('=');
    col.Add(nameValueItem[0], nameValueItem[1]);
}
like image 25
Amor Avatar answered Oct 17 '22 12:10

Amor