WPF MVVM with mvvmlight and Fluent.Validation

本文关键字:Fluent Validation and mvvmlight MVVM with WPF | 更新日期: 2023-09-27 18:05:30

我在使用我最新的应用程序时遇到了一点难题。

这是一个Master-Detail WPF MVVM应用程序,使用MVVM Light和Fluent.Validation。

View的DataContext是一个MainViewModel : ViewModelBase,左边是ListView的ObservableCollection<ProviderDto>,右边是详细属性的ProviderDto SelectedProvider

也有几个RelayCommands添加,编辑和删除单个ProviderDto

ViewModel使用ProviderService来执行这些动作,它被注入到它的构造函数中,mvvmlight的SimpleIoC在一个单独的ViewModelLocator中。

到目前为止一切都很好,我还设法拥有Design-Time-Data。

我现在尝试添加Fluent。Mix的验证和实现就像这篇文章中描述的那样(我的ProviderDto现在继承自ValidationBase而不是ObservableObject)。基础现在继承自ObservableObject。我还在ViewModelLocator中注册了ProviderDtoValidator。)

这允许我有我的ObservableObjects自动验证,并调用.IsValid对他们。

到目前为止一切顺利,我相信我将能够使它工作到视图,并使那些错误框变成红色:)。


现在是我真正的问题:

我想在视图上有一个按钮来保存SelectedProvider上的更改。这应该绑定到This:

Relaycommand SaveProviderCommand = new RelayCommand(SaveProvider, CanSaveProvider)
private bool CanSaveProvider()
{
    return SelectedProvider.IsValid;
}
private void SaveProvider()
{
    if (SelectedProvider.IsValid)
        _providerController.SaveProvider(SelectedProvider);
}

我在哪里把SaveProviderCommand SaveCommand?

如果我把它放在ViewModel中,那么我只能从SelectedProvider-Property中调用它:

public ProviderDto SelectedProvider
{
    get { return _selectedProvider; }
    set
    {
        Set(() => SelectedProvider, ref prV_selectedProvider, value);
        SaveProviderCommand.RaiseCanExecuteChanged(); // Here!
    }
}

SelectedProvider中只有一个属性被改变时,这显然不起作用。

另一种可能性是将Command放在DTO本身上,并在每次属性更改时调用它。例如,当Email-Property被更改时:

//A Property from Provider
public string Email
{
    get { return _email; }
    set
    {
        Set(() => Email, ref _email, value.TrimSafe());
        SaveProviderCommand.RaiseCanExecuteChanged(); // Here!
    }
}

这里的优点是,当我更改每个属性时,验证可以开箱即用地工作到视图级别。缺点是我必须在DTO的构造函数中注入ProviderController,以便可以在私有的Save-Method中调用它。我认为国防部不应该知道如何自救。它只应该能够判断.IsValid和save逻辑是否属于ViewModel。

我希望你能看到我的困境:

  • 如果我把SaveCommand在ViewModel,那么我将不得不这样做我不知道如何在视图中验证我的SelectedProvider。验证是如何工作的?我查看了datatem电镀控件,但我似乎无法使它与Fluent.Validation一起工作…

  • 如果我把SaveCommand在DTO本身,然后验证工作得很好,但我不认为这是正确的注入这么多的功能,应该保持愚蠢的东西

当然这是一个简单的例子,但我认为它足以说明这个问题。

WPF MVVM with mvvmlight and Fluent.Validation

我找到了一个适合解决这个问题的方法,也许会对别人有所帮助。

因为视图模型中的SelectedProvider和它的单个属性都实现了INotifyPropertyChanged(通过ViewModelBase或ObservableObject),我可以简单地订阅SelectedProvider。

public MainViewModel()
{
    // Constructor
    if (SelectedProvider != null)
        SelectedProvider.PropertyChanged += SelectedProvider_PropertyChanged;
}
private void SelectedProvider_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    SaveProviderCommand.RaiseCanExecuteChanged();
}

在视图中,我可以根据这篇文章实现控件

<Window.Resources>
    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <DockPanel>
                        <Grid DockPanel.Dock="Right" Width="16" Height="16"
                            VerticalAlignment="Center" Margin="3 0 0 0">
                            <Ellipse Width="16" Height="16" Fill="Red"/>
                            <Ellipse Width="3" Height="8" 
                                VerticalAlignment="Top" HorizontalAlignment="Center" 
                                Margin="0 2 0 0" Fill="White"/>
                            <Ellipse Width="2" Height="2" VerticalAlignment="Bottom" 
                                HorizontalAlignment="Center" Margin="0 0 0 2" 
                                Fill="White"/>
                        </Grid>
                        <Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
                            <AdornedElementPlaceholder/>
                        </Border>
                    </DockPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip" Value="{Binding RelativeSource=
                    {x:Static RelativeSource.Self}, 
                    Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<TextBox Name="TxtEmail" Margin="5" Text="{Binding Path=SelectedProvider.Email, 
    UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, Mode=TwoWay}" />

这种方法为我提供了我想要的关注点分离和很好的开箱即用的验证。唯一的小缺点:我觉得在VM中使用Event-Subscription不太美观。