欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-22 11:43:04
一、过滤器(filter) asp.net mvc中的每一个请求,都会分配给对应controller(以下简称“控制器”)下的特定action(以下简称“方法”)处理,正...

一、过滤器(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!" });
}

直接访问得到结果:

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

现在假设这个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:

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

传token:

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

不传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;
}

效果:

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

实战:授权过滤器最广泛的应用还是做权限管理系统,用户登录成功后服务端输出一个加密的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类型,必然报错,页面响应:

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

同时日志也记录下来了:

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

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 执行结束";
}

看下结果:

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

四个方法执行顺序: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 });
}

调用下:

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

可以看出,“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。

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

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