# Form Submission & Model Binding Handle validated form submissions with strongly-typed models — no manual dictionary access required. --- ## Basic Submit (No Model Binding) The simplest approach uses an `onSuccess` callback with direct `HttpContext` access: ```csharp app.MapFormValidation("/api/forms/contact", onSuccess: async ctx => { var form = ctx.Request.Form; var name = form["name"].ToString(); var email = form["email"].ToString(); // Save to database, send email, etc. await SaveContactAsync(name, email); }); ``` The `onSuccess` callback fires only after all validation rules pass. If validation fails, the callback is never invoked. --- ## Strongly-Typed Model Binding Define a POCO model and let `FormModelBinder` handle the mapping automatically: ### Step 1: Create a Model ```csharp public class ContactFormModel { public string Name { get; set; } = ""; public string Email { get; set; } = ""; public string Password { get; set; } = ""; public int? Age { get; set; } public DateOnly? Birthdate { get; set; } public TimeOnly? Preferredtime { get; set; } public DateTime? Appointment { get; set; } public string Confirmation { get; set; } = ""; } ``` ### Step 2: Use the Typed Overload ```csharp app.MapFormValidation("/api/forms/contact", onSuccess: async model => { Console.WriteLine($"Name: {model.Name}"); Console.WriteLine($"Email: {model.Email}"); Console.WriteLine($"Age: {model.Age}"); Console.WriteLine($"Birth Date: {model.Birthdate}"); Console.WriteLine($"Preferred Time: {model.Preferredtime}"); Console.WriteLine($"Appointment: {model.Appointment}"); await SaveToDbAsync(model); }); ``` ### Step 3: Customize the Success Message (Optional) ```csharp app.MapFormValidation("/api/forms/contact", onSuccess: async model => { /* ... */ }, successMessage: "Thank you! Your form has been submitted."); ``` --- ## FormModelBinder `FormModelBinder.Bind()` maps form fields to model properties using reflection with these rules: - **Case-insensitive matching** — form field `name` matches property `Name` - **Automatic type conversion** for all common types - **Nullable support** — empty values become `null` for nullable types ### Supported Types | Type | Format Expected | |---|---| | `string` | Any text | | `int`, `long` | Integer text | | `float`, `double`, `decimal` | Numeric text (invariant culture) | | `bool` | `true`/`false`, `on`, `1` | | `DateTime` | Parseable datetime (e.g. `2025-12-25T10:30`) | | `DateOnly` | Parseable date (e.g. `2025-12-25`) | | `TimeOnly` | Parseable time (e.g. `14:30`) | | `Guid` | Standard GUID format | | `Enum` | Case-insensitive enum member name | All types support their `Nullable` equivalents (`int?`, `DateTime?`, etc.). --- ## API Reference ### MapFormValidation (without model binding) ```csharp public static RouteGroupBuilder MapFormValidation( this IEndpointRouteBuilder endpoints, string basePath, string successMessage = "✓ Form submitted successfully!", Func? onSuccess = null) where TValidator : FormValidator, new(); ``` ### MapFormValidation (with model binding) ```csharp public static RouteGroupBuilder MapFormValidation( this IEndpointRouteBuilder endpoints, string basePath, Func onSuccess, string successMessage = "✓ Form submitted successfully!") where TValidator : FormValidator, new() where TModel : new(); ``` ### Parameters | Parameter | Type | Description | |---|---|---| | `basePath` | `string` | URL prefix (e.g. `/api/forms/contact`) | | `successMessage` | `string` | HTML text shown on successful submission | | `onSuccess` | `Func?` or `Func` | Callback invoked after validation passes | ### Return Value Returns a `RouteGroupBuilder` for further endpoint configuration if needed. --- ## Registered Endpoints Both overloads register the same endpoint structure: | Method | Path | Purpose | |---|---|---| | `POST` | `{basePath}/validate` | Per-field validation (called on input blur) | | `POST` | `{basePath}/submit` | Full form validation and submission | Both endpoints have `.DisableAntiforgery()` applied since htmx sends raw form data. --- ## Response Format ### Validation Error Response When validation fails, the `/submit` endpoint returns HTML with OOB (out-of-band) swap fragments: ```html

Please enter a valid email address.

``` htmx processes each OOB fragment, updating every field's error element in a single response. ### Success Response ```html
✓ Form submitted successfully!
``` --- ## Complete Example ```csharp // ContactFormValidator.cs using Enciphered.Blazor.UIComponents.Validation; public class ContactFormValidator : FormValidator { public ContactFormValidator() { RuleFor("name", required: true, minLength: 2); RuleFor("email", required: true, pattern: @".+@.+\..+", message: "Please enter a valid email address."); } } // ContactFormModel.cs public class ContactFormModel { public string Name { get; set; } = ""; public string Email { get; set; } = ""; } // Program.cs app.MapFormValidation("/api/forms/contact", onSuccess: async model => { await db.Contacts.AddAsync(new Contact { Name = model.Name, Email = model.Email }); await db.SaveChangesAsync(); }); ``` ```razor @* ContactForm.razor *@ @page "/contact" ```