Asp.net MVC 示例项目"Suteki.Shop"分析之---数据验证

简介:
  在Suteki.Shop,实现了自己的数据校验机制,可以说其设计思路还是很有借鉴价值的。而使用这种机制也很容易在Model中对相应的实体对象(属性)添加校验操作方法。下面就来介绍一下其实现方式。
 
        首先,看一下这样类图:
        在Suteki.Shop定义一个“IValidatingBinder”接口,其派生自IModelBinder:  
 
public   interface  IValidatingBinder : IModelBinder 
{
    
void  UpdateFrom( object  target, NameValueCollection values);
    
void  UpdateFrom( object  target, NameValueCollection values,  string  objectPrefix);
    
void  UpdateFrom( object  target, NameValueCollection values, ModelStateDictionary modelStateDictionary);
    
void  UpdateFrom( object  target, NameValueCollection values, ModelStateDictionary modelStateDictionary,  string  objectPrefix);
}
 
        其接口中定义了一个重载方法UpdateFrom,其要实现的功能与MVC中UpdateFrom一样,就是自动读取我们在form中定义的有些元素及其中所包含的内容。 实现IValidatingBinder接口的类叫做:ValidatingBinder,下面是其核心代码说明。
    
       首先是BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)该方法是在IModelBinder接口中定义的,是其核心功能,用于将客户端数据转成我们希望Model类型。
       
///   <summary>
///  IModelBinder.BindModel
///   </summary>
///   <param name="controllerContext"></param>
///   <param name="bindingContext"></param>
///   <returns></returns>
public   object  BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    
if  (bindingContext  ==   null )
    {
        
throw   new  ArgumentNullException( " bindingContext " );
    }

    
if  (IsBasicType(bindingContext.ModelType))
    {
        
return   new  DefaultModelBinder().BindModel(controllerContext, bindingContext);
    }

    var instance 
=  Activator.CreateInstance(bindingContext.ModelType);
    var request 
=  controllerContext.HttpContext.Request;

    var form 
=  request.RequestType  ==   " POST "   ?  request.Form : request.QueryString;

    UpdateFrom(instance, form);

    
return  instance;
}
   
       上面代码第二个 if 用于判断bindingContext的Model类型是否是系统类型,比如decimal,string等。如果是则使用MVC自带的DefaultModelBinder来进行处理。否则就使用该类自己的UpdateFrom方法,从而实现对当前form中的数据与Model中相应类型的信息绑定,并返相应的 Model 实例(instance)。下面是其核心代码:
 
public   virtual   void  UpdateFrom(BindingContext bindingContext)
{
    
foreach  (var property  in  bindingContext.Target.GetType().GetProperties())
    {
        
try
        {
            
foreach  (var binder  in  propertyBinders)
            {
                binder.Bind(property, bindingContext);
            }
        }
        
catch  (Exception exception)
        {
            
if  (exception.InnerException  is  FormatException  ||
                exception.InnerException 
is  IndexOutOfRangeException)
            {
    
string  key  =  BuildKeyForModelState(property, bindingContext.ObjectPrefix);
                bindingContext.AddModelError(key, bindingContext.AttemptedValue, 
" Invalid value for {0} " .With(property.Name));
    bindingContext.ModelStateDictionary.SetModelValue(key, 
new  ValueProviderResult(bindingContext.AttemptedValue, bindingContext.AttemptedValue, CultureInfo.CurrentCulture));
            }
            
else   if  (exception  is  ValidationException)
            {
    
string  key  =  BuildKeyForModelState(property, bindingContext.ObjectPrefix);
                bindingContext.AddModelError(key, bindingContext.AttemptedValue, exception.Message);
    bindingContext.ModelStateDictionary.SetModelValue(key, 
new  ValueProviderResult(bindingContext.AttemptedValue, bindingContext.AttemptedValue, CultureInfo.CurrentCulture));
            }
            
else   if  (exception.InnerException  is  ValidationException)
            {
    
string  key  =  BuildKeyForModelState(property, bindingContext.ObjectPrefix);
                bindingContext.AddModelError(key, bindingContext.AttemptedValue, exception.InnerException.Message);
    bindingContext.ModelStateDictionary.SetModelValue(key, 
new  ValueProviderResult(bindingContext.AttemptedValue, bindingContext.AttemptedValue, CultureInfo.CurrentCulture));
            }
            
else
            {
                
throw ;
            }
        }

    }
    
if  ( ! bindingContext.ModelStateDictionary.IsValid)
    {
        
throw   new  ValidationException( " Bind Failed. See ModelStateDictionary for errors " );
    }
}

        上面代码中的TRY部分就是其数据绑定的代码,而其Catch部分实现了在数据绑定过程中出现的错误异常(主要是数据验证等,会在后面提到)收集到ModelState(ModelStateDictionary)中便于后续处理。而这里Suteki.Shop还定义了自己的验证异常类“ValidationException”(位于:Suteki.Common\Validation\ValidationException.cs,因为代码很简单,就不多做解释了。
        有了ValidatingBinder之后,下面就来看一下Suteki.Shop是如何使用它的。这里以一个业务流程---“编辑用户”来进行说明。
 
       下面就是UserController(Suteki.Shop\Controllers\UserController.cs) 中的Edit操作:
[AcceptVerbs(HttpVerbs.Post), UnitOfWork]
public  ActionResult Edit([DataBind] User user,  string  password)
{
 
if ( !   string .IsNullOrEmpty(password))
 {
  user.Password 
=  userService.HashPassword(password);
 }
    ..    
}

         在该Action中,我们看到其定义并使用了DataBind这个ModelBinder进行绑定处理,所以我们要先看一下DataBinder(注:它是Suteki.Shop中关于数据绑定的“ModelBinder的基类)中倒底做了些什么,下面是其实现代码:
   
public   class  DataBinder : IModelBinder, IAcceptsAttribute
{
public   virtual   object  BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
 {
  
object  entity;

  
if (declaringAttribute  ==   null   ||  declaringAttribute.Fetch)
  {
   entity 
=  FetchEntity(bindingContext, controllerContext);
  }
  
else  
  {
   entity 
=  Activator.CreateInstance(bindingContext.ModelType);
  }
  
  
try
  {
   validatingBinder.UpdateFrom(entity, controllerContext.HttpContext.Request.Form, bindingContext.ModelState, bindingContext.ModelName);
  }
  
catch (ValidationException ex) 
  {}

  
return  entity;
 } 
 
}
 
        其BindModel方法中“获取当前要编辑的用户数据操作”就是通过下面这一行完成的:      
FetchEntity(bindingContext, controllerContext)

      
       而try中的代码 validatingBinder.UpdateFrom()就是对上面所说的“ValidatingBinder”中的“UpdateFrom”调用。通过UpdateFrom之后就会将绑定时出现的错误异常进行收集。
       有了这种绑定,可以说设置上完成了,而如何将验证规则绑定到相应的Model对象上呢?
    
       为了实现这个功能,Suteki.Shop提供了一个叫做ValidationProperty的泛型类,它提供了对于数字,是否为空, IsDecimal,最大值,最小值,IsEmail等验证功能。并以扩展方法的行式提供出来,相应代码如下:     
Code
    
        使用它就可以很方便的对Model中的相关属性添加验证规则了。以User为例,其验证规则添加内容如下(Suteki.Shop\Models\User.cs):
public   void  Validate()
{
   Validator validator 
=   new  Validator
   {
       () 
=>  Email.Label( " Email " ).IsRequired().IsEmail(),
       () 
=>  Password.Label( " Password " ).IsRequired(),
   };

    validator.Validate();
}
    
       在规则添加完成后,就把对获取到的信息进行验证了,下面是验证的实现方法:
public   class  Validator : List < Action >
{
    
public   void  Validate()
    {
        var errors 
=   new  List < ValidationException > ();

        
foreach  (Action validation  in   this )
        {
            
try
            {
                validation();
            }
            
catch  (ValidationException validationException)
            {
                 errors.Add(validationException);
            }
        }

        
if  (errors.Count  >   0 )
        {
            
// backwards compatibility
             string  error  =   string .Join( "" , errors.Select(x  =>  x.Message  +   " <br /> " ).ToArray());
            
throw   new  ValidationException(error, errors);
        }
    }
}
 
        代码比较简单,大家看一下就可以了。
    
         到这里,主要的代码就介绍完了,下面再后到UserController中看看Action是如何调用验证方法并发验证错误信息复制到ModelState中的,接着看一下编辑用户信息这个Action:
[AcceptVerbs(HttpVerbs.Post), UnitOfWork]
public  ActionResult Edit([DataBind] User user,  string  password)
{
    
if ( !   string .IsNullOrEmpty(password))
    {
       user.Password 
=  userService.HashPassword(password);
    }

    
try
    {
        user.Validate();
    }
    
catch  (ValidationException validationException)
    {
        validationException.CopyToModelState(ModelState, 
" user " );
        
return  View( " Edit " , EditViewData.WithUser(user));
    }

    
return  View( " Edit " , EditViewData.WithUser(user).WithMessage( " Changes have been saved " )); 
}
      
         大家看到了吧,Try中的user.Validate()就是启动验证的功能,而在Catch中使用CopyToModelState方法将错误信息Copy到当前Controller中的ModelState中,如下:
 
public   void  CopyToModelState(ModelStateDictionary dictionary,  string  prefix)
{
    
foreach (var error  in  errors)
    {
        
string  key  =   string .IsNullOrEmpty(prefix)  ?  error.propertyKey : prefix  +   " . "   +  error.propertyKey;

       dictionary.AddModelError(key, error.Message);
    }
 
        这样在前台View中,通过Html.ValidationSummary()方法来显示验证结果,现在我们看一下最终的运行效果:
         以“输入错误的Email地址”为例:
    
    
    
        好了,今天的内容就先到这里了。


本文转自 daizhenjun 51CTO博客,原文链接:http://blog.51cto.com/daizhj/158846,如需转载请自行联系原作者
相关文章
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
348 5
Visual Studio 快速分析 .NET Dump 文件
【11月更文挑战第10天】.NET Dump 文件是在 .NET 应用程序崩溃或出现问题时生成的,记录了应用程序的状态,包括内存对象、线程栈和模块信息。通过分析这些文件,开发人员可以定位和解决内存泄漏、死锁等问题。在 Visual Studio 中,可以通过调试工具、内存分析工具和符号加载等功能来详细分析 Dump 文件。此外,还可以使用第三方工具如 WinDbg 进行更深入的分析。
964 1
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
330 7
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
428 2
|
开发框架 JSON .NET
ASP.NET Core 标识(Identity)框架系列(三):在 ASP.NET Core Web API 项目中使用标识(Identity)框架进行身份验证
ASP.NET Core 标识(Identity)框架系列(三):在 ASP.NET Core Web API 项目中使用标识(Identity)框架进行身份验证
314 1
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
335 0
|
开发框架 缓存 .NET
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
180 0
|
Linux C# C++
【Azure App Service For Container】创建ASP.NET Core Blazor项目并打包为Linux镜像发布到Azure应用服务
【Azure App Service For Container】创建ASP.NET Core Blazor项目并打包为Linux镜像发布到Azure应用服务
207 0
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
287 0
|
开发框架 .NET API
如何在 ASP.NET Core Web Api 项目中应用 NLog 写日志?
如何在 ASP.NET Core Web Api 项目中应用 NLog 写日志?
794 0