net maui form validation

1.8k Views Asked by At

Still learning. I am trying to do a model validation using CommunityToolkit.Mvvm and I stuck in the styles (min there).

Here is what i tried:

Model:

public abstract partial class BaseModel : ObservableObject, IDataErrorInfo
{
    private Dictionary<string, string> validationErrors = new Dictionary<string, string>();
    private bool isValid = false;

    protected Dictionary<string, string> ValidationErrors
    {
        get => validationErrors;
        private set => SetProperty(ref validationErrors, value);
    }

    protected bool IsValid
    {
        get => isValid;
        private set => SetProperty(ref isValid, value);
    }

    [RelayCommand]
    protected void Validate()
    {
        var context = new ValidationContext(this);
        var errors = new List<ValidationResult>();

        isValid = Validator.TryValidateObject(this, context, errors, true);
        foreach (var error in errors)
        {
            var columnName = error.MemberNames.First();

            if(!validationErrors.ContainsKey(columnName))
                validationErrors.Add(columnName, error.ErrorMessage);
        }
    }


    // check for general model error
    public string Error { get; private set; } = null;

    // check for property errors
    public string this[string columnName]
    {
        get
        {
            Validate();
            return validationErrors[columnName];
        }
    }
}

public class PlayerModel : BaseModel
{
    public int Id { get; set; }

    [Required]
    [StringLength(255)]
    public string Name { get; set; }

    [StringLength(4096)]
    public string LocalImageLink { get; set; }

    [Required]
    [StringLength(4096)]
    public string WebImageLink { get; set; }

    [Required]
    [StringLength(255)]
    public string Club { get; set; }

    [Required]
    [StringLength(32)]
    public string Birthday { get; set; }

    [Required]
    [StringLength(255)]
    public string BirthPlace { get; set; }

    [Required]
    [Range(0, 100)]
    public int Weight { get; set; }

    [Required]
    [Range(0, 2.5)]
    public double Height { get; set; }

    [Required]
    public string Description { get; set; }

    public string PositionName { get; set; }

    [Required]
    [Range(1, 7)]
    public int PositionId { get; set; }

    public PlayerModel()
    {
    }

    public PlayerModel(int id, string name, string localImageLink, string webImageLink, string club, string birthday, string birthPlace, int weight, double height, string description, string positionName, int positionId)
    {
        Id = id;
        Name = name;
        LocalImageLink = localImageLink;
        WebImageLink = webImageLink;
        Club = club;
        Birthday = birthday;
        BirthPlace = birthPlace;
        Weight = weight;
        Height = height;
        Description = description;
        PositionName = positionName;
        PositionId = positionId;
    }

    public PlayerModel(int id, string name, string localImageLink, string webImageLink, string club, string birthday, string birthPlace, int weight, double height, string description, PositionModel position)
    {
        Id = id;
        Name = name;
        LocalImageLink = localImageLink;
        WebImageLink = webImageLink;
        Club = club;
        Birthday = birthday;
        BirthPlace = birthPlace;
        Weight = weight;
        Height = height;
        Description = description;
        PositionName = position.Name;
        PositionId = position.Id;
    }

    public PlayerModel(PlayerEntity player)
    {
        Id = player.Id;
        Name = player.Name;
        LocalImageLink = player.LocalImageLink;
        WebImageLink = player.WebImageLink;
        Club = player.Club;
        Birthday = player.Birthday;
        BirthPlace = player.BirthPlace;
        Weight = player.Weight;
        Height = player.Height;
        Description = player.Description;
        PositionName = player.Position.Name;
        PositionId = player.Position.Id;
    }

    public PlayerEntity ToEntity()
    {
        return new PlayerEntity
        {
            Id = Id,
            Name = Name,
            LocalImageLink = LocalImageLink,
            WebImageLink = WebImageLink,
            Club = Club,
            Birthday = Birthday,
            BirthPlace = BirthPlace,
            Weight = Weight,
            Height = Height,
            Description = Description,
            Position = new PositionEntity
            { 
                Id = PositionId,
                Name = PositionName
            }
        };
    }

    public void ToEntity(PlayerEntity player)
    {
        player.Id = Id;
        player.Name = Name;
        player.LocalImageLink = LocalImageLink;
        player.WebImageLink = WebImageLink;
        player.Club = Club;
        player.Birthday = Birthday;
        player.BirthPlace = BirthPlace;
        player.Weight = Weight;
        player.Height = Height;
        player.Description = Description;

        player.Position.Id = PositionId;
        player.Position.Name = PositionName;
    }
}

View:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiUI.Pages.AddOrUpdatePlayer"
             xmlns:local="clr-namespace:Backend.Models;assembly=Backend.Models"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit">

    <ContentPage.BindingContext>
        <local:PlayerModel />
    </ContentPage.BindingContext>

    <ContentPage.ToolbarItems>
        <ToolbarItem IconImageSource="save.svg" Clicked="OnSaveClick"/>
    </ContentPage.ToolbarItems>

    <ScrollView Margin="10">
        <VerticalStackLayout>
            <VerticalStackLayout>
                <Label Text="Name" />
                <Entry x:Name="name" Text="{Binding Name, Mode=TwoWay}"
                       ClearButtonVisibility="WhileEditing">
                    <Entry.Behaviors>
                        <toolkit:EventToCommandBehavior
                                EventName="TextChanged"
                                Command="{Binding Validate}" />
                    </Entry.Behaviors>
                </Entry>
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Position" />
                <Picker x:Name="position" Title="Select..."
                        ItemDisplayBinding="{Binding Name}"
                        SelectedItem="{Binding PositionId, Mode=TwoWay}" />
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Club" />
                <Entry x:Name="club" Text="{Binding Club, Mode=TwoWay}" 
                       ClearButtonVisibility="WhileEditing" />
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Birthday" />
                <DatePicker  x:Name="birthday" Date="{Binding Birthday, Mode=TwoWay}" />
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Birth place" />
                <Entry x:Name="birthplace" Text="{Binding BirthPlace, Mode=TwoWay}" 
                       ClearButtonVisibility="WhileEditing" />
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Weight" />
                <Entry x:Name="weight" Text="{Binding Weight, Mode=TwoWay}"
                       ClearButtonVisibility="WhileEditing" Keyboard="Numeric"/>
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Height" />
                <Entry x:Name="height" Text="{Binding Height, Mode=TwoWay}" 
                       ClearButtonVisibility="WhileEditing" Keyboard="Numeric"/>
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Image link" />
                <Entry x:Name="webImageLink" Text="{Binding WebImageLink, Mode=TwoWay}"
                       ClearButtonVisibility="WhileEditing"/>
            </VerticalStackLayout>
            <VerticalStackLayout Margin="0,10">
                <Label Text="Description" />
                <Editor x:Name="description" Text="{Binding Description, Mode=TwoWay}"
                        AutoSize="TextChanges"/>
            </VerticalStackLayout>
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

View code behind:

[QueryProperty(nameof(Player), "player")]
public partial class AddOrUpdatePlayer : ContentPage
{
    private PlayerModel player;
    public PlayerModel Player
    {
        get => player;
        set
        {
            player = value;
            OnPropertyChanged();
        }
    }


    private readonly IPositionClient positionClient;
    private readonly IPlayerClient playerClient;

    private delegate Task Action();
    private Action asyncAction;

    public AddOrUpdatePlayer(IPositionClient positionClient, IPlayerClient playerClient)
    {
        InitializeComponent();
        SetUpControls();
        SetTitle();
        SetActionPointer();

        this.positionClient = positionClient;
        this.playerClient = playerClient;
    }

    protected async override void OnAppearing()
    {
        BindingContext = player;
        await SetUpPositionPicker();
    }

    private void SetUpControls()
    {
        birthday.MinimumDate = new DateTime(1900, 1, 1);
        birthday.MaximumDate = DateTime.Now.Date;
    }

    private async Task SetUpPositionPicker()
    {
        position.ItemsSource = await this.positionClient.GetAllAsync();
    }

    private void SetTitle()
    {
        Title = this.player is null ?
                "Add new player" :
                 $"Update {player.Name}";
    }

    private void SetActionPointer()
    {
        asyncAction = this.player is null ?
                      AddNewPlayer :
                      UpdatePlayer;
    }

    private async Task AddNewPlayer()
    {
        var player = BindingContext as PlayerModel;

        await playerClient.CreateAsync(player);

        await Shell.Current.GoToAsync(PageRoutes.HomePage, true);
    }

    private async Task UpdatePlayer()
    { }

    private async void OnSaveClick(object sender, EventArgs e)
    {
        await asyncAction();
    }
}

Global style:

<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit">

    <Entry.Style>
        <OnPlatform x:TypeArguments="Style">
            <On Platform="iOS, Android" Value="{StaticResource EntryStyle}" />
            <On Platform="WinUI" Value="{StaticResource WinUIEntryStyle}" />
        </OnPlatform>
    </Entry.Style>
    <Entry.Behaviors>
        <mct:EventToCommandBehavior
        EventName="TextChanged"
        Command="{Binding Validate}" />
    </Entry.Behaviors>
    <Entry.Triggers>
        <DataTrigger
        TargetType="Entry"
        Binding="{Binding UserName.IsValid}"
        Value="False">
            <Setter Property="BackgroundColor" Value="{StaticResource ErrorColor}" />
        </DataTrigger>
    </Entry.Triggers>

</ResourceDictionary>

I need to change the Entry.Triggers DataTrigger Binding property to adapt to my solution if it's possible. My another problem is that when the button is clicked to save the BindingContext is null, no data is banded to the model in the AddNewPlayer() function.

0

There are 0 best solutions below