
Syncfusion Blazor Dataform
- 234 installs
- 4 repo stars
- Updated July 28, 2026
- syncfusion/blazor-ui-components-skills
Use syncfusion-blazor-dataform for development tasks
About
syncfusion-blazor-dataform: A skill for development. This provides functionality for development workflows.
- syncfusion-blazor-dataform
Syncfusion Blazor Dataform by the numbers
- 234 all-time installs (skills.sh)
- +12 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #1,623 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/blazor-ui-components-skills --skill syncfusion-blazor-dataformAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 234 |
|---|---|
| repo stars | ★ 4 |
| Last updated | July 28, 2026 |
| Repository | syncfusion/blazor-ui-components-skills ↗ |
What it does
Use syncfusion-blazor-dataform for development tasks
Files
Implementing Syncfusion Blazor DataForm Component
A comprehensive skill for implementing the Syncfusion Blazor DataForm component. This skill covers installation, configuration, data binding, validation, event handling, templating, and customization of forms in Blazor applications.
Component Overview
The Syncfusion Blazor DataForm (SfDataForm) is a powerful form component that streamlines the creation of dynamic, data-driven forms with automatic validation, field binding, and event handling. It supports:
- Automatic field generation from model properties
- Built-in validation with data annotations and custom rules
- Multiple binding approaches (model, EditContext)
- Customizable layouts with columns, grouping, and sections
- Template-based rendering for custom field designs
- Event-driven architecture for form and field-level reactions
- Localization support for validation messages and UI text
- Accessible form rendering with proper labels and ARIA attributes
Documentation and Navigation Guide
Getting Started
📄 Read: references/getting-started.md
Start here for installation, NuGet package setup, namespace imports, service registration, theme configuration, and a basic DataForm example.
- Installation & NuGet packages
- Blazor project setup (Server/WebAssembly/Web App)
- Namespace imports & service registration
- Theme setup & configuration
- Basic DataForm creation
- Running your first form
Form Items and Field Configuration
📄 Read: references/form-items.md
Learn how to define form items, configure labels, placeholders, hints, editor types, and customize individual field behavior.
- FormItem element configuration
- Label, placeholder configurations
- Editor type selection
- Disabling and hiding fields
- Custom attributes (required, pattern, data annotations)
- Form grouping and column organization
Auto-Generation of Form Fields
📄 Read: references/autogeneration.md
Discover how to automatically generate form fields from model properties using FormAutoGenerateItems, including type mapping and combining auto-generated with custom fields.
- FormAutoGenerateItems overview
- Type-to-component mapping (int, string, DateTime, bool, enum, etc.)
- Auto-generating fields for primitive types
- Combining auto-generated and custom fields
- Canceling auto-generation for specific fields
- Configuration options
Data Binding
📄 Read: references/data-binding.md
Understand model binding, EditContext binding, two-way data binding, and binding to complex data structures.
- Model binding basics
- EditContext binding approach
- Form-level and field-level binding
- Two-way binding with nested objects
- Binding to complex models
- Data initialization and default values
- Form name and ID configuration
Form Validation
📄 Read: references/validation.md
Master form validation with data annotations, custom validation rules, Fluent Validation integration, and error message display.
- Data annotation validation (Required, Range, EmailAddress, etc.)
- DataAnnotationsValidator setup
- Custom validation attributes and rules
- Fluent Validation framework integration
- Complex model validation scenarios
- Displaying validation messages
- IsValid() method and validation state
- Conditional validation
Form and Field Events
📄 Read: references/events.md
Learn about form submission events and field-level events for handling user interactions and form state changes.
- OnSubmit event (fires on every submit attempt)
- OnValidSubmit event (fires only on valid submission)
- OnInvalidSubmit event (fires only on validation failure)
- OnUpdate event (fires on field value changes)
- Event arguments and EditContext access
- Asynchronous event handlers
- Common event patterns
Layout Customization
📄 Read: references/layout-customization.md
Customize form layout with columns, column spans, label positioning, floating labels, and custom button placement.
- Column layout configuration
- Column span and row span
- Columns count and responsiveness
- Label positioning (top, left, floating)
- Floating label styling
- Button alignment and positioning
- Custom submit/reset buttons
- Form grouping with FormGroup
Templates and Custom Rendering
📄 Read: references/templates.md
Create custom form layouts and field renderings using FormTemplate and FormItemTemplate with render fragments.
- FormTemplate for overall form layout customization
- FormItemTemplate for individual field rendering
- Template context and variables
- Custom editor rendering
- Render fragments and composition
- Advanced template patterns
- Template reusability
Localization
📄 Read: references/localization.md
Set up localization for validation messages, form labels, and error messages in multiple languages.
- Built-in localization resources
- Localizing error messages
- Custom localization setup
- Culture and language configuration
- RTL (right-to-left) support
Quick Start Example
Here's a minimal working example to get started:
@page "/dataform-example"
@using System.ComponentModel.DataAnnotations
@using Syncfusion.Blazor.DataForm
<SfDataForm ID="MyDataForm"
Model="@employeeModel">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
public class EmployeeModel
{
[Required(ErrorMessage = "First Name is required")]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email address")]
[Display(Name = "Email")]
public string Email { get; set; }
[Range(18, 65, ErrorMessage = "Age must be between 18 and 65")]
[Display(Name = "Age")]
public int Age { get; set; }
[Display(Name = "Date of Birth")]
public DateTime? DateOfBirth { get; set; }
}
private EmployeeModel employeeModel = new EmployeeModel();
}Common Patterns
Pattern 1: Form with Validation and Submission
<SfDataForm ID="ContactForm" Model="@contact" OnValidSubmit="HandleValidSubmit">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormItem Field="@nameof(contact.Name)" LabelText="Full Name"></FormItem>
<FormItem Field="@nameof(contact.Email)" LabelText="Email Address"></FormItem>
<FormItem Field="@nameof(contact.Message)" EditorType="FormEditorType.TextArea"></FormItem>
</FormItems>
</SfDataForm>
@code {
private Contact contact = new();
private async Task HandleValidSubmit(EditContext context)
{
// Save contact to database
await SaveContactAsync(contact);
}
}Pattern 2: Auto-Generated Form with Custom Fields
<SfDataForm Model="@product">
<FormItems>
<FormItem Field="@nameof(product.Name)" LabelText="Product Name"></FormItem>
<FormAutoGenerateItems></FormAutoGenerateItems>
<FormItem Field="@nameof(product.Category)" EditorType="FormEditorType.DropDownList"></FormItem>
</FormItems>
</SfDataForm>
@code {
private Product product = new();
}Pattern 3: Form with Cascading Fields
<SfDataForm Model="@order" OnUpdate="HandleFieldUpdate">
<FormItems>
<FormItem Field="@nameof(order.Country)" LabelText="Country"></FormItem>
<FormItem Field="@nameof(order.City)" LabelText="City"></FormItem>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
private Order order = new();
private async Task HandleFieldUpdate(FormUpdateEventArgs args)
{
if (args.FieldName == nameof(Order.Country))
{
// Update cities based on selected country
order.City = await GetCitiesForCountryAsync(order.Country);
}
}
}Pattern 4: Custom Layout with Columns
<SfDataForm Model="@employee" ColumnCount="2">
<FormItems>
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name" ColumnSpan="1"></FormItem>
<FormItem Field="@nameof(employee.LastName)" LabelText="Last Name" ColumnSpan="1"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email" ColumnSpan="2"></FormItem>
<FormItem Field="@nameof(employee.Department)" LabelText="Department" ColumnSpan="1"></FormItem>
<FormItem Field="@nameof(employee.Salary)" LabelText="Salary" ColumnSpan="1"></FormItem>
</FormItems>
</SfDataForm>
@code {
private Employee employee = new();
}Key Properties and Methods
Common Properties
| Property | Type | Purpose |
|---|---|---|
Model | object | The data model bound to the form |
EditContext | EditContext | Blazor EditContext for advanced binding scenarios |
ColumnCount | int | Number of columns for form layout |
OnValidSubmit | EventCallback | Fires when form is submitted with valid data |
OnInvalidSubmit | EventCallback | Fires when form is submitted with invalid data |
OnSubmit | EventCallback | Fires on every submit attempt |
OnUpdate | EventCallback | Fires when a field value changes |
Available FormEditorType Values
Important: In most cases, you don't need to specify EditorType as the DataForm automatically selects the appropriate editor based on your model property type. Use these values only when you need to override the default behavior.
| FormEditorType | Use Case | When to Use |
|---|---|---|
TextBox | Single-line text input | Override for string fields (default), not needed for numeric types |
TextArea | Multi-line text input | When you need multi-line text entry for string properties |
DatePicker | Date selection only | Override for DateTime (default for DateTime) |
DateTimePicker | Date and time selection | When you need both date and time for DateTime properties |
TimePicker | Time selection only | When you need only time selection for TimeSpan or DateTime |
DropDownList | Dropdown selection from list | For enum or custom list selection |
ComboBox | Editable dropdown with filtering | When you need searchable dropdown |
AutoComplete | Auto-complete with suggestions | When you need auto-suggest functionality |
Checkbox | Boolean toggle (note: lowercase 'b') | Override for bool (default), NOT CheckBox |
Switch | Toggle switch for boolean | Alternative to checkbox for bool properties |
Password | Password input with masking | For password string fields |
Critical Notes:
- ⚠️ Automatic Type Mapping: The DataForm intelligently maps C# types to appropriate editors automatically:
int,long,decimal,double,float→ Numeric textbox (no EditorType needed)string→ Textbox (no EditorType needed)bool→ Checkbox (no EditorType needed)DateTime→ DatePicker (no EditorType needed)enum→ DropDownList (no EditorType needed)- ⚠️ Use
Checkbox(lowercase 'b'), NOTCheckBox(capital B) - ⚠️ There is NO
NumericTextBoxeditor type - numeric behavior is automatic based on property type - ⚠️ Only specify
EditorTypewhen you want to override the default behavior
Common Methods
| Method | Purpose |
|---|---|
Refresh() | Refresh form and re-render fields |
Submit() | Programmatically submit the form |
Reset() | Clear form and reset to initial values |
IsValid() | Check if form is valid |
Validate() | Trigger validation without submitting |
Important EditorType Guidelines
When configuring FormItem elements with EditorType, keep these critical points in mind:
1. Automatic Editor Selection: The DataForm automatically selects the appropriate editor based on your model property type. You typically don't need to specify EditorType unless you want to override the default behavior.
- Numeric properties (int, long, float, decimal, double) → Automatically render as numeric textbox
- String properties → Automatically render as textbox
- Boolean properties → Automatically render as checkbox
- DateTime properties → Automatically render as date picker
- Enum properties → Automatically render as dropdown
2. When to Specify EditorType: Only specify EditorType when you want to override the default:
- Use
FormEditorType.TextAreafor multi-line string input - Use
FormEditorType.Switchinstead of checkbox for boolean - Use
FormEditorType.DateTimePickerinstead ofDatePickerfor DateTime with time - Use
FormEditorType.Passwordfor password strings - Use
FormEditorType.DropDownListorComboBoxfor custom lists
3. Checkbox Casing: When explicitly specifying checkbox, use FormEditorType.Checkbox with lowercase 'b', not FormEditorType.CheckBox.
4. No NumericTextBox Type: There is no FormEditorType.NumericTextBox. Numeric behavior is automatic based on property type.
Example:
<!-- Numeric field - EditorType not needed, automatically renders numeric textbox -->
<FormItem Field="@nameof(model.Quantity)"
LabelText="Quantity">
</FormItem>
<!-- Boolean field - EditorType not needed, automatically renders checkbox -->
<FormItem Field="@nameof(model.IsActive)"
LabelText="Is Active">
</FormItem>
<!-- String field with TextArea override -->
<FormItem Field="@nameof(model.Description)"
LabelText="Description"
EditorType="FormEditorType.TextArea">
</FormItem>
<!-- Boolean field with Switch override -->
<FormItem Field="@nameof(model.IsEnabled)"
LabelText="Enabled"
EditorType="FormEditorType.Switch">
</FormItem>Related Skills
- Syncfusion Blazor Components - Main library skill for all Blazor components
- AutoComplete - For dropdown field editors
- File Upload - For file input fields
Auto-Generation of Form Fields
Auto-generation automatically creates form fields based on your model properties without manually defining each FormItem. The DataForm maps property types to appropriate editor controls.
FormAutoGenerateItems Overview
The <FormAutoGenerateItems> element automatically generates fields for all public properties in your model:
<SfDataForm Model="@employee">
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime DateOfBirth { get; set; }
public int Age { get; set; }
public bool IsActive { get; set; }
}
private Employee employee = new();
}The form automatically generates:
- First Name → TextBox
- Last Name → TextBox
- Email → TextBox
- Date of Birth → DateTimePicker
- Age → NumericTextBox
- Is Active → Checkbox
Type-to-Component Mapping
The DataForm maps C# types to specific editor components:
| C# Type | Default Editor | Notes |
|---|---|---|
string | TextBox | Single-line text input |
int | NumericTextBox | Integer with step buttons |
long | NumericTextBox | Long integer |
float | NumericTextBox | Decimal number |
decimal | NumericTextBox | Currency/precision numbers |
double | NumericTextBox | Double precision |
bool | Checkbox | Boolean toggle |
DateTime | DateTimePicker | Date and time selection |
DateOnly | DatePicker | Date selection only |
TimeOnly | TimePicker | Time selection only |
enum | DropDownList | Enumeration values |
Guid | TextBox | Unique identifier |
byte[] | (Not auto-generated) | Use explicit FormItem |
Important: Numeric types (int, long, float, decimal, double) use NumericTextBox editor with automatic numeric input handling based on the property type. There is no separate NumericTextBox editor type.
Enumeration Type Mapping and Editor Types
The DataForm supports comprehensive enumeration type mapping with the FormEditorType enum. Each enumeration type automatically maps to a specific Syncfusion editor control:
Available FormEditorType Options
| FormEditorType | Description | Use Case |
|---|---|---|
TextBox | Basic single-line text input | General text entry, usernames, URLs |
TextArea | Multi-line text editor | Long descriptions, comments, notes |
DropDownList | Dropdown selection from list | Predefined choices, enumerations |
ComboBox | Combo box with filtering | Searchable selection with typed input |
AutoComplete | Auto-complete input with suggestions | Search with real-time filtering |
DatePicker | Date selection only | Date input without time |
DateTimePicker | Date and time selection | Full date-time input |
TimePicker | Time selection only | Time input without date |
Checkbox | Boolean toggle | Yes/no, true/false values |
Switch | Toggle switch component | Modern boolean input |
Password | Password input with masking | Secure password entry |
Enumeration Type Auto-Detection
When your model contains enum properties, the DataForm automatically detects them and creates a DropDownList editor populated with the enum values:
public enum OrderStatus
{
Pending,
Processing,
Shipped,
Delivered,
Cancelled
}
public enum Priority
{
Low,
Medium,
High,
Urgent
}
public enum PaymentMethod
{
CreditCard,
DebitCard,
PayPal,
BankTransfer,
Cash
}
public class Order
{
public string OrderNumber { get; set; }
public OrderStatus Status { get; set; }
public Priority Priority { get; set; }
public PaymentMethod PaymentMethod { get; set; }
public decimal Amount { get; set; }
}Auto-Generated Fields Example
@page "/auto-form"
@using System.ComponentModel.DataAnnotations
<SfDataForm Model="@contact">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
public class Contact
{
public string Name { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public DateTime DateOfContact { get; set; }
public int Priority { get; set; }
public bool HasFollowUp { get; set; }
}
private Contact contact = new();
}Generated fields:
- Name → TextBox
- Email → TextBox
- PhoneNumber → TextBox
- DateOfContact → DateTimePicker
- Priority → NumericTextBox
- HasFollowUp → Checkbox
Using Display Attribute
The [Display] attribute customizes field labels and configuration:
public class Employee
{
[Display(Name = "First Name",
Description = "Enter employee's first name")]
public string FirstName { get; set; }
[Display(Name = "Email Address")]
public string Email { get; set; }
[Display(Name = "Date of Birth")]
public DateTime DOB { get; set; }
[Display(Name = "Is Active Employee")]
public bool IsActive { get; set; }
}When auto-generating, the DataForm uses the [Display(Name = "...")] value as the field label.
Combining Auto-Generated and Custom Fields
Mix FormAutoGenerateItems with explicit FormItem elements. Explicitly defined fields are not auto-generated:
<SfDataForm Model="@employee">
<FormItems>
<!-- Explicit custom field -->
<FormItem Field="@nameof(employee.FirstName)"
LabelText="First Name (Custom)"
Placeholder="Enter first name">
</FormItem>
<!-- Auto-generate remaining fields -->
<FormAutoGenerateItems></FormAutoGenerateItems>
<!-- Another explicit field -->
<FormItem Field="@nameof(employee.ConfirmPassword)"
LabelText="Confirm Password"
EditorType="FormEditorType.TextBox">
</FormItem>
</FormItems>
</SfDataForm>
@code {
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
private Employee employee = new();
}Result:
- FirstName → Custom TextBox with placeholder
- LastName → Auto-generated TextBox
- Email → Auto-generated TextBox
- Password → Auto-generated TextBox
- ConfirmPassword → Custom TextBox
Excluding Specific Fields from Auto-Generation
Explicitly define fields you want to exclude from auto-generation:
<SfDataForm Model="@user">
<FormItems>
<!-- Define sensitive field explicitly with hidden editor -->
<FormItem Field="@nameof(user.Password)"
LabelText="Password"
EditorType="FormEditorType.TextBox">
</FormItem>
<!-- Auto-generate all other fields -->
<FormAutoGenerateItems></FormAutoGenerateItems>
<!-- Fields needing special handling -->
<FormItem Field="@nameof(user.CountryCode)"
LabelText="Country"
EditorType="FormEditorType.DropDownList">
</FormItem>
</FormItems>
</SfDataForm>
@code {
public class User
{
public string Username { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string PhoneNumber { get; set; }
public string CountryCode { get; set; }
public bool TermsAccepted { get; set; }
}
private User user = new();
}Result: Password and CountryCode use custom editors, others auto-generated.
Disabling Auto-Generation
Completely Disable Auto-Generation
Omit FormAutoGenerateItems to only show explicitly defined fields:
<SfDataForm Model="@employee">
<FormItems>
<!-- Only these fields will render -->
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
<FormItem Field="@nameof(employee.Salary)" LabelText="Salary"></FormItem>
<!-- Other properties are NOT rendered -->
</FormItems>
</SfDataForm>
@code {
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public decimal Salary { get; set; }
public string InternalNotes { get; set; }
}
private Employee employee = new();
}Only FirstName, Email, and Salary display. LastName, Password, and InternalNotes are hidden.
Conditionally Display Auto-Generated Fields
<SfDataForm Model="@employee">
<FormItems>
@if (editMode)
{
<FormAutoGenerateItems></FormAutoGenerateItems>
}
else
{
<!-- Show only specific fields in view mode -->
<FormItem Field="@nameof(employee.FirstName)"
LabelText="First Name"
IsEnabled="false">
</FormItem>
<FormItem Field="@nameof(employee.Email)"
LabelText="Email"
IsEnabled="false">
</FormItem>
}
</FormItems>
</SfDataForm>
@code {
private bool editMode = true;
private Employee employee = new();
}Auto-Generation with Validation
Auto-generated fields respect data annotation validation attributes:
public class Registration
{
[Required(ErrorMessage = "Name is required")]
[StringLength(100)]
public string Name { get; set; }
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email address")]
public string Email { get; set; }
[Range(18, 100, ErrorMessage = "Age must be 18-100")]
public int Age { get; set; }
[Range(0.01, double.MaxValue, ErrorMessage = "Price must be greater than 0")]
public decimal Price { get; set; }
[Required]
public bool TermsAccepted { get; set; }
}When auto-generating these fields, validation rules are automatically enforced:
<SfDataForm Model="@registration">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>Custom Editor Type for Auto-Generated Fields
Override default editor type for specific properties using [EditorType] custom attribute or by explicitly defining that field:
public class Settings
{
public string Name { get; set; }
// Override: Use TextArea instead of TextBox for long text
public string Description { get; set; }
public int Quantity { get; set; }
}Override in markup:
<SfDataForm Model="@settings">
<FormItems>
<FormItem Field="@nameof(settings.Name)"></FormItem>
<!-- Override auto-generated TextBox with TextArea -->
<FormItem Field="@nameof(settings.Description)"
LabelText="Description"
EditorType="FormEditorType.TextArea">
</FormItem>
<!-- Continue auto-generating others -->
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
public class Settings
{
public string Name { get; set; }
public string Description { get; set; }
public int Quantity { get; set; }
}
private Settings settings = new();
}Auto-Generation with Enums
Auto-generated fields for enum properties create DropDownList with enum values:
public enum OrderStatus
{
Pending,
Processing,
Shipped,
Delivered,
Cancelled
}
public enum Priority
{
Low,
Medium,
High,
Urgent
}
public class Order
{
public string OrderNumber { get; set; }
public OrderStatus Status { get; set; }
public Priority Priority { get; set; }
public DateTime CreatedDate { get; set; }
}Auto-generated result:
- OrderNumber → TextBox
- Status → DropDownList (Pending, Processing, Shipped, Delivered, Cancelled)
- Priority → DropDownList (Low, Medium, High, Urgent)
- CreatedDate → DateTimePicker
<SfDataForm Model="@order">
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
private Order order = new();
}Best Practices
✅ DO:
- Use auto-generation for straightforward models with standard types
- Combine with explicit FormItems for special fields
- Leverage
[Display]attributes for better labels - Use data annotations for validation rules
- Group related fields visually
❌ DON'T:
- Auto-generate sensitive fields like passwords (define explicitly)
- Mix auto-generated and custom fields without clear organization
- Rely on auto-generation for complex nested objects
- Forget to add
[Display]attributes for user-friendly labels - Auto-generate without validation attributes
See Also
- Form Items - Explicit field configuration
- Validation - Add validation to auto-generated fields
- Data Binding - Model binding with auto-generation
Data Binding in Blazor DataForm
Table of Contents
- Binding Overview
- Model Binding
- EditContext Binding
- Two-Way Data Binding
- Nested Object Binding
- Data Initialization
- Form Name and ID
Binding Overview
The DataForm component supports two primary binding approaches: 1. Model Binding - Direct model property binding (simplest) 2. EditContext Binding - Advanced scenario with custom validation and state management
Model Binding
The simplest way to bind data to a DataForm is passing a model instance to the Model property:
<SfDataForm Model="@employee">
<FormItems>
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
<FormItem Field="@nameof(employee.Age)" LabelText="Age"></FormItem>
</FormItems>
</SfDataForm>
@code {
public class Employee
{
public string FirstName { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
private Employee employee = new();
}Key Properties
| Property | Type | Purpose |
|---|---|---|
Model | object | The data model to bind |
Field | string | Model property name (use nameof() for type safety) |
T (Generic) | Type | Model type for compile-time validation |
Pre-Populated Model
Initialize your model with default values:
@code {
private Employee employee = new()
{
FirstName = "John",
LastName = "Doe",
Email = "john.doe@example.com",
Age = 30
};
}The form displays with these initial values.
EditContext Binding
For advanced scenarios, use Blazor's EditContext for custom validation and state management:
<EditForm EditContext="@editContext" OnSubmit="HandleSubmit">
<SfDataForm EditContext="@editContext">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
<button type="submit" class="btn btn-primary">Submit</button>
</EditForm>
@code {
public class Employee
{
[Required]
public string Name { get; set; }
[EmailAddress]
public string Email { get; set; }
}
private Employee employee = new();
private EditContext editContext;
protected override void OnInitialized()
{
editContext = new EditContext(employee);
}
private async Task HandleSubmit()
{
if (editContext.Validate())
{
await SaveEmployeeAsync();
}
}
private async Task SaveEmployeeAsync()
{
// Save to database
Console.WriteLine($"Saved: {employee.Name}");
}
}EditContext Advantages
- Manual validation control - Call
editContext.Validate()explicitly - Custom validation rules - Add custom validators
- State tracking - Monitor field changes
- Nested form support - Use within EditForm component
Two-Way Data Binding
Changes in form fields automatically update the model properties and vice versa:
<div class="container">
<SfDataForm Model="@person">
<FormItems>
<FormItem Field="@nameof(person.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(person.LastName)" LabelText="Last Name"></FormItem>
<FormItem Field="@nameof(person.Email)" LabelText="Email"></FormItem>
</FormItems>
</SfDataForm>
<div class="mt-3">
<h4>Current Values:</h4>
<p>Name: @person.FirstName @person.LastName</p>
<p>Email: @person.Email</p>
</div>
</div>
@code {
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
private Person person = new();
}As users type in the form fields, the person object updates in real-time, and the display section reflects changes immediately.
Update Events
Track specific field changes using the OnUpdate event:
<SfDataForm Model="@order" OnUpdate="HandleFieldUpdate">
<FormItems>
<FormItem Field="@nameof(order.Quantity)" LabelText="Quantity"></FormItem>
<FormItem Field="@nameof(order.UnitPrice)" LabelText="Unit Price"></FormItem>
<FormItem Field="@nameof(order.TotalPrice)" LabelText="Total Price" IsEnabled="false"></FormItem>
</FormItems>
</SfDataForm>
<p>Last Updated: @lastUpdatedField</p>
@code {
public class Order
{
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal TotalPrice { get; set; }
}
private Order order = new();
private string lastUpdatedField;
private Task HandleFieldUpdate(FormUpdateEventArgs args)
{
lastUpdatedField = $"{args.FieldName} updated at {DateTime.Now:HH:mm:ss}";
// Auto-calculate total when quantity or price changes
if (args.FieldName == nameof(Order.Quantity) ||
args.FieldName == nameof(Order.UnitPrice))
{
order.TotalPrice = order.Quantity * order.UnitPrice;
}
return Task.CompletedTask;
}
}Nested Object Binding
Bind to properties of nested objects in your model:
public class Employee
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}Use dot notation to bind nested properties:
<SfDataForm Model="@employee">
<FormItems>
<FormGroup LabelText="Personal Information">
<FormItem Field="@nameof(employee.Name)" LabelText="Name"></FormItem>
</FormGroup>
<FormGroup LabelText="Address">
<!-- Bind to nested Address properties -->
<FormItem Field="@nameof(employee.Address.Street)" LabelText="Street"></FormItem>
<FormItem Field="@nameof(employee.Address.City)" LabelText="City"></FormItem>
<FormItem Field="@nameof(employee.Address.State)" LabelText="State"></FormItem>
<FormItem Field="@nameof(employee.Address.ZipCode)" LabelText="Zip Code"></FormItem>
</FormGroup>
</FormItems>
</SfDataForm>
@code {
private Employee employee = new()
{
Name = "John Doe",
Address = new Address
{
Street = "123 Main St",
City = "New York",
State = "NY",
ZipCode = "10001"
}
};
}Complex Nested Objects
Handle deeply nested structures:
public class Company
{
public string Name { get; set; }
public Employee CEO { get; set; }
}
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Contact Contact { get; set; }
}
public class Contact
{
public string Email { get; set; }
public string Phone { get; set; }
}Bind at any depth:
<SfDataForm Model="@company">
<FormItems>
<FormItem Field="@nameof(company.Name)" LabelText="Company Name"></FormItem>
<FormItem Field="@nameof(company.CEO.FirstName)" LabelText="CEO First Name"></FormItem>
<FormItem Field="@nameof(company.CEO.Contact.Email)" LabelText="CEO Email"></FormItem>
<FormItem Field="@nameof(company.CEO.Contact.Phone)" LabelText="CEO Phone"></FormItem>
</FormItems>
</SfDataForm>
@code {
private Company company = new()
{
Name = "Tech Corp",
CEO = new Employee
{
FirstName = "Jane",
LastName = "Smith",
Contact = new Contact
{
Email = "jane@techcorp.com",
Phone = "+1-555-0100"
}
}
};
}⚠️ Important: Ensure nested objects are initialized before binding.
Data Initialization
Initialize with Empty Values
private Employee employee = new();Initialize with Default Values
private Employee employee = new()
{
IsActive = true,
StartDate = DateTime.Today,
Department = "Engineering"
};Load from Database
@code {
private Employee employee;
protected override async Task OnInitializedAsync()
{
employee = await LoadEmployeeAsync(employeeId);
}
private async Task<Employee> LoadEmployeeAsync(int id)
{
// Fetch from API or database
var response = await httpClient.GetAsync($"/api/employees/{id}");
return await response.Content.ReadAsAsync<Employee>();
}
}Load from API
@inject HttpClient HttpClient
@code {
private Product product;
private string errorMessage;
protected override async Task OnInitializedAsync()
{
try
{
product = await HttpClient.GetFromJsonAsync<Product>("/api/products/1");
}
catch (Exception ex)
{
errorMessage = $"Failed to load product: {ex.Message}";
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
}
}Form Name and ID
Setting Form Identity
<SfDataForm ID="EmployeeForm"
Model="@employee"
FormName="EmployeeRegistration">
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
private Employee employee = new();
}Using FormName for Multiple Forms
When you have multiple DataForms on a page, use FormName to differentiate them:
<!-- Employee Form -->
<SfDataForm FormName="EmployeeForm" Model="@employee">
<FormItems>
<FormItem Field="@nameof(employee.Name)" LabelText="Employee Name"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
</FormItems>
</SfDataForm>
<!-- Contact Form -->
<SfDataForm FormName="ContactForm" Model="@contact">
<FormItems>
<FormItem Field="@nameof(contact.Name)" LabelText="Contact Name"></FormItem>
<FormItem Field="@nameof(contact.PhoneNumber)" LabelText="Phone"></FormItem>
</FormItems>
</SfDataForm>
@code {
private Employee employee = new();
private Contact contact = new();
}Accessing Form by ID
Use @ref directive to access form methods and properties:
<SfDataForm @ref="employeeForm" ID="EmployeeForm" Model="@employee">
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
<button @onclick="ValidateForm">Validate</button>
@code {
private SfDataForm employeeForm;
private Employee employee = new();
private async Task ValidateForm()
{
if (await employeeForm.IsValid())
{
Console.WriteLine("Form is valid");
}
else
{
Console.WriteLine("Form has errors");
}
}
}Best Practices
✅ DO:
- Use
nameof()for type-safe field binding - Initialize nested objects before binding
- Use EditContext for advanced validation scenarios
- Handle
OnUpdateevent for dependent field updates - Load data in
OnInitializedAsynclifecycle method
❌ DON'T:
- Reference fields as strings like
Field="FirstName"(usenameof()) - Leave nested objects uninitialized (causes null reference errors)
- Bind sensitive data directly without encryption
- Modify model during rendering (causes infinite loops)
See Also
- Form Items - Explicit field configuration
- Validation - Add validation rules
- Events - Handle form and field events
Form and Field Events in Blazor DataForm
Table of Contents
- Overview
- OnSubmit Event
- OnValidSubmit Event
- OnInvalidSubmit Event
- OnUpdate Event
- Event Arguments
- Asynchronous Event Handlers
- Common Event Patterns
Overview
The DataForm provides form-level and field-level events for handling user interactions:
| Event | When it fires | Use case |
|---|---|---|
OnSubmit | Every submit attempt (valid or invalid) | Logging, pre-processing |
OnValidSubmit | Submit with all validation passing | Save data, redirect |
OnInvalidSubmit | Submit with validation failures | Show error summary |
OnUpdate | Any field value changes | Dependent field updates, calculations |
OnSubmit Event
The OnSubmit event fires every time the form is submitted, regardless of validation results. Use this for actions that must occur on every submit attempt:
<SfDataForm Model="@contact" OnSubmit="HandleSubmit">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
public class Contact
{
[Required]
public string Name { get; set; }
[EmailAddress]
public string Email { get; set; }
}
private Contact contact = new();
private async Task HandleSubmit(EditContext context)
{
// Log submission attempt
Console.WriteLine($"Form submitted at {DateTime.Now}");
// Pre-processing: trim whitespace
contact.Name = contact.Name?.Trim();
contact.Email = contact.Email?.Trim();
// Continue to validation
await Task.CompletedTask;
}
}OnSubmit with Model Binding
private async Task HandleSubmit(EditContext context)
{
var model = (Contact)context.Model;
Console.WriteLine($"Submitted: {model.Name} ({model.Email})");
}OnValidSubmit Event
The OnValidSubmit event fires only when the form is submitted AND all validation rules pass. Use this to save data or navigate:
<SfDataForm Model="@employee" OnValidSubmit="HandleValidSubmit">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
<FormButtons>
<button type="submit" class="btn btn-success">Save Employee</button>
</FormButtons>
</SfDataForm>
@code {
public class Employee
{
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Range(1, 1000000)]
public decimal Salary { get; set; }
}
private Employee employee = new();
private string message;
private async Task HandleValidSubmit(EditContext context)
{
// Only executes if all validation passes
try
{
await SaveEmployeeAsync(employee);
message = "Employee saved successfully!";
}
catch (Exception ex)
{
message = $"Error saving employee: {ex.Message}";
}
}
private async Task SaveEmployeeAsync(Employee emp)
{
// Simulate API call
await Task.Delay(1000);
Console.WriteLine($"Saved: {emp.Name}");
}
}OnValidSubmit with Navigation
@inject NavigationManager Navigation
private async Task HandleValidSubmit(EditContext context)
{
await SaveEmployeeAsync(employee);
// Navigate to success page
Navigation.NavigateTo("/employees/success");
}OnInvalidSubmit Event
The OnInvalidSubmit event fires when the form is submitted but validation fails. Use this to handle errors or show messages:
<SfDataForm Model="@form" OnInvalidSubmit="HandleInvalidSubmit">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
@if (!string.IsNullOrEmpty(errorMessage))
{
<div class="alert alert-danger" role="alert">
<strong>Validation Error!</strong> @errorMessage
</div>
}
<FormItems>
<FormItem Field="@nameof(form.Email)"
LabelText="Email"></FormItem>
<FormItem Field="@nameof(form.Age)"
LabelText="Age"></FormItem>
</FormItems>
</SfDataForm>
@code {
public class Form
{
[Required]
[EmailAddress(ErrorMessage = "Please enter a valid email")]
public string Email { get; set; }
[Range(18, 100, ErrorMessage = "Age must be 18-100")]
public int Age { get; set; }
}
private Form form = new();
private string errorMessage;
private Task HandleInvalidSubmit(EditContext context)
{
// Get first validation error
var firstError = context.GetValidationMessages().FirstOrDefault();
errorMessage = firstError ?? "Please fix the errors above";
return Task.CompletedTask;
}
}OnUpdate Event
The OnUpdate event fires whenever a field value changes. This is useful for:
- Dependent field updates (cascading dropdowns)
- Real-time calculations
- Dynamic form behavior
<SfDataForm Model="@order" OnUpdate="HandleFieldUpdate">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormItem Field="@nameof(order.Quantity)"
LabelText="Quantity"
>
</FormItem>
<FormItem Field="@nameof(order.UnitPrice)"
LabelText="Unit Price"
>
</FormItem>
<FormItem Field="@nameof(order.TotalPrice)"
LabelText="Total Price"
IsEnabled="false">
</FormItem>
</FormItems>
</SfDataForm>
@code {
public class Order
{
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal TotalPrice { get; set; }
}
private Order order = new();
private Task HandleFieldUpdate(FormUpdateEventArgs args)
{
// Calculate total whenever quantity or price changes
if (args.FieldName == nameof(Order.Quantity) ||
args.FieldName == nameof(Order.UnitPrice))
{
order.TotalPrice = order.Quantity * order.UnitPrice;
}
return Task.CompletedTask;
}
}Cascading Dropdowns Example
<SfDataForm Model="@location" OnUpdate="HandleLocationUpdate">
<FormItems>
<FormItem Field="@nameof(location.Country)"
LabelText="Country"
EditorType="FormEditorType.DropDownList">
</FormItem>
<FormItem Field="@nameof(location.State)"
LabelText="State"
EditorType="FormEditorType.DropDownList">
</FormItem>
<FormItem Field="@nameof(location.City)"
LabelText="City"
EditorType="FormEditorType.DropDownList">
</FormItem>
</FormItems>
</SfDataForm>
@code {
public class Location
{
public string Country { get; set; }
public string State { get; set; }
public string City { get; set; }
}
private Location location = new();
private async Task HandleLocationUpdate(FormUpdateEventArgs args)
{
// Reset dependent fields when parent changes
if (args.FieldName == nameof(Location.Country))
{
location.State = null;
location.City = null;
// Load states for selected country
var states = await GetStatesForCountryAsync(location.Country);
}
else if (args.FieldName == nameof(Location.State))
{
location.City = null;
// Load cities for selected state
var cities = await GetCitiesForStateAsync(location.State);
}
}
private async Task<List<string>> GetStatesForCountryAsync(string country)
{
// Load from API or database
return new List<string> { "State 1", "State 2" };
}
private async Task<List<string>> GetCitiesForStateAsync(string state)
{
// Load from API or database
return new List<string> { "City 1", "City 2" };
}
}Event Arguments
FormUpdateEventArgs
Passed to OnUpdate event handler:
public class FormUpdateEventArgs
{
public string FieldName { get; set; } // Name of updated field
public object Value { get; set; } // New value
public object OldValue { get; set; } // Previous value
}Usage:
private Task HandleUpdate(FormUpdateEventArgs args)
{
Console.WriteLine($"Field '{args.FieldName}' changed from {args.OldValue} to {args.Value}");
return Task.CompletedTask;
}EditContext
Passed to form-level event handlers. Access validation and model:
private async Task HandleSubmit(EditContext context)
{
// Get the model
var model = (MyModel)context.Model;
// Check if valid
bool isValid = !context.GetValidationMessages().Any();
// Get validation messages
var messages = context.GetValidationMessages();
// Mark field as modified
context.MarkAsUnmodified(context.FieldIdentifiers.First());
}Asynchronous Event Handlers
Event handlers support async operations for API calls:
<SfDataForm Model="@user" OnValidSubmit="HandleValidSubmit">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@if (!string.IsNullOrEmpty(statusMessage))
{
<div class="alert alert-info">@statusMessage</div>
}
@code {
public class User
{
[Required]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
}
private User user = new();
private string statusMessage;
private async Task HandleValidSubmit(EditContext context)
{
statusMessage = "Saving user...";
try
{
// Make API call
var response = await httpClient.PostAsJsonAsync("/api/users", user);
if (response.IsSuccessStatusCode)
{
statusMessage = "User saved successfully!";
}
else
{
statusMessage = "Failed to save user";
}
}
catch (Exception ex)
{
statusMessage = $"Error: {ex.Message}";
}
}
}Async Field Update
private async Task HandleFieldUpdate(FormUpdateEventArgs args)
{
if (args.FieldName == nameof(user.Email))
{
// Check if email is available
var isAvailable = await CheckEmailAvailabilityAsync(user.Email);
if (!isAvailable)
{
// Handle unavailable email
}
}
}
private async Task<bool> CheckEmailAvailabilityAsync(string email)
{
var response = await httpClient.GetAsync($"/api/users/check-email?email={email}");
return response.IsSuccessStatusCode;
}Common Event Patterns
Pattern 1: Validation and Save
private async Task HandleValidSubmit(EditContext context)
{
try
{
// Validation already passed, save data
await SaveDataAsync();
await ShowSuccessMessage("Saved successfully");
await NavigateAsync("/success");
}
catch (Exception ex)
{
await ShowErrorMessage($"Save failed: {ex.Message}");
}
}Pattern 2: Dependent Field Updates
private Task HandleFieldUpdate(FormUpdateEventArgs args)
{
switch (args.FieldName)
{
case nameof(Order.Country):
ResetCountryDependentFields();
break;
case nameof(Order.ProductType):
UpdateAvailableDiscounts();
break;
case nameof(Order.Quantity):
RecalculateTotal();
break;
}
StateHasChanged();
return Task.CompletedTask;
}Pattern 3: Real-Time Validation
private async Task HandleFieldUpdate(FormUpdateEventArgs args)
{
if (args.FieldName == nameof(User.Username))
{
var isUnique = await CheckUsernameAvailabilityAsync(user.Username);
if (!isUnique)
{
// Show warning to user
usernameError = "Username already taken";
}
else
{
usernameError = null;
}
}
}Pattern 4: Conditional Field Display
private Task HandleFieldUpdate(FormUpdateEventArgs args)
{
if (args.FieldName == nameof(Order.HasInsurance))
{
// Toggle visibility of insurance-related fields
showInsuranceFields = (bool)args.Value;
}
StateHasChanged();
return Task.CompletedTask;
}See Also
- Validation - Set up form validation rules
- Form Items - Configure individual fields
- Data Binding - Bind data to forms
Form Items and Field Configuration
Table of Contents
- Overview
- FormItem Element Basics
- Label Configuration
- Placeholder Text
- Editor Type Selection
- Disabling Fields
- Custom Attributes
- Form Grouping
- Column Organization
Overview
FormItem elements allow you to explicitly define individual form fields with custom configuration. Use FormItem when you need precise control over field behavior, appearance, labels, and editor types.
FormItem Element Basics
The <FormItem> element represents a single form field with configurable properties:
<SfDataForm Model="@employee">
<FormItems>
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email Address"></FormItem>
<FormItem Field="@nameof(employee.Age)" LabelText="Age"></FormItem>
</FormItems>
</SfDataForm>
@code {
public class Employee
{
public string FirstName { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
private Employee employee = new();
}Key FormItem Properties
| Property | Type | Purpose |
|---|---|---|
Field | string | Model property name to bind to (e.g., nameof(model.PropertyName)) |
LabelText | string | Display label for the field |
EditorType | EditorType | Type of editor control (TextBox, DatePicker, etc.) |
Placeholder | string | Placeholder text when field is empty |
IsEnabled | bool | Whether field is enabled (default: true) |
Label Configuration
Setting Custom Labels
<FormItem Field="@nameof(employee.EmployeeId)"
LabelText="Employee ID">
</FormItem>
<FormItem Field="@nameof(employee.EmailAddress)"
LabelText="Email Address">
</FormItem>Using Display Attribute
If your model has [Display] attribute, the DataForm uses that automatically:
public class Employee
{
[Display(Name = "Full Name")]
public string Name { get; set; }
[Display(Name = "Work Email")]
public string Email { get; set; }
}Then simply use FormItem without LabelText:
<FormItem Field="@nameof(employee.Name)"></FormItem>
<FormItem Field="@nameof(employee.Email)"></FormItem>Removing Labels
<FormItem Field="@nameof(employee.AgreeToTerms)"
LabelText="">
</FormItem>Placeholder Text
Placeholder appears inside the input field when empty:
<FormItem Field="@nameof(user.Email)"
LabelText="Email"
Placeholder="Enter your email address">
</FormItem>
<FormItem Field="@nameof(user.PhoneNumber)"
LabelText="Phone"
Placeholder="(123) 456-7890">
</FormItem>Editor Type Selection
The EditorType property determines which control renders for a field. Common editor types:
public enum FormEditorType
{
TextBox, // Single-line text input
TextArea, // Multi-line text input
DatePicker, // Date selection
DateTimePicker, // Date and time selection
TimePicker, // Time selection
DropDownList, // Dropdown selection
ComboBox, // Editable dropdown
AutoComplete, // Auto-complete with suggestions
Checkbox, // Boolean checkbox (note: lowercase 'b')
Switch, // Toggle switch
Password // Password input with masking
}Examples
<SfDataForm Model="@order">
<FormItems>
<!-- Text input -->
<FormItem Field="@nameof(order.ProductName)"
LabelText="Product Name"
EditorType="FormEditorType.TextBox">
</FormItem>
<!-- Multi-line text -->
<FormItem Field="@nameof(order.Description)"
LabelText="Description"
EditorType="FormEditorType.TextArea">
</FormItem>
<!-- Date picker -->
<FormItem Field="@nameof(order.OrderDate)"
LabelText="Order Date"
EditorType="FormEditorType.DatePicker">
</FormItem>
<!-- Number input (uses TextBox with numeric model type) -->
<FormItem Field="@nameof(order.Quantity)"
LabelText="Quantity"
>
</FormItem>
<!-- Dropdown -->
<FormItem Field="@nameof(order.Status)"
LabelText="Status"
EditorType="FormEditorType.DropDownList">
</FormItem>
<!-- Checkbox (note lowercase 'b') -->
<FormItem Field="@nameof(order.IsUrgent)"
LabelText="Urgent Order"
EditorType="FormEditorType.Checkbox">
</FormItem>
</FormItems>
</SfDataForm>
@code {
public class Order
{
public string ProductName { get; set; }
public string Description { get; set; }
public DateTime OrderDate { get; set; }
public int Quantity { get; set; }
public string Status { get; set; }
public bool IsUrgent { get; set; }
}
private Order order = new();
}Disabling Fields
Disable a Field
Use the IsEnabled property to disable field input while keeping it visible:
<FormItem Field="@nameof(employee.EmployeeId)"
LabelText="Employee ID"
IsEnabled="false">
</FormItem>
<!-- Or conditionally -->
<FormItem Field="@nameof(employee.ApprovalStatus)"
LabelText="Status"
IsEnabled="@(employee.Status == 'Draft')">
</FormItem>Custom Attributes
Required Fields
Use data annotations to mark fields as required:
public class Employee
{
[Required(ErrorMessage = "Employee Name is required")]
public string Name { get; set; }
[Required]
public string Department { get; set; }
}The DataForm automatically validates these when submitted.
Pattern Validation
public class User
{
[RegularExpression(@"^\d{3}-\d{3}-\d{4}$",
ErrorMessage = "Phone must be in format: 123-456-7890")]
public string PhoneNumber { get; set; }
[RegularExpression(@"^[a-zA-Z0-9_]+$",
ErrorMessage = "Username can only contain letters, numbers, and underscores")]
public string Username { get; set; }
}Range Validation
public class Product
{
[Range(0, 1000, ErrorMessage = "Price must be between 0 and 1000")]
public decimal Price { get; set; }
[Range(1, int.MaxValue, ErrorMessage = "Quantity must be at least 1")]
public int Quantity { get; set; }
}String Length
public class Article
{
[StringLength(200, MinimumLength = 10,
ErrorMessage = "Title must be between 10 and 200 characters")]
public string Title { get; set; }
[StringLength(5000, MinimumLength = 100,
ErrorMessage = "Content must be between 100 and 5000 characters")]
public string Content { get; set; }
}Form Grouping
Organize fields into logical groups using <FormGroup>:
<SfDataForm Model="@employee">
<FormItems>
<FormGroup LabelText="Personal Information">
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.LastName)" LabelText="Last Name"></FormItem>
<FormItem Field="@nameof(employee.DateOfBirth)" LabelText="Date of Birth"></FormItem>
</FormGroup>
<FormGroup LabelText="Contact Information">
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
<FormItem Field="@nameof(employee.PhoneNumber)" LabelText="Phone"></FormItem>
</FormGroup>
<FormGroup LabelText="Employment Details">
<FormItem Field="@nameof(employee.EmployeeId)" LabelText="Employee ID"></FormItem>
<FormItem Field="@nameof(employee.Department)" LabelText="Department"></FormItem>
<FormItem Field="@nameof(employee.Salary)" LabelText="Salary"></FormItem>
</FormGroup>
</FormItems>
</SfDataForm>
@code {
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string EmployeeId { get; set; }
public string Department { get; set; }
public decimal Salary { get; set; }
}
private Employee employee = new();
}Column Organization
Multi-Column Layout
Set ColumnCount to arrange fields across multiple columns:
<SfDataForm Model="@employee" ColumnCount="2">
<FormItems>
<!-- These will arrange in 2 columns by default -->
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.LastName)" LabelText="Last Name"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
<FormItem Field="@nameof(employee.PhoneNumber)" LabelText="Phone"></FormItem>
</FormItems>
</SfDataForm>Column Span
Control how many columns a field spans:
<SfDataForm Model="@employee" ColumnCount="2">
<FormItems>
<FormItem Field="@nameof(employee.FirstName)"
LabelText="First Name"
ColumnSpan="1">
</FormItem>
<FormItem Field="@nameof(employee.LastName)"
LabelText="Last Name"
ColumnSpan="1">
</FormItem>
<!-- Spans both columns -->
<FormItem Field="@nameof(employee.FullAddress)"
LabelText="Full Address"
ColumnSpan="2">
</FormItem>
<FormItem Field="@nameof(employee.City)"
LabelText="City"
ColumnSpan="1">
</FormItem>
<FormItem Field="@nameof(employee.State)"
LabelText="State"
ColumnSpan="1">
</FormItem>
</FormItems>
</SfDataForm>Responsive Column Layout
<!-- Desktop: 3 columns, Tablet: 2 columns, Mobile: 1 column -->
<SfDataForm Model="@employee" ColumnCount="3">
<FormItems>
<!-- Form items automatically wrap based on screen size -->
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.LastName)" LabelText="Last Name"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
</FormItems>
</SfDataForm>See Also
- Auto-Generation - Automatically generate fields from model
- Validation - Add validation rules to form items
- Layout Customization - Customize form appearance
Getting Started with Syncfusion Blazor DataForm
Installation and NuGet Packages
The Syncfusion Blazor DataForm requires two NuGet packages:
Syncfusion.Blazor.DataForm- Core DataForm componentSyncfusion.Blazor.Themes- Theme styles
Install via Visual Studio
1. Open Tools → NuGet Package Manager → Manage NuGet Packages for Solution 2. Search for Syncfusion.Blazor.DataForm and install the latest version 3. Search for Syncfusion.Blazor.Themes and install 4. Click "Install" to confirm
Install via Package Manager Console
Install-Package Syncfusion.Blazor.DataForm
Install-Package Syncfusion.Blazor.ThemesInstall via .NET CLI
dotnet add package Syncfusion.Blazor.DataForm
dotnet add package Syncfusion.Blazor.Themes
dotnet restoreBlazor Project Setup
The DataForm component works with all three Blazor hosting models:
Blazor Server App
- Full server-side rendering with SignalR
- Best for real-time updates and complex business logic
Blazor WebAssembly App
- Client-side execution in the browser
- Best for offline scenarios and reduced server load
Blazor Web App (.NET 8+)
- Hybrid model with multiple render modes
- Can mix server and client rendering per component
Regardless of your hosting model, the DataForm setup remains the same.
Namespace Imports
Add these imports to your _Imports.razor file:
@using Syncfusion.Blazor
@using Syncfusion.Blazor.DataForm
@using System.ComponentModel.DataAnnotationsService Registration
Register the Syncfusion Blazor service in Program.cs:
using Syncfusion.Blazor;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
// Register Syncfusion Blazor service
builder.Services.AddSyncfusionBlazor();
await builder.Build().RunAsync();Theme Setup and Configuration
Add stylesheet and script references to your index.html (WebAssembly/Web App) or _Host.cshtml (Server):
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Blazor DataForm</title>
<!-- Add Syncfusion theme stylesheet -->
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />
</head>
<body>
<div id="app"></div>
<!-- Add Syncfusion script -->
<script src="_content/Syncfusion.Blazor.Core/scripts/syncfusion-blazor.min.js"></script>
</body>Available Themes
Choose one of these theme stylesheets:
<!-- Bootstrap 5 (Default) -->
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />
<!-- Material Design -->
<link href="_content/Syncfusion.Blazor.Themes/material.css" rel="stylesheet" />
<!-- Fluent UI (Microsoft) -->
<link href="_content/Syncfusion.Blazor.Themes/fluent.css" rel="stylesheet" />
<!-- Tailwind CSS -->
<link href="_content/Syncfusion.Blazor.Themes/tailwind.css" rel="stylesheet" />
<!-- Material Dark -->
<link href="_content/Syncfusion.Blazor.Themes/material-dark.css" rel="stylesheet" />
<!-- Fabric (Office) -->
<link href="_content/Syncfusion.Blazor.Themes/fabric.css" rel="stylesheet" />Basic DataForm Creation
Create a simple form with auto-generated fields based on your model:
@page "/dataform-basic"
@using System.ComponentModel.DataAnnotations
<div class="container mt-5">
<h2>Employee Registration Form</h2>
<SfDataForm ID="EmployeeForm"
Model="@employeeData">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
</div>
@code {
// Define your data model
public class Employee
{
[Required(ErrorMessage = "First Name is required")]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name is required")]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email format")]
[Display(Name = "Email")]
public string Email { get; set; }
[Display(Name = "Date of Birth")]
public DateTime? DateOfBirth { get; set; }
[Range(0, 150, ErrorMessage = "Please enter valid age")]
[Display(Name = "Age")]
public int Age { get; set; }
[Display(Name = "Active")]
public bool IsActive { get; set; } = true;
}
// Initialize your model
private Employee employeeData = new Employee
{
FirstName = "John",
LastName = "Doe"
};
}Running Your First Form
1. Build and Run:
dotnet run2. Navigate to Component:
- Add route to your component page
- Open browser to the component URL
3. Interact with Form:
- Fill in form fields
- See real-time validation feedback
- Submit the form
Complete Minimal Example
Here's a complete page-level example with submission handling:
@page "/employee-form"
@using System.ComponentModel.DataAnnotations
@inject NavigationManager NavManager
<div class="form-container">
<h3>New Employee Registration</h3>
<SfDataForm ID="EmployeeForm"
Model="@employee"
OnValidSubmit="HandleValidSubmit">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
<ValidationSummary></ValidationSummary>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
<FormButtons>
<button type="submit" class="btn btn-primary">Submit</button>
<button type="reset" class="btn btn-secondary">Clear</button>
</FormButtons>
</SfDataForm>
</div>
@code {
public class Employee
{
[Required]
[StringLength(50)]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
}
private Employee employee = new();
private async Task HandleValidSubmit(EditContext context)
{
// Handle form submission
await SaveEmployeeAsync(employee);
NavManager.NavigateTo("/employees");
}
private async Task SaveEmployeeAsync(Employee emp)
{
// Save employee to database
await Task.Delay(500);
Console.WriteLine($"Saved: {emp.FirstName} {emp.LastName}");
}
}Troubleshooting Common Setup Issues
Issue: Component not rendering
- Solution: Verify
AddSyncfusionBlazor()is called inProgram.cs - Solution: Check theme stylesheet is loaded in HTML head
- Solution: Ensure NuGet packages are installed:
dotnet restore
Issue: Styles not applying
- Solution: Clear browser cache (Ctrl+Shift+Delete)
- Solution: Check theme CSS file path in HTML
- Solution: Try different theme to isolate CSS issue
Issue: Validation not working
- Solution: Verify
DataAnnotationsValidatoris included - Solution: Check model properties have
[Required]or validation attributes - Solution: Ensure
System.ComponentModel.DataAnnotationsis imported
Issue: Form binding not working
- Solution: Check
Modelproperty is set onSfDataForm - Solution: Verify model property names match
Fieldattributes - Solution: Ensure model is public class with public properties
Next Steps
- Configure form items and fields - Learn to customize individual form fields
- Set up validation - Implement comprehensive form validation
- Handle form events - Respond to user interactions
Layout Customization in Blazor DataForm
Overview
Customize form layout with multi-column designs, column spanning, label positioning, button placement, and visual grouping.
Column Layout Configuration
Single Column (Default)
By default, forms render in a single column:
<SfDataForm Model="@employee">
<FormItems>
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.LastName)" LabelText="Last Name"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
</FormItems>
</SfDataForm>Multi-Column Layout
Set ColumnCount to arrange fields across multiple columns:
<!-- 2-column layout -->
<SfDataForm Model="@employee" ColumnCount="2">
<FormItems>
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.LastName)" LabelText="Last Name"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
<FormItem Field="@nameof(employee.PhoneNumber)" LabelText="Phone"></FormItem>
</FormItems>
</SfDataForm>
<!-- 3-column layout -->
<SfDataForm Model="@employee" ColumnCount="3">
<FormItems>
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.LastName)" LabelText="Last Name"></FormItem>
<FormItem Field="@nameof(employee.MiddleName)" LabelText="Middle Name"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
<FormItem Field="@nameof(employee.PhoneNumber)" LabelText="Phone"></FormItem>
<FormItem Field="@nameof(employee.FaxNumber)" LabelText="Fax"></FormItem>
</FormItems>
</SfDataForm>
@code {
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string FaxNumber { get; set; }
}
private Employee employee = new();
}Column Span
Control how many columns a single field spans using ColumnSpan:
<SfDataForm Model="@employee" ColumnCount="2">
<FormItems>
<!-- Single columns -->
<FormItem Field="@nameof(employee.FirstName)"
LabelText="First Name"
ColumnSpan="1">
</FormItem>
<FormItem Field="@nameof(employee.LastName)"
LabelText="Last Name"
ColumnSpan="1">
</FormItem>
<!-- Spans both columns -->
<FormItem Field="@nameof(employee.FullAddress)"
LabelText="Full Address"
ColumnSpan="2"
EditorType="FormEditorType.TextArea">
</FormItem>
<!-- Back to single columns -->
<FormItem Field="@nameof(employee.City)"
LabelText="City"
ColumnSpan="1">
</FormItem>
<FormItem Field="@nameof(employee.State)"
LabelText="State"
ColumnSpan="1">
</FormItem>
</FormItems>
</SfDataForm>
@code {
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
}
private Employee employee = new();
}Label Positioning
Control where labels appear relative to input fields:
Top Label (Default)
<SfDataForm Model="@employee" LabelPosition="FormLabelPosition.Top">
<FormItems>
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
</FormItems>
</SfDataForm>Result:
First Name
[________]
Email
[________]Left Label
<SfDataForm Model="@employee" LabelPosition="FormLabelPosition.Left">
<FormItems>
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
</FormItems>
</SfDataForm>Result:
First Name [________]
Email [________]Floating Labels
Floating labels appear inside the input and move up when the field has focus or value:
<SfDataForm Model="@user" LabelPosition="FormLabelPosition.Floating">
<FormItems>
<FormItem Field="@nameof(user.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(user.Email)" LabelText="Email Address"></FormItem>
<FormItem Field="@nameof(user.PhoneNumber)" LabelText="Phone Number"></FormItem>
</FormItems>
</SfDataForm>
@code {
public class User
{
public string FirstName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
}
private User user = new();
}Behavior:
- Empty state: Label floats inside input field (placeholder-like)
- Focus/with value: Label moves above input field
- Provides better UX and cleaner appearance
Form Grouping
Organize related fields into logical groups using FormGroup:
<SfDataForm Model="@employee">
<FormItems>
<FormGroup LabelText="Personal Information">
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.LastName)" LabelText="Last Name"></FormItem>
<FormItem Field="@nameof(employee.DateOfBirth)" LabelText="Date of Birth"></FormItem>
</FormGroup>
<FormGroup LabelText="Contact Information">
<FormItem Field="@nameof(employee.Email)" LabelText="Email"></FormItem>
<FormItem Field="@nameof(employee.PhoneNumber)" LabelText="Phone"></FormItem>
</FormGroup>
<FormGroup LabelText="Employment Details">
<FormItem Field="@nameof(employee.EmployeeId)" LabelText="Employee ID"></FormItem>
<FormItem Field="@nameof(employee.Department)" LabelText="Department"></FormItem>
<FormItem Field="@nameof(employee.Salary)" LabelText="Salary"></FormItem>
</FormGroup>
</FormItems>
</SfDataForm>
@code {
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string EmployeeId { get; set; }
public string Department { get; set; }
public decimal Salary { get; set; }
}
private Employee employee = new();
}Visual Result:
┌─ Personal Information ─────┐
│ First Name: [_________] │
│ Last Name: [_________] │
│ DOB: [_________] │
└───────────────────────────┘
┌─ Contact Information ──────┐
│ Email: [_________] │
│ Phone: [_________] │
└───────────────────────────┘
┌─ Employment Details ───────┐
│ Employee ID: [_________] │
│ Department: [_________] │
│ Salary: [_________] │
└───────────────────────────┘Collapsible Form Groups
Make form groups collapsible:
<SfDataForm Model="@employee">
<FormItems>
<FormGroup LabelText="Personal Information" IsCollapsed="false">
<FormItem Field="@nameof(employee.FirstName)" LabelText="First Name"></FormItem>
<FormItem Field="@nameof(employee.LastName)" LabelText="Last Name"></FormItem>
</FormGroup>
<FormGroup LabelText="Advanced Settings" IsCollapsed="true">
<FormItem Field="@nameof(employee.InternalNotes)" LabelText="Notes"></FormItem>
<FormItem Field="@nameof(employee.CustomField)" LabelText="Custom Field"></FormItem>
</FormGroup>
</FormItems>
</SfDataForm>Button Customization
Button Alignment
Control button positioning:
<SfDataForm Model="@employee"
ButtonsAlignment="FormButtonsAlignment.Right">
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
<FormButtons>
<button type="submit" class="btn btn-primary">Save</button>
<button type="reset" class="btn btn-secondary">Clear</button>
</FormButtons>
</SfDataForm>Options: Left, Right, Center, Stretch
Custom Buttons
Add custom buttons with custom actions:
<SfDataForm @ref="dataForm" Model="@employee" OnValidSubmit="HandleSubmit">
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
<FormButtons>
<button type="submit" class="btn btn-primary">Save</button>
<button type="reset" class="btn btn-secondary">Clear</button>
<button type="button" @onclick="HandleCancel" class="btn btn-outline-secondary">Cancel</button>
<button type="button" @onclick="HandlePreview" class="btn btn-info">Preview</button>
</FormButtons>
</SfDataForm>
@code {
private SfDataForm dataForm;
private Employee employee = new();
private async Task HandleSubmit(EditContext context)
{
await SaveEmployeeAsync();
}
private async Task HandleCancel()
{
await Task.CompletedTask;
// Navigate back or close
}
private async Task HandlePreview()
{
// Show preview
Console.WriteLine($"Preview: {employee.FirstName}");
}
private async Task SaveEmployeeAsync()
{
// Save logic
}
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}Hide Default Buttons
<SfDataForm Model="@employee" ShowSubmitButton="false" ShowResetButton="false">
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
<FormButtons>
<!-- Your custom buttons only -->
<button type="button" @onclick="SaveAndContinue" class="btn btn-success">
Save & Continue
</button>
</FormButtons>
</SfDataForm>Responsive Layout
Automatically adapt to screen size:
<SfDataForm Model="@employee"
ColumnCount="3"
ResponsiveLayoutSettings="@ResponsiveSettings">
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
private ResponsiveLayoutSettings ResponsiveSettings = new()
{
DesktopColumns = 3,
TabletColumns = 2,
MobileColumns = 1
};
}Behavior:
- Desktop (1200px+): 3 columns
- Tablet (768px-1199px): 2 columns
- Mobile (<768px): 1 column
Advanced Layout Example
Complete form with custom layout, groups, and responsive design:
<SfDataForm Model="@employee"
ColumnCount="2"
LabelPosition="FormLabelPosition.Top"
ButtonsAlignment="FormButtonsAlignment.Right"
OnValidSubmit="HandleSubmit">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormGroup LabelText="Personal Information">
<FormItem Field="@nameof(employee.FirstName)"
LabelText="First Name"
ColumnSpan="1">
</FormItem>
<FormItem Field="@nameof(employee.LastName)"
LabelText="Last Name"
ColumnSpan="1">
</FormItem>
<FormItem Field="@nameof(employee.DateOfBirth)"
LabelText="Date of Birth"
ColumnSpan="2">
</FormItem>
</FormGroup>
<FormGroup LabelText="Contact Information">
<FormItem Field="@nameof(employee.Email)"
LabelText="Email"
ColumnSpan="2">
</FormItem>
<FormItem Field="@nameof(employee.Phone)"
LabelText="Phone"
ColumnSpan="1">
</FormItem>
<FormItem Field="@nameof(employee.Mobile)"
LabelText="Mobile"
ColumnSpan="1">
</FormItem>
</FormGroup>
<FormGroup LabelText="Employment">
<FormItem Field="@nameof(employee.EmployeeId)"
LabelText="Employee ID"
ColumnSpan="1">
</FormItem>
<FormItem Field="@nameof(employee.Department)"
LabelText="Department"
ColumnSpan="1">
</FormItem>
<FormItem Field="@nameof(employee.Designation)"
LabelText="Designation"
ColumnSpan="2">
</FormItem>
</FormGroup>
</FormItems>
<FormButtons>
<button type="submit" class="btn btn-primary">Save Employee</button>
<button type="reset" class="btn btn-outline-secondary">Clear</button>
</FormButtons>
</SfDataForm>
@code {
public class Employee
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
[EmailAddress]
public string Email { get; set; }
public string Phone { get; set; }
public string Mobile { get; set; }
public string EmployeeId { get; set; }
public string Department { get; set; }
public string Designation { get; set; }
}
private Employee employee = new();
private async Task HandleSubmit(EditContext context)
{
await SaveEmployeeAsync();
}
private async Task SaveEmployeeAsync()
{
Console.WriteLine($"Saved: {employee.FirstName} {employee.LastName}");
}
}Best Practices
✅ DO:
- Use column layouts to reduce form height
- Group logically related fields
- Use floating labels for modern appearance
- Make forms responsive for mobile
- Provide clear visual hierarchy
- Align buttons consistently
❌ DON'T:
- Create overly wide columns (harder to read)
- Mix too many label positions
- Overcrowd form groups
- Ignore mobile responsiveness
- Hide buttons or make them unclear
See Also
- Form Items - Configure individual fields
- Templates - Custom form rendering
Localization in Blazor DataForm
Localize form validation messages, labels, and error messages for multi-language support.
Built-in Localization Resources
Syncfusion provides default localization strings for validation messages in multiple languages:
<SfDataForm Model="@employee">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
public class Employee
{
[Required]
public string Name { get; set; }
[EmailAddress]
public string Email { get; set; }
}
private Employee employee = new();
}Supported locales: en, de, fr, es, pt, ar, ja, ko, zh, it, nl, ru, and more.
Supported Languages
| Language | Locale Code | Notes |
|---|---|---|
| English | en | Default |
| German | de | German validation messages |
| French | fr | French validation messages |
| Spanish | es | Spanish validation messages |
| Portuguese | pt | Portuguese validation messages |
| Arabic | ar | Arabic with RTL support |
| Japanese | ja | Japanese validation messages |
| Korean | ko | Korean validation messages |
| Chinese | zh | Simplified Chinese |
| Italian | it | Italian validation messages |
| Dutch | nl | Dutch validation messages |
| Russian | ru | Russian validation messages |
Localizing Validation Messages
Localizing Field Labels
Use [Display] with ResourceType for label localization:
public class EmployeeLocalized
{
[Display(Name = nameof(Resources.FirstName),
ResourceType = typeof(Resources))]
[Required(ErrorMessageResourceName = nameof(Resources.FirstNameRequired),
ErrorMessageResourceType = typeof(Resources))]
public string FirstName { get; set; }
[Display(Name = nameof(Resources.Email),
ResourceType = typeof(Resources))]
[Required(ErrorMessageResourceName = nameof(Resources.EmailRequired),
ErrorMessageResourceType = typeof(Resources))]
[EmailAddress(ErrorMessageResourceName = nameof(Resources.EmailInvalid),
ErrorMessageResourceType = typeof(Resources))]
public string Email { get; set; }
}Resource File Setup
Create resource files for different languages:
Resources.en.resx (English):
FirstName = First Name
Email = Email Address
FirstNameRequired = First Name is required
EmailRequired = Email is required
EmailInvalid = Invalid email formatResources.de.resx (German):
FirstName = Vorname
Email = E-Mail-Adresse
FirstNameRequired = Vorname ist erforderlich
EmailRequired = E-Mail ist erforderlich
EmailInvalid = Ungültiges E-Mail-FormatCustom Localization Setup
Create Custom Localization Resource Class
public static class LocalizationStrings
{
public static Dictionary<string, Dictionary<string, string>> Resources = new()
{
{
"en",
new Dictionary<string, string>
{
{ "Required", "This field is required" },
{ "Email", "Please enter a valid email" },
{ "MinLength", "Minimum {0} characters required" },
{ "MaxLength", "Maximum {0} characters allowed" },
{ "Range", "Value must be between {0} and {1}" },
{ "FirstName", "First Name" },
{ "LastName", "Last Name" },
{ "EmailAddress", "Email Address" },
{ "PhoneNumber", "Phone Number" },
{ "Age", "Age" }
}
},
{
"de",
new Dictionary<string, string>
{
{ "Required", "Dieses Feld ist erforderlich" },
{ "Email", "Bitte geben Sie eine gültige E-Mail ein" },
{ "MinLength", "Mindestens {0} Zeichen erforderlich" },
{ "MaxLength", "Maximal {0} Zeichen zulässig" },
{ "Range", "Der Wert muss zwischen {0} und {1} liegen" },
{ "FirstName", "Vorname" },
{ "LastName", "Nachname" },
{ "EmailAddress", "E-Mail-Adresse" },
{ "PhoneNumber", "Telefonnummer" },
{ "Age", "Alter" }
}
},
{
"es",
new Dictionary<string, string>
{
{ "Required", "Este campo es obligatorio" },
{ "Email", "Por favor ingrese un correo válido" },
{ "MinLength", "Se requieren mínimo {0} caracteres" },
{ "MaxLength", "Se permiten máximo {0} caracteres" },
{ "Range", "El valor debe estar entre {0} y {1}" },
{ "FirstName", "Nombre" },
{ "LastName", "Apellido" },
{ "EmailAddress", "Correo electrónico" },
{ "PhoneNumber", "Número de teléfono" },
{ "Age", "Edad" }
}
}
};
public static string GetString(string key, string culture = "en")
{
if (Resources.TryGetValue(culture, out var translations))
{
return translations.TryGetValue(key, out var value) ? value : key;
}
return key;
}
}Using Custom Localization
@page "/custom-form/{culture}"
@inject NavigationManager Nav
<div class="language-selector">
<button @onclick="() => ChangeCulture('en')">English</button>
<button @onclick="() => ChangeCulture('de')">Deutsch</button>
<button @onclick="() => ChangeCulture('es')">Español</button>
</div>
<SfDataForm Model="@employee">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormItem Field="@nameof(employee.FirstName)"
LabelText="@GetLabel('FirstName')">
</FormItem>
<FormItem Field="@nameof(employee.LastName)"
LabelText="@GetLabel('LastName')">
</FormItem>
<FormItem Field="@nameof(employee.Email)"
LabelText="@GetLabel('EmailAddress')">
</FormItem>
</FormItems>
</SfDataForm>
@code {
[Parameter]
public string Culture { get; set; } = "en";
public class Employee
{
[Required(ErrorMessage = "")]
public string FirstName { get; set; }
[Required(ErrorMessage = "")]
public string LastName { get; set; }
[EmailAddress(ErrorMessage = "")]
public string Email { get; set; }
}
private Employee employee = new();
private string GetLabel(string key) => LocalizationStrings.GetString(key, Culture);
private void ChangeCulture(string culture)
{
Nav.NavigateTo($"/custom-form/{culture}", true);
}
}Culture Configuration
Set Application Culture
Configure default culture in Program.cs:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
// Set default culture
var culture = CultureInfo.GetCultureInfo("de");
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
builder.RootComponents.Add<App>("#app");
builder.Services.AddSyncfusionBlazor();
await builder.Build().RunAsync();Culture from Browser Settings
protected override async Task OnInitializedAsync()
{
var culture = await GetBrowserCultureAsync();
SetCulture(culture);
}
private async Task<string> GetBrowserCultureAsync()
{
// Get culture from browser or user preference
return "de";
}
private void SetCulture(string culture)
{
CultureInfo.CurrentCulture = new CultureInfo(culture);
CultureInfo.CurrentUICulture = new CultureInfo(culture);
}RTL (Right-to-Left) Support
For Arabic and other RTL languages:
<SfDataForm Model="@employee"
EnableRtl="true">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
public class Employee
{
public string Name { get; set; }
public string Email { get; set; }
}
private Employee employee = new();
}RTL automatically adjusts:
- Field and label alignment
- Text direction
- Button order
- Layout flow
Best Practices
✅ DO:
- Set culture at application startup
- Use resource files for maintenance
- Support RTL for Arabic/Hebrew
- Test with multiple languages
- Provide fallback English strings
- Allow user language preference selection
❌ DON'T:
- Hardcode validation messages
- Forget RTL support for RTL languages
- Leave error messages untranslated
- Ignore date/number format differences
- Skip testing with different cultures
See Also
- Validation - Set up validation rules
- Getting Started - Initial setup
Templates and Custom Rendering in Blazor DataForm
Table of Contents
- Overview
- FormTemplate
- FormItemTemplate
- Template Context
- Custom Editor Rendering
- Render Fragments
- Advanced Template Patterns
Overview
Templates allow you to customize the form structure and individual field rendering. Two primary templates:
1. FormTemplate - Customize overall form layout 2. FormItemTemplate - Customize individual field rendering
FormTemplate
The FormTemplate lets you wrap the form with custom HTML and styling:
<SfDataForm Model="@employee">
<FormTemplate>
<div class="custom-form-wrapper">
<div class="form-header">
<h2>Employee Registration</h2>
<p>Please fill in all required fields</p>
</div>
<div class="form-content">
@context
</div>
<div class="form-footer">
<small>All fields are required</small>
</div>
</div>
</FormTemplate>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
<style>
.custom-form-wrapper {
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
background: #f9f9f9;
}
.form-header {
margin-bottom: 20px;
border-bottom: 2px solid #007bff;
padding-bottom: 10px;
}
.form-content {
margin: 20px 0;
}
.form-footer {
margin-top: 20px;
text-align: center;
color: #666;
}
</style>
@code {
public class Employee
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
private Employee employee = new();
}FormItemTemplate
The FormItemTemplate customizes individual field rendering:
<SfDataForm Model="@user">
<FormItems>
<FormItem Field="@nameof(user.FirstName)" LabelText="First Name">
<FormItemTemplate Context="formItemContext">
<div class="custom-field">
<label class="custom-label">@formItemContext.LabelText</label>
<input type="text"
class="form-control custom-input"
@bind="user.FirstName"
placeholder="Enter first name" />
</div>
</FormItemTemplate>
</FormItem>
<FormItem Field="@nameof(user.Email)" LabelText="Email">
<FormItemTemplate Context="formItemContext">
<div class="custom-field">
<label class="custom-label">@formItemContext.LabelText</label>
<input type="email"
class="form-control custom-input"
@bind="user.Email"
placeholder="Enter email" />
</div>
</FormItemTemplate>
</FormItem>
</FormItems>
</SfDataForm>
<style>
.custom-field {
margin-bottom: 15px;
padding: 10px;
background: #fff;
border-radius: 4px;
}
.custom-label {
font-weight: bold;
color: #333;
margin-bottom: 5px;
display: block;
}
.custom-input {
border: 2px solid #ddd;
transition: border-color 0.3s;
}
.custom-input:focus {
border-color: #007bff;
}
</style>
@code {
public class User
{
[Required]
public string FirstName { get; set; }
[EmailAddress]
public string Email { get; set; }
}
private User user = new();
}Template Context
Access form metadata through template context:
public class FormItemTemplateContext
{
public string FieldName { get; set; } // Field property name
public string LabelText { get; set; } // Display label
public object Value { get; set; } // Current value
public string PlaceHolder { get; set; } // Placeholder text
public bool Enabled { get; set; } // Is field enabled
public List<string> ErrorMessages { get; set; } // Validation errors
public string EditorType { get; set; } // Field editor type
}Usage example:
<FormItemTemplate Context="itemContext">
<div class="field-wrapper">
<label>@itemContext.LabelText</label>
@if (itemContext.ErrorMessages?.Any() == true)
{
<div class="error-messages">
@foreach (var error in itemContext.ErrorMessages)
{
<span class="error-text">@error</span>
}
</div>
}
<input type="text"
value="@itemContext.Value"
disabled="@(!itemContext.Enabled)"
placeholder="@itemContext.PlaceHolder" />
</div>
</FormItemTemplate>Custom Editor Rendering
Render custom editors for specific field types:
<SfDataForm Model="@product">
<FormItems>
<!-- Standard field -->
<FormItem Field="@nameof(product.Name)" LabelText="Product Name"></FormItem>
<!-- Custom color picker editor -->
<FormItem Field="@nameof(product.Color)" LabelText="Color">
<FormItemTemplate>
<div class="color-picker-field">
<label>Color</label>
<input type="color"
@bind="product.Color"
class="form-control"
style="height: 40px;" />
<span>Selected: @product.Color</span>
</div>
</FormItemTemplate>
</FormItem>
<!-- Custom rating editor -->
<FormItem Field="@nameof(product.Rating)" LabelText="Rating">
<FormItemTemplate>
<div class="rating-field">
<label>Rating</label>
<div class="stars">
@for (int i = 1; i <= 5; i++)
{
var star = i;
<span @onclick="() => product.Rating = star"
class="star @(product.Rating >= star ? "filled" : "")">
★
</span>
}
</div>
</div>
</FormItemTemplate>
</FormItem>
<!-- Custom slider editor -->
<FormItem Field="@nameof(product.Quantity)" LabelText="Quantity">
<FormItemTemplate>
<div class="slider-field">
<label>Quantity: @product.Quantity</label>
<input type="range"
min="1"
max="100"
@bind="product.Quantity"
class="form-range" />
</div>
</FormItemTemplate>
</FormItem>
</FormItems>
</SfDataForm>
<style>
.color-picker-field input {
cursor: pointer;
}
.rating-field .stars {
font-size: 24px;
letter-spacing: 10px;
}
.star {
cursor: pointer;
color: #ddd;
}
.star.filled {
color: #ffc107;
}
.slider-field input {
width: 100%;
}
</style>
@code {
public class Product
{
public string Name { get; set; }
public string Color { get; set; } = "#000000";
public int Rating { get; set; }
public int Quantity { get; set; }
}
private Product product = new();
}Render Fragments
Use render fragments for reusable template components:
@page "/form-with-fragments"
<SfDataForm Model="@contact">
<FormItems>
<FormItem Field="@nameof(contact.Name)" LabelText="Name">
@FieldWithHint(contact.Name, "Enter full name")
</FormItem>
<FormItem Field="@nameof(contact.Email)" LabelText="Email">
@FieldWithValidation(contact.Email, "Invalid email format")
</FormItem>
<FormItem Field="@nameof(contact.Phone)" LabelText="Phone">
@FieldWithIcon(contact.Phone, "📱")
</FormItem>
</FormItems>
</SfDataForm>
@code {
public class Contact
{
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
private Contact contact = new();
// Reusable template with hint
private RenderFragment FieldWithHint(string value, string hint) => @<div class="field-with-hint">
<small class="hint">@hint</small>
</div>;
// Reusable template with validation message
private RenderFragment FieldWithValidation(string value, string validationMsg) => @<div class="field-with-validation">
@if (string.IsNullOrEmpty(value))
{
<span class="validation-error">@validationMsg</span>
}
</div>;
// Reusable template with icon
private RenderFragment FieldWithIcon(string value, string icon) => @<div class="field-with-icon">
<span class="icon">@icon</span>
</div>;
}Advanced Template Patterns
Pattern 1: Conditional Field Rendering
<SfDataForm Model="@user">
<FormItems>
<FormItem Field="@nameof(user.UserType)"
LabelText="User Type"
EditorType="FormEditorType.DropDownList">
</FormItem>
@if (user.UserType == "Admin")
{
<FormItem Field="@nameof(user.AdminLevel)"
LabelText="Admin Level"
EditorType="FormEditorType.DropDownList">
</FormItem>
}
else if (user.UserType == "Employee")
{
<FormItem Field="@nameof(user.Department)"
LabelText="Department"
EditorType="FormEditorType.DropDownList">
</FormItem>
}
</FormItems>
</SfDataForm>
@code {
public class User
{
public string UserType { get; set; }
public string AdminLevel { get; set; }
public string Department { get; set; }
}
private User user = new();
}Pattern 2: Nested Template with Binding
<SfDataForm Model="@employee">
<FormTemplate>
<div class="advanced-form">
<div class="section">
<h3>Basic Information</h3>
@BasicSection(employee)
</div>
<div class="section">
<h3>Contact Details</h3>
@ContactSection(employee)
</div>
</div>
</FormTemplate>
</SfDataForm>
@code {
private RenderFragment BasicSection(Employee emp) => @<div class="form-section">
<div class="form-group">
<label>Name</label>
<input type="text" @bind="emp.Name" class="form-control" />
</div>
<div class="form-group">
<label>Date of Birth</label>
<input type="date" @bind="emp.DOB" class="form-control" />
</div>
</div>;
private RenderFragment ContactSection(Employee emp) => @<div class="form-section">
<div class="form-group">
<label>Email</label>
<input type="email" @bind="emp.Email" class="form-control" />
</div>
<div class="form-group">
<label>Phone</label>
<input type="tel" @bind="emp.Phone" class="form-control" />
</div>
</div>;
public class Employee
{
public string Name { get; set; }
public DateTime DOB { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
private Employee employee = new();
}Pattern 3: Dynamic Field List
<SfDataForm Model="@survey">
<FormTemplate>
<div class="dynamic-form">
<h2>Survey Questions</h2>
@foreach (var question in survey.Questions)
{
@QuestionField(question)
}
<button @onclick="AddQuestion" class="btn btn-secondary">
+ Add Question
</button>
</div>
</FormTemplate>
</SfDataForm>
@code {
private RenderFragment QuestionField(Question q) => @<div class="question-field">
<input type="text" @bind="q.Text" placeholder="Question text" />
<button @onclick="() => RemoveQuestion(q)" class="btn btn-sm btn-danger">Remove</button>
</div>;
public class Survey
{
public List<Question> Questions { get; set; } = new();
}
public class Question
{
public string Text { get; set; }
}
private Survey survey = new();
private void AddQuestion()
{
survey.Questions.Add(new Question());
}
private void RemoveQuestion(Question q)
{
survey.Questions.Remove(q);
}
}Best Practices
✅ DO:
- Keep templates clean and readable
- Reuse fragments across multiple fields
- Use proper HTML structure and semantics
- Add appropriate CSS classes for styling
- Bind data properly with two-way binding
- Test template rendering on different screen sizes
❌ DON'T:
- Create overly complex nested templates
- Duplicate template code (use fragments)
- Forget to handle null values
- Create templates that break form validation
- Neglect accessibility (labels, ARIA attributes)
See Also
- Layout Customization - Form layout customization
- Form Items - Field configuration
- Events - Handle template interactions
Form Validation in Blazor DataForm
Table of Contents
- Overview
- Data Annotation Validation
- Built-in Validation Attributes
- DataAnnotationsValidator Setup
- Custom Validation Rules
- Complex Model Validation
- Fluent Validation Integration
- Displaying Validation Messages
- IsValid Method
- Conditional Validation
Overview
Form validation ensures data integrity and provides user feedback. The DataForm supports:
- Data Annotations - Built-in validation attributes
- Custom Validators - Business logic validation
- Fluent Validation - Fluent API for complex rules
- Server-side Validation - Backend validation integration
Data Annotation Validation
Data annotations are declarative validation attributes applied to model properties:
using System.ComponentModel.DataAnnotations;
public class Employee
{
[Required(ErrorMessage = "First Name is required")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name is required")]
public string LastName { get; set; }
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email format")]
public string Email { get; set; }
[Range(18, 65, ErrorMessage = "Age must be between 18 and 65")]
public int Age { get; set; }
}Built-in Validation Attributes
Required
Ensures a field has a value:
public class User
{
[Required]
public string Username { get; set; }
[Required(ErrorMessage = "Password is mandatory")]
public string Password { get; set; }
[Required(AllowEmptyStrings = false)]
public string Email { get; set; }
}StringLength
Validates string length:
public class Article
{
[StringLength(200, MinimumLength = 10,
ErrorMessage = "Title must be 10-200 characters")]
public string Title { get; set; }
[StringLength(5000)]
public string Content { get; set; }
[StringLength(100)]
public string Author { get; set; }
}Range
Validates numeric values within a range:
public class Product
{
[Range(0.01, 10000, ErrorMessage = "Price must be between 0.01 and 10000")]
public decimal Price { get; set; }
[Range(1, int.MaxValue, ErrorMessage = "Quantity must be at least 1")]
public int Quantity { get; set; }
[Range(0, 100, ErrorMessage = "Discount must be 0-100%")]
public decimal DiscountPercent { get; set; }
}EmailAddress
Validates email format:
public class Contact
{
[EmailAddress(ErrorMessage = "Invalid email format")]
public string Email { get; set; }
[EmailAddress]
public string BackupEmail { get; set; }
}RegularExpression
Validates against regex pattern:
public class User
{
[RegularExpression(@"^\d{3}-\d{3}-\d{4}$",
ErrorMessage = "Phone must be: 123-456-7890")]
public string PhoneNumber { get; set; }
[RegularExpression(@"^[a-zA-Z0-9_]{3,20}$",
ErrorMessage = "Username: 3-20 chars, letters/numbers/_")]
public string Username { get; set; }
[RegularExpression(@"^(?=.*[A-Z])(?=.*\d).{8,}$",
ErrorMessage = "Password: 8+ chars, uppercase, numbers")]
public string Password { get; set; }
}Compare
Compares two properties:
public class PasswordReset
{
[Required]
[StringLength(100, MinimumLength = 8)]
public string NewPassword { get; set; }
[Compare("NewPassword", ErrorMessage = "Passwords must match")]
public string ConfirmPassword { get; set; }
}URL
Validates URL format:
public class Website
{
[Url(ErrorMessage = "Invalid URL format")]
public string Url { get; set; }
[Url]
public string Logo { get; set; }
}MinLength / MaxLength
Validates collection length:
public class Survey
{
[MinLength(2, ErrorMessage = "Select at least 2 options")]
public string[] Options { get; set; }
[MaxLength(5, ErrorMessage = "Maximum 5 files allowed")]
public string[] Files { get; set; }
}Custom Display and Error Messages
public class Employee
{
[Required]
[Display(Name = "First Name", Description = "Enter your first name")]
public string FirstName { get; set; }
[Range(18, 65)]
[Display(Name = "Age", Description = "Your age in years")]
public int Age { get; set; }
[EmailAddress(ErrorMessage = "Please enter a valid email address")]
[Display(Name = "Work Email")]
public string Email { get; set; }
}DataAnnotationsValidator Setup
Add validation to your DataForm:
<SfDataForm ID="EmployeeForm" Model="@employee">
<!-- Add this FormValidator block -->
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
public class Employee
{
[Required]
public string FirstName { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Range(18, 65)]
public int Age { get; set; }
}
private Employee employee = new();
}The DataForm automatically validates on submit using the data annotation rules.
Custom Validation Rules
Create custom validators for business logic:
[AttributeUsage(AttributeTargets.Property)]
public class NotBlacklistedEmailAttribute : ValidationAttribute
{
private static readonly List<string> BlacklistedDomains = new()
{
"spam.com",
"fake.com",
"test.com"
};
protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (value is null)
return ValidationResult.Success;
var email = value.ToString();
var domain = email.Split('@')[1];
if (BlacklistedDomains.Contains(domain))
{
return new ValidationResult("Email domain is not allowed");
}
return ValidationResult.Success;
}
}Apply custom validator:
public class User
{
[Required]
[EmailAddress]
[NotBlacklistedEmail]
public string Email { get; set; }
}Business Logic Validation
[AttributeUsage(AttributeTargets.Property)]
public class FutureeDateAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (value is DateTime dateValue)
{
if (dateValue <= DateTime.Now)
{
return new ValidationResult("Date must be in the future");
}
}
return ValidationResult.Success;
}
}Usage:
public class Event
{
[Required]
public string Name { get; set; }
[Required]
[FutureDate(ErrorMessage = "Event date must be in the future")]
public DateTime EventDate { get; set; }
}Complex Model Validation
Nested Object Validation
public class Employee
{
[Required]
public string Name { get; set; }
[ValidateComplexType]
public Address Address { get; set; }
}
public class Address
{
[Required]
[StringLength(100)]
public string Street { get; set; }
[Required]
[StringLength(50)]
public string City { get; set; }
[RegularExpression(@"^\d{5}(-\d{4})?$",
ErrorMessage = "Invalid zip code")]
public string ZipCode { get; set; }
}Conditional Validation
[AttributeUsage(AttributeTargets.Class)]
public class ConditionalRequiredAttribute : ValidationAttribute
{
private readonly string _dependentProperty;
private readonly object _targetValue;
public ConditionalRequiredAttribute(string dependentProperty, object targetValue)
{
_dependentProperty = dependentProperty;
_targetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var property = context.ObjectType.GetProperty(_dependentProperty);
var dependentValue = property?.GetValue(context.ObjectInstance);
if (dependentValue?.Equals(_targetValue) == true && value == null)
{
return new ValidationResult($"This field is required when {_dependentProperty} is {_targetValue}");
}
return ValidationResult.Success;
}
}Apply to model:
[ConditionalRequired(nameof(Order.Expedite), true, ErrorMessage = "Tracking Number required for expedited orders")]
public class Order
{
public string OrderNumber { get; set; }
[Display(Name = "Expedite Shipping")]
public bool Expedite { get; set; }
[Display(Name = "Tracking Number")]
public string TrackingNumber { get; set; }
}Fluent Validation Integration
For complex validation scenarios, use Fluent Validation library:
Install NuGet Package
dotnet add package FluentValidationCreate Validator Class
using FluentValidation;
public class EmployeeValidator : AbstractValidator<Employee>
{
public EmployeeValidator()
{
RuleFor(x => x.FirstName)
.NotEmpty().WithMessage("First Name is required")
.Length(2, 50).WithMessage("First Name must be 2-50 characters");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email is required")
.EmailAddress().WithMessage("Invalid email format")
.Must(x => !x.Contains("test")).WithMessage("Test emails not allowed");
RuleFor(x => x.Age)
.InclusiveBetween(18, 65).WithMessage("Age must be 18-65");
RuleFor(x => x.Salary)
.GreaterThan(0).WithMessage("Salary must be greater than 0")
.LessThan(999999).WithMessage("Salary cannot exceed 999,999");
RuleFor(x => x.Department)
.Must(x => new[] { "HR", "IT", "Sales" }.Contains(x))
.WithMessage("Invalid department selected");
}
}
public class Employee
{
public string FirstName { get; set; }
public string Email { get; set; }
public int Age { get; set; }
public decimal Salary { get; set; }
public string Department { get; set; }
}Use in DataForm
@inject IValidator<Employee> EmployeeValidator
<SfDataForm ID="EmployeeForm"
Model="@employee"
OnValidSubmit="HandleValidSubmit">
<FormValidator>
<!-- Use custom validator instead of DataAnnotationsValidator -->
<FluentValidationValidator></FluentValidationValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
private Employee employee = new();
private async Task HandleValidSubmit(EditContext context)
{
var validator = EmployeeValidator;
var result = await validator.ValidateAsync(employee);
if (!result.IsValid)
{
// Handle validation errors
return;
}
// Save employee
await SaveEmployeeAsync(employee);
}
private async Task SaveEmployeeAsync(Employee emp)
{
// Save to database
await Task.Delay(500);
}
}Displaying Validation Messages
Automatic Message Display
Validation messages display automatically below invalid fields:
<SfDataForm Model="@user">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormItem Field="@nameof(user.Email)"
LabelText="Email"
Placeholder="Enter email">
</FormItem>
</FormItems>
</SfDataForm>Validation Summary
Display all validation errors in one place:
<SfDataForm Model="@employee" OnInvalidSubmit="HandleInvalidSubmit">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
<ValidationSummary></ValidationSummary>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
private Employee employee = new();
private Task HandleInvalidSubmit(EditContext context)
{
// ValidationSummary automatically displays errors
return Task.CompletedTask;
}
}Custom Error Display
<SfDataForm Model="@form" OnInvalidSubmit="OnInvalidSubmit">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
@if (validationErrors.Any())
{
<div class="alert alert-danger">
<h4>Please fix the following errors:</h4>
<ul>
@foreach (var error in validationErrors)
{
<li>@error</li>
}
</ul>
</div>
}
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
@code {
private List<string> validationErrors = new();
private Form form = new();
private Task OnInvalidSubmit(EditContext context)
{
validationErrors = context.GetValidationMessages()
.GroupBy(x => x.Split(": ")[0])
.Select(g => g.Key + ": " + string.Join(", ", g.Select(x => x.Split(": ")[1])))
.ToList();
return Task.CompletedTask;
}
public class Form
{
[Required]
public string Name { get; set; }
}
}IsValid Method
Check if form is valid without submitting:
<SfDataForm @ref="dataForm" Model="@employee">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormAutoGenerateItems></FormAutoGenerateItems>
</FormItems>
</SfDataForm>
<button @onclick="ValidateForm" class="btn btn-primary">Validate</button>
@code {
private SfDataForm dataForm;
private Employee employee = new();
private async Task ValidateForm()
{
if (await dataForm.IsValid())
{
Console.WriteLine("Form is valid");
}
else
{
Console.WriteLine("Form has validation errors");
}
}
public class Employee
{
[Required]
public string Name { get; set; }
[EmailAddress]
public string Email { get; set; }
}
}Conditional Validation
Validate fields based on other field values:
<SfDataForm Model="@order" OnUpdate="HandleUpdate">
<FormValidator>
<DataAnnotationsValidator></DataAnnotationsValidator>
</FormValidator>
<FormItems>
<FormItem Field="@nameof(order.OrderType)"
LabelText="Order Type"
EditorType="FormEditorType.DropDownList">
</FormItem>
@if (order.OrderType == "Express")
{
<FormItem Field="@nameof(order.TrackingNumber)"
LabelText="Tracking Number"
Placeholder="Required for express orders">
</FormItem>
}
<FormItem Field="@nameof(order.Notes)"
LabelText="Notes"
EditorType="FormEditorType.TextArea">
</FormItem>
</FormItems>
</SfDataForm>
@code {
public class Order
{
[Required]
public string OrderType { get; set; }
[RequiredIf(nameof(OrderType), "Express",
ErrorMessage = "Tracking Number required for express orders")]
public string TrackingNumber { get; set; }
public string Notes { get; set; }
}
private Order order = new();
private Task HandleUpdate(FormUpdateEventArgs args)
{
StateHasChanged();
return Task.CompletedTask;
}
}Best Practices
✅ DO:
- Use
[Required]for mandatory fields - Add meaningful error messages
- Combine multiple validation rules
- Validate on both client and server
- Use
[Display]for user-friendly labels - Test validation edge cases
❌ DON'T:
- Use vague error messages ("Error occurred")
- Skip server-side validation
- Validate sensitive data only on client
- Leave required fields without indicators
- Ignore edge cases
See Also
- Form Items - Field configuration
- Events - Handle validation events
- Data Binding - Model binding setup