• Data Annotations


    本文地址:http://www.cnblogs.com/egger/p/3404159.html  欢迎转载 ,请保留此链接๑•́ ₃•̀๑!

    数据注解(Data Annotations)

      Web应用开发中表单验证是是一个系统必不可少的功能!我们可以通过将验证逻辑写在action方法中(不推荐)来实现。MVC提供了数据注解(Data Annotations)功能,相比前者,它更省时、提高验证逻辑的复用、减少action方法的复杂度。通过数据注解(Data Annotations) 与 jquery.validate 的结合实现服务端和客户端的双重验证。Model是自验证的, 我们要只需给Model类的各属性加上对应的验证特性(Attributes)就可以让MVC框架帮我们完成验证。甚是方便。

    下面我们常用基本验证:
    Required:必输校验
    StringLength:长度校验(注意重载)
    RegularExpression:正则表达式校验

     情景:EmailAddress特性不能用?

      今天MVC4学习中按照示例给一个属性添加了 “ [EmailAddress(ErrorMessage = "We don't recognize this as a valid email address")] ”,但是程序编译报错!请看下图:

      

         看了项目中引用的System.ComponentModel.DataAnnotations.dll中,没这个类:

      

      不禁疑惑,难道是我配置有问题:dll引入的有问题?毕竟这里的使用不是随意的,其间尝试了许多方法,当我将项目的.NET Framework 版本由4.0改成4.5,发现错误消失了!但是这里的给的示例就是基于NET Framework 4.0!我就打开GAC中的System.ComponentModel.DataAnnotations.dll[4.0],发现有EmailAddressAttribute类的定义,然后就凌乱了[打开的方式不对吗!!!]

      

      然后百思不得其解,就有了这个提问 http://q.cnblogs.com/q/56482/  。这里感谢 【Arnold】的回答,知道了怎么去解决这个问题!要想使用需要引用DataAnnotationsExtensions库。

      using DataAnnotationsExtensions;
      ...  
      [Required] [Email]
    public string Email { get; set; }

      通过Nuget下载DataAnnotationsExtensions类库 

      

      页面引入DataAnnotationsExtensions,将EmailAddress改成Email编辑通过,运行效果:

      

     

     DataAnnotationsExtensions类库

      官网传送门:http://dataannotationsextensions.org/

      Github传送门:https://github.com/srkirkland/DataAnnotationsExtensions

      DataAnnotationsExtensions类库对内置DataAnnotations验证特性(Required, Range, RegularExpression 和 StringLength)进行了扩展。
    核心库提供的服务器端验证特性可用于在任何.NET 4.0项目。

      这是类库提供的扩展特性:

      

    总结

      通过引入DataAnnotationsExtensions的类库方式实现Email格式的数据验证,而不用通过RegularExpression方式甚是方便!

      但为什么 4.0中EmailAddressAttribute有定义但是为什么不能使用!难道是挖的坑到了4.5才填了!(知道真相的请科普下)

    相关文章:

      INTRODUCING DATA ANNOTATIONS EXTENSIONS  http://weblogs.asp.net/srkirkland/archive/2011/02/23/introducing-data-annotations-extensions.aspx

    白话学习MVC(七)Action的执行一

     

    一、概述

      在此系列开篇的时候介绍了MVC的生命周期 , 对于请求的处理,都是将相应的类的方法注册到HttpApplication事件中,通过事件的依次执行从而完成对请求的处理。对于MVC来说,请求是先 经过路由系统,然后由一个MvcHandler来处理的,当请求到来时,执行此MvcHandler的ProcessRequest方法(因为已将 MvcHandler类的ProcessRequest方法注册到HttpApplication的事件中,所以事件的执行就触发了此方法)。详细请看之前介绍MVC生命周期的两篇博客
      下面我们就以MVC声明周期为主线,来分析下MVC源码

    复制代码
    public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
    {
        protected virtual void ProcessRequest(HttpContext httpContext)
        {
            //使用HttpContextWrapper对HttpContext进行封装,封装的目的是为了解耦以获得可测试性.然后从RequestContext.RouteData中提取Controller名称.
            HttpContextBase httpContext2 = new HttpContextWrapper(httpContext);
            this.ProcessRequest(httpContext2);
        }
        
        protected internal virtual void ProcessRequest(HttpContextBase httpContext)
        {
            IController controller;
            IControllerFactory controllerFactory;
            this.ProcessRequestInit(httpContext, out controller, out controllerFactory);//获取到Controler和ControllerFactory实例,并赋值给局部变量
            try
            {
              //Action的调用
                    controller.Execute(this.RequestContext);
                    
            }
            finally
            {
                //释放当前Controler对象
                controllerFactory.ReleaseController(controller); 
            }
        }
    }
    复制代码

      MVC中Action的调用,就是通过调用Contrller对象的Execute方法触发执行的!这个Controller对象是Controller激活的产物,Controller激活请参考上一篇博客。

    二、Action的调用

      我们知道Action的执行就是调用通过Controller激活得到的Controller对象的Execute方法,这个Controller对象就是我们创建的Controller(例如:HomeController)类的实例,而我们创建的HomeController等控制器都继承自Controller类、Controller抽象类继承ControllerBase抽象类、ControllerBase抽象类实现了IController接口。继承和实现关系为:

      我们创建的控制器通过Controller的激活创建了实例,然后执行该实例的Execute方法,Execute方法定义在接口IController中,实现在类ControllerBase中,而该Excute方法内又调用ControllerBase类的抽象方法ExecuteCore,抽象方法ExecuteCore又在Controller类中实现。所以Action调用的流程为:先执行ControllerBase类的Execute方法,再执行Controller类的ExcuteCore方法。所以执行过程为:【ControllerBase类中的Execute方法】-->【Controller类中的ExecuteCore方法】

     IController
     ControllerBase
     Controller

      由上述代码可以看出Action的执行最终实现在Controller类的ExecuteCore方法中,而其中ActionInvoker就是实现Action调用的组件,执行ActionInvoker的InvokeAction方法实现对Action的调用。

    整个执行过程的功能为:【检查对请求只做一次处理】-->【封装请求上下文】-->【获取上一次没有被使用的TempData】-->【过滤器、Action的执行】-->【View的呈现(下一节介绍)】-->【将没有被使用的TempData放入Session中】

    复制代码
    //整个流程
        public abstract class ControllerBase : IController
        {
            protected virtual void Execute(RequestContext requestContext)
            {
                //检查对请求只做一次处理
                VerifyExecuteCalledOnce();
                //封装请求上下文(RequestContext对象是在路由系统中创建的。其中封装了请求上下文和路由信息。)
                Initialize(requestContext);
                //定义用于包含临时作用域存储的类。    基于 CurrentScope 属性中的作用域,返回用于存储临时作用域内的数据的字典。
                //这个的作用暂时还没有弄清楚,不过通过重写Execute方法,在using块内可以获取ScopeStorage的属性CurrentScope的三个键值对。
                using (ScopeStorage.CreateTransientScope())
                {
                    //执行Controller类中的ExecuteCore方法
                    ExecuteCore();
                }
            }
        }
        public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer
        {    
            protected override void ExecuteCore()
            {
                //获取上一次没有被使用的TempData
                PossiblyLoadTempData();
                try
                {
                    //从路由数据中获取请求的Action的名字(路由系统从请求地址中获取)
                    string actionName = RouteData.GetRequiredString("action");
                    //过滤器、Action的执行、View的呈现
                    if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
                    {
                        HandleUnknownAction(actionName);
                    }
                }
                finally
                {
                    //将没有被使用的TempData放入Session中
                    PossiblySaveTempData();
                }
            }
        }
    复制代码

     ==从以上的执行过程中各代码的功能可以看出ExecuteCore()方法是Action执行的主操作,而VerifyExecuteCalledOnce()、Initialize(requestContext)两个方法是前戏了。我们就先来分析下这两个前戏的方法,主操作ExecuteCore()方法留着最后,并对其内部操作再进行详细分析。

     1、VerifyExecuteCalledOnce()

      此方法保证了一次Http请求只进行一次处理

     ControllerBase
     SingleEntryGate

        ==上述代码,在ControllerBase类中私有只读字段_executeWasCalledGate创建了一个SingleEntryGate对象,而VerifyExecuteCalledOnce方法的功能就是通过 这个对象的TryTryEnter方法来实现的!如果TryTryEnter方法返回值为:true,则表示是第一次执行;否则非第一次执行,那么就抛出非法操作异常了。从而保证了一次Http请求只进行一次处理。
    而内部到底是如何实现的呢?我们就在来看看SingleEntryGate类,其中的TeyEnter方法中调用了Interlocked.Exchange(ref int1,int2)方法,此方法定义的System.Threading命名空间内,方法的功能为:将第二个参数的值赋值给第一个参数,并将第一个参数原来的值作为方法的返回值。
    例如:
      如果是第一次调用TryEnter方法【Entered=1,_status=0,NotEntered = 0】执行完Interlocked.Exchange方法后【Entered = 1,_status=1,NotEntered = 0,oldStatus=0】,此时oldStatus=NotEntered = 0,返回true
      如果是第n次调用TryEnter方法,那么此时的变量值还是第一次执行完的状态【Entered = 1,_status=1,NotEntered = 0】,而执行完Interlocked.Exchange方法后【Entered = 1,_status=1,NotEntered = 0,oldStatus=1】,此时NotEntered = 0,oldStatus=1,不相等,返回false

    注意,在ControllerBase类中私有只读字段_executeWasCalledGate是非静态的字段,所以实现的功能是【检查每一次请求只执行一次】,如果是静态字段,那么就变成了程序只执行一次请求的处理。(这功能的实现值得收藏)

    2、Initialize(requestContext)

      将 requestContext 和 当前请求的控制对象 封装到一个 ControllerContext对象中!其中requestContext是已封装了请求上下文和当前请求的路由信息的一个上下文。

     ControllerBase
     Controller
     ControllerContext

        ==上述代码,对于Initialize方法的执行,由于ControllerBase中的Initialize方法在派生类Controller类中被重写,所以要执行Controller类中的Initialize方法。方法内首先调用了父类ControllerBase中的Initialize方法创建了一个控制器上下文对象,并赋值给一个公有属性。之后又创建了UrlHelper对象也赋值给了一个公有属性。这个控制器上下文对象包含了从请求到现在的所有有用的数据,所以在之后对请求处理的步骤中随处可见!这个UrlHelper对象还没用到,暂且不议。

    3、ExecuteCore()

      ControllerBase类中定义了抽象方法ExecuteCore,该方法被派生类Controller类中实现!所以,要执行Controller类中的ExecuteCore方法,如上所言,此方法是Action执行的主操作,其中先对上一次操作没有被TempData做处理,然后执行过滤器和请求的Action,最后进行View的呈现(下一节再对View的呈现做分析,现在只需知道这个执行的流程即可)。下面,我们就来用分析所有过程的代码!

    复制代码
        public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer
        {
            protected override void ExecuteCore()
            {
                //获取上次处理过程中没有被使用的TempData
                PossiblyLoadTempData();
                try
                {
                    //从路由数据中获取请求的Action的名字
                    string actionName = RouteData.GetRequiredString("action");
                    //ActionInvoker为一个AsyncControllerActionInvoker对象
                    if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
                    {
                        HandleUnknownAction(actionName);
                    }
                }
                finally
                {
                    //将TempData保存到Session中。等待之后将Session的key【__ControllerTempData】发送到响应流中!
                    PossiblySaveTempData();
                }
            }
        }
    复制代码

    3-1、PossiblyLoadTempData()

      此方法创建保存TempData的集合并获取上一次请求中没有被使用TempData添加到之前创建的那个集合中!

    在介绍这个方法之前,有必要先了解下MVC框架下TempData的机制:
    客户端向服务发请求时,程序在执行本次请求的Action前,会先创建一个用来保存TempData的集合A,然后根据key=__ControllerTempData去服务器Session中获取值并转换为Dictionary<string, object>类型,如果得到的值为null,表示三种情况(1、当前是客户端第一次向服务端发送请求。2、上次请求中没有定义TempData值。3、上次请求中的TempData被View中用完了。);如果得到的值不为null,则表示上一次对请求的处理时TempData没有被使用完,此时获取的值就是上次处理请求时没有被使用的TempData集合,然后将这集合赋值给我们开始创建的用于保存TempData的集合A中,再将key=__ControllerTempData的Session移除;之后执行Action方法内的代码时,将本次的请求的Action中定义的TempData[""]=XXX也添加到最开始创建的那个集合A中,执行完Action方法后,在View的呈现中,如果使用了TempData,就将这个TempData从集合A中移除,执行完View后,则将保存TempData的集合A当作value,key=__ControllerTempData保存到服务器Session中。最后将所有Session的keys发送到响应流中!

    上述提到那个保存TempData的集合A实际上是TempDataDictionary类型中的一个变量,该变量是一个字典类对象(new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase))
    TempData是ControllerBase类中的一个类型为TempDataDictionary的属性,TempData属性其实就是一个TempDataDictionary对象,该对象中有一个字典类型的私有字段保存着所有TempData的值!
    我们在Action方法中使用的TempData["kk"]=XX,其实就是调用的TempDataDictionary对象的索引器,将该键值对添加到保存所有TempData的私有字典表中!
    扩展:Session的机制,Session保存在服务器,但是请求的最后服务端会将Session的所有key发送到响应流中!当再次发来请求时,可以从请求上下文中获取到所有的keys。

     Controller
     ControllerBase
     TempDataDictionary
     SessionStateTempDataProvider
     DependencyResolver

       上述代码,TempDataDictionary类就是程序中使用的TempData的类型,SessionStateTempDataProvider类用于服务器Session中获取上一次没有被使用的TempData的集合,DependencyResolver类在上一节的Controller激活中已经介绍过了,它主要是用于根据类型通过反射创建实例(还具有缓存的功能)。
    补充:对于我们定义的TempData["kk"]=value,如果在使用时也是执行的TempDataDictionary类的索引器,在索引器的Set中可看到,如果使用TempData之后,是在【_initialKeys】中将key移除,而不是直接在保存所有TempData的集合【_data】中移除。
    猜想:MVC3和MVC4中对TempData的使用不一样

     3-2、ActionInvoker.InvokeAction(ControllerContext, actionName)

       此代码实现了:Action的执行、过滤器的执行、View的呈现。
      由于这部分的内容太多,为避免影响知识点混乱,将再开一篇博文来详细介绍!《白话学习MVC(八)Action的执行2》--整理中...

     Controller
     AsyncControllerActionInvoker
     ControllerActionInvoker

    3-3、PossiblySaveTempData()

      从保存着所有TempData的集合中移除已经被使用的TempData,最后再将所有没有被使用TempData集合保存在key=__ControllerTempData的Session中!以便下次请求中使用。

     Controller
     ControllerBase
     TempDataDictionary
     DictionaryExtensions
     SessionStateTempDataProvider

       上述代码,PossiblySaveTempData方法执行 TempData.Save(ControllerContext, TempDataProvider)来完成所有的功能,TempData得到的之前创建的TempDataDictionary对象(该对象的_data字段保存这定义的所有TempData键值对),参数TempDataProvider是创建的SessionStateTempDataProvider对象(该对象的SaveTempData方法的作用就是将没有被使用的TempData保存到Session中)。所以,TempDataDictionary对象的Save方法首先去遍历所有的TempData,检查TempData是否被使用过了,如果已被使用,则将该TempData在保存所有TempData的集合中移除,最后执行SessionStateTempDataProvider对象的SaveTempData方法将经过处理后的集合添加到Session中,以便这些TempData在写一次请求中使用!
      如3-1中补充到,对于我们定义的TempData["kk"]=value,如果在使用时是执行的TempDataDictionary类的索引器,在索引器的Set中可看到,如果使用TempData之后,是在【_initialKeys】中将key移除,而不是直接在保存所有TempData的集合【_data】中移除。此处做的就是在根据【_initialKeys】将被使用的TempData在【_data】中移除。

     以上就是全部内容,如有不合适之处请指教,觉得还不错的可以推荐下哦!!

     遗留问题:

      1、如何利用各扩展点暂没有分析。

    疑问:

      1、既然利用DependencyResolver通过反射创建对象时,接口和抽象类都不可以,MVC中为什么还在使用GetService方法使用接口来创建对象呀?明知是Null

  • 相关阅读:
    经典业务: 删除最老的文件
    The LIVE555TM HLS Proxy
    OpenGL
    OpenCV
    STUN, TURN, and ICE
    【AI学习总结】均方误差(Mean Square Error,MSE)与交叉熵(Cross Entropy,CE)损失函数
    SpringCloud中一些基本配置
    Redmibook笔记本触摸板失灵
    对称加密
    Java数字签名工具类
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3404545.html
Copyright © 2020-2023  润新知