この記事では、主に ASP.NET MVC の一般的な拡張ポイントであるフィルターとモデル バインディングについて詳しく紹介します。必要な方は参考にしてください。
1. ASP .NET MVC のすべてのリクエストは、対応するコントローラー (以下「コントローラー」と呼びます) の下の特定のアクション (以下「メソッド」と呼びます) に割り当てられます。 通常の状況では、コードを直接記述するだけで十分です。ただし、メソッドの実行前または後にロジックを処理したい場合は、ここでフィルターを使用する必要があります。
一般的に使用されるフィルターは 3 つあります: Authorize (承認フィルター)、HandleError (例外フィルター)、ActionFilter (カスタム フィルター) 対応するクラスは次のとおりです: AuthorizeAttribute、HandleErrorAttribute、および ActionFilterAttributeInherit
さまざまな関数を使用できます。メソッドを記述することで実装されます。認可フィルターはその名の通り、メソッドの実行前に実行され、リクエストがこのメソッドに入ることができるかどうかを制限するために使用されます。新しいメソッド:
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; } } }
[TokenValidate] public JsonResult AuthorizeFilterTest() { return Json(new ReturnModel_Common { msg = "hello world!" }) }
トークンが渡されていない: トークンが渡されていない:
トークンが渡されていない 認可に失敗すると、MVC のデフォルトの認可されていないページに入ります。ここで改善を加えましょう。認可が成功したか失敗したかに関係なく、フロントエンドの処理を容易にするために、戻り値の形式は一貫している必要があります。現時点では、AuthorizeAttribute クラスの HandleUnauthorizedRequest メソッドを書き換えるだけです。
認可フィルター 最も広く使用されているアプリケーションは権限管理
システムです。ユーザーログインが成功すると、サーバーは暗号化されたトークンを出力し、AuthorizeCoreメソッドでトークンのロックを解除します。ユーザー ID を取得するには、ユーザー ID がデータベースにアクセスして、現在の
インターフェースを要求する権限があるかどうかを確認し、そうである場合は true を返し、そうでない場合は false を返します。ログイン成功のための Cookie や Session と比較して、この認証方法の利点は、1 つのインターフェイスを PC とアプリの両方で使用できることです。 2. HandleError 例外フィルター 例外フィルターはコード例外を処理し、MVC がデフォルトで例外フィルターを実装し、App_Start ディレクトリの FilterConfig.cs に登録されているときに実行されます。
/// <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; }
例外処理 が実行され、デフォルトのエラー ページ: Views/Shared/Error (これはプログラムがサーバーに送信されたときにのみ表示されます) を返します。エラーを報告するため) このページ、ローカルの デバッグ
には高い権限があり、特定のエラー情報を表示することができます)filters.Add(new HandleErrorAttribute());
1) エラー報告では、エラー コードが存在するコントローラーとメソッド、およびエラー報告時のリクエスト パラメーターと時刻を記録できます。
2) 特定の形式で JSON を返します。フロントエンド処理を容易にします。現在、システムのほとんどは Ajax リクエストを使用しているため、エラーが報告されると MVC のデフォルトのエラー ページに戻ります。これは、フロントエンドでの処理が困難です。HandleErrorAttribute を継承し、内部 OnException メソッドをオーバーライドする新しいクラス LogExceptionAttribute を作成します。 :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 中国語 Web サイトの他の関連記事を参照してください。