How to amend the built-in behavior of adding fields and associating workflows with new forms
By default, a single workflow is added when a new form is created. This workflow will send a copy of the form to the email address of the current backoffice user.
A single "data consent" field will also be added unless it has been disabled via configuration.
It's possible to amend this behavior and change it to fit your needs.
Implementing a Custom Behavior
Two interfaces are used to abstract the logic for setting default fields and workflows for a form. They are IApplyDefaultFieldsBehavior and IApplyDefaultWorkflowsBehavior respectively.
The default behaviors are defined using built-in, internal classes that implement this interface.
You can create your own implementation of these interfaces.
Example - Providing a Custom Apply Workflows Behavior
An illustrative example, adding a custom workflow that writes to the log, is shown below.
Firstly, the custom workflow:
usingSystem;usingSystem.Collections.Generic;usingUmbraco.Core.Composing;usingUmbraco.Core.Logging;usingUmbraco.Forms.Core.Attributes;usingUmbraco.Forms.Core.Enums;usingUmbraco.Forms.Core.Persistence.Dtos;namespaceMyNamespace{publicclassLogMessageWorkflow:WorkflowType {publicconststring LogMessageWorkflowId ="7ca500a7-cb34-4a82-8ae9-2acac777382d";privatereadonlyILogger<LogMessageWorkflow> _logger;publicLogMessageWorkflow(ILogger<LogMessageWorkflow> logger) { Id =newGuid(LogMessageWorkflowId); Name ="Test Workflow"; Description ="A test workflow that writes a log line"; Icon ="icon-edit"; _logger = logger; } [Setting("Message", Description ="The log message to write", View ="TextField")]publicstring Message { get; set; }publicoverrideList<Exception> ValidateSettings() {var exs =newList<Exception>();if (string.IsNullOrEmpty(Message)) {exs.Add(newException("'Message' setting has not been set")); }return exs; }publicoverrideWorkflowExecutionStatusExecute(WorkflowExecutionContext context) {_logger.LogInformation($"'{Message}' written at {DateTime.Now}");returnWorkflowExecutionStatus.Completed; } }}
Secondly, the custom implementation of IApplyDefaultWorkflowsBehavior:
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingUmbraco.Cms.Core.Hosting;usingUmbraco.Forms.Core;usingUmbraco.Forms.Core.Enums;usingUmbraco.Forms.Core.Providers;usingUmbraco.Forms.Web.Behaviors;usingUmbraco.Forms.Web.Models.Backoffice;namespaceMyNamespace{publicclassCustomApplyDefaultWorkflowsBehavior:IApplyDefaultWorkflowsBehavior {privatereadonlyWorkflowCollection _workflowCollection;privatereadonlyIHostingEnvironment _hostingEnvironment;publicCustomApplyDefaultWorkflowsBehavior(WorkflowCollection workflowCollection,IHostingEnvironment hostingEnvironment) { _workflowCollection = workflowCollection; _hostingEnvironment = hostingEnvironment; }publicvoidApplyDefaultWorkflows(FormDesign form) { // Retrieve the type of the default workflow to add.WorkflowType testWorkflowType =_workflowCollection[newGuid(LogMessageWorkflow.LogMessageWorkflowId)]; // Create a workflow object based on the workflow type.var defaultWorkflow =newFormWorkflowWithTypeSettings { Id =Guid.Empty, Name ="Log a message", Active =true, IncludeSensitiveData =IncludeSensitiveData.False, SortOrder =1, WorkflowTypeId =testWorkflowType.Id, WorkflowTypeName =testWorkflowType.Name, WorkflowTypeDescription =testWorkflowType.Description, WorkflowTypeGroup =testWorkflowType.Group, WorkflowTypeIcon =testWorkflowType.Icon, // Optionally set the default workflow to be mandatory (which means editors won't be able to remove it // via the back-office user interface). IsMandatory =true }; // Retrieve the settings from the type.Dictionary<string,Core.Attributes.Setting> workflowTypeSettings =testWorkflowType.Settings(); // Create a collection for the specific settings to be applied to the workflow. // Populate with the setting details from the type.var workflowSettings =newList<SettingWithValue>();foreach (KeyValuePair<string,Core.Attributes.Setting> setting in workflowTypeSettings) {Core.Attributes.Setting settingItem =setting.Value;var settingItemToAdd =newSettingWithValue { Name =settingItem.Name, Alias =settingItem.Alias, Description =settingItem.Description, Prevalues =settingItem.GetPreValues(), View =_hostingEnvironment.ToAbsolute(settingItem.GetSettingView()), Value =string.Empty };workflowSettings.Add(settingItemToAdd); } // For each setting, provide a value for the workflow instance (in this example, we only have one).SettingWithValue messageSetting =workflowSettings.SingleOrDefault(x =>x.Alias=="Message");if (messageSetting !=null) {messageSetting.Value="A test log message"; } // Apply the settings to the workflow.defaultWorkflow.Settings= workflowSettings; // Associate the workflow with the appropriate form submission event.form.FormWorkflows.OnSubmit.Add(defaultWorkflow); } }}
Finally, to register the custom implementation in place of the default one:
When adding a default workflow in code, it's possible to make it mandatory, which will prevent editors from removing it from a form.
You can see this in the example above, where the IsMandatory property of the created FormWorkflowWithTypeSettings instance is set to true.
Example - Providing a Custom Apply Fields Behavior
The following class shows the default implementation provided with Forms. You can copy this and customize it to your needs.
usingMicrosoft.Extensions.Options;usingUmbraco.Forms.Core.Configuration;usingUmbraco.Forms.Core.Models;usingUmbraco.Forms.Web.Extensions;usingUmbraco.Forms.Web.Models.Backoffice;namespaceUmbraco.Forms.Web.Behaviors{internalclassCustomApplyDefaultFieldsBehavior:IApplyDefaultFieldsBehavior {privatereadonlyFormDesignSettings _formDesignSettings;publicCustomApplyDefaultFieldsBehavior(IOptions<FormDesignSettings> formDesignSettings) => _formDesignSettings =formDesignSettings.Value;publicvirtualvoidApplyDefaultFields(FormDesign form) { // Add one page as a starting point.var page =newPage();form.Pages.Add(page); // Add one empty fieldset to the page to start with.var fieldset =newFieldSet { Id =Guid.NewGuid() };page.FieldSets.Add(fieldset); // Add one full-width (12cols) container/row to the fieldset.var container =newFieldsetContainer { Width =12 };fieldset.Containers.Add(container); // As all forms default to having StoreRecordsLocally we need to add the data consent field to the the form // (unless this feature has been explicitly disabled).if (_formDesignSettings.DisableAutomaticAdditionOfDataConsentField) {return; }container.AddDataConsentField(_formDesignSettings, _fieldCollection); // Add any further fields you require. } }}
Again, you will need to register your custom class, for example, in a composer with: