Skip navigation

Model Binding with Episerver Forms Webhooks

As we know, webhooks are POST requests containing JSON data in the body of the request. We also know that in version 1.0.0.9000 of Episerver Forms, the Content-Type header is set to application/x-www-form-urlencoded. Because of this mismatch, we are unable to use the default MVC model binding. It would be really nice to just define our action method as:

public ActionResult WebhookTest(IEnumerable<KeyValuePair<string, object>> fields)

But “fields” will be null every time.  Are we out of luck?  Must we wait and see if this issue gets resolved?  Did Pete the Cat cry?  Goodness, no.  He just kept walking along and singing his song  (I have kids that are into Pete the Cat, what can I say?).

The good news here is that the content type mismatch bug has been resolved in version 1.1.2.9000 of Episerver Forms, thanks to Episerver’s continuous release process. This means that if you are running the latest version of Episerver Forms, you can use the above signature for your action method, and “fields” will no longer be null every time.  Wahoo! We’re done, right?  Pack up and go on holiday?  I think not.

In my last post, I showed that some very relevant data for the submission is sent to your webhook in the form of HTTP headers. It would be nice to include this in our model. Plus, it would be great to add a little more structure to our incoming data. Roll up your sleeves, it’s about to get fun!

One last note of caution:  at some point, a property was added to the webhooks.  If you’re on the latest version, make sure the checkbox for “POST data in JSON format” is checked.

Edit Webhook

First off, let’s build a model for storing all this submission data. My model will contain two Dictionary<string, object> properties – one for the fields, and another for all those system column fields. Also, I recall there being three form-specific headers in the request, so I’ll make three string fields for those. The request body is going to deserialize as a list of key-value pairs, so I’ll create a single public method for adding a key-value pair. This method will take care of adding it to the proper dictionary:

[ModelBinder(typeof(WebhookFormDataModelBinder))]
public class WebhookFormData
{
    const string SystemColumnNamePrefix = "SYSTEMCOLUMN_";
    public string Event { get; set; }
    public string Signature { get; set; }
    public string SubmissionID { get; set; }
    public Dictionary<string, object> Fields { get; set; }
    public Dictionary<string, object> SystemColumns { get; set; }
    public WebhookFormData()
    {
        Fields = new Dictionary<string, object>();
        SystemColumns = new Dictionary<string, object>();
    }
    public void AddItem(KeyValuePair<string, object> item)
    {
        var isSystemColumn = item.Key.StartsWith(SystemColumnNamePrefix);
        if (isSystemColumn)
        {
            AddSystemColumn(item);
            return;
        }
        Fields[item.Key] = item.Value;
    }
    private void AddSystemColumn(KeyValuePair<string, object> item)
    {
        var key = "";
        if (item.Key != null && item.Key.Length > SystemColumnNamePrefix.Length)
        {
            key = item.Key.Substring(SystemColumnNamePrefix.Length);
        }
        SystemColumns[key] = item.Value;
    }
}

Now that we have our model, we need a model binder. This will tell .NET how to translate the request into our model class:

public class WebhookFormDataModelBinder : IModelBinder
{
    const string FormsEventHeaderName = "X-EPiServer-Forms-Event";
    const string FormsSignatureHeaderName = "X-EPiServer-Forms-Signature";
    const string FormsSubmissionIDHeaderName = "X-EPiServer-Forms-Delivery";
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var body = GetRequestBody(controllerContext);
        var formData = GetFormData(body);
        var headers = controllerContext.RequestContext.HttpContext.Request.Headers;
        var model = new WebhookFormData
        {
            Event = headers[FormsEventHeaderName],
            Signature = headers[FormsSignatureHeaderName],
            SubmissionID = headers[FormsSubmissionIDHeaderName]
        };
        foreach (var item in formData)
        {
            model.AddItem(item);
        }
        return model;
    }
    private string GetRequestBody(ControllerContext controllerContext)
    {
        var request = controllerContext.HttpContext.Request;
        request.InputStream.Seek(0, SeekOrigin.Begin);
        using (var reader = new StreamReader(request.InputStream))
        {
            return reader.ReadToEnd();
        }
    }
    private IEnumerable<KeyValuePair<string, object>> GetFormData(string requestBody)
    {
        if (string.IsNullOrWhiteSpace(requestBody))
            return Enumerable.Empty<KeyValuePair<string, object>>();
        return JsonConvert.DeserializeObject<IEnumerable<KeyValuePair<string, object>>>(requestBody);
    }
}

Now we can change the signature of our action method:

[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult WebhookTest(WebhookFormData formData)
{
    if (formData != null)
    {
        Debug.Write(JsonConvert.SerializeObject(formData, Formatting.Indented));
    } 
    var method = Request.HttpMethod;
    return Content(method + ": Magical webhook unicorns abound.");
}

Obviously, you wouldn’t go through all this just to turn around, serialize it, and write to the debug output window.  But this should give you some ideas for what’s possible with webhooks and Episerver Forms.

Happy webhooking!