Maui.DataForms
This is a proof of concept library for easily creating validable data entry forms in .NET MAUI. This is not published as a Nuget package yet, so please clone locally and add reference to the Maui.FluentForms project to use.
Install / Use
/learn @rjygraham/Maui.DataFormsREADME
Maui.DataForms
This is a proof of concept for easily creating validable data entry forms in .NET MAUI. This is not published as a Nuget package yet, so please clone locally and add reference to the Maui.DataForms.Core, Maui.DataForms.Controls, and either Maui.DataForms.Dynamic or Maui.DataForms.Fluent projects to use.
Maui.DataForms provides 2 modes of defining data forms within your applications:
- Fluent: this method is strongly typed and uses a classes representing your model and
Maui.DataFormsform. - Dynamic: this method allows for easy creation of dynamic forms at runtime. Forms can be created in code based on certain criteria or even generated by a server-side API and then deserialized from JSON into a
DynamicDataForm.
Fluent Demo
The included Maui.DataForms.Sample project illustrates how to use Mail.DataForms. Below are the highlights of using the Fluent API. There is also a Dynamic API which will be discussed at the bottom of this page.
Person
The Person class represents the underlying data model from which the form will be created.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public TimeSpan TimeOfBirth { get; set; }
public string Biography { get; set; }
public double Height { get; set; }
public double Weight { get; set; }
public bool LikesPizza { get; set; }
public bool IsActive { get; set; }
}
PersonValidator
The PersonValidator class is the FluentValidation validator used to validate user inputs in our data form. You can use any validation framewwork you wish, just be sure to implement Maui.DataForms.Validation.IDataFormValidator<TModel> where TModel is the data model class (in this case Person) to be validated.
public class PersonValidator : AbstractValidator<Person>, IDataFormValidator<Person>
{
public PersonValidator()
{
RuleFor(r => r.FirstName)
.NotEmpty()
.MaximumLength(20);
RuleFor(r => r.LastName)
.NotEmpty()
.MaximumLength(50);
RuleFor(r => r.DateOfBirth)
.NotEmpty()
.GreaterThanOrEqualTo(new DateTime(2000, 1, 1, 0, 0, 0))
.LessThanOrEqualTo(new DateTime(2021, 12, 31, 23, 59, 59));
RuleFor(r => r.Biography)
.NotEmpty()
.MaximumLength(500);
RuleFor(r => r.Height)
.GreaterThan(0.2)
.LessThanOrEqualTo(0.8);
RuleFor(r => r.Weight)
.GreaterThan(20.0)
.LessThanOrEqualTo(80.0);
}
public FormFieldValidationResult ValidateField(Person model, string formFieldName)
{
var members = new string[] { formFieldName };
var validationContext = new ValidationContext<Person>(model, new PropertyChain(), new MemberNameValidatorSelector(members));
var validationResults = Validate(validationContext);
var errors = validationResults.IsValid
? Array.Empty<string>()
: validationResults.Errors.Select(s => s.ErrorMessage).ToArray();
return new FormFieldValidationResult(validationResults.IsValid, errors);
}
public DataFormValidationResult ValidateForm(Person model)
{
var validationResults = Validate(model);
var errors = validationResults.IsValid
? new Dictionary<string, string[]>()
: validationResults.ToDictionary();
return new DataFormValidationResult(validationResults.IsValid, errors);
}
}
PersonForm
The PersonDataForm class is where the UI elements for the data entry form are defined. To build the form, just inherit from FluentFormBase<TModel> and then create a constructor which passes the model and validator (optional) instances to the base class and the define your fields using the fluent syntax. Finally call Build()
public class PersonDataForm : FluentFormBase<Person>
{
public PersonDataForm(Person model, IDataFormValidator<Person> validator = null)
: base(model, validator)
{
FieldFor(f => f.FirstName)
.AsEntry()
.WithConfiguration(config => config.Placeholder = "First Name")
.WithLayout(layout => layout.GridRow = 0)
.WithValidationMode(ValidationMode.Auto);
FieldFor(f => f.LastName)
.AsEntry()
.WithConfiguration(config => config.Placeholder = "Last Name")
.WithLayout(layout => layout.GridRow = 1)
.WithValidationMode(ValidationMode.Auto);
FieldFor(f => f.DateOfBirth)
.AsDatePicker()
.WithConfiguration(config =>
{
config.Format = "D";
config.MinimumDate = DateTime.MinValue;
config.MaximumDate = DateTime.MaxValue;
})
.WithLayout(layout => layout.GridRow = 2)
.WithValidationMode(ValidationMode.Auto);
FieldFor(f => f.TimeOfBirth)
.AsTimePicker()
.WithConfiguration(config => config.Format = "t")
.WithLayout(layout => layout.GridRow = 3)
.WithValidationMode(ValidationMode.Auto);
FieldFor(f => f.Biography)
.AsEditor()
.WithConfiguration(config => config.Placeholder = "Biography")
.WithLayout(layout => layout.GridRow = 4)
.WithValidationMode(ValidationMode.Auto);
FieldFor(f => f.Height)
.AsSlider()
.WithConfiguration(config =>
{
config.Minimum = 0.1;
config.Maximum = 0.9;
})
.WithLayout(layout => layout.GridRow = 5)
.WithValidationMode(ValidationMode.Auto);
FieldFor(f => f.Weight)
.AsStepper()
.WithConfiguration(config =>
{
config.Minimum = 10.0;
config.Maximum = 90.0;
})
.WithLayout(layout => layout.GridRow = 6)
.WithValidationMode(ValidationMode.Auto);
FieldFor(f => f.LikesPizza)
.AsSwitch()
.WithLayout(layout => layout.GridRow = 7);
FieldFor(f => f.IsActive)
.AsCheckBox()
.WithLayout(layout => layout.GridRow = 8);
Build();
}
}
FluentDemoPageViewModel.cs
The FluentDemoPageViewModel class then sets the PersonDataForm (autogenerated by CTK MVVM source generators) to a new instance of PersonDataForm with an instances of Person model and PersonValidator as constructor parameters.
public partial class FluentDemoPageViewModel : ObservableObject
{
[ObservableProperty]
private PersonDataForm personDataForm;
public FluentDemoPageViewModel()
{
PersonDataForm = new PersonDataForm(new Models.Person(), new PersonValidator());
}
[RelayCommand]
private async Task Submit()
{
// no-op for now.
}
}
FluentDemoPage.xaml.cs
Set the BindingContext to a new instance of FluentDemoPageViewModel. In the sample, the FluentDemoPageViewModel is configured in IServiceCollection and automatically injected by MAUI at runtime.
public partial class FluentDemoPage : ContentPage
{
public FluentDemoPage(FluentDemoPageViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
}
FluentDemoPage.xaml
Add the mdfc namespace and then a Grid with BindableLayout.ItemsSource="{Binding PersonDataForm.Fields}" which binds to the Fields property of the PersonDataForm which is a property of the view model previously set to the BindingContext. Finally, set the Forms BindableLayout.ItemTemplateSelector to an instance of DataFormsDataTemplateSelector.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="Maui.DataForms.Sample.Views.FluentDemoPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mdfc="clr-namespace:Maui.DataForms.Controls;assembly=Maui.DataForms.Core"
xmlns:viewModels="clr-namespace:Maui.DataForms.Sample.ViewModels"
Title="Fluent Demo"
x:DataType="viewModels:FluentDemoPageViewModel">
<ScrollView>
<Grid
Margin="20"
RowDefinitions="Auto,Auto,Auto"
RowSpacing="10">
<Label
Grid.Row="0"
FontSize="18"
HorizontalOptions="Center"
Text="The data form below was generated using a model class and is validated using FluentValidation." />
<Grid
Grid.Row="1"
BindableLayout.ItemsSource="{Binding PersonDataForm.Fields}"
RowDefinitions="*,*,*,*,*,*,*,*,*"
VerticalOptions="Start">
<BindableLayout.ItemTemplateSelector>
<mdfc:DataFormsDataTemplateSelector />
</BindableLayout.ItemTemplateSelector>
</Grid>
<Button
Grid.Row="2"
Command="{Binding SubmitCommand}"
Text="Submit" />
</Grid>
</ScrollView>
</ContentPage>
FormField Controls
The default FormField controls are defined in the Maui.DataForms.Controls project. For now, the controls are very basic to prove the proof of concept. The controls must be registered at application startup in MauiProgram using the UseDefaultFormFieldContentControls extension method:
using CommunityToolkit.Maui;
using Maui.DataForms.Sample.ViewModels;
using Maui.DataForms.Sample.Views;
using Maui.DataForms.Controls;
namespace Maui.DataForms.Sample;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCommunityToolkit()
.UseDefaultFormFieldContentControls()
Related Skills
node-connect
347.6kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
108.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
347.6kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
347.6kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
