이 글에서는 주로 ASP.NET MVC의 공통 확장 포인트인 필터와 모델 바인딩을 자세히 소개합니다. 도움이 필요한 친구들이 참고할 수 있습니다.
1. 필터(Filter)
ASP.NET MVC의 모든 요청은 해당 컨트롤러(이하 "메소드") 아래의 특정 작업(이하 "메소드")에 할당됩니다. "Controller") ) 처리, 일반적인 상황에서는 메서드에 직접 코드를 작성하면 되지만, 메서드 실행 전후에 일부 로직을 처리하려면 여기에서 필터를 사용해야 합니다.
일반적으로 사용되는 세 가지 필터는 Authorize(권한 부여 필터), HandleError(예외 필터), ActionFilter(사용자 정의 필터)이며 해당 클래스는 AuthorizeAttribute, HandleErrorAttribute 및 ActionFilterAttribute입니다. 상속 이러한 클래스를 사용하고 해당 메서드를 재정의하여 다양한 기능을 수행합니다.
1. Authorize 인증 필터
이름에서 알 수 있듯이 인증 필터는 메소드가 실행되기 전에 실행되어 인증 여부를 제한합니다. 요청은 이 메소드를 입력할 수 있으며, 새 메소드를 생성합니다:
public JsonResult AuthorizeFilterTest() { return Json(new ReturnModel_Common { msg = "hello world!" }); }
결과에 직접 액세스:
이제 이 AuthorizeFilterTest 메소드가 백그라운드 메소드라고 가정하면 사용자는 유효한 토큰을 가지고 있어야 접근할 수 있습니다. 일반적인 방법은 AuthorizeFilterTest 메서드에서 토큰을 받아 확인하는 것입니다. 그러나 메서드가 너무 많으면 각 메서드에 확인 코드를 작성하는 것은 명백히 비현실적입니다. 인증 필터를 사용해야 합니다.
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 메서드를 재정의하는 새 클래스를 생성했습니다. 이 의사 코드는 토큰에 값이 있으면 true를 반환한다는 것입니다. false를 반환합니다. 액세스 메서드:
[TokenValidate] public JsonResult AuthorizeFilterTest() { return Json(new ReturnModel_Common { msg = "hello world!" }) }
TokenValidate를 표시한 후 AuthorizeFilterTest가 true를 반환하면 승인이 AuthorizeFilterTest에서 코드를 성공적으로 실행합니다. . 토큰 전달 안 함:
토큰 전달:
토큰 전달 안 함 인증이 실패하면 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; }
효과:
실용: 가장 널리 사용되는 인증 필터 적용은 사용자 로그인 후권한 관리 시스템입니다. > 성공적으로 서버는 암호화된 토큰을 출력합니다. 이후 모든 요청은 이 토큰을 가져옵니다. 서버는 사용자 ID에 따라 권한이 있는지 확인하기 위해 AuthorizeCore 메서드에서 토큰을 잠금 해제합니다. 현재 인터페이스 를 요청합니다. 그렇다면 true를 반환하고, 그렇지 않으면 false를 반환합니다. 쿠키, 세션에 성공적으로 로그인하는 것과 비교하여 이 인증 방법을 사용하면 하나의 인터페이스를 PC 측과 앱 측에서 모두 사용할 수 있다는 장점이 있습니다.
2. HandleError 예외 필터
예외 필터는 코드 예외를 처리하며 MVC에서 기본적으로 예외 필터링을 구현하여 등록했을 때 실행됩니다. App_Start 디렉터리의 FilterConfig.cs:filters.Add(new HandleErrorAttribute());이는 전체 시스템에 적용됩니다. 모든 인터페이스 또는 페이지 오류는 MVC의 기본
예외 처리를 실행하고 기본 오류 페이지인 Views/Shared를 반환합니다. /오류(오류를 보고하기 위해 프로그램이 서버로 전송될 때만 이 페이지를 볼 수 있습니다. 로컬 디버깅에 높은 권한이 있는 경우에도 특정 오류 정보를 볼 수 있습니다)
@{ 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 기본 오류 페이지로 돌아가는데, 이는 프런트엔드에서 처리하기 어렵습니다. HandleErrorAttribute를 상속하는 새 클래스 LogExceptionAttribute를 만듭니다. 내부 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。
【相关推荐】
2. ASP.NET教程
위 내용은 ASP.NET의 필터 및 모델 바인딩 예제에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!