MVC 常用扩展点:过滤器、模型绑定等
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。
扩展阅读:
ASP.NET MVC: 使用自定义 ModelBinder
ASP.NET MVC: 使用自定义 ModelBinder 过滤敏感信息
MVC 常用扩展点:过滤器、模型绑定等的更多相关文章
- ASP.NET MVC 常用扩展点:过滤器、模型绑定等
一.过滤器(Filter) ASP.NET MVC中的每一个请求,都会分配给对应Controller(以下简称“控制器”)下的特定Action(以下简称“方法”)处理,正常情况下直接在方法里写代码就可 ...
- ASP.NET Core 中文文档 第四章 MVC(2.1)模型绑定
原文:Model Binding 作者:Rachel Appel 翻译:娄宇(Lyrics) 校对:许登洋(Seay).何镇汐 模型绑定介绍 ASP.NET Core MVC 中的模型绑定从 HTTP ...
- 三万字盘点Spring/Boot的那些常用扩展点
大家好,我是三友. Spring对于每个Java后端程序员来说肯定不陌生,日常开发和面试必备的.本文就来盘点Spring/SpringBoot常见的扩展点,同时也来看看常见的开源框架是如何基于这些扩展 ...
- 4.ASP.NET MVC 5.0 视图之模型绑定
大家好,这篇文章,我将向大家介绍ASP.NET MVC的模型视图绑定,ASP.MVC的模型绑定分为两种:一种是动态绑定[Dynamic Binding];还有一种就是强类型绑定[Strongly ty ...
- MVC重写DefaultModelBinder实现自定义模型绑定
在编写前台页面的时候为了使url传递参数的简短,比如personId="1" 我们通过url传递成pid=1 那么在后台action方法接受的模型Person类 的属性为per ...
- [转]ASP.NET MVC 4 (九) 模型绑定
本文转自:http://www.cnblogs.com/duanshuiliu/p/3706701.html 模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C ...
- ASP.NET MVC 4 (九) 模型绑定
模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C#间起着桥梁的作用.模型绑定的一个最简单的例子是带参数的控制器action方法,比如我们注册这样的路径映射: ...
- ASP.NET MVC 模型绑定
模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C#间起着桥梁的作用.模型绑定的一个最简单的例子是带参数的控制器action方法,比如我们注册这样的路径映射: ...
- MVC 模型绑定
在WebForm,获取提交表单的值一般都是Request.Form["Title"]这样的方式.在MVC中,提供了模型绑定机制.让后台获取表单或Url中的参数变得更加简单. 一.基 ...
随机推荐
- Java 架构师之路(2)
一.技术 J2EE技术是架构师的基础.1.<Java编程思想> 初学Java时阅读这本书觉得好难,阅读第二遍时才觉得讲的很细致.这是一本不怕多读的好书. 2.<J2EE应用与BEA ...
- python细碎语法点
在系统入门python有的是没有遇到,有的是学过了缺乏使用没有记住,就开篇随笔记录这些基础的语法点,随时更新. with...as... 也就是说with是一个控制流语句,跟if/for/while/ ...
- 初探 spring data(一)--- spring data 概述
由于自己一个项目要用多到Sql与NoSql两种截然不同的数据结构,但在编程上我希望统一接口API,让不同类型的数据库能在相同的编程接口模式下运作.于是找了一个spring的官网,发现一个spring ...
- leetcode——262. Trips and Users
The Trips table holds all taxi trips. Each trip has a unique Id, while Client_Id and Driver_Id are b ...
- Spring集成RabbiMQ-Spring AMQP新特性
上一篇<Spring集成RabbitMQ-使用RabbitMQ更方便>中,我们只需要添加响应jar的依赖,就可以写一个Spring集成RabbitMQ下非常简单收发消息的程序. 我们使用的 ...
- 学习笔记TF051:生成式对抗网络
生成式对抗网络(gennerative adversarial network,GAN),谷歌2014年提出网络模型.灵感自二人博弈的零和博弈,目前最火的非监督深度学习.GAN之父,Ian J.Goo ...
- ubuntu安装mysql可视化工具MySQL-workbench及简单操作
一.使用命令行在ubuntu下安装mysql可视化工具MySQL-workbench Step1:安装MySQL-workbench 方案一:如果你已经装好mysql的相关服务,那么直接使用如下命令即 ...
- 对vue生命周期/钩子函数的理解
对于实现页面逻辑交互等效果,我们必须知晓vue的生命周期,才能愉快的玩耍,知道我们写的东西应该挂载到哪里,vue官方给出的api讲解的那叫一个简单啊,如下: 所有的生命周期钩子自动绑定this上下文到 ...
- 【Ubuntu 16】DEB软件包管理
一.背景介绍 开源软件最早的时候没有软件包和软件包管理器,用户只能下载源码包自行配置 编译 安装. 后来linux各发行版本推出了软件包格式和软件包管理程序 Red Hat.Centos使用RPM格式 ...
- 4_SSRF
SSRF SSRF(服务请求伪造)是一种由攻击者构造形成由服务器端发起请求的一个安全漏洞,它将一个可以发起网络请求的服务当作跳板来攻击其他服务器,SSRF的攻击目标一般是内网.当服务器提供了从其他服务 ...