2

Kentico 12 only supports forms using the "Form" page-builder widget out of the box.

Can anyone provide examples of how one might utilise BizForms on an MVC _Layout.cshtml or in pages that do not use the page builder?

Acceptance criteria:

  • Must allow CMS users to edit the form and have the changes reflected on the site
  • Must allow the developer to manipulate/transform the submitted data prior to saving to Kentico and prior to sending notifications/autoresponders
  • Must correctly render custom FormSections and custom FormComponents used in the form builder
Hades
  • 1,975
  • 1
  • 23
  • 39

4 Answers4

2

The OP posted a blog about how to make this work.

The solution requires using some of Kentico's internal APIs for Form Builder rendering with Form Widgets and putting that code into a Controller action.

var formInfo = BizFormInfoProvider
    .GetBizFormInfo(formName, SiteContext.CurrentSiteName);

string className = DataClassInfoProvider
    .GetClassName(formInfo.FormClassID);

var existingBizFormItem = className is null
    ? null
    : BizFormItemProvider
        .GetItems(className)?.GetExistingItemForContact(
           formInfo, contactContext.ContactGuid);

var formComponents = formProvider
    .GetFormComponents(formInfo)
    .GetDisplayedComponents(
      ContactManagementContext.CurrentContact, 
      formInfo, existingBizFormItem, visibilityEvaluator);

var settings = new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    TypeNameHandling = TypeNameHandling.Auto,
    StringEscapeHandling = StringEscapeHandling.EscapeHtml
};

var formConfiguration = JsonConvert.DeserializeObject<FormBuilderConfiguration>(
    formInfo.FormBuilderLayout, settings);

return new FormWidgetViewModel
{
    DisplayValidationErrors = true,
    FormComponents = formComponents.ToList(),
    FormConfiguration = formConfiguration,
    FormName = formName,
    FormPrefix = Guid.NewGuid().ToString(),
    IsFormSubmittable = true,
    SiteForms = new List<SelectListItem>(),
    SubmitButtonImage = formInfo.FormSubmitButtonImage,
    SubmitButtonText = string.IsNullOrEmpty(formInfo.FormSubmitButtonText) 
      ? ResHelper.GetString("general.submit")
      : ResHelper.LocalizeString(formInfo.FormSubmitButtonText)
};

I took that idea and wrote a follow up post, Kentico EMS: MVC Widget Experiments Part 3 - Rendering Form Builder Forms Without Widgets, which shows we can also use Kentico's pre-built Form Widget view code to also get the expected rendering and form submission functionality.

<!-- ~/Views/Form/Form.cshtml -->

@using Kentico.Forms.Web.Mvc;
@using Kentico.Forms.Web.Mvc.Widgets;
@using Kentico.Forms.Web.Mvc.Widgets.Internal

@model FormWidgetViewModel

@{
    var config = FormWidgetRenderingConfiguration.Default;

    // @Html.Kentico().FormSubmitButton(Model) requires 
    // this ViewData value to be populated. Normally it
    // executes as part of the Widget rendering, but since
    // we aren't rendering a Widget, we have to do it manually

    ViewData.AddFormWidgetRenderingConfiguration(config);
}

@using (Html.Kentico().BeginForm(Model))
{
    @Html.Kentico().FormFields(Model)

    @Html.Kentico().FormSubmitButton(Model)
}
seangwright
  • 17,245
  • 6
  • 42
  • 54
1

The Form widget is rendered using a combination of the following view structure and the view model FormWidgetViewModel:

using (Ajax.Kentico().BeginForm( ... ))
{
    @Html.AntiForgeryToken()

    @Html.Kentico().FormFields(Model.FormComponents, Model.FormConfiguration, FormFieldRenderingConfiguration.Widget)

    // Render Model.SubmitButtonImage using @Html.Kentico().ImageInput( ... )
    // Or render a plain <input> using Model.SubmitButtonText
}

If you have the BizFormInfo object for the form, it is needed for the following properties:

new FormWidgetViewModel
{
    FormName = formInfo.FormName,
    FormConfiguration = IFormBuilderConfigurationRetriever.Retrieve(formInfo),
    FormComponents = IFormProvider.GetFormComponents(formInfo).GetDisplayedComponents( ... ),
    FormPrefix = // This may be optional outside of the Page Builder context,
    SubmitButtonText = formInfo.FormSubmitButtonText,
    SubmitButtonImage = formInfo.FormSubmitButtonImage
}

Inside Ajax.Kentico().BeginForm you can pass in the controller and action handling the form.

Use methods in IFormProvider to update or add the form submission and to send emails.

Update (see comments):

IFormBuilderConfigurationRetriever is marked internal, so it is not directly accessible. Its implementation in turn uses IFormBuilderConfigurationSerializer to deserialize formInfo.FormBuilderLayout. That interface is also marked internal. Further, the implementation of that interface uses the internal FormBuilderTypesBinder.

This means that there is no API available to retrieve Model.FormConfiguration. As of Kentico 12.0.16, you would need to recreate the internal functionality. The basic implementation is like this:

JsonConvert.DeserializeObject<FormBuilderConfiguration>(formInfo.FormBuilderLayout, new JsonSerializerSettings
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver(),
        TypeNameHandling = TypeNameHandling.Auto,
        SerializationBinder = // Set to the internal FormBuilderTypesBinder, which validates only known form builder types
        StringEscapeHandling = StringEscapeHandling.EscapeHtml
    });

Yuriy Sountsov
  • 179
  • 1
  • 6
  • Hi Yuyiy. I don't seem to have access to `IFormBuilderConfigurationRetriever` as it's marked as `internal`. Looking at the internal implementation of this it uses `IFormBuilderConfigurationSerializer` to deserialize the form-builder layout but `IFormBuilderConfigurationSerializer` is also marked as `internal`. The serialization settings also make use of `internal class FormBuilderTypesBinder` ... Is there a way to do all of this without using these and without having to rip all of the internal code out of dotPeek? – Hades Apr 02 '19 at 12:26
  • Good catch! Looks like there is no other way to do that than to recreate that `internal` code, or at least the deserialization part. Luckily, `FormBuilderTypesBinder` is mostly validating that the types are appropriate, so you may be able to skip that part as long as you are not creating the form layout outside of the admin's Form Builder. This is a good use case though, so I recommend sending a description of it to the Kentico Product team at: productmanagement@kentico.com or if you want you can send the email to support@kentico.com which will be able to forward it to the Product team. – Yuriy Sountsov Apr 02 '19 at 23:46
  • We've already been in touch with support and the consultancy team at Kentico. Both have basically said that it's not a supported use-case in the current version and that they *might* consider it for a future version - which doesn't help us with the current project.Thanks for your input - if you update your answer to include the info about the internal code and manual deserialisation I'll accept it as the answer (since this is what we've done now and it seems to work) – Hades Apr 03 '19 at 08:20
0

Hades, You might user Forms API (https://docs.kentico.com/api12/content-management/form-data) to save/access form data and implement completely custom layout for it. Hope that helps!

Roman Hutnyk
  • 1,549
  • 9
  • 14
  • Thanks for the reply Roman but this doesn't meet the acceptance criteria. – Hades Mar 11 '19 at 14:37
  • Please see the list of event you might hook up for item 2 from acceptance criteria: https://docs.kentico.com/k12/custom-development/handling-global-events/reference-global-system-events#Reference-Globalsystemevents-BizFormItemEvents – Roman Hutnyk Mar 11 '19 at 15:35
  • Hi Roman. The global events are only useful in the CMS context. In my use-case I need to set the value of a field based on which section of the site the visitor is within. The value of this field will be used as the "To" email address for the notification email. – Hades Mar 12 '19 at 09:24
  • Have you considered custom macro for "To" email address field? I'm not 100% sure, but I think current document is available in macro method context – Roman Hutnyk Mar 12 '19 at 09:27
  • Hi Roman - How do I apply the data from the MVC application to the macro? – Hades Mar 12 '19 at 12:50
0

You can take a look at the Kentico.Forms.Web.Mvc.Widgets namespace in your MVC project (it should be included by default).

In there is a KenticoFormWidgetController controller class which renders form partials and accepts form submissions. You could possibly use the Index route of that controller to render a partial of your form, however it is unknown to me how exactly the route looks.

If you have source code access to Kentico you could check the internals of it by yourself.

Alen Genzić
  • 1,398
  • 10
  • 17
  • Hi Alen, thanks for the response. I've had a look at the source-code relating to that controller and the built-in form widget - most of the classes it uses are marked as `internal` preventing their use in my own custom code. – Hades Mar 13 '19 at 15:38
  • Yes you are right about that, I guess it is best to contact Kentico support in that case, maybe they have some ideas but it seems to me that it is not possible as of yet – Alen Genzić Mar 14 '19 at 10:11