Reusable async validation for WPF with Prism 5
WPF has supported validation since the first release in .NET 3.0. That support is built into the binding object and allows you to indicate validation errors through exceptions, an implementation of the IDataErrorInfo interface, or by using WPF ValidationRules. Additional support was added in .NET 4.5 for INotifyDataErrorInfo, an async variant of IDataErrorInfo that was first introduced in Silverlight 4. WPF not only supports evaluating whether input data is valid using one of those mechanisms, but will change the appearance of the control to indicate errors, and it holds those errors on the control as properties that can be used to further customize the presentation of the errors.
Prism is a toolkit produced by Microsoft patterns & practices that helps you build loosely coupled, extensible, maintainable and testable WPF and Silverlight applications. It has had several releases since Prism 1 released in June 2008. There is also a Prism for Windows Runtime for building Windows Store applications. I’ve had the privilege to work with the Prism team as a vendor and advisor contributing to the requirements, design and code of Prism since the first release. The Prism team is working on a new release that will be titled Prism 5 that targets WPF 4.5 and above and introduces a number of new features. For the purposes of this post, let’s leverage some stuff that was introduced in Prism 4, as well as some new stuff in Prism 5.
Picking a validation mechanism
Of the choices for supporting validation, the best one to choose in most cases is the new support for INotifyDataErrorInfo because it’s evaluated by default by the bindings, supports more than one error per property and allows you to evaluate validation rules both synchronously and asynchronously.
The definition of INotifyDataErrorInfo looks like this:
public interface INotifyDataErrorInfo
{
IEnumerable GetErrors(string propertyName); event EventHandler ErrorsChanged; bool HasErrors { get; }
}
The way INotifyDataErrorInfo works is that by default the Binding will inspect the object it is bound to and see if it implements INotifyDataErrorInfo. If it does, any time the Binding sets the bound property or gets a PropertyChanged notification for that property, it will query GetErrors to see if there are any validation errors for the bound property. If it gets any errors back, it will associate the errors with the control and the control can display the errors immediately.
Additionally, the Binding will subscribe to the ErrorsChanged event and call GetErrors again when it is raised, so it gives the implementation the opportunity to go make an asynchronous call (such as to a back end service) to check validity and then raise the ErrorsChanged event when those results are available. The display of errors is based on the control’s ErrorTemplate, which is a red box around the control by default. That is customizable through control templates and you can easily do things like add a ToolTip to display the error message(s).

Implementing INotifyPropertyChanged with Prism 5 BindableBase
One other related thing to supporting validation on your bindable objects is supporting INotifyPropertyChanged. Any object that you are going to bind to that can have its properties changed by code other than the Binding should implement this interface and raise PropertyChanged events from the bindable property set blocks so that the UI can stay in sync with the current data values. You can just implement this interface on every object you are going to bind to, but that results in a lot of repetitive code. As a result, most people encapsulate that implementation into a base class.
Prism 4 had a base class called NotificationObject that did this. However, the project templates for Windows 8 (Windows Store) apps in Visual Studio introduced a base class for this purpose that had a better pattern that reduced the amount of code in the derived data object classes even more. This class was called BindableBase. Prism for Windows Runtime reused that pattern, and in Prism 5 the team decided to carry that over to WPF to replace NotificationObject.
BindableBase has an API that looks like this:
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged; protected virtual bool SetProperty(ref T storage, T value,
[CallerMemberName] string propertyName = null)
{
...
} protected void OnPropertyChanged(string propertyName)
{
...
} protected void OnPropertyChanged(
Expression> propertyExpression)
{
...
}
}
When you derive from BindableBase, your derived class properties can simply call SetProperty in their property set blocks like this:
public string FirstName
{
get { return _FirstName; }
set { SetProperty(ref _FirstName, value); }
}
Additionally if you have properties that need to raise PropertyChanged for other properties (such as raising a PropertyChanged for a computed FullName property that concatenates FirstName and LastName from those two properties set blocks), you can simply call OnPropertyChanged:
public string FirstName
{
get { return _FirstName; }
set
{
SetProperty(ref _FirstName, value);
OnPropertyChanged(() => FullName);
}
} public string LastName
{
get { return _LastName; }
set
{
SetProperty(ref _LastName, value);
OnPropertyChanged(() => FullName);
}
} public string FullName { get { return _FirstName + " " + _LastName; } }
So the starting point for bindable objects that support async validation is to first derive from BindableBase for the PropertyChanged support encapsulated there. Then implement INotifyDataErrorInfo to add in the async validation support.
Implementing INotifyDataErrorInfo with Prism 5
To implement INotifyDataErrorInfo, you basically need a dictionary indexed by property name that you can reach into from GetErrors, where the value of each dictionary entry is a collection of error objects (typically just strings) per property. Additionally, you have to be able to raise an event whenever the errors for a given property change. Additionally you need to be able to raise ErrorsChanged events whenever the set of errors in that dictionary for a given property changes. Prism has a class that was introduced for the MVVM QuickStart in Prism 4 called ErrorsContainer<T>. This class gives you a handy starting point for the dictionary of errors that you can reuse.
To encapsulate that and get the BindableBase support, I will derive from BindableBase, implement INotifyDataErrorInfo, and encapsulate an ErrorsContainer to manage the implementation details.
public class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo
{
ErrorsContainer _errorsContainer;
...
}
I’ll get into more details on more of the implementation a little later in the post.
Defining validation rules
There are many ways and different places that people need to implement validation rules. You can put them on the data objects themselves, you might use a separate framework to define the rules and execute them, or you may need to call out to a remote service that encapsulates the business rules in a service or rules engine on the back end. You can support all of these due to the flexibility of INotifyDataErrorInfo, but one mechanism you will probably want to support “out of the box” is to use DataAnnotations. The System.ComponentModel.DataAnnotations namespace in .NET defines an attribute-based way of putting validation rules directly on the properties they affect that can be automatically evaluated by many parts of the .NET framework, including ASP.NET MVC, Web API, and Entity Framework. WPF does not have any hooks to automatically evaluate these, but we have the hooks we need to evaluate them based on PropertyChanged notifications and we can borrow some code from Prism for Windows Runtime on how to evaluate them.
An example of using a DataAnnotation attribute is the following:
[CustomValidation(typeof(Customer), "CheckAcceptableAreaCodes")]
[RegularExpression(@"^\D?(\d{3})\D?\D?(\d{3})\D?(\d{4})$",
ErrorMessage="You must enter a 10 digit phone number in a US format")]
public string Phone
{
get { return _Phone; }
set { SetProperty(ref _Phone, value); }
}
DataAnnotations have built in rules for Required fields, RegularExpressions, StringLength, MaxLength, Range, Phone, Email, Url, and CreditCard. Additionally, you can define a method and point to it with the CustomValidation attribute for custom rules. You can find lots of examples and documentation on using DataAnnotations out on the net due to their widespread use.
Implementing ValidatableBindableBase
The idea for this occurred to me because when we were putting together the Prism for Windows Runtime guidance, we wanted to support input validation even though WinRT Bindings did not support validation. To do that we implemented a ValidatableBindableBase class for our bindable objects and added some behaviors and separate controls to the UI to do the display aspects. But the code there had to go out and bind to that error information separately since the Bindings in WinRT have no notion of validation, nor do the input controls themselves.
Now that BindableBase has been added to Prism 5 for WPF, it immediately made me think “I want the best of both – what we did in ValidatableBindableBase, but tying in with the built-in support for validation in WPF bindings”.
So basically we just need to tie together the implementation of INotifyDataErrorInfo with our PropertyChanged support that we get from BindableBase and the error management container we get from ErrorsContainer.
I went and grabbed some code from Prism for Windows Runtime’s ValidatableBindableBase (all the code of the various Prism releases is open source) and pulled out the parts that do the DataAnnotations evaluation. I also followed the patterns we used there for triggering an evaluation of the validation rules when SetProperty is called by the derived data object class from its property set blocks.
The result is an override of SetProperty that looks like this:
protected override bool SetProperty(ref T storage,
T value, [CallerMemberName] string propertyName = null)
{
var result = base.SetProperty(ref storage, value, propertyName); if (result && !string.IsNullOrEmpty(propertyName))
{
ValidateProperty(propertyName);
}
return result;
}
This code calls the base class SetProperty method (which raises PropertyChanged events) and triggers validation for the property that was just set.
Then we need a ValidateProperty implementation that checks for and evaluates DataAnnotations if present. The code for that is a bit more involved:
public bool ValidateProperty(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentNullException("propertyName");
} var propertyInfo = this.GetType().GetRuntimeProperty(propertyName);
if (propertyInfo == null)
{
throw new ArgumentException("Invalid property name", propertyName);
} var propertyErrors = new List();
bool isValid = TryValidateProperty(propertyInfo, propertyErrors);
ErrorsContainer.SetErrors(propertyInfo.Name, propertyErrors); return isValid;
}
Basically this code is using reflection to get to the property and then it calls TryValidateProperty, another helper method. TryValidateProperty will find if the property has any DataAnnotation attributes on it, and, if so, evaluates them and then the calling ValidateProperty method puts any resulting errors into the ErrorsContainer. That will end up triggering an ErrorsChanged event on the INotifyDataErrorInfo interface, which causes the Binding to call GetErrors and display those errors.
There is some wrapping of the exposed API of the ErrorsContainer within ValidatableBindableBase to flesh out the members of the INotifyDataErrorInfo interface implementation, but I won’t show all that here. You can check out the full source for the sample (link at the end of the post) to see that code.
Using ValidatableBindableBase
We have everything we need now. First let’s define a data object class we want to bind to with some DataAnnotation rules attached:
public class Customer : ValidatableBindableBase
{
private string _FirstName;
private string _LastName;
private string _Phone; [Required]
public string FirstName
{
get { return _FirstName; }
set
{
SetProperty(ref _FirstName, value);
OnPropertyChanged(() => FullName);
}
} [Required]
public string LastName
{
get { return _LastName; }
set
{
SetProperty(ref _LastName, value);
OnPropertyChanged(() => FullName);
}
} public string FullName { get { return _FirstName + " " + _LastName; } } [CustomValidation(typeof(Customer), "CheckAcceptableAreaCodes")]
[RegularExpression(@"^\D?(\d{3})\D?\D?(\d{3})\D?(\d{4})$",
ErrorMessage="You must enter a 10 digit phone number in a US format")]
public string Phone
{
get { return _Phone; }
set { SetProperty(ref _Phone, value); }
} public static ValidationResult CheckAcceptableAreaCodes(string phone,
ValidationContext context)
{
string[] areaCodes = { "760", "442" };
bool match = false;
foreach (var ac in areaCodes)
{
if (phone != null && phone.Contains(ac)) { match = true; break; }
}
if (!match) return
new ValidationResult("Only San Diego Area Codes accepted");
else return ValidationResult.Success;
}
}
Here you can see the use of both built-in DataAnnotations with Required and RegularExpression, as well as a CustomValidation attribute pointing to a method encapsulating a custom rule.
Next we bind to it from some input controls in the UI:
<TextBox x:Name="firstNameTextBox"
Text="{Binding Customer.FirstName, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" ... />
In this case I have a ViewModel class backing the view that exposes a Customer property with an instance of the Customer class shown before, and that ViewModel is set as the DataContext for the view. I use UpdateSourceTrigger=PropertyChanged so that the user gets feedback on every keystroke about validation rules.
With just inheriting our data object (Customer) from ValidatableBindableBase, using DataAnnotation attributes on our properties we have nice validation all ready and working.
In the full demo, I show some additional bells and whistles including a custom style to show a ToolTip with the first error message when you hover over the control as shown in the screen shot earlier, and showing all errors in a simple custom validation errors summary. To do that I had to add an extra member to the ErrorsContainer from Prism called GetAllErrors so that I could get all errors in the container at once to support the validation summary. You can check that out in the downloadable code.
Calling out to Async Validation Rules
To leverage the async aspects of INotifyDataErrorInfo, lets say you have to make a service call to check a validation rule. How can we integrate that with the infrastructure we have so far?
You could potentially build this into the data object itself, but I am going to let my ViewModel take care of making the service calls. Once it gets back the async results of the validation call, I need to be able to push those errors (if any) back onto the data object’s validation errors for the appropriate property.
To keep the demo code simple, I am not really going to call out to a service, but just use a Task with a thread sleep in it to simulate a long running blocking service call.
public class SimulatedValidationServiceProxy
{
public Task> ValidateCustomerPhone(string phone)
{
return Task>.Factory.StartNew(() =>
{
Thread.Sleep(5000);
return phone != null && phone.Contains("555") ?
new string[] { "They only use that number in movies" }
.AsEnumerable() : null;
});
}
}
Next I subscribe to PropertyChanged events on my Customer object in the ViewModel:
Customer.PropertyChanged += (s, e) => PerformAsyncValidation();
And use the async/await pattern to call out to the service, pushing the resulting errors into the ErrorsContainer:
private async void PerformAsyncValidation()
{
SimulatedValidationServiceProxy proxy =
new SimulatedValidationServiceProxy();
var errors = await proxy.ValidateCustomerPhone(Customer.Phone);
if (errors != null) Customer.SetErrors(() => Customer.Phone, errors);
}
Having a good base class for your Model and ViewModel objects in which you need PropertyChanged support and validation support is a smart idea to avoid duplicate code. In this post I showed you how you can leverage some of the code in the Prism 5 library to form the basis for a simple but powerful standard implementation of async validation support for your WPF data bindable objects.
You can download the full sample project code here.
Reusable async validation for WPF with Prism 5的更多相关文章
- [WPF系列]-Prism+EF
源码:Prism5_Tutorial 参考文档 Data Validation in WPF √ WPF 4.5 – ASYNCHRONOUS VALIDATION Reusable asyn ...
- [Windows] Prism 8.0 入门(下):Prism.Wpf 和 Prism.Unity
1. Prism.Wpf 和 Prism.Unity 这篇是 Prism 8.0 入门的第二篇文章,上一篇介绍了 Prism.Core,这篇文章主要介绍 Prism.Wpf 和 Prism.Unity ...
- WPF & EF & Prism useful links
Prism Attributes for MEF https://msdn.microsoft.com/en-us/library/ee155691%28v=vs.110%29.aspx Generi ...
- Simple Validation in WPF
A very simple example of displaying validation error next to controls in WPF Introduction This is a ...
- Writing a Reusable Custom Control in WPF
In my previous post, I have already defined how you can inherit from an existing control and define ...
- 异步函数async await在wpf都做了什么?
首先我们来看一段控制台应用代码: class Program { static async Task Main(string[] args) { System.Console.WriteLine($& ...
- 【.NET6+WPF】WPF使用prism框架+Unity IOC容器实现MVVM双向绑定和依赖注入
前言:在C/S架构上,WPF无疑已经是"桌面一霸"了.在.NET生态环境中,很多小伙伴还在使用Winform开发C/S架构的桌面应用.但是WPF也有很多年的历史了,并且基于MVVM ...
- C# 一个基于.NET Core3.1的开源项目帮你彻底搞懂WPF框架Prism
--概述 这个项目演示了如何在WPF中使用各种Prism功能的示例.如果您刚刚开始使用Prism,建议您从第一个示例开始,按顺序从列表中开始.每个示例都基于前一个示例的概念. 此项目平台框架:.NET ...
- WPF MVVM,Prism,Command Binding
1.添加引用Microsoft.Practices.Prism.Mvvm.dll,Microsoft.Practices.Prism.SharedInterfaces.dll: 2.新建文件夹,Vie ...
随机推荐
- Vivado神器之DocNav
Vivado2014安装完成以后会有2个文件出现在桌面上,具体如下图: 上一个是vivado的软件,是主要的工具,但是一定不要忽略下面一个DocNav,今天我要讲的就是这个工具,打开一个会看到这样一个 ...
- 深入理解Linux内核-内核同步
内核基本的同步机制: 抢占内核的主要特点:一个在内核态运行的进程,可能在执行内核函数期间被另外一个进程取代. 内核抢占:Linux 2.6允许用户在编译内核的时候配置十分启用 进程临界区:每个进程中访 ...
- 简单修改文件名python脚本
import os import sys path = "D:\emojis" for (path,dirs,files) in os.walk(path): for filena ...
- C++ Primer笔记 容器和算法(2)
erase 删除后 返回的是删除元素的后一个迭代器位置 int main() { //怎样正确的删除全部元素 循环 int a[]={1,2,3,4,5,6,7,8,9}; vector<in ...
- 分析 ThreadLocal 内存泄漏问题
ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用 ThreadLocal,就可能会导 ...
- HTTP响应状态码
1XX:代表提示信息 2XX:代表成功信息 3XX:代表重定向 4XX:代表客户端错误信息 5XX:代表服务器错误 信息 500:500 错误是服务器内部错误 ,而且是程序上错误 为多,可能是你的用户 ...
- .NET MVC结构框架下的微信扫码支付模式二 API接口开发测试
直接上干货 ,我们的宗旨就是为人民服务.授人以鱼不如授人以渔.不吹毛求疵.不浮夸.不虚伪.不忽悠.一切都是为了社会共同进步,繁荣昌盛,小程序猿.大程序猿.老程序猿还是嫩程序猿,希望这个社会不要太急功近 ...
- (原创)拨开迷雾见月明-剖析asio中的proactor模式(二)
在上一篇博文中我们提到异步请求是从上层开始,一层一层转发到最下面的服务层的对象win_iocp_socket_service,由它将请求转发到操作系统(调用windows api),操作系统处理完异步 ...
- 【Android】LayoutInflater
LayoutInflater的作用 LayoutInflater的作用类似于findViewById(). 不同点是: LayoutInflater是用来找res/layout/下的xml布局文件,并 ...
- frame自适应
<html> <head> <title>frame自适应</title> </head> <frameset rows=" ...