orchardcore-forms
Skill for building and managing forms in Orchard Core using the OrchardCore.Forms module. Covers form widget content types, form validation, form submissions with workflows, anti-forgery tokens, and c
SKILL.mdsrc/CrestApps.AgentSkills/orchardcore/orchardcore-forms/SKILL.md
GitHubOrchard Core Forms
Enable OrchardCore.Forms and use its widget types inside a FlowPart.
Form contains FormElementPart, FormPart, and FlowPart. Input,
Select, and TextArea each contain FormInputElementPart,
FormElementPart, FormElementLabelPart, their specific element part,
FormElementValidationPart, and FormInputElementVisibilityPart.
| Part | Current responsibility |
|---|---|
FormPart |
Action, Method, WorkflowTypeId, EncType, anti-forgery, and form-location behavior |
FormElementPart |
Element Id |
FormInputElementPart |
Submitted field Name |
InputPart |
Input Type, DefaultValue, and Placeholder |
SelectPart |
Options, DefaultValue, and Editor |
TextAreaPart |
DefaultValue, Placeholder, and Rows |
LabelPart / FormElementLabelPart |
Label widget For; per-element Label with Option (None, Standard, ScreenReaders) |
ButtonPart |
Text and Type (e.g., submit) |
ValidationPart |
For: the field name whose errors are shown |
ValidationSummaryPart |
Renders all model-state errors |
FormInputElementVisibilityPart |
Action (None, Show, Hide) and rule Groups |
Widget content types created by the module: Form, Input, Select, TextArea, Label, Button, Validation, and ValidationSummary. With OrchardCore.ReCaptcha enabled, a ReCaptcha widget (reCAPTCHA v2, configured under Settings > Security > reCaptcha) and the Validate ReCaptcha workflow task (Valid/Invalid/Done) are added.
FormPart.EnableAntiForgeryToken and FormPart.SaveFormLocation default to true.
The Form wrapper emits an anti-forgery token when enabled and, by default,
the __RequestOriginatedFrom workflow input used by HTTP workflow redirects.
Enable Forms
OrchardCore.Forms depends on OrchardCore.Widgets and OrchardCore.Flows. Add the workflow features to process submissions without a custom controller.
{
"steps": [
{
"name": "Feature",
"enable": [
"OrchardCore.Forms",
"OrchardCore.Flows",
"OrchardCore.Widgets",
"OrchardCore.Workflows",
"OrchardCore.Workflows.Http"
]
}
]
}
Form Element Content
SelectPart.Options is an array of SelectOption objects, not newline text:
{
"ContentType": "Select",
"FormInputElementPart": {
"Name": "ContactReason"
},
"FormElementPart": {
"Id": "contact-reason"
},
"SelectPart": {
"Editor": "Dropdown",
"DefaultValue": "support",
"Options": [
{
"Text": "Technical support",
"Value": "support"
},
{
"Text": "Sales",
"Value": "sales"
}
]
}
}
Conditional Visibility
Each input widget has Visibility Settings stored in FormInputElementVisibilityPart. Rules within a group are combined with AND; groups are combined with OR. Fields appear in the rule editor only after the form has been saved with them. Operators: Is, IsNot, Empty, NotEmpty, Contains, DoesNotContain, StartsWith, EndsWith, GreaterThan, LessThan.
{
"FormInputElementVisibilityPart": {
"Action": "Show",
"Groups": [
{
"Rules": [
{
"Field": "MembershipType",
"Operator": "Contains",
"Values": [ "Premium" ],
"CaseSensitive": false
}
]
}
]
}
}
Visibility is applied client-side; hidden fields are still accepted by the server, so validate server-side as well.
Workflow Submission and Validation
Set the form action to a workflow HTTP endpoint and start the workflow with
the real HttpRequestEvent; FormSubmissionEvent is not an Orchard Core
activity. FormPart.WorkflowTypeId exists on the model, but the built-in
editor does not render an input for it; set it through code or imported data
when needed. It does not replace an HTTP event route.
Forms supplies these validation activities when OrchardCore.Workflows is
enabled:
ValidateAntiforgeryTokenTaskBindModelStateTaskValidateFormFieldTaskAddModelValidationErrorTaskValidateFormTaskHttpRedirectToFormLocationTask
In JavaScript workflow expressions, use the HTTP globals requestForm(name)
or deserializeRequestData() and workflow globals property(name),
setProperty(name, value), and setOutcome(name). Do not use fictional
requestFormAsDict, addModelError, or modelState globals.
var email = requestForm("ContactEmail");
if (!email || !email.includes("@")) {
setOutcome("Invalid");
} else {
setProperty("ContactEmail", email);
setOutcome("Valid");
}
For required fields and model-state errors, add ValidateFormFieldTask or
AddModelValidationErrorTask in the workflow and then use
ValidateFormTask to branch to its Valid or Invalid outcome.
Typical contact-form workflow:
- HTTP Request event (method
POST, set as start activity); paste its URL path into the Form widget's Action. - Bind Form Model State (
BindModelStateTask) so entered values survive a redirect back to the form. - Fork with one branch per check, each running Validate Form Field (or Validate ReCaptcha); connect every
Doneoutcome to a Join in Wait All mode. - Validate Form (
ValidateFormTask):Invalidgoes to Redirect back to the form (orHttpRedirectToFormLocationTask), whereValidationSummary/Validationwidgets show the errors;Validcontinues. - Send Email using Liquid, e.g. subject
Contact form from {{ Request.Form.Name }}, then Redirect to a thank-you page.
Custom Form Elements
Custom elements are ordinary content parts and widgets. Add the part,
display driver, view model, and content type definition; use
FormInputElementPart.Name when the element must submit a value. View models
remain unsealed for model binding; seal other classes.