Migrate to Optimizely Forms 6.0.0
Migrate from Optimizely Forms 5.x (EPiServer.Forms 5.x) to the major version 6.0.0.
This guide covers the steps required to migrate a project from Optimizely Forms 5.x to Optimizely Forms 6.0.0. Optimizely Forms 6.0.0 targets Optimizely Content Management System (CMS) 13, so you must migrate to CMS 13 before you can migrate to Optimizely Forms 6.0.0.
Prerequisites
- .NET 10 or later
- CMS 13
- CMS 13 migration (if not already on CMS 13)
Migration steps
-
Update NuGet package references – Update the Forms NuGet package version references to 6.0.0 in your
.csproj:<PackageReference Include="EPiServer.Forms.Core" Version="6.0.0" /> <PackageReference Include="EPiServer.Forms.UI" Version="6.0.0" /> <!-- Or using umbrella package: --> <PackageReference Include="EPiServer.Forms" Version="6.0.0" /> <!-- If using Azure Key Vault crypto: --> <PackageReference Include="EPiServer.Forms.Crypto.AzureKeyVault" Version="3.0.0" />Remove the following package reference (the package no longer exists):
<PackageReference Include="EPiServer.AddOns.Helpers" /> -
Register Forms services in DI – Forms 6.0.0 drops the old
[ServiceConfiguration]auto-registration pattern (where possible). You must now call the Forms extension methods explicitly in yourProgram.csorStartup.cs.- Before (5.10.7) – Services were auto-registered with
InitializationModule.ConfigureContainer()and[ServiceConfiguration]attributes. No explicit registration required. - After (6.0.0) – Add the following code in
Program.csorStartup.cstoto register all Forms, FormsUI, and FormsCore services:If you need only partial registration:builder.Services.AddForms();For Azure Key Vault encryption:builder.Services.AddFormsCore(); // Core only builder.Services.AddFormsUI(); // UI + Core builder.Services.AddForms(); // Full stack (recommended)builder.Services.AddFormsCryptoAzureKeyVault();
- Before (5.10.7) – Services were auto-registered with
-
Migrate configuration from
Forms.configtoappsettings.json. The legacyForms.configXML configuration file is no longer supported. All settings must be inappsettings.json.-
Forms.configbefore (5.10.7)<EPiServerForms WorkInNonJSMode="false" InjectFormOwnJQuery="false" InjectFormOwnStylesheet="true" SendMessageInHTMLFormat="true" DefaultUploadExtensionBlackList=".exe,.bat,.cmd" RenderingFormUsingDivElement="false" DisableFormCookies="false" HidePartialSubmissions="false" /> -
appsettings.jsonafter (6.0.0){ "Forms": { "WorkInNonJSMode": false, "InjectFormOwnJQuery": false, "InjectFormOwnStylesheet": true, "SendMessageInHTMLFormat": true, "DefaultUploadExtensionBlackList": ".exe,.bat,.cmd", "RenderingFormUsingDivElement": false, "DisableFormCookies": false, "HidePartialSubmissions": false } }You must also move your storage provider configuration from
Forms.configtoappsettings.json:{ "Forms": { "Providers": { "DefaultProvider": "ddsProvider", "ddsProvider": { "type": "EPiServer.Forms.Core.Data.Internal.DdsEncryptedPermanentStorage, EPiServer.Forms.Core" } } } }
-
-
Remove references to deleted interfaces and classes –
IEPiServerFormsCoreConfig,IEPiServerFormsUIConfig, and theEPiServer.Forms.Configurationnamespace were removed.IEPiServerFormsCoreConfigandIEPiServerFormsUIConfigBeforeAfterpublic class MyService { private readonly IEPiServerFormsCoreConfig _config; public MyService(IEPiServerFormsCoreConfig config) { _config = config; } public bool IsDisabled => _config.DisableFormCookies; }using Microsoft.Extensions.Options; using EPiServer.Forms.Core.Options; public class MyService { private readonly FormsConfigOptions _config; public MyService(IOptions<FormsConfigOptions> config) { _config = config.Value; } public bool IsDisabled => _config.DisableFormCookies; }EPiServer.Forms.Configurationnamespace – Remove allusing EPiServer.Forms.Configuration;directives and replace withappsettings.json-based configuration.
-
Update custom post-submission actors – Remove
ActiveExternalFieldMappingTableusage and removing overrides of removed methods inSendEmailAfterSubmissionActor. If your code subclassesSendEmailAfterSubmissionActorand overridesGetFriendlySummaryTextorGetPredefinedPlaceHolders, remove those overrides. The logic now lives inDefaultPlaceHolderProvider.Before
public class MyActor : PostSubmissionActorBase { public override object Run(object input) { var table = ActiveExternalFieldMappingTable; // REMOVED // ... } }After
public class MyActor : PostSubmissionActorBase { private readonly IExternalFieldMappingService _externalFieldMappingService; public MyActor(IExternalFieldMappingService externalFieldMappingService) { _externalFieldMappingService = externalFieldMappingService; } public override object Run(object input) { var table = _externalFieldMappingService.GetActiveFieldMappingTable(FormIdentity); // ... } } -
Update validator types implementations – Update
IExcludeValidatorTypesimplementations.Before
public class MyElement : ValidatableElementBlockBase, IExcludeValidatorTypes { public Type[] ValidatorTypesToBeExcluded => new[] { typeof(RegularExpressionValidator) }; }After
[AvailableValidatorTypes(Include = new[] { typeof(RequiredValidator) })] public class MyElement : ValidatableElementBlockBase { // Use AvailableValidatorTypesAttribute instead (AvailableValidatorTypesAttribute inherits from IAvailableValidatorTypesAttribute) } -
Update subclasses of
InitializationModule– If you have a partial class or subclass ofEPiServer.Forms.EditView.InitializationModule:- Remove any
ConfigureContainer(ServiceConfigurationContext)override and move its logic toservices.AddForms()registration. - Remove overrides of
Instance_DeletedContentandInstance_PublishedContent. If you need to handle these events, subscribe directly toIContentEvents.
- Remove any
-
Replace removed
FormBusinessServicemethod calls – Replace the following removedFormBusinessServicemethod calls:ReCalculateStepIndexIfNeeded(FormIdentity, FormContainerBlock, Submission, int, Guid, bool)– Replace withReCalculateStepIndexIfNeeded(FormContainerBlock, Submission, int, bool).IsMatchDependCondition(IElementDependant, IDictionary<string, object>)– Replace withIsConditionMatched(IElementDependant, object submittedFieldValue).ReCalculateStepIndex(FormContainerBlock, int, IDictionary<string, object>, bool)– Replace withReCalculateStepIndexIfNeeded(FormContainerBlock, Submission, int, bool).BuildStepsAndElements(FormContainerBlock, bool)– Use overload requiringcurrentFormparameter.ToHtmlStringWithFriendlyUrls(XhtmlString)– Replace withXhtmlRenderService.ToHtmlStringWithFriendlyUrls(...).ReplaceFragmentWithFriendlyUrls(IStringFragment)– Replace withXhtmlRenderService.ReplaceFragmentWithFriendlyUrls(...).
-
Replace removed extension methods – Replace the following removed extension methods:
FormsExtensions.HasAnyExternalSystem()– Replace withExternalSystemService.HasExternalSystem().contentRef.GetContent(string language)(two-parameter) – Replace withcontentRef.GetContent(bool shouldFallback, string language).str.SplitBySeparator(string separator)– Replace withstr.SplitBySeparators(string[] separators).FormsExtensionsUI.RenderWebFormViewToString(...)– Use standard ASP.NET Core view rendering.IExternalFieldMappingService.GetAllExternalSystems()– Replace withExternalSystemService.GetAllExternalSystems().ExternalSystemEditorDescriptors.GetSelectedFieldsName(FormIdentity, string)– Use its protected methodGetSelectedFieldsName(FormContainerBlock, ElementBlockBase, string).
-
Remove form data usage –
IForm.DataandForm.Datausage.Before
var data = form.Data; // PropertyBagAfter
var data = submission.Data; // IDictionary<string, object> -
Update subclasses – Update sublcasses of
PropertyGenericList\<T\>.Before
public override object SaveData(PropertyDataCollection properties) { return base.SaveData(properties); }After
public override object SaveData() { return base.SaveData(); } -
Review
PropertyGenericList\<T\>model types forSystem.Text.Jsoncompatibility –PropertyGenericList<T>now usesSystem.Text.Jsonwith camelCase naming. If your model types useNewtonsoft.Json-specific attributes, update them.Before
using Newtonsoft.Json; public class MyModel { [JsonIgnore] public string InternalProp { get; set; } [JsonProperty("my_key")] public string MyKey { get; set; } }After
using System.Text.Json.Serialization; public class MyModel { [JsonIgnore] public string InternalProp { get; set; } [JsonPropertyName("my_key")] public string MyKey { get; set; } }ImportantExisting data serialized with
Newtonsoft.Json(camelCase or otherwise) should round-trip correctly if the property names match. Verify this for each model type used inPropertyGenericList<T>. -
Update scheduled job references – If you reference scheduled job types directly, switch to
IScheduledJobRepository.Before
var job = new UpdateUploadFolderACLJob(); // REMOVED (now internal)After
var job = _scheduledJobRepository.Get(Guid.Parse("EB289EAA-5485-4886-A59C-5C5E0B950199")); -
Update
SpecializedPropertiesif subclassed – If you subclass anySpecializedPropertiestype, verify database property definition names are consistent. CMS 13 does not allow spaces in property definition names, so the new display names are without spaces. The following display names changed:Dependency conditions collection– Changed toDependencyConditionsCollection.Message Template– Changed toMessageTemplate.Property Field Mapping Collection– Changed toPropertyFieldMappingCollection.Property Connected Data Source Collection– Changed toPropertyConnectedDataSourceCollection.Validator with message collection– Changed toValidatorWithMessageCollection.Web Hook– Changed toWebHook.
NoteThis should sync automatically when the CMS starts, but if you have custom code that queries property definitions by name, you may need to update it.
-
Update
FreeGeolocationProviderconfiguration – If you configuredFreeGeolocationProviderinForms.configorweb.configprovider XML, update it toappsettings.json.Before
<providers> <add name="FreeGeoIP" type="..." geoApiUrl="https://api.freegeoip.app/json/{0}?apikey=YOUR_KEY" /> </providers>After
services.AddFreeGeolocationProvider(options => { options.GeoApiUrl = "https://api.freegeoip.app/json/{0}?apikey=YOUR_KEY"; }); // OR services.AddFreeGeolocationProvider("https://api.freegeoip.app/json/{0}?apikey=YOUR_KEY");NoteIf you are using the
EPiServer.CloudPlatform.Cmspackage, it registers a defaultIGeolocationProvider. Register the FormsFreeGeoLocationProviderafterAddCmsCloudPlatformSupport(...)so it takes precedence.services.AddCmsCloudPlatformSupport(_configuration); services.AddFreeGeolocationProvider("https://api.freegeoip.app/json/{0}?apikey=YOUR_KEY"); -
Update custom actor constructors – If you have custom actors with parameterless constructors that use
ServiceLocator, update it to let the DI container provide dependencies.Before
public class MyActor : PostSubmissionActorBase { public MyActor() : this(ServiceLocator.Current.GetInstance<MyService>()) { } public MyActor(MyService service) { ... } }After
public class MyActor : PostSubmissionActorBase { public MyActor(MyService service) { ... } }
Troubleshoot common migration errors
Missing services.AddForms() call
services.AddForms() call- Symptom –
InvalidOperationException: Unable to resolve service for type 'EPiServer.Forms.Core.Internal.DataSubmissionService'. - Fix – Add
services.AddForms()inProgram.cs.
Forms.config settings silently ignored
Forms.config settings silently ignored- Symptom – Behaviors configured in
Forms.config(likeWorkInNonJSModeand upload blacklist) no longer apply after upgrade. - Fix – Migrate all settings to
appsettings.jsonunder theFormskey.
PropertyGenericList deserialization failures
PropertyGenericList deserialization failures- Symptom – Editors show empty lists or errors for properties that previously had data.
- Fix – Check that your model types are
System.Text.Json-compatible. Verify camelCase property naming matches stored JSON. Consider a one-time data migration if necessary.
Scheduled jobs not found
- Symptom – Accessing
UpdateUploadFolderACLJoband others causes a compile or runtime error. - Fix – Use
IScheduledJobRepository.Get(Guid)with the job GUIDs instead.
Azure Key Vault crypto engine not registered
- Symptom – Decrypted CSV export not available, or
IFormCryptoEnginenot resolved. - Fix – Call
services.AddFormsCryptoAzureKeyVault()in addition toservices.AddForms().
Email not sent after upgrade
- Symptom – Emails are not delivered with errors in logs mentioning
ISmtpClient. - Fix – Ensure
FormsSmtpClient(MailKit) is properly registered withservices.AddFormsUI(). Verify SMTP settings inappsettings.json.
Migration checklist
- Update
EPiServer.FormsNuGet package to 6.0.0. - Remove
EPiServer.AddOns.Helperspackage reference. - Add
services.AddForms()inProgram.cs. - Migrate
Forms.configsettings toappsettings.json. - Remove all references to
IEPiServerFormsCoreConfig,IEPiServerFormsUIConfig,EPiServer.Forms.Configuration.*. - Update or remove custom
InitializationModulesubclass code. - Update custom actors by removing
ActiveExternalFieldMappingTableand updating constructors. - Remove
IExcludeValidatorTypesimplementations and useIAvailableValidatorTypesAttribute. - Replace removed
FormBusinessServicemethods with their non-obsolete equivalents. - Replace removed
FormsExtensionsandFormsExtensionsUIextension methods. - Update
PropertyGenericListmodel types forSystem.Text.Jsoncompatibility. - Update
SaveData()overrides to remove parameter. - Verify scheduled job GUIDs are accessible in
IScheduledJobRepositoryif needed. - Verify property definitions in CMS database after upgrade.
- Test existing form submission data for deserialization correctness.
- Configure geo provider URL in
appsettings.jsonif used. - Call
services.AddFormsCryptoAzureKeyVault()if using AzureKeyVault crypto.
Updated 1 day ago