博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

稳扎稳打Silverlight(59) - 4.0通信之WCF RIA Services: 数据验证

Posted on 2010-10-21 08:47  webabcd  阅读(5616)  评论(9编辑  收藏

[索引页]
[源码下载]


稳扎稳打Silverlight(59) - 4.0通信之WCF RIA Services: 数据验证



作者:webabcd


介绍
Silverlight 4.0 之 WCF RIA Services:数据验证


在线DEMO
http://www.cnblogs.com/webabcd/archive/2010/08/09/1795417.html


示例
演示在 WCF RIA Services 框架中实现数据验证的各种方法
1、服务端
MyDomainService.cs

代码
/*
 * 一个 Domain Service 服务
 
*/

namespace Silverlight40.Web.Service
{
    
using System;
    
using System.Collections.Generic;
    
using System.ComponentModel;
    
using System.ComponentModel.DataAnnotations;
    
using System.Data;
    
using System.Linq;
    
using System.ServiceModel.DomainServices.EntityFramework;
    
using System.ServiceModel.DomainServices.Hosting;
    
using System.ServiceModel.DomainServices.Server;
    
using Silverlight40.Web.Model;

    
/*
     * 用 LinqToEntities 实现 Domain Service 则继承 LinqToSqlDomainService<NorthwindEntities>;用 LinqToSql 实现 Domain Service 则继承 LinqToSqlDomainService<NorthwindEntities>
     * Domain Service 内所有对客户端可见的方法都应该是 public 的,Domain Service 内的方法不支持重载
     * 对客户端可见的方法要满足命名约定,或为其指定对应的 Attribute。当前支持 6 种数据操作:Query, Update, Insert, Delete, Invoke, Named Update, 详见文档
     * 
     * [EnableClientAccess()] - 该类对客户端可见
     * [EnableClientAccess(RequiresSecureEndpoint = true)] - 使用 HTTPS 协议
     * [Ignore] - 指定的方法不作为服务而公开
     * [Query(IsComposable=true)] - 支持客户端的 linq 查询,而且此查询会被在服务端执行
     * 
     * 在多个 Domain Services 间共享实体:通过 [ExternalReference], [Association()], Context.AddReference() 实现,详见文档
     
*/

    
// 服务端的类名为:MyDomainService,则其生成的客户端上下文的类名为:MyDomainContext
    [EnableClientAccess()]
    
public class MyDomainService : LinqToEntitiesDomainService<NorthwindEntities>
    {
        [Query(IsDefault 
= true)]
        
public IQueryable<Category> GetCategories()
        {
            
return this.ObjectContext.Categories;
        }

        
public void InsertCategory(Category category)
        {
            
if ((category.EntityState != EntityState.Detached))
            {
                
this.ObjectContext.ObjectStateManager.ChangeObjectState(category, EntityState.Added);
            }
            
else
            {
                
this.ObjectContext.Categories.AddObject(category);
            }
        }

        
public void UpdateCategory(Category currentCategory)
        {
            
this.ObjectContext.Categories.AttachAsModified(currentCategory, this.ChangeSet.GetOriginal(currentCategory));
        }

        
public void DeleteCategory(Category category)
        {
            
if ((category.EntityState == EntityState.Detached))
            {
                
this.ObjectContext.Categories.Attach(category);
            }
            
this.ObjectContext.Categories.DeleteObject(category);
        }



        [Query(IsDefault 
= true)]
        
public IQueryable<Product> GetProducts()
        {
            
return this.ObjectContext.Products;
        }

        
public IQueryable<Product> GetProductsBySort(string sort)
        {
            
return ObjectContext.Products.OrderBy(sort);
        }

        
public void InsertProduct(Product product)
        {
            
if ((product.EntityState != EntityState.Detached))
            {
                
this.ObjectContext.ObjectStateManager.ChangeObjectState(product, EntityState.Added);
            }
            
else
            {
                
this.ObjectContext.Products.AddObject(product);
            }
        }

        
public void UpdateProduct(Product currentProduct)
        {
            
this.ObjectContext.Products.AttachAsModified(currentProduct, this.ChangeSet.GetOriginal(currentProduct));
        }

        
public void DeleteProduct(Product product)
        {
            
if ((product.EntityState == EntityState.Detached))
            {
                
this.ObjectContext.Products.Attach(product);
            }
            
this.ObjectContext.Products.DeleteObject(product);
        }

        
public IQueryable<Product> GetProductsByCategoryId(int categoryId)
        {
            
return this.ObjectContext.Products.Where(p => p.CategoryID == categoryId);
        }
    }
}



MyDomainService.metadata.cs

代码
/*
 * [MetadataTypeAttribute()] - 指定类的元数据对象
 * [Include] - 生成的客户端上下文包含此字段
 * [Exclude] - 生成的客户端上下文不包含此字段
 * [Composition] - 指定字段为关联数据,即父实体增删改查时,此关联数据也会做相应的变化。指定此 Attribute 后,需显式指定其为 [Include]
 *     注:如果使用 DomainDataSource 则不能将字段设置为 [Composition]
 * [Editable(true)], [Editable(false)] - 指定字段是否可编辑
 * 
 * 支持 Data Annotation 方式的数据验证:DataTypeAttribute(具有很多常用的数据类型的验证), RangeAttribute, RegularExpressionAttribute, RequiredAttribute, StringLengthAttribute 等
 * 注:如果需要将自定义的验证既作用于服务端又作用于客户端,则需要把自定义的验证逻辑代码设置为 shared 模式
 
*/

namespace Silverlight40.Web.Model
{
    
using System;
    
using System.Collections.Generic;
    
using System.ComponentModel;
    
using System.ComponentModel.DataAnnotations;
    
using System.Data.Objects.DataClasses;
    
using System.Linq;
    
using System.ServiceModel.DomainServices.Hosting;
    
using System.ServiceModel.DomainServices.Server;

    
using Silverlight40.Web.Service.Validation;

    [MetadataTypeAttribute(
typeof(Category.CategoryMetadata))]
    [CustomValidation(
typeof(CategoryValidator), "ValidateCategory")] // 演示通过自定义的实体验证器来实现验证
    public partial class Category
    {
        
internal sealed class CategoryMetadata
        {
            
private CategoryMetadata()
            {
            }

            [Key()]
            
public int CategoryID { getset; }

            [Display(Name 
= "类别名称")] // 显示用
            [Numeric(ErrorMessage = "字段“{0}”必须是数字")] // 演示通过继承 RegularExpressionAttribute 实现字段的自定义验证
            [StartsWith(ErrorMessage = "{0}")] // 演示通过继承 ValidationAttribute 实现字段的自定义验证
            [CustomValidation(typeof(EndsWidthValidator), "ValidateCategoryName")]  // 演示通过自定义的字段验证器来实现验证
            public string CategoryName { getset; }

            
public string Description { getset; }

            [Exclude]
            
public byte[] Picture { getset; }

            [Include]
            [Composition]
            
public EntityCollection<Product> Products { getset; }
        }
    }

    [MetadataTypeAttribute(
typeof(Product.ProductMetadata))]
    
public partial class Product
    {
        
internal sealed class ProductMetadata
        {
            
private ProductMetadata()
            {
            }

            
public Category Category { getset; }

            
public Nullable<int> CategoryID { getset; }

            
public bool Discontinued { getset; }

            
public EntityCollection<Order_Detail> Order_Details { getset; }

            [Display(Order 
= 0, Name = "产品ID")]
            
public int ProductID { getset; }

            [Display(Order 
= 1, Name = "产品名称")]
            
public string ProductName { getset; }

            
public string QuantityPerUnit { getset; }

            
public Nullable<short> ReorderLevel { getset; }

            
public Supplier Supplier { getset; }

            
public Nullable<int> SupplierID { getset; }

            
public Nullable<decimal> UnitPrice { getset; }

            
public Nullable<short> UnitsInStock { getset; }

            
public Nullable<short> UnitsOnOrder { getset; }
        }
    }
}


StartsWithAttribute.shared.cs

代码
/*
 * 验证规则:“必须以 1 开头”
 
*/

using System;
using System.Collections.Generic;
using System.Linq;

using System.ComponentModel.DataAnnotations;

namespace Silverlight40.Web.Service.Validation
{
    [AttributeUsage(AttributeTargets.Property 
| AttributeTargets.Field)]
    
public class StartsWithAttribute : ValidationAttribute
    {
        
private readonly string _param;

        
/*
        /// <summary>
        /// 构造函数
        /// 如果使用 shared 方式,则对于客户端来说不支持构造函数传参(服务端还是支持的)
        /// </summary>
        /// <param name="param">指定的开头字符串</param>
        public StartsWithAttribute(string param)
        {
            _param = param;
        }
         
*/

        
public StartsWithAttribute()
        {
            _param 
= "1";
        }

        
/// <summary>
        
/// 指定的值是否可以通过验证
        
/// </summary>
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var boolean 
= value.ToString().StartsWith(this._param);
            
            
if (boolean)
                
return ValidationResult.Success;
            
else
                
// new string[] { validationContext.MemberName } - 要对应到相关的 name 上,这样客户端才会有正确的验证提示效果
                return new ValidationResult("必须以 1 开头"new string[] { validationContext.MemberName });
        }
    }
}


EndsWidthValidator.shared.cs

代码
/*
 * 验证规则:“CategoryName 必须以 1 结尾”,用于验证字段的验证器
 
*/

using System;
using System.Collections.Generic;
using System.Linq;

using System.ComponentModel.DataAnnotations;

namespace Silverlight40.Web.Service.Validation
{
    
public class EndsWidthValidator
    {
        
public static ValidationResult ValidateCategoryName(string s, ValidationContext context)
        {
            ValidationResult result 
= ValidationResult.Success;
            
if (s.EndsWith("1"))
                result 
= ValidationResult.Success;
            
else
                
// new string[] { context.MemberName } - 要对应到相关的 name 上,这样客户端才会有正确的验证提示效果
                result = new ValidationResult("必须以 1 结尾"new string[] { "CategoryName" });

            
return result;
        }
    }
}


NumericAttribute.shared.cs

代码
/*
 * 验证规则:“必须是数字”
 
*/

using System;
using System.Collections.Generic;
using System.Linq;

using System.ComponentModel.DataAnnotations;

namespace Silverlight40.Web.Service.Validation
{
    
public class NumericAttribute : RegularExpressionAttribute
    {
        
public NumericAttribute()
            : 
base("^[0-9]*$")
        {

        }

        
/// <summary>
        
/// 格式化错误信息
        
/// </summary>
        
/// <param name="name">指定的字段名</param>
        
/// <returns></returns>
        public override string FormatErrorMessage(string name)
        {
            
return string.Format(ErrorMessageString, name);
        }
    }
}


CategoryValidator.shared.cs

代码
/*
 * 验证规则:“CategoryName 必须包含 3”,用于验证实体的验证器
 
*/

using System;
using System.Collections.Generic;
using System.Linq;

using System.ComponentModel.DataAnnotations;
using Silverlight40.Web.Model;

namespace Silverlight40.Web.Service.Validation
{
    
public class CategoryValidator
    {
        
public static ValidationResult ValidateCategory(Category category, ValidationContext context)
        {
            ValidationResult result 
= ValidationResult.Success;
            
if (category.CategoryName.Contains("3"))
                result 
= ValidationResult.Success;
            
else
                
// new string[] { context.MemberName } - 要对应到相关的 name 上,这样客户端才会有正确的验证提示效果
                result = new ValidationResult("必须包含 3"new string[] { "CategoryName" });

            
return result;
        }
    }
}



2、客户端
ValidationDemo.xaml

代码
<navigation:Page x:Class="Silverlight40.WCFRIAServices.ValidationDemo" 
           xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:d
="http://schemas.microsoft.com/expression/blend/2008"
           xmlns:mc
="http://schemas.openxmlformats.org/markup-compatibility/2006"
           xmlns:navigation
="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
           xmlns:sdk
="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
           xmlns:ns
="clr-namespace:Silverlight40.WCFRIAServices"
           Title
="ValidationDemo Page">
    
<Grid x:Name="LayoutRoot">

        
<StackPanel HorizontalAlignment="Left" DataContext="{Binding .}">

            
<!--
                用于显示验证错误列表
            
-->
            
<sdk:ValidationSummary Margin="3">
                
<sdk:ValidationSummary.Header>
                    错误列表:
                
</sdk:ValidationSummary.Header>
            
</sdk:ValidationSummary>

            
<!--
                ValidatesOnDataErrors - 指定是否绑定数据实体上的 IDataErrorInfo 实现的验证错误
                ValidatesOnNotifyDataErrors - 指定是否绑定数据实体上的 INotifyDataErrorInfo 实现的验证错误
                ValidatesOnExceptions - 指定当出现异常时,是否报告验证错误
                NotifyOnValidationError - 指定出现验证错误时是否引发 BindingValidationError 事件
            
-->
            
<StackPanel Orientation="Horizontal">
                
<TextBlock Name="lblCategoryName" Text="产品类别名称:" />
                
<TextBox Name="txtCategoryName" Text="{Binding Path=CategoryName, Mode=TwoWay, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True, ValidatesOnExceptions=True}" />
                
<sdk:Label Target="{Binding ElementName=txtCategoryName}" />
                
<sdk:DescriptionViewer Description="必须是数字;必须以 1 开头;必须以 1 结尾;必须包含 3" />
            
</StackPanel>

            
<!--
                单击按钮后看验证提示效果
            
-->
            
<Button Name="btnAdd" Content="新增" Click="btnAdd_Click" />

        
</StackPanel>
        
    
</Grid>
</navigation:Page>


ValidationDemo.xaml.cs

代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Navigation;

using Silverlight40.Web.Service;
using System.ComponentModel.DataAnnotations;
using System.ServiceModel.DomainServices.Client;
using Silverlight40.Web.Model;

namespace Silverlight40.WCFRIAServices
{
    
public partial class ValidationDemo : Page
    {
        MyDomainContext _context 
= new MyDomainContext();

        
public ValidationDemo()
        {
            InitializeComponent();
        }

        
protected override void OnNavigatedTo(NavigationEventArgs e)
        {

        }

        
private void btnAdd_Click(object sender, RoutedEventArgs e)
        {
            Category category 
= new Category();
            category.CategoryName 
= txtCategoryName.Text;
            
this.DataContext = category;

            
// 为实体添加验证错误信息
            List<ValidationResult> validationResults = new List<ValidationResult>();
            
if (!Validator.TryValidateObject(this.DataContext, new ValidationContext(this.DataContext, nullnull), validationResults))
            {
                Entity entity 
= (Entity)this.DataContext;
                
foreach (var item in validationResults)
                {
                    entity.ValidationErrors.Add(item);
                }
            }

            
// 如果发现验证错误则。。。(客户端)
            if (category.HasValidationErrors)
            {
                
foreach (ValidationResult result in category.ValidationErrors)
                {
                    
string error = string.Format("属性 [{0}] 验证出错 [{1}]", result.MemberNames.First(), result.ErrorMessage);
                    MessageBox.Show(error, 
"Error", MessageBoxButton.OK);
                }

                
this.DataContext = category;
            }
            
else
            {
                _context.Categories.Add(category);
                _context.SubmitChanges(OnSubmitCompleted, category);
            }
        }

        
private void OnSubmitCompleted(SubmitOperation so)
        {
            
if (so.HasError)
            {
                MessageBox.Show(so.Error.ToString());
                so.MarkErrorAsHandled();

                
// 如果发现验证错误则。。。(服务端)
                if (so.EntitiesInError.Any())
                {
                    
foreach (ValidationResult result in so.EntitiesInError.First().ValidationErrors)
                    {
                        
string error = string.Format("属性 [{0}] 验证出错 [{1}]", result.MemberNames.First(), result.ErrorMessage);
                        MessageBox.Show(error, 
"Error", MessageBoxButton.OK);
                    }

                    
this.DataContext = so.UserState as Category;
                }
            }
            
else
            {
                MessageBox.Show(
"添加成功");
            }
        }
    }
}



OK
[源码下载]