c# - 带有 mvvmlight 和 Fluent.Validation 的 WPF MVVM

标签 c# wpf mvvm mvvm-light fluentvalidation

我对我的最新应用程序有点困惑。

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

View 的 DataContext 是 MainViewModel : ViewModelBaseObservableCollection<ProviderDto>对于左侧的 ListView 和属性 ProviderDto SelectedProvider右侧的详细属性。

还有几个RelayCommands添加、编辑和删除单个 ProviderDto

ViewModel 使用 ProviderService执行这些操作,这些操作通过 mvvmlight 的 SimpleIoC 注入(inject)到它的构造函数中在一个单独的ViewModelLocator .

到目前为止一切正常,我还设法获得了设计时数据。

我现在尝试将 Fluent.Validation 添加到 Mix 并按照 this post 中的描述进行实现(我的 ProviderDto 现在继承自 ValidationBase 而不是 ObservableObject 。Base 现在继承自 ObservableObject 。而且我在 ProviderDtoValidator 中注册了 ViewModelLocator。)

这让我可以自动验证我的 ObservableObjects 并调用 .IsValid在他们身上。

到目前为止一切顺利,我相信我能够让它适应 View 并使那些错误框变红 :)。


现在回答我真正的问题:

我想在 View 上有一个按钮来保存对 SelectedProvider 的更改.这自然应该与此绑定(bind):

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 中只有一个属性时,这显然不起作用。发生变化。

另一种可能性是将命令放在 DTO 本身上,并在每次更改属性时调用它。例如,当电子邮件属性更改时:

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

这里的优点是,当我更改每个属性时,验证工作开箱即用,直到 View 级别。缺点是我必须注入(inject) ProviderController在 DTO 的构造函数中,因此可以在私有(private)保存方法中调用它。我认为 DTO 不应该知道如何自救。它只应该能够判断它是否.IsValid保存逻辑应该属于 ViewModel。

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

  • 如果我将 SaveCommand 放在 ViewModel 中,那么我将不得不做 我不知道如何在 View 中验证我的 SelectedProvider。验证将如何工作?我查看了控件的 DataTemplating,但我似乎无法使其与 Fluent.Validation 一起工作。

  • 如果我将 SaveCommand 放在 DTO 本身中,那么验证会很好地工作,但我认为在应该保持愚蠢的东西中注入(inject)如此多的功能是不正确的。

当然这是一个浓缩的例子,但我认为足以说明问题。希望得到一些关于模式和实践的好的建议。

最佳答案

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

由于 Viewmodel 中的 SelectedProvider 及其单个属性都实现了 INotifyPropertyChanged(通过 ViewModelBase 或 ObservableObject),因此我可以简单地订阅 ViewModel 中的 SelectedProvider.PropertyChanged。

public MainViewModel()
{
    // Constructor
    if (SelectedProvider != null)
        SelectedProvider.PropertyChanged += SelectedProvider_PropertyChanged;
}

private void SelectedProvider_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    SaveProviderCommand.RaiseCanExecuteChanged();
}

在 View 中,我可以根据 this post 实现控件

<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 中进行事件订阅不太美观......

关于c# - 带有 mvvmlight 和 Fluent.Validation 的 WPF MVVM,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31695804/

相关文章:

c# - BitmapImage 缺少 BeginInit() 和 EndInit() 函数?

c# - 使用本地 .NET Core 系统库而不是将它们添加到每个包中

wpf - 避免输出窗口中的绑定(bind)错误

c# - 绑定(bind)到 Combobox ToString 方法的 ObservableCollection 不更新

c# - 在 XAML 中将 CallMethodAction 中的 TargetObject 设置为父 DataContext 中的 ViewModel

c# - WPF - 将数据从 DAL 传递到 UI

c# - pdatASP.NET Forms 身份验证 CSS 样式不起作用

c# - 如何提取visual studio项目模板图标?

c - 加载 MFC View 时,调试断言在\$InstallDir\VC\atlmfc\src\mfc\appgui3.cpp 第 385 行失败

c# - DependencyProperty 默认值取决于强制逻辑