SkillAgentSearch skills...

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.DataForms
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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.DataForms form.
  • 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

View on GitHub
GitHub Stars72
CategoryDevelopment
Updated14d ago
Forks13

Languages

C#

Security Score

95/100

Audited on Mar 20, 2026

No findings