详解ASP.NET MVC 常用扩展点:过滤器、模型绑定

一、过滤器(Filter)

ASP.NET MVC中的每一个请求,都会分配给对应Controller(以下简称“控制器”)下的特定Action(以下简称“方法”)处理,正常情况下直接在方法里写代码就可以了,但是如果想在方法执行之前或者之后处理一些逻辑,这里就需要用到过滤器。

常用的过滤器有三个:Authorize(授权过滤器),HandleError(异常过滤器),ActionFilter(自定义过滤器),对应的类分别是:AuthorizeAttribute、HandleErrorAttribute和ActionFilterAttribute,继承这些类并重写其中方法即可实现不同的功能。

1.Authorize授权过滤器

授权过滤器顾名思义就是授权用的,授权过滤器在方法执行之前执行,用于限制请求能不能进入这个方法,新建一个方法:

public JsonResult AuthorizeFilterTest()
{
 return Json(new ReturnModel_Common { msg = "hello world!" });
}

直接访问得到结果:

现在假设这个AuthorizeFilterTest方法是一个后台方法,用户必须得有一个有效的令牌(token)才能访问,常规做法是在AuthorizeFilterTest方法里接收并验证token,但是这样一旦方法多了,每个方法里都写验证的代码显然不切实际,这个时候就要用到授权过滤器:

public class TokenValidateAttribute : AuthorizeAttribute
  {
    /// <summary>
    /// 授权验证的逻辑处理。返回true则通过授权,false则相反
    /// </summary>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
      string token = httpContext.Request["token"];
      if (string.IsNullOrEmpty(token))
      {
        return false;
      }
      else
      {
        return true;
      }
    }
  }

新建了一个继承AuthorizeAttribute的类,并重写了其中的AuthorizeCore方法,这段伪代码实现的就是token有值即返回true,没有则返回false,标注到需要授权才可以访问的方法上面:

[TokenValidate]
public JsonResult AuthorizeFilterTest()
{
  return Json(new ReturnModel_Common { msg = "hello world!" })
}

标注TokenValidate后,AuthorizeCore方法就在AuthorizeFilterTest之前执行,如果AuthorizeCore返回true,那么授权成功执行AuthorizeFilterTest里面的代码,否则授权失败。不传token:

传token:

不传token授权失败时进入了MVC默认的未授权页面。这里做下改进:不管授权是成功还是失败都保证返回值格式一致,方便前端处理,这个时候重写AuthorizeAttribute类里的HandleUnauthorizedRequest方法即可:

/// <summary>
/// 授权失败处理
/// </summary>
/// <param name="filterContext"></param>
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
  base.HandleUnauthorizedRequest(filterContext);

  var json = new JsonResult();
  json.Data = new ReturnModel_Common
  {
    success = false,
    code = ReturnCode_Interface.Token过期或错误,
    msg = "token expired or error"
  };
  json.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
  filterContext.Result = json;
}

效果:

实战:授权过滤器最广泛的应用还是做权限管理系统,用户登录成功后服务端输出一个加密的token,后续的请求都会带上这个token,服务端在AuthorizeCore方法里解开token拿到用户ID,根据用户ID去数据库里查是否有请求当前接口的权限,有就返回true,反之返回false。这种方式做授权,相比登录成功给Cookie和Session的好处就是一个接口PC端、App端共同使用。

2.HandleError异常过滤器

异常过滤器是处理代码异常的,在系统的代码抛错的时候执行,MVC默认已经实现了异常过滤器,并且注册到了App_Start目录下的FilterConfig.cs:

filters.Add(new HandleErrorAttribute());

这个生效于整个系统,任何接口或者页面报错都会执行MVC默认的异常处理,并返回一个默认的报错页面:Views/Shared/Error(程序发到服务器上报错时才可以看到本页面,本地调试权限高,还是可以看到具体报错信息的)

@{
  Layout = null;
}
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>错误</title>
</head>
<body>
  <hgroup>
    <h1>错误。</h1>
    <h2>处理你的请求时出错。</h2>
  </hgroup>
</body>
</html>

默认的异常过滤器显然无法满足使用需求,重写下异常过滤器,应付项目实战中的需求:

1)报错可以记录错误代码所在的控制器和方法,以及报错时的请求参数和时间;

2)返回特定格式的JSON方便前端处理。因为现在系统大部分是ajax请求,报错了返回MVC默认的报错页面,前端不好处理

新建一个类LogExceptionAttribute继承HandleErrorAttribute,并重写内部的OnException方法:

 public override void OnException(ExceptionContext filterContext)
 {
   if (!filterContext.ExceptionHandled)
   {
     string controllerName = (string)filterContext.RouteData.Values["controller"];
     string actionName = (string)filterContext.RouteData.Values["action"];
     string param = Common.GetPostParas();
     string ip = HttpContext.Current.Request.UserHostAddress;
     LogManager.GetLogger("LogExceptionAttribute").Error("Location:{0}/{1} Param:{2}UserIP:{3} Exception:{4}", controllerName, actionName, param, ip, filterContext.Exception.Message);

     filterContext.Result = new JsonResult
     {
       Data = new ReturnModel_Common { success = false, code = ReturnCode_Interface.服务端抛错, msg = filterContext.Exception.Message },
       JsonRequestBehavior = JsonRequestBehavior.AllowGet
     };
   }
   if (filterContext.Result is JsonResult)
     filterContext.ExceptionHandled = true;//返回结果是JsonResult,则设置异常已处理
   else
     base.OnException(filterContext);//执行基类HandleErrorAttribute的逻辑,转向错误页面
 }

异常过滤器就不像授权过滤器一样标注在方法上面了,直接到App_Start目录下的FilterConfig.cs注册下,这样所有的接口都可以生效了:

filters.Add(new LogExceptionAttribute());

异常过滤器里使用了NLog作为日志记录工具,Nuget安装命令:

Install-Package NLog
Install-Package NLog.Config

相比Log4net,NLog配置简单,仅几行代码即可,NLog.config:

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <targets>
  <target xsi:type="File" name="f" fileName="${basedir}/log/${shortdate}.log" layout="${uppercase:${level}} ${longdate} ${message}" />
  <target xsi:type="File" name="f2" fileName="D:\log\MVCExtension\${shortdate}.log" layout="${uppercase:${level}} ${longdate} ${message}" />
 </targets>
 <rules>
  <logger name="*" minlevel="Debug" writeTo="f2" />
 </rules>
</nlog>

如果报错,日志就记录在D盘的log目录下的MVCExtension目录下,一个项目一个日志目录,方便管理。全部配置完成,看下代码:

public JsonResult HandleErrorFilterTest()
{
  int i = int.Parse("abc");
  return Json(new ReturnModel_Data { data = i });
}

字符串强转成int类型,必然报错,页面响应:

同时日志也记录下来了:

3.ActionFilter自定义过滤器

自定义过滤器就更加灵活了,可以精确的注入到请求前、请求中和请求后。继承抽象类ActionFilterAttribute并重写里面的方法即可:

public class SystemLogAttribute : ActionFilterAttribute
{
  public string Operate { get; set; }

  public override void OnActionExecuted(ActionExecutedContext filterContext)
  {
    filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnActionExecuted");
    base.OnActionExecuted(filterContext);
  }

  public override void OnActionExecuting(ActionExecutingContext filterContext)
  {
    filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnActionExecuting");
    base.OnActionExecuting(filterContext);
  }

  public override void OnResultExecuted(ResultExecutedContext filterContext)
  {
    filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnResultExecuted");
    base.OnResultExecuted(filterContext);
  }

  public override void OnResultExecuting(ResultExecutingContext filterContext)
  {
    filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnResultExecuting");
    base.OnResultExecuting(filterContext);
  }
}

这个过滤器适合做系统操作日志记录功能:

[SystemLog(Operate = "添加用户")]
public string CustomerFilterTest()
{
  Response.Write("<br/>Action 执行中...");
  return "<br/>Action 执行结束";
}

看下结果:

四个方法执行顺序:OnActionExecuting—>OnActionExecuted—>OnResultExecuting—>OnResultExecuted,非常精确的控制了整个请求过程。

实战中记录日志过程是这样的:在OnActionExecuting方法里写一条操作日志到数据库里,全局变量存下这条记录的主键,到OnResultExecuted方法里说明请求结束了,这个时候自然知道用户的这个操作是否成功了,根据主键更新下这条操作日志的是否成功字段。

二、模型绑定(ModelBinder)

先看一个普通的方法:

public ActionResult Index(Student student)
{
  return View();
}

这个方法接受的参数是一个Student对象,前端传递过来的参数跟Student对象里的属性保持一直,那么就自动被绑定到这个对象里了,不需要在方法里new Student这个对象并挨个绑定属性了,绑定的过程由MVC中的DefaultModelBinder完成的,DefaultModelBinder同时继承了IModelBinder接口,现在就利用IModelBinder接口和DefaultModelBinder来实现更加灵活的模型绑定。

场景一、前端传过来了一个加密的字符串token,方法里需要用token里的某些字段,那就得在方法里接收这个字符串、解密字符串、转换成对象,这样一个方法还好说,多了的话重复代码非常多,就算提取通用方法,还是要在方法里调用这个通用方法,有没有办法直接在参数里就封装好这个对象?

模型绑定的对象:

public class TokenModel
{
  /// <summary>
  /// 主键
  /// </summary>
  public int Id { get; set; }

  /// <summary>
  /// 姓名
  /// </summary>
  public string Name { set; get; }

  /// <summary>
  /// 简介
  /// </summary>
  public string Description { get; set; }

}

新建一个TokenBinder继承IModelBinder接口并实现其中的BindModel方法:

public class TokenBinder : IModelBinder
{
  public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  {
    var token = controllerContext.HttpContext.Request["token"];
    if (!string.IsNullOrEmpty(token))
    {
      string[] array = token.Split(':');
      if (array.Length == 3)
      {
        return new TokenModel() { Id = int.Parse(array[0]), Name = array[1], Description = array[2] };
      }
      else
      {
        return new TokenModel() { Id = 0 };
      }
    }
    else
    {
      return new TokenModel() { Id = 0 };
    }
  }
}

这个方法里接收了一个token参数,并对token参数进行了解析和封装。代码部分完成了需要到Application_Start方法里进行下注册:

ModelBinders.Binders.Add(typeof(TokenModel), new TokenBinder());

现在模拟下这个接口:

public JsonResult TokenBinderTest(TokenModel tokenModel)
{
  var output = "Id:" + tokenModel.Id + ",Name:" + tokenModel.Name + ",Description:" + tokenModel.Description;
  return Json(new ReturnModel_Common { msg = output });
}

调用下:

可以看出,“1:汪杰:oppoic.cnblogs.com”已经被绑定到tokenModel这个对象里面了。但是如果稍复杂的模型绑定IModelBinder就无能为力了。

场景二、去除对象某个属性的首位空格

public class Student
{
  public int Id { get; set; }

  public string Name { get; set; }

  public string Class { get; set; }
}

如果前端传来的Name属性有空格,如何去除呢?利用DefaultModelBinder即可实现更灵活的控制

public class TrimModelBinder : DefaultModelBinder
{
  protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
  {
    var obj = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    if (obj is string && propertyDescriptor.Attributes[typeof(TrimAttribute)] != null)//判断是string类型且有[Trim]标记
    {
      return (obj as string).Trim();
    }
    return obj;
  }
}

标注下需要格式化首位属性的实体:

[ModelBinder(typeof(TrimModelBinder))]
public class Student
{
  public int Id { get; set; }

  [Trim]
  public string Name { get; set; }

  public string Class { get; set; }
}

好了,测试下:

public JsonResult TrimBinderTest(Student student)
{
  if (string.IsNullOrEmpty(student.Name) || string.IsNullOrEmpty(student.Class))
  {
    return Json(new ReturnModel_Common { msg = "未找到参数" });
  }
  else
  {
    return Json(new ReturnModel_Common { msg = "Name:" + student.Name + ",长度:" + student.Name.Length + " Class:" + student.Class + ",长度:" + student.Class.Length });
  }
}

可见,标注了Trim属性的Name长度是去除空格的长度:7,而没有标注的Class属性的长度则是6。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Asp.Net MVC学习总结之过滤器详解

     一.过滤器简介 1.1.理解什么是过滤器 1.过滤器(Filters)就是向请求处理管道中注入额外的逻辑.提供了一个简单而优雅的方式来实现横切关注点. 2.所谓的过滤器(Filters),MVC框架里面的过滤器完全不同于ASP.NET平台里面的Request.Filters和Response.Filter对象,它们主要是实现请求和响应流的传输.通常我们所说的过滤器是指MVC框架里面的过滤器. 3.过滤器可以注入一些代码逻辑到请求处理管道中,是基于C#的Attribute的实现.当负责调用Act

  • ASP.NET过滤器的应用方法介绍

    在J2EE Web开发中有过滤器filter,该filter可以对指定的URL访问进行拦截,并执行过滤器的方法,根据实际应用情况,在过滤器中修改请求的代码.判断会话信息,也可以做权限控制,总之这个过滤器是非常有意义的,也可以说是责任链设计模式在J2EE中的一个应用. 那么在ASP.NET中是否也可以定义这样的过滤器结构,并在过滤器中进行相应的逻辑操作呢?答案是肯定,本文将告诉你如果编写一个过滤器,又如何配置到IIS的Web应用之中. 过程一:如何编写过滤器 编写过滤器,其实就是编写一个过滤器的类

  • ASP.NET mvc4中的过滤器的使用

    mvc4中的过滤器 过滤器(Filter)把附加逻辑注入到MVC框架的请求处理.实现了交叉关注. 交叉关注:用于整个应用程序,又不适合放在某个局部位置的功能. 过滤器是.NET的注解属性(Attribute),它们对请求处理管道添加了额外的步骤. 注解属性是派生于System.Attribute的特殊的.NET类. 可以被附加到类.方法.属性.字段等代码元素上.其目的是把附加信息嵌入到已编译的代码中,以便在运行时读回这些信息. 过滤器的基本类型: 过滤器类型 接口 默认实现 描述 Authori

  • asp.net core MVC 过滤器之ActionFilter过滤器(2)

    本系类将会讲解asp.net core MVC中的内置过滤器的使用,将分为以下章节 asp.net core MVC 过滤器之ExceptionFilter过滤器(一) asp.net core MVC 过滤器之ActionFilter过滤器(二) asp.net core MVC 过滤器之ResultFilter过滤器(三) asp.net core MVC 过滤器之ResourceFilter过滤器(四) asp.net core MVC 过滤器之AuthorizationFilter过滤器

  • asp.net core MVC 全局过滤器之ExceptionFilter过滤器(1)

    本系类将会讲解asp.net core MVC中的内置全局过滤器的使用,将分为以下章节 asp.net core MVC 过滤器之ExceptionFilter过滤器(一) asp.net core MVC 过滤器之ActionFilter过滤器(二) asp.net core MVC 过滤器之ResultFilter过滤器(三) asp.net core MVC 过滤器之ResourceFilter过滤器(四) asp.net core MVC 过滤器之AuthorizationFilter过

  • 详解ASP.NET MVC 常用扩展点:过滤器、模型绑定

    一.过滤器(Filter) ASP.NET MVC中的每一个请求,都会分配给对应Controller(以下简称"控制器")下的特定Action(以下简称"方法")处理,正常情况下直接在方法里写代码就可以了,但是如果想在方法执行之前或者之后处理一些逻辑,这里就需要用到过滤器. 常用的过滤器有三个:Authorize(授权过滤器),HandleError(异常过滤器),ActionFilter(自定义过滤器),对应的类分别是:AuthorizeAttribute.Han

  • 详解ASP.NET MVC的整个生命周期

    目录 一.介绍 二.MVC生命周期详述 View的初始化和渲染呈现 三.结束 一.介绍 我们做开发的,尤其是做微软技术栈的,有一个方向是跳不过去的,那就是MVC开发.我相信大家,做ASP.NET MVC 开发有的有很长时间,当然,也有刚进入这个行业的.无论如何,如果有人问你,你知道ASP.NET MVC的生命周期吗?你知道它的来世今生吗?你知道它和 ASP.NET WEBFORM 有什么区别吗?估计,这些问题,有很多人会答不上来,或者说不清楚.今天,我就把我的理解写出来,也是对我自己学习的一次回

  • 详解Asp.Net MVC——控制器与动作(Controller And Action)

    一.理解控制器 1.1.什么是控制器 控制器是包含必要的处理请求的.NET类,控制器的角色封装了应用程序逻辑,控制器主要是负责处理请求,实行对模型的操作,选择视图呈现给用户. 简单理解:实现了IController接口,修饰符必须是public,不能是抽象的,不能是泛型的,类名必须以Controller结尾. 在MVC框架中,控制器类必须实现System.Web.Mvc命名空间下的IController接口,如上图所示,这是一个非常简单的接口,该接口仅有一个Execute方法,当请求该控制器时E

  • 详解ASP.NET MVC 解析模板生成静态页(RazorEngine)

    简述 Razor是ASP.NET MVC 3中新加入的技术,以作为ASPX引擎的一个新的替代项.在早期的MVC版本中默认使用的是ASPX模板引擎,Razor在语法上的确不错,用起来非常方便,简洁的语法与.NET Framework 结合,广泛应用于ASP.NET MVC 项目. 我们在很多项目开发中会常常用到页面静态化,页面静态化有许多方式,最常见的就是类似很多PHP CMS种使用的 标签替换的方式(如:帝国CMS.EcShop等),还有很多都是伪静态,伪静态我们就不做过多解释,通过路由或Url

  • 详解ASP.NET MVC 利用Razor引擎生成静态页

    最近在研究ASP.NET MVC生成静态页,那么今天也算个学习笔记吧! 实现原理及步骤: 1.通过ViewEngines.Engines.FindView查找到对应的视图,如果是部分视图,则用:ViewEngines.Engines.FindPartialView: 2.设置上下文对象里的Model: 3.调用视图的Render()方法,将渲染结果保存到物理静态文件: using System; using System.IO; using System.Text; using System.W

  • 详解ASP.NET MVC下的异步Action的定义和执行原理

    Visual Studio提供的Controller创建向导默认为我们创建一个继承自抽象类Controller的Controller类型,这样的Controller只能定义同步Action方法.如果我们需要定义异步Action方法,必须继承抽象类AsyncController.这篇问你讲述两种不同的异步Action的定义方法和底层执行原理. 一.基于线程池的请求处理 ASP.NET通过线程池的机制处理并发的HTTP请求.一个Web应用内部维护着一个线程池,当探测到抵达的针对本应用的请求时,会从池

  • 详解ASP.NET MVC的筛选器

    在ActionInvoker对Action的执行过程中,除了通过利用ActionDescriptor对Action方法的执行,以及之前进行的Model绑定与验证之外,还具有一个重要的工作,那就是对相关筛选器(Filter)的执行.ASP.NET MVC的筛选器是一种基于AOP(面向方面编程)的设计,我们将一些非业务的逻辑实现在相应的筛选器中,然后以一种横切(Crosscutting)的方式应用到对应的Action方法.当Action方法执行前后,这些筛选器会自动执行.ASP.NET MVC提供了

  • 详解ASP.NET MVC之下拉框绑定四种方式

    前言 上两节我们讲了文件上传的问题,关于这个上传的问题还未结束,我也在花时间做做分割大文件处理以及显示进度的问题,到时完成的话再发表,为了不耽误学习MVC其他内容的计划,我们今天开始好好讲讲关于MVC中下拉框中绑定枚举的几种方式. 话题引入 一般在下拉框中绑定数据的话,分为几种情况. (1)下拉框中的数据是写死的,我们直接给出死代码即可. (2)下拉框中的数据从数据库中读取出来,从而进行显示. (3)下拉框中直接用枚举显示. (4)下拉框中一个选择的值改变另外一个下拉框中的值. 关于下拉框中绑定

  • 详解Asp.Net MVC的Bundle捆绑

    大多数浏览器会对同一域名的请求限制请求数量,一般是在8个以内.每次最多可以同时请求8个,要是资源多于8个,那么剩下的就要排队等待请求了.所以为了提高首次加载页面的速度.提高请求并发请求数量,降低请求次数就是一个很重要的点. Bundle Asp.Net MVC4和.NET Framework4.5提供了支持捆绑和压缩的新类库System.Web.Optimization. 该类库提供了如下特性: 捆绑-将多个资源文件(javascript,css)合并成一个单独的文件,但是合并成的单独文件必须是

  • 详解ASP.NET MVC 下拉框的传值的两种方式

    以前使用WebForm变成时,下拉框传值只需直接在后台绑定代码就可以了.现在我们来看看在MVC中DropDownList是如果和接受从Controller传过来的值的. 第一种:使用DropDownList 控制器代码: public ActionResult Index() { //1.1查询YzSeriesEntity的数据 List<Model.YzSeriesEntity> seriesList = seriesBLL.LoadEnities().ToList(); //1.2将YzS

随机推荐