一. 基本認識javascript
1. 簡介:HttpContext用於保持單個用戶、單個請求的數據,而且數據只在該請求期間保持;css
也能夠用於保持須要在不一樣的HttpModules和HttpHandlers之間傳遞的值;html
也能夠用於保持某個完整請求的相應信息。前端
2. 五大核心對象包括:Response、Request、Application、Server、Sessionjava
3. 源碼分析:在MVC框架中HttpContext對象來源於一個HttpContextBase類的一個實例,該類中包括如下幾個重要屬性:(非MVC框架中有一個單獨HttpContext類)node
①:HttpRequestBase Requestjquery
②:HttpResponseBase Responseweb
③:HttpApplicationStateBase Applicationajax
④:HttpServerUtilityBase Server正則表達式
⑤:HttpSessionStateBase Session
即所謂的五大核心對象,因此在MVC框架中一般能夠這麼用: HttpContext.Request、HttpContext.Response、HttpContext.Application、 HttpContext.Server、HttpContext.Session .
但咱們也常常看到省略HttpContext對象,直接:Request、Response、Server、Session 來使用,隨意選中其中一個,點擊F12查看源碼可知,Controller類中也包含如下四個屬性:
①:HttpRequestBase Request
②:HttpResponseBase Response
③:HttpServerUtilityBase Server
④:HttpSessionStateBase Session
二. 逐個分析
1. Request對象
1. Request:接收處理客戶端發送過來的請求 (已完成)
①:HttpMethod屬性:獲取客戶端發送請求的類型
②:RawUrl屬性:獲取當前請求完整的URL
③:UrlReferrer屬性:獲取有關連接到當前 URL 的客戶端請求的 URL
④:Request["XX"]:獲取xx屬性值
2. 代碼測試
1 //1.測試Request對象 2 $("#btn1").click(function () { 3 $.ajax({ 4 type: "Post", 5 url: "TestRequest", 6 data: { 7 "name": "123" 8 }, 9 success: function (data) { 10 if (data.status == "ok") { 11 alert("測試經過"); 12 $("#s1").html(data.HttpMethod); 13 $("#s2").html(data.RawUrl); 14 $("#s3").html(data.UrlReferrer); 15 $("#s4").html(data.name); 16 } 17 if (data == "error") { 18 alert("測試未經過"); 19 } 20 } 21 }); 22 });
1 /// <summary> 2 /// 測試Request對象相關 3 /// </summary> 4 /// <returns></returns> 5 public ActionResult TestRequest() 6 { 7 var HttpMethod = Request.HttpMethod; 8 var RawUrl = Request.RawUrl; 9 var UrlReferrer = Request.UrlReferrer; 10 var name = Request["name"]; 11 var data = new 12 { 13 status="ok", 14 HttpMethod=HttpMethod, 15 RawUrl= RawUrl, 16 UrlReferrer= UrlReferrer, 17 name= name 18 }; 19 return Json(data); 20 }
測試結果:
2. Server對象
1. Server:一個輔助類 (已完成)
①.HtmlEncode方法:將html編碼轉換成對應的字符串
②.HtmlDecode方法:將字符串轉換成Html編碼
③.MapPath方法:獲取該地址對應的物理路徑
④.UrlEncode方法:將url編碼轉換成對應的字符串
⑤.UrlDecode方法:將對應的字符串轉換成Url格式的編碼
2. 代碼測試
1 //2.測試Server對象 2 $("#btn2").click(function () { 3 $.ajax({ 4 type: "Post", 5 url: "TestServer", 6 data: { 7 8 }, 9 success: function (data) { 10 if (data.status == "ok") { 11 alert("測試經過"); 12 $("#s11").html(data.HtmlEncode); 13 $("#s22").html(data.HtmlDecode); 14 $("#s33").html(data.MapPath); 15 $("#s44").html(data.UrlEncode); 16 $("#s55").html(data.UrlDecode); 17 } 18 if (data == "error") { 19 alert("測試未經過"); 20 } 21 } 22 }); 23 });
1 public ActionResult TestServer() 2 { 3 string HtmlEncode = Server.HtmlEncode("<p>積極向上</p>"); 4 string HtmlDecode = Server.HtmlDecode(HtmlEncode); 5 string MapPath = Server.MapPath("/home/index"); //只能作物理文件的映射 6 string UrlEncode = Server.UrlEncode("https://www.2345.com/"); 7 string UrlDecode = Server.UrlDecode(UrlEncode); 8 var data = new 9 { 10 status = "ok", 11 HtmlEncode = HtmlEncode, 12 HtmlDecode = HtmlDecode, 13 MapPath = MapPath, 14 UrlEncode = UrlEncode, 15 UrlDecode = UrlDecode 16 }; 17 return Json(data); 18 }
測試結果:
3. Application對象
1. Application: 用於在ASP.NET 應用程序內的多個會話和請求之間共享信息(案例:單點登陸)
①. Lock方法:鎖定對象的訪問
②. unLock方法:取消鎖定對象的訪問
③. Add和Set方法:向集合中添加一個新對象
④. Application[]和Get方法:獲取對象的值
⑤. Remove、RemoveAt、RemoveAll方法:用來刪除對象
補充一個單一對話: HttpContextBase context.Item[""] ; 是指是一個IDictionary類,有Add、Clear、Contains、Remove等方法(不作測試)
2. 測試:
①. 用不一樣的瀏覽器打開Index頁面,模擬多線程多個會話,發現name2的值是相同的。
4. Response對象
1. Response:用戶服務器端將信息返回給客戶端
①.ContentType屬性:獲取或設置當前響應的 HTTP MIME 類型 (很是重要!),下面補充幾個經常使用的類型(多用於下載)
a. Json格式: "application/json"
b. JavaScript格式:"application/x-javascript"
c. 普通文本格式:"text/plain"
d. Html格式:"text/html"
e. XML格式:"text/xml"
f. Excel格式:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
g. gif圖片格式:"image/gif"
②.Redirect方法:用於站內跳轉或站點間的跳轉
③.Write方法:能夠將字符、字符串、字節等寫入到Http響應輸出流 (MVC中的Content方法就是基於該方法進行擴展的)
④.WriteFile和TransmitFile方法:將指定文件寫入到Http相應輸出流 (MVC中的FileResult就是基於該方法封裝的,簡化了下載流程)
(原生的四種下載方法詳見:https://www.cnblogs.com/weixing/archive/2012/02/27/2369567.html (不作測試了))
2. 測試利用FileResult下載文件
1 /// <summary> 2 /// 測試下載文件 3 /// </summary> 4 /// <param name="type">1表明圖片 2表明Excel文件</param> 5 /// <returns></returns> 6 public ActionResult TestDownFile(string type) 7 { 8 if (type == "1") 9 { 10 //須要下載的文件在服務器上的物理路徑 11 string path = Server.MapPath("/Content/imageHandle/pipe1.jpg"); 12 //須要下載的文件下載後保存到本地後的名字 13 string fileName = DateTime.Now.ToString("yyyyMMddHHmmssffffff") + ".jpg"; 14 return File(path, "image/jpg", fileName); 15 } 16 if (type == "2") 17 { 18 string path = Server.MapPath("/Content/myExcel/text.xlsx"); 19 string fileName = DateTime.Now.ToString("yyyyMMddHHmmssffffff") + ".xlsx"; 20 return File(path, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName); 21 } 22 return Content(""); 23 } 24
1 //4. 測試下載文件 2 //4.1 下載圖片 3 $("#btn5").click(function () { 4 window.location.href = "TestDownFile?type=1"; 5 }); 6 //4.2 下載Excel 7 $("#btn6").click(function () { 8 window.location.href = "TestDownFile?type=2"; 9 });
結果:
5. Session對象
Session:存儲於服務器內存中key-value集合
(1).補充Cookie的概念:Cookie存儲於客戶端,經過瀏覽器的【Response Header】標頭下的Set-Cookie進行存儲,由瀏覽器進行創建、
保存、和到期刪除。Cookie默認的生命週期是瀏覽器的聲明週期,瀏覽器關閉,cookie消失,固然也能夠顯示的設置Cookie的到期時間,瀏覽器會根據這個到期
時間,將Cookie存放到客戶端硬盤的。
注意:Cookie在瀏覽器能夠被看到,因此不適合存放敏感信息;Cookie能存儲的數據有限,最大4kb;Cookie是按照瀏覽器進行劃分的。
(2).Http的請求流程:
①:客戶端第一次訪問,沒有Cookie,沒有Session,服務器端會生成一個SessionId和一個空的Value,保存到服務器內存上。
②:客戶端第一次返回,經過瀏覽器的【Response Header】標頭下的Set-Cookie進行輸出。
③:客戶端第二次訪問,會帶上Cookie進行訪問,且Cookie中存放着SessionId,服務器端經過SessionMoudle解析獲得SessionId,獲取內存
中存放的數據,而且知道剛纔你來過。
④:接下來在Session沒有過時和不關閉的瀏覽器的狀況下,每次訪問服務器端,都會帶着同一個SessionId進行訪問。
圖解:
(3).深刻剖析Session
①:同一個瀏覽器在不關閉的狀況下(且服務器端Session沒有過時),不管訪問幾回服務器端,SessionId都是相同的,由於每次請求都會帶着Cookie中存儲的SessionId的。
②:關閉瀏覽器,再次請求服務器,由於【Request Header】表頭中,沒有提交剛纔的SessionId,服務器會認爲這是一個新的請求,服務器會分配給你一個新的
SessionId,這就解釋了原SessionId並無過時爲何服務器會從新分配給你一個SessionId的緣由了。
③:同一個瀏覽器登陸狀態下,長時間沒有任何操做(Session默認過時時間爲20分鐘),再次操做的時候,雖然Cookie中帶着SessionId進行
訪問服務器端,可是原Session已經被服務器端給清除了,只保留SessionId,原存放到Session中的內容均爲null。
④:Session不能跨進程訪問,只能由當前會話的用戶訪問,由於SessionId是以Cookie的形式存放在訪問者的客戶端瀏覽器中。
⑤:在同一個會話中,Session是能夠跨頁面進行全局使用的。
⑥:服務器上的Session保存的是一個SessionId和一個Value,而Value是一個集合,該集合中存放着當前用戶的Key
(4).平常開發Session的侷限性
在咱們開發管理系統中,一般登陸後會把該用戶的權限放到Session中(如:放到Session["user"]中),這時候,在同一個瀏覽器中登陸admin帳戶,
會把a、b、c權限放到Session["user"]中,在不關閉該瀏覽器的狀況下,登陸admin2帳戶,這時候同一個瀏覽器請求的【Request Header】表頭中
的cookie存放的SessionId是相同的,因此服務器端會認爲是同一個會話,admin2帳戶的c,d權限會把原先的Session["user"]中的a、b、c權限覆蓋,
致使admin帳戶操做系統的時候,發現本身沒有 a、b、c權限,而是有c、d權限。
(若是解決這個問題呢?可使用NoSQL來替代Session)
(5).Session的聲明和銷燬
①:從寫入開始,若是該頁面一直沒有操做,默認20分鐘,服務器會把session銷燬,但SessionId仍是存在的。
②:手動經過Abandon的方式取消當前會話
③:刪除客戶端的Cookie,會致使服務器端從新分配給客戶端一個SessionId,其實服務器端的原Session並無消失
(6).總結:
客戶端向服務器發請求,若是請求頭中沒有帶SessionId,服務器會分配一個SessionId給客戶端,並存放在客戶端的Cookie中;
客戶端向服務器發送請求,若是cookie中有值,會帶着該值一塊兒發送到服務器,若是沒有,則不帶。
(7).補充Session的幾個經常使用方法
①:Session[]:用於設置或讀取Session的值
②:Timeout屬性:設置Session過時時間
(補充經過配置文件的形式設置全局session過時:System.Web下添加 <sessionState mode="InProc" timeout="30"/> )
InProc表示進程內;timeout表示多少分鐘後Session失效。
③:SessionID屬性:每一個會話的惟一標識符
④:Remove方法:從會話集合中刪除一項;Clear方法:從會話集合中刪除全部的鍵和值;Abandon方法:取消當前會話
MVC中的TempData就是基於Session實現的,可是TempData只能讀取一次
(8). 測試:
①:測試同一個瀏覽器,不關閉狀況下SessionId相同,且同一個key的數據會被後面覆蓋
②:測試服務器端Session過時後,SessionId還存在,Session中的值均爲Null(銷燬也是這個效果)
③:經過F12 瀏覽器驗證http流程,並查看【Response Header】和【Request Header】中何時又SessionId
④:手動刪除客戶端的Cookie,再次請求服務器,會從新分配新的SessionId
先貼出來幾塊代碼:
a:打開該頁面,顯示SessionId和name的值,以下圖:
b:分析該頁面打開的請求,以下圖:印證Http請求第一次請求和第一次返回的請求流程
c. 點擊按鈕屢次,獲取SessionId和name的值,結果以下圖:SessionId和name的值都是相同的。
d. 查看該按鈕對應的請求,以下圖:
e: 不關閉該瀏覽器的狀況下,從新在一個新的選項卡中打開Index頁面,SessionId沒有變,name從新賦值了。
f:回到第一次打開的頁面,從新點擊測試按鈕,獲取SessionId和name值,SessionId沒有變,可是name值被第二次打開的頁面的name值覆蓋了。(印證了管理系統中權限覆蓋的問題)
g:點擊銷燬Session,而後從新點擊測試按鈕,查看結果,SessionId依舊存在,可是Session的value集合變成null了(等待服務器session自動過時也是這種效果)
第十四節:再探MVC中路由的奧祕
一. 基於RouteBase擴展
1. 原理
擴展RouteBase,一樣使用的是MVC框架提供的MvcRouteHandler進行處理。
2. 步驟
1. 新建YpfRoute1類,繼承RouteBase類型,並覆寫裏面的兩個抽象方法, 在GetRouteData編寫業務邏輯,(這裏是禁止Chrome瀏覽器訪問),經過RouteData添加地址,並返回。
1 /// <summary> 2 /// 基於RouteBase進行擴展 3 /// </summary> 4 public class YpfRoute1 : RouteBase 5 { 6 /// <summary> 7 /// 解析路由信息 8 /// </summary> 9 /// <param name="httpContext"></param> 10 /// <returns></returns> 11 public override RouteData GetRouteData(HttpContextBase httpContext) 12 { 13 //編寫本身業務邏輯(這裏禁止Chrome瀏覽器的訪問 不是很準確) 14 if (httpContext.Request.UserAgent.IndexOf("Chrome")>=0) 15 { 16 //返回指定的頁面 17 RouteData rd = new RouteData(this, new MvcRouteHandler()); 18 rd.Values.Add("controller","Route"); 19 rd.Values.Add("action", "RefuseView"); 20 return rd; 21 } 22 23 return null; 24 } 25 26 /// <summary> 27 /// 指定虛擬路徑處理(這裏暫不使用) 28 /// </summary> 29 /// <param name="requestContext"></param> 30 /// <param name="values"></param> 31 /// <returns></returns> 32 public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 33 { 34 throw new NotImplementedException(); 35 } 36 }
2. 在RouteConfig文件中新建RefuseChrome方法,並在該方法中添加YpfRoute1類。
1 /// <summary> 2 /// 基於RouteBase進行擴展的路由 3 /// </summary> 4 /// <param name="route"></param> 5 public static void RefuseChrome(RouteCollection route) 6 { 7 route.Add("myRoute", new YpfRoute1()); 8 }
3. 在Global中將RefuseChrome方法註冊進去。
注意:是放到系統自帶路由註冊的上面仍是下面,哪一個在上,哪一個先執行。
4. 測試
使用Chrome瀏覽器打開任何頁面,均跳轉到拒絕訪問頁面。
二. 基於IRouteHandler擴展
1. 原理
擴展IRouteHandler而且自定義IHttpHandler 。
2. 步驟
1. 新建YpfRoute1類,實現接口IRouteHandler,並實現裏面的GetHttpHandler方法。
2. 自定義ypfHttpHandler類,並實現IhttpHandler接口。
1 /// <summary> 2 /// 擴展IRouteHandler和自定義IHttpHandler 3 /// </summary> 4 public class YpfRoute2 : IRouteHandler 5 { 6 public IHttpHandler GetHttpHandler(RequestContext requestContext) 7 { 8 return new ypfHttpHandler(); 9 } 10 } 11 12 public class ypfHttpHandler : IHttpHandler 13 { 14 15 bool IHttpHandler.IsReusable 16 { 17 get 18 { 19 return false; 20 } 21 } 22 23 void IHttpHandler.ProcessRequest(HttpContext context) 24 { 25 string url = context.Request.Url.AbsoluteUri; 26 context.Response.Write(string.Format("這裏是ypf定製:{0}", this.GetType().Name)); 27 context.Response.Write((string.Format("當前地址爲:{0}", url))); 28 29 context.Response.End(); 30 } 31 }
3. 在RouteConfig文件中新建RegisterSpecialRoute方法,並在該方法中添加YpfRoute1類(註冊規則,並使用RouteTable往裏添加)
1 /// <summary> 2 /// 擴展IRouteHandler和自定義IHttpHandler 3 /// </summary> 4 /// <param name="routes"></param> 5 public static void RegisterSpecialRoute(RouteCollection routes) 6 { 7 Route route = new Route("Ypf/{other}",new YpfRoute2()); 8 RouteTable.Routes.Add(route); 9 }
4. 在Global中將RefuseChrome方法註冊進去,注意:是放到系統自帶路由註冊的上面仍是下面,哪一個在上, 哪一個先執行。
5. 訪問地址:http://localhost:7559/Ypf/1 ,統一跳轉到指定頁面顯示指定的信息(地址中的 1 能夠換成任何信息)
第十三節:HttpHander擴展及應用(自定義擴展名、圖片防盜鏈)
一. 自定義擴展名
1. 前言
凡是實現了IHttpHandler接口的類均爲Handler類,HttpHandler是一個HTTP請求的真正處理中心,在HttpHandler容器中,ASP.NET Framework才調用HttpHandler的ProcessRequest方法來對這個HTTP請求進行真正的處理,真正地對客戶端請求的服務器頁面作出編譯和執行,並將處理事後的信息附加在HTTP請求信息流中再次返回到HttpModule中。
2. 背景
咱們在平常開發中,也許會碰到一些這樣的特殊需求,在路由規則以外,想給本身預留一個後門,進行一些小動做;或者想自定義一種特有的看起來很酷炫的後綴;或者默寫後綴不想走MVC框架默認提供的處理流程,那麼使用HttpHander進行擴展,再合適不過。
下面咱們先擴展一個後綴爲 .ypf的處理請求,凡是該後綴的請求,統一顯示驗證碼頁面。
3. 詳細步驟
①. 新建一個類(ypfCode),實現IHttpHandler接口。
1 /// <summary> 2 ///這裏自定義的handle爲通常處理程序,實現了IHttpHandler接口 3 ///IRequiresSessionState是爲了使http請求具備會話狀態的讀寫權限,即能操控: context.Session["CheckCode"] = code; 4 /// </summary> 5 public class ypfCode : IHttpHandler, IRequiresSessionState 6 { 7 /// <summary> 8 /// 您將須要在網站的 Web.config 文件中配置此處理程序 9 /// 並向 IIS 註冊它,而後才能使用它。有關詳細信息, 10 /// 請參閱如下連接: https://go.microsoft.com/?linkid=8101007 11 /// </summary> 12 #region IHttpHandler Members 13 14 public bool IsReusable 15 { 16 // 若是沒法爲其餘請求重用託管處理程序,則返回 false。 17 // 若是按請求保留某些狀態信息,則一般這將爲 false。 18 get { return true; } 19 } 20 21 public void ProcessRequest(HttpContext context) 22 { 23 //在此處寫入您的處理程序實現。 24 string code = ""; 25 Bitmap bitmap = VerifyCodeHelper.CreateVerifyCode(out code); 26 context.Session["CheckCode"] = code; 27 bitmap.Save(context.Response.OutputStream, ImageFormat.Gif); 28 context.Response.ContentType = "image/gif"; 29 } 30 #endregion 31 }
補充一個驗證碼實現類:
1 public class VerifyCodeHelper 2 { 3 public static Bitmap CreateVerifyCode(out string code) 4 { 5 //創建Bitmap對象,繪圖 6 Bitmap bitmap = new Bitmap(200, 60); 7 Graphics graph = Graphics.FromImage(bitmap); 8 graph.FillRectangle(new SolidBrush(Color.White), 0, 0, 200, 60); 9 Font font = new Font(FontFamily.GenericSerif, 48, FontStyle.Bold, GraphicsUnit.Pixel); 10 Random r = new Random(); 11 string letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ0123456789"; 12 13 StringBuilder sb = new StringBuilder(); 14 15 //添加隨機的五個字母 16 for (int x = 0; x < 5; x++) 17 { 18 string letter = letters.Substring(r.Next(0, letters.Length - 1), 1); 19 sb.Append(letter); 20 graph.DrawString(letter, font, new SolidBrush(Color.Black), x * 38, r.Next(0, 15)); 21 } 22 code = sb.ToString(); 23 24 //混淆背景 25 Pen linePen = new Pen(new SolidBrush(Color.Black), 2); 26 for (int x = 0; x < 6; x++) 27 graph.DrawLine(linePen, new Point(r.Next(0, 199), r.Next(0, 59)), new Point(r.Next(0, 199), r.Next(0, 59))); 28 return bitmap; 29 } 30 }
②. 在Web.config文件中的 <system.webServer>→<handlers>下添加如下節點,表示以.ypf爲後綴的請求統一由ypfCode處理,其中
<add name="ypf" path="*.ypf" verb="*" type="Ypf.Web.Core.PipeLine.ypfCode,Ypf.Web.Core" />
Ypf.Web.Core.PipeLine.ypfCode:表示該類的命名空間
Ypf.Web.Core:表示該類所在庫的程序集名稱
1 <!--VS2013及之後/IIS7.0以後的集成模式 須要添加下面的system.webServer節點--> 2 <system.webServer> 3 4 <!--1. 在此處配置modules--> 5 <modules runAllManagedModulesForAllRequests="false"> 6 <!--1.1 runAllManagedModulesForAllRequests處理靜態文件的請求--> 7 <remove name="FormsAuthentication" /> 8 <!--1.2 優化網站性能,去掉不須要的module--> 9 <remove name="RoleManager" /> 10 <remove name="FileAuthorization" /> 11 <remove name="UrlAuthorization" /> 12 </modules> 13 <!--2. 在此處配置handlers--> 14 <handlers> 15 <!--2.1 優化網站性能,去掉不須要的module--> 16 <remove name="ExtensionlessUrlHandler-Integrated-4.0" /> 17 <remove name="OPTIONSVerbHandler" /> 18 <remove name="TRACEVerbHandler" /> 19 <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" /> 20 21 <!--2.2 添加自定義handlers--> 22 <!--2.2.1 後綴爲ypf的請求 --> 23 <add name="ypf" path="*.ypf" verb="*" type="Ypf.Web.Core.PipeLine.ypfCode,Ypf.Web.Core" /> 24 25 <!--2.2.2 圖片處理請求 --> 26 <!--方案一 逐個添加不一樣格式的圖片 特別注意:不支持在一個path寫多個擴展名--> 27 <!--<add name="img" path="*.jpg" verb="*" type="Ypf.Web.Core.PipeLine.imgHandle,Ypf.Web.Core" /> 28 <add name="png" path="*.png" verb="*" type="Ypf.Web.Core.PipeLine.imgHandle,Ypf.Web.Core" /> 29 <add name="gif" path="*.gif" verb="*" type="Ypf.Web.Core.PipeLine.imgHandle,Ypf.Web.Core" />--> 30 <!--方案二 經過HangerFactory來處理 特別注意:不支持在一個path寫多個擴展名 --> 31 <add name="img" path="*.jpg" verb="*" type="Ypf.Web.Core.PipeLine.ImageHandlerFactory,Ypf.Web.Core" /> 32 <add name="png" path="*.png" verb="*" type="Ypf.Web.Core.PipeLine.ImageHandlerFactory,Ypf.Web.Core" /> 33 <add name="gif" path="*.gif" verb="*" type="Ypf.Web.Core.PipeLine.ImageHandlerFactory,Ypf.Web.Core" /> 34 35 </handlers> 36 </system.webServer>
③. 因爲自己框架自己已經有一套路由規則用來處理http請求,因此須要在RouteConfig中忽略該自定義Handle的規則,以下:
routes.IgnoreRoute("MyTest/{*pathInfo}"); 表示MyTest/格式的請求不進行路由驗證
④. 測試:http://localhost:7559/MyTest/xxx.ypf 均返回驗證碼圖片
二. 圖片防盜鏈
1. 什麼是圖片防盜鏈
本身網站中展現的本身的圖片一般是放在本身服務器上,不少無恥的網站獲取到別人網站的圖片地址,放到本身網站上,這就是圖片盜鏈。
圖片被盜鏈損耗的是本身服務器的流量。
2. 防盜鏈的代碼實現原理
當接受到一張圖片請求的時候
1. 判斷該圖片請求是從哪個請求鏈接過來的(或者從哪個頁面鏈接過來的),若是爲空,表示是一個單獨的圖片請求,確定不應站內的,
因此判斷爲盜鏈。兩種判斷方式以下
a: context.Request.ServerVariables["HTTP_REFERER"]
b: context.Request.UrlReferrer
2. 若是上一個請求不爲空,則判斷上一個請求中是否含有該站的域名或ip地址,若是沒有,則爲非法請求,不是該站內的請求,
因此判斷爲盜鏈。兩種判斷方法
a:context.Request.UrlReferrer.Host.Contains("121.42.223.23")
b:context.Request.ServerVariables["HTTP_REFERER"].Contains("121.42.223.23")
3. 自定義hander進行處理
①. 自定義imgHandle,實現IHttpHandler接口
1 /// <summary> 2 /// 圖片防盜鏈 3 /// 該自定義handle用來處理各類獲取圖片的請求 4 /// (這裏處理 jpg gif png三種格式的圖片) 5 /// </summary> 6 public class imgHandle : IHttpHandler 7 { 8 9 public bool IsReusable 10 { 11 // 若是沒法爲其餘請求重用託管處理程序,則返回 false。 12 // 若是按請求保留某些狀態信息,則一般這將爲 false。 13 get { return true; } 14 } 15 16 public void ProcessRequest(HttpContext context) 17 { 18 19 //0.獲取圖片的返回類型 20 string type = GetContentType(context.Request.Url.ToString()); 21 22 var beforeUrl = context.Request.ServerVariables["HTTP_REFERER"]; 23 24 //1. 若是請求的url爲空,或者請求的主機部分爲空,返回一張禁止倒鏈的圖片 25 if (context.Request.UrlReferrer == null || context.Request.UrlReferrer.Host == null) 26 { 27 //context.Response.ContentType = "image/JPEG"; 28 //context.Response.WriteFile("/Content/imageHandle/Forbidden.jpg"); 29 30 context.Response.Write("您的請求是非法,請勿再試"); 31 } 32 else 33 { 34 //2. url不空,且包含本身主機域名,表示爲本身網站的請求,顯示正常圖片 35 //正常發佈的時候這裏用域名或者ip,localhost爲了本地調試 36 if (context.Request.UrlReferrer.Host.Contains("121.42.200.127")) 37 { 38 string FileName = context.Server.MapPath(context.Request.FilePath); 39 context.Response.ContentType = type; 40 context.Response.WriteFile(FileName); 41 } 42 else 43 { 44 //3. url不空,但不包含本身主機域名,表示該請求爲盜鏈請求,返回一張禁止倒鏈的圖片 45 //context.Response.ContentType = "image/JPEG"; 46 //context.Response.WriteFile("/Content/imageHandle/Forbidden.jpg"); 47 48 context.Response.Write("您的請求是非法,請勿再試"); 49 } 50 51 } 52 } 53 54 /// <summary> 55 /// 將地址轉換成圖片返回值 56 /// </summary> 57 /// <param name="url"></param> 58 /// <returns></returns> 59 private static string GetContentType(string url) 60 { 61 switch (Path.GetExtension(url)) 62 { 63 case ".gif": 64 return "Image/gif"; 65 case ".jpg": 66 return "Image/jpeg"; 67 case ".png": 68 return "Image/png"; 69 default: 70 break; 71 } 72 return null; 73 } 74 75 }
擴展:也能夠自定義handleFactory,實現IHttpHandlerFactory接口,在handleFactory中指定不一樣hander。
1 /// <summary> 2 /// 自定義的一個HandleFactory,用來指定不一樣的後綴調用不一樣的Handle 3 /// 這裏是一個ImageHandlerFactory,處理不一樣 圖片後綴 4 /// </summary> 5 public class ImageHandlerFactory : IHttpHandlerFactory 6 { 7 public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated) 8 { 9 string path = context.Request.PhysicalPath; 10 if (Path.GetExtension(path).Equals(".jpg")) 11 { 12 return new imgHandle(); 13 } 14 else if (Path.GetExtension(path).Equals(".png")) 15 { 16 return new imgHandle(); 17 } 18 else 19 { 20 //這裏能夠繼續擴展,這裏測試不繼續擴展了 21 return new imgHandle(); 22 } 23 24 } 25 26 public void ReleaseHandler(IHttpHandler handler) 27 { 28 29 } 30 }
②. 在Web.config文件中的 <system.webServer>→<handlers>下添加:
<add name="img" path="*.jpg" verb="*" type="Ypf.Web.Core.PipeLine.imgHandle,Ypf.Web.Core" />
<add name="png" path="*.png" verb="*" type="Ypf.Web.Core.PipeLine.imgHandle,Ypf.Web.Core" />
<add name="gif" path="*.gif" verb="*" type="Ypf.Web.Core.PipeLine.imgHandle,Ypf.Web.Core" />
1 <!--VS2013及之後/IIS7.0以後的集成模式 須要添加下面的system.webServer節點--> 2 <system.webServer> 3 4 <!--1. 在此處配置modules--> 5 <modules runAllManagedModulesForAllRequests="false"> 6 <!--1.1 runAllManagedModulesForAllRequests處理靜態文件的請求--> 7 <remove name="FormsAuthentication" /> 8 <!--1.2 優化網站性能,去掉不須要的module--> 9 <remove name="RoleManager" /> 10 <remove name="FileAuthorization" /> 11 <remove name="UrlAuthorization" /> 12 </modules> 13 <!--2. 在此處配置handlers--> 14 <handlers> 15 <!--2.1 優化網站性能,去掉不須要的module--> 16 <remove name="ExtensionlessUrlHandler-Integrated-4.0" /> 17 <remove name="OPTIONSVerbHandler" /> 18 <remove name="TRACEVerbHandler" /> 19 <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" /> 20 21 <!--2.2 添加自定義handlers--> 22 <!--2.2.1 後綴爲ypf的請求 --> 23 <add name="ypf" path="*.ypf" verb="*" type="Ypf.Web.Core.PipeLine.ypfCode,Ypf.Web.Core" /> 24 25 <!--2.2.2 圖片處理請求 --> 26 <!--方案一 逐個添加不一樣格式的圖片 特別注意:不支持在一個path寫多個擴展名--> 27 <add name="img" path="*.jpg" verb="*" type="Ypf.Web.Core.PipeLine.imgHandle,Ypf.Web.Core" /> 28 <add name="png" path="*.png" verb="*" type="Ypf.Web.Core.PipeLine.imgHandle,Ypf.Web.Core" /> 29 <add name="gif" path="*.gif" verb="*" type="Ypf.Web.Core.PipeLine.imgHandle,Ypf.Web.Core" /> 30 <!--方案二 經過HangerFactory來處理 特別注意:不支持在一個path寫多個擴展名 --> 31 <!--<add name="img" path="*.jpg" verb="*" type="Ypf.Web.Core.PipeLine.ImageHandlerFactory,Ypf.Web.Core" /> 32 <add name="png" path="*.png" verb="*" type="Ypf.Web.Core.PipeLine.ImageHandlerFactory,Ypf.Web.Core" /> 33 <add name="gif" path="*.gif" verb="*" type="Ypf.Web.Core.PipeLine.ImageHandlerFactory,Ypf.Web.Core" />--> 34 35 </handlers> 36 </system.webServer>
4. 代碼測試
新建一個Index頁面,在該頁面經過img標籤直接連接到圖片地址,顯示圖片,而後將該項目發佈到測試服務器上。
①:訪問地址:http://121.42.200.127:8099/Pipe/Index, 正常顯示該頁面中的圖片, 表示該圖片請求爲站內正常的請求
②:訪問地址:http://121.42.200.127:8099/Content/imageHandle/pipe1.jpg, 不能正常訪問,表示該圖片請求爲盜鏈
第十二節:MVC中的一些特殊優化
一. 刪除WebForm視圖引擎
在MVC框架中檢索視圖的順序爲:當前控制器下對應的文件夾的aspx文件→share文件夾aspx文件→當前控制器下對應文件夾的cshtml文件→share文件夾的cshtml文件。
鑑於以上順序,若是咱們使用的MVC5框架中不須要WebForm的視圖引擎,能夠刪除,來提升視圖的檢索速度。
一樣是在Global.asax中操做,代碼以下:
1 public class MvcApplication : System.Web.HttpApplication 2 { 3 /// <summary> 4 /// 刪除WebForm視圖即aspx文件視圖檢索引擎 5 /// </summary> 6 protected void RemoveWebFormEngines() 7 { 8 var viewEngines = ViewEngines.Engines; 9 var webFormEngines = viewEngines.OfType<WebFormViewEngine>().FirstOrDefault(); 10 if (webFormEngines != null) 11 { 12 viewEngines.Remove(webFormEngines); 13 } 14 } 15 16 17 /// <summary> 18 /// 網站第一次啓動的時候會率先執行,且只執行一次 19 /// </summary> 20 protected void Application_Start() 21 { 22 AreaRegistration.RegisterAllAreas(); //區域 23 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); //過濾器 24 RouteConfig.RegisterRoutes(RouteTable.Routes); //常規路由 25 BundleConfig.RegisterBundles(BundleTable.Bundles); //js包、css包、combres 26 27 //刪除WebForm視圖即aspx文件視圖檢索引擎 28 RemoveWebFormEngines(); 29 //阻止MVC將版本號發回給瀏覽器 30 MvcHandler.DisableMvcResponseHeader = true; 31 //註冊自定義實例化控制器的容器 32 ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory()); 33 } 34 }
二. 隱藏MVC版本號
在Asp.Net MVC框架中,默認打開一個頁面,會將MVC的版本顯示在瀏覽器上,以下:
在Global.asax中的Application_Start()方法中添加一句話:MvcHandler.DisableMvcResponseHeader = true; 便可阻止將MVC版本暴露給瀏覽器,以下:
第十一節:Bundles壓縮合並js和css及原理分析
一. 簡介
1.背景:瀏覽器默認一次性請求的網絡數是有上限的,若是你得js和css文件太多,就會致使瀏覽器須要屢次加載,影響頁面的加載速度, MVC中提供Bundles的方式壓縮合並js和css,是MVC中特有的一種優化方式。
(固然如今前端也有不少基於node的工做流插件,能夠合併壓縮混淆js或css)
2. 原理(瞭解便可):核心方法ApplyTransforms,經過StreamReader不斷一次一次進行讀取
參考文檔:https://docs.microsoft.com/en-us/aspnet/mvc/overview/performance/bundling-and-minification
二. 如何使用
①:首先要在Global中進行註冊,代碼:BundleConfig.RegisterBundles(BundleTable.Bundles); 【系統默認已經註冊,此步驟不須要咱們操做】
②:在BundleConfig類中進行自定義合併壓縮js或css
a. 壓縮css方法:bundles.Add(new StyleBundle("~/XXX").Include("","",""));
StyleBundle中的參數爲一個虛擬路徑,單必須以【~/】開頭,後面自定義,該名稱即爲在前端頁面引入的名稱。
Include中的參數爲一個可變的string數組,用來聲明要壓縮合並的css文件的路徑。
b. 壓縮js方法:bundles.Add(new ScriptBundle("~/XXX").Include("","",""));
ScriptBundle中的參數爲一個虛擬路徑,單必須以【~/】開頭,後面自定義,該名稱即爲在前端頁面引入的名稱
Include中的參數爲一個可變的string數組,用來聲明要壓縮合並的css文件的路徑。
③:在前端頁面經過Render方法或者原生方法引入合併後的js或css的虛擬目錄
④:將webconfig中的調試模式設置爲false,這樣在不發佈的狀況下就能夠看到壓縮後的效果。
<compilation debug="false" targetFramework="4.5" />
若是設置爲true,直接在vs中運行,是看不到壓縮效果的,仍然是多個js或css文件 (適用於經過Render方式的引入)
⑤:該步驟和④的效果相同.
若是不設置④的狀況下,還想在vs中運行看到壓縮效果,也能夠在BundleConfig中加一句話:
BundleTable.EnableOptimizations = true; (特別注意:這裏是true,而配置文件的方式是設置爲false)
(適用於經過Render方式的引入)
補充:經過表達式擴展:(詳細語法能夠參考上面的地址,下面擴展幾個經常使用的)
①: T* 該目錄下以T開頭的js或css文件
②: *.js *.css 該目錄下的全部js或css文件
③: T*.js T*.css 這個是不合法,違規的
三. 代碼測試
1. 新建兩個js文件和css文件
2. 在BundleConfig文件中進行註冊
3. 編寫前端頁面,以傳統的方式引入,並查看效果。
4. 引入壓縮合並後的路徑。
方式一的運行結果:
方式二的運行結果:發現並無壓縮
至於爲何,上面已經解釋了。
解決方案:
方案①:BundleTable.EnableOptimizations = true;
方案②:<compilation debug="false" targetFramework="4.5" />
從新運行:
最後補充幾個含格式驗證的壓縮:
js壓縮後產生的分號,是由於Razor語法不須要以分號結尾
第十節:數據批註(DataAnnotationModel)和自定義驗證(包括Model級別的驗證)
一. 簡介
寫完上一個章節MVC中的經常使用特性,火燒眉毛將該系列補全,該章節主要介紹數據批註(也叫:註解)。
一聽【數據批註】,好高大上的名字,但仔細一看,它們實際上是【System.ComponentModel.DataAnnotations】程序集下的一些特性類,O(∩_∩)O哈哈~,既然是特性,就符合特性的全部特徵,只不過這些特性是做用於「屬性」上的。
再一看【System.ComponentModel.DataAnnotations】這個命名空間,有點眼熟,與以前EF中的一篇文章【EF的CodeFirst模式經過DataAnnotations修改默認協定】中的一類操做來源於同一個命名空間下。
因此綜上所述:該命名空間下的特性,在EF中能夠用來映射生成數據庫中的表字段,在平常開發中也能夠用於作類中屬性的限制和驗證。
原理:均繼承了ValidationAttribute特性,經過覆寫IsValide方法進行校驗。
適用場景:不少項目須要客戶端和服務器端進行雙重格式驗證,使之更加安全,這時服務器端就可使用數據批註了來進行校驗了。
以Required特性爲例,查看一下源碼:
二. 經常使用的數據批註
這裏總結一下【System.ComponentModel.DataAnnotations】命名空間下經常使用的數據批註,即特性。
① Key :聲明主鍵
② Required:非空聲明
③ MinLength和MaxLength:設置string類型的最大長度和最小長度,數據庫的對應nvarchar
④ StringLength:設置string類型的長度,數據庫對應nvarchar
⑤ Compare:新老密碼對比
⑥ RegularExpression:正則的匹配
⑦ Phone:驗證手機號碼
⑧ Range:驗證範圍
⑨ Timestamp:將byte[]類型設置爲timestamp類型
⑩ ConcurrencyCheck:併發檢查,執行update操做時,會檢查併發性(樂觀鎖) (在後面併發章節着重介紹Timestamp和ConcurrencyCheck)
另外還有一些不是很經常使用的,如:
① DisplayName:聲明屬性的名稱
② Remote:遠程驗證,須要JQuery插件的支持 (這裏不作測試等待補充 參考: https://www.cnblogs.com/JustRun1983/p/3505151.html)
下面補充一下該命名空間反射源碼,能夠自行查找須要的批註:
代碼測試:
(1). 實體類,在其屬性上添加數據標註
1 /// <summary> 2 /// 用戶信息類 ,用於測試框架自己提供的數據批註 3 /// </summary> 4 public class UserInfor 5 { 6 [Required] 7 public string id { get; set; } 8 9 [StringLength(4)] 10 public string userName { get; set; } 11 12 [MaxLength(8)] 13 public string userMsg { get; set; } 14 15 [Range(2, 10)] 16 public int userAge { get; set; } 17 18 [RegularExpression("[a-d]")] //a-d中的一個 19 public string userMsg3 { get; set; } 20 21 [Phone] 22 public string userPhone { get; set; } 23 24 public string userOldPwd { get; set; } 25 26 [Compare("userOldPwd")] //比較和userOldPwd的值是否相等 27 public string userNewPwd { get; set; } 28 29 }
(2). 前端代碼
1 //1. 測試數據批註 2 $("#btn1").click(function () { 3 $.ajax({ 4 type: "Post", 5 url: "TestDataAnnotationModel", 6 data: { 7 "id":"123", 8 "userName": "mr12", 9 "userMsg": "ypf1234", 10 "userAge": 6, 11 "userMsg3": "a", 12 "userPhone": "15764222366", 13 "userOldPwd": "123456", 14 "userNewPwd":"123456" 15 16 }, 17 success: function (data) { 18 if (data == "ok") { 19 alert("測試經過"); 20 } 21 if (data == "error") { 22 alert("測試未經過"); 23 } 24 } 25 }); 26 });
(3). 服務器端代碼
1 public ActionResult TestDataAnnotationModel(UserInfor user) 2 { 3 //經過該方法進行驗證 4 var isValidate = ModelState.IsValid; 5 6 if (isValidate) 7 { 8 return Content("ok"); 9 } 10 return Content("error"); 11 }
三. 自定義數據批註
思路:經過上面的批註源碼可知,均爲自定義類繼承:ValidationAttribute,覆寫IsValid方法
需求:這裏咱們自定義一個批註,要求不爲空,且長度區間爲6-12位
使用方法一樣爲:action中經過實體接收,經過ModelState.IsValid的值爲true或false來判斷驗證是否經過
代碼測試:
(1). 實體類和自定義批註
1 /// <summary> 2 /// 角色類,用於測試自定義業務的數據批註 3 /// </summary> 4 public class RoleInfor 5 { 6 7 public string id { get; set; } 8 9 [myOwnCheck] 10 public string roleName { get; set; } 11 } 12 13 /// <summary> 14 /// 自定義數據批註,要求非空且長度爲6-12位 15 /// </summary> 16 public class myOwnCheckAttribute : ValidationAttribute 17 { 18 public override bool IsValid(object value) 19 { 20 if (value != null && value.ToString().Length > 6 && value.ToString().Length < 12) 21 { 22 return true; 23 } 24 return false; 25 } 26 }
(2). 前端代碼
1 //2. 測試自定義業務邏輯的驗證 2 $("#btn2").click(function () { 3 $.ajax({ 4 type: "Post", 5 url: "TestMyOwnCheck", 6 data: { 7 "id": "123", 8 "roleName": "mr12345" 9 }, 10 success: function (data) { 11 if (data == "ok") { 12 alert("測試經過"); 13 } 14 if (data == "error") { 15 alert("測試未經過"); 16 } 17 } 18 }); 19 });
(3). 服務器端代碼
1 public ActionResult TestDataAnnotationModel(UserInfor user) 2 { 3 //經過該方法進行驗證 4 var isValidate = ModelState.IsValid; 5 6 if (isValidate) 7 { 8 return Content("ok"); 9 } 10 return Content("error"); 11 }
四. Model級別的驗證擴展
實現IValidaableObjec接口,實現Validate方法。(瞭解便可)
1 public class CarInfor: IValidatableObject 2 { 3 public string id { get; set; } 4 5 public string carName { get; set; } 6 7 public int carAge { get; set; } 8 9 IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext) 10 { 11 if (carAge % 2 == 0) 12 { 13 var result = new ValidationResult("車齡驗證不經過", new string[] { "carAge" }); 14 15 yield return result; 16 } 17 } 18 }
第九節:從源碼的角度分析MVC中的一些特性及其用法
一. 前世此生
乍眼一看,該標題寫的有點煽情,最近也是在不斷反思,怎麼能把博客寫好,讓人能讀下去,通俗易懂,深刻淺出。
接下來幾個章節都是圍繞框架自己提供特性展開,有MVC程序集提供的,也有其它程序集提供;在本章節將重點介紹幾個MVC框架提供的且做用於方法上的特性,而且模仿其源碼自定義特性。
其實早在前面的 DotNet進階章節,就寫過一篇關於特性的文章了,這裏從新總結一下特性核心要點。
1. 什麼是特性?
特性是一個類,在不影響程序封裝的狀況下,額外的給程序添加一些信息,用於運行時描述程序或者影響程序的行爲。
2. 特性的做用範圍?
提到特性的做用範圍,就不得不提到 AttributeUsage了,該類自己就是一個特性,繼承了Attribute類,用於約束自定義特性(你能夠看到系統提供的不少特性中,均能看到它的身影),下面先看一 下它的源碼:
該特性有一個參數,兩個核心屬性,AttributeTargets參數約束了該給特性能夠做用的範圍,經過右面的代碼可知,能夠做用於:類、方法、參數、屬性、返回值等等,該參數默認爲ALL。
AllowMultiple:約束該特性可否同時做用於某個元素(類、方法、屬性等等)屢次,默認爲false。
Inherited:約束該特性做用於基類(或其它)上,其子類可否繼承該特性,默認爲true。
3. 如何自定義特性?
簡單來講,聲明一個以Attribute結尾的類,繼承Attribute類,而後加上AttributeTargets特性約束,一個簡單的特性就產生了。
二. MVC中的經常使用特性
有了前面的鋪墊,這裏講解【System.Web.Mvc】程序集下的一些特性就很好理解,理解源碼的同時,主要掌握其如何使用。
MVC中提供的經常使用特性有:【HttpGet】、【HttpPost】、【AcceptVerbs】、【ActionName】、【NoAction】、【AllowAnonymous】、【ValidateAntiForgeryToken】、【ChildActionOnly】、【Bind】這九個特性。
查看源碼可知,其中【HttpGet】【HttpPost】【AcceptVerbs】【ActionName】【NoAction】【ChildActionOnly】均繼承ActionNameSelectorAttribute類,實現了IsValidForRequest這個抽象方法,且特性約束爲: [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)],顯然這些特性均是做用於方法上的,且不須要同時做用,容許子類繼承該特性。
以【HttpGet】的源碼爲例:
其中【AllowAnonymous】直接繼承Attribute類,源碼以下:
其中【ValidateAntiForgeryToken】繼承了FilterAttribute類,實現了IAuthorizationFilter接口,源碼以下:
(擴展一下:AuthorizeAttribute類也是繼承了FilterAttribute類,實現了IAuthorizationFilter接口)
1. HttpGet和HttpPost
(1). HttpGet:只容許Get請求訪問
底層運用的AcceptVerb特性實現的,因此等價於[AcceptVerbs(HttpVerbs.Get)]或[AcceptVerbs("Get")]
測試:前端用Ajax請求,若是非Get請求方式進行請求,則提示404找不到
(2). HttpPost:只容許Post請求訪問
底層運用的AcceptVerb特性實現的,因此等價於[AcceptVerbs(HttpVerbs.Post)]或[AcceptVerbs("Post")]
測試:前端用Ajax請求,若是非Post請求方式進行請求,則提示404找不到
特別注意:若是一個方法要同時容許Get和Post請求[HttpGet]和[HttpPost]同時加載上面是錯誤的!!這個時候就須要使用AcceptVerb特性了(固然方法上若是什麼特性也不加,什麼請求均支持)
2. AcceptVerbs
AccetpVerbs:用於限定請求方式(包括:Get、Post、Put、Delete、Head、Patch、Options)
查看源碼可知:該特性有兩個構造函數,全部兩種寫法,如:只容許Get請求,能夠:[AcceptVerbs(HttpVerbs.Get)]和[AcceptVerbs("Post")]
若要同時支持多種請求,能夠:[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]或[AcceptVerbs("Get", "Post")]
相關測試代碼以下:
1 //1. 下面三種寫法均爲只容許Get請求 2 //[HttpGet] 3 //[AcceptVerbs(HttpVerbs.Get)] 4 //[AcceptVerbs("Get")] 5 6 //2. 下面三種寫法均爲只容許Get請求 7 //[HttpPost] 8 //[AcceptVerbs(HttpVerbs.Post)] 9 //[AcceptVerbs("Post")] 10 11 //3. 下面兩種寫法表示:既容許Get請求也容許Post請求 12 [AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)] 13 //[AcceptVerbs("Get", "Post")] 14 public ActionResult TestMethordWay() 15 { 16 return Content("請求成功"); 17 }
1 //1. 測試只容許Get或Post請求 2 $("#btn1").click(function () { 3 $.ajax({ 4 type: "Put", //Post 、Put 5 url: "TestMethordWay", 6 data: "", 7 success: function (msg) { 8 alert(msg); 9 } 10 }); 11 });
3. ActionName
ActionName:修改Action自己的方法名
測試:請求TestActionName1方法,報404找不到
請求TestActionName2方法,正常訪問
相關測試代碼以下:
1 /// <summary> 2 /// 名字變爲:TestActionName2了 3 /// </summary> 4 /// <returns></returns> 5 [ActionName("TestActionName2")] 6 public ActionResult TestActionName1() 7 { 8 return Content("我是TestActionName1"); 9 }
1 //2. 測試ActionName特性 2 $("#btn2").click(function () { 3 $.ajax({ 4 type: "Get", 5 url: "TestActionName2", //TestActionName1 6 data: "", 7 success: function (msg) { 8 alert(msg); 9 } 10 }); 11 });
4. NoAction
NoAction: 標記控制器中的action將不在是一個方法,不能前端Http請求訪問
測試:前端頁面ajax進行請求,報404找不到
但在其它action中進行調用,能正常調用
相關測試代碼以下:
1 /// <summary> 2 /// 標記該方法將不是一個方法 3 /// </summary> 4 /// <returns></returns> 5 [NonAction] 6 public ActionResult TestNoAction() 7 { 8 return Content("請求成功"); 9 }
1 //3. 測試NoAction特性 2 $("#btn3").click(function () { 3 $.ajax({ 4 type: "Get", 5 url: "TestNoAction", //TestNoAction 6 data: "", 7 success: function (msg) { 8 alert(msg); 9 } 10 }); 11 });
5. AllowAnonymous
AllowAnonymous:該特性用於標記在受權期間要跳過 AuthorizeAttribute 過濾器的驗證
解釋:AuthorizeAttribute是MVC框架自帶的實現IAuthorizationFilter過濾器的一個類,內部有一套自身業務驗證(感興趣的能夠本身研究源碼)
而AllowAnonymous就是爲了標記跨過AuthorizeAttribute驗證的
這裏不作詳細測試
6. ValidateAntiForgeryToken
ValidateAntiForgeryToken:阻止跨站請求僞造攻擊(CSRF).
①. CSRF原理是什麼:
a.用戶mr訪問正規網站A,登陸驗證經過,並在用戶mr處產生Cookie
b.用戶mr在不關閉A網站的狀況下打開危險的B網站,在B網站中要求訪問A網站,發出一個Request請求
c.這時候瀏覽器帶着A網站在mr出產生的Cookie進行訪問A網站
d.這時候A網站就沒法判斷這個cookie是誰產生的,默認就給經過了
詳細見:https://www.cnblogs.com/hechunming/p/4647646.html
②:解決方案
a. 在Controller中的action上加上特性[ValidateAntiForgeryToken]
b. 對於增刪改查操做前端調用: $.ajaxAntiForgery方法進行ajax請求(須要引入jqueryToken的js文件)
這裏不作詳細測試
7. ChildActionOnly
ChildActionOnly:限制操做方法只能由子操做進行調用。
①. 測試直接輸入:http://localhost:7559/SpecialAttribute/Index2, 沒法訪問報錯.
②. 須要經過RenderAction來調用(存在問題,與Unity改造框架衝突衝突)
這裏RenderAction不作詳細測試
8. Bind
①. 源碼的角度分析:該特性能夠做用於類或參數(本章節測試做用於參數)
②. 該特性有三個核心屬性:
a. Exclude:獲取或設置不容許綁定的屬性名稱的列表(各屬性名稱之間用逗號分隔)
b. Include:獲取或設置容許綁定的屬性名稱的列表(各屬性名稱之間用逗號分隔),與Exclude一個道理,一般根據狀況使用一個便可
c. Prefix:獲取或設置在呈現表示綁定到操做參數或模型屬性的標記時要使用的前綴
不適用與ajax提交,適用於razor語法中的@{Html.TextBox(stu.id)},在如今先後端分離盛行的狀況下,有點不適合了
測試:
前端過個ajax傳過來三個參數:id、name、sex, 參數中的stu只能收到id和name,sex爲null,若想收到sex,須要在方法中經過request進行接收
1 /// <summary> 2 /// stu中的sex屬性爲null 3 /// var 中sex爲男 4 /// </summary> 5 /// <param name="stu"></param> 6 /// <returns></returns> 7 public ActionResult TestBindAttribute([Bind(Include = "id,name")]Student stu) 8 { 9 var sex = Request["sex"]; 10 return Content("請求成功"); 11 } 12
1 //4.測試BindAttribute特性 2 $("#btn4").click(function () { 3 $.ajax({ 4 type: "Get", 5 url: "TestBindAttribute", 6 data: {"id":"123","name":"ypf","sex":"男"}, 7 success: function (msg) { 8 alert(msg); 9 } 10 }); 11 });
三. 自定義一個相似的特性
要求:支持Get請求,且必須是Ajax請求
思路:由HttpGet特性能夠知道:須要繼承ActionMethodSelectorAttribute類,而後覆寫IsValidForRequest方法便可
1 public class HttpGetAndAjaxAttribute : ActionMethodSelectorAttribute 2 { 3 public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) 4 { 5 //1.獲取請求方式 6 string HttpWay = controllerContext.HttpContext.Request.GetHttpMethodOverride(); 7 8 //2. 獲取是不是Ajax請求 9 bool isAjax = controllerContext.HttpContext.Request.IsAjaxRequest(); 10 11 if (HttpWay.ToLower() == "get" && isAjax == true) 12 { 13 return true; 14 } 15 return false; 16 17 } 18 } 19 20 [HttpGetAndAjax] 21 public ActionResult GetAndAjax() 22 { 23 return Content("請求成功"); 24 }
1 //5.測試自定義特性 2 $("#btn5").click(function () { 3 $.ajax({ 4 type: "Get", 5 url: "GetAndAjax", 6 data: "", 7 success: function (msg) { 8 alert(msg); 9 } 10 }); 11 });
第五節:從源碼的角度理解各類Result(ActionResult、JsonResult、JavaScriptResult等)
一. 背景
提到MVC不得不說MVC中的各類Result,這些高度封裝的xxxResult以及在xxxResult再度封裝的xxx,大大提升了MVC框架的開發效率。
相信作過MVC開發的朋友都會用到過 return Content("xx"), 給客戶端的Ajax請求返值,那麼Content內部是怎麼實現的呢?Content和ContentResult之間又是什麼關係呢?ContentResult內部又是怎麼實現的呢?
與此相似的還有不少:Json方法和JsonResult、JavaScript方法和JavaScriptResult、Empty方法和EmptyResult 等等。
解決上面的問題以前,咱們須要準備兩件利器:
①:ILSpy代碼反射工具(網上下載破解版)。
②:Reflector Vs的反射插件(經過Nuget下載)。
有了上面這兩種利器中任何一種,就能夠清晰的看到各類Result中的代碼實現了。
下面以Content爲例,簡單介紹一下大體思路,在第二模塊,將詳細介紹每一個Result。
①:Content方法是返回值爲ContentResult類型的一個方法,即new了一個ContentResult類進行return,代碼以下圖:
②:ContentResult類內部又是怎麼實現的呢?代碼以下,最後核心代碼是經過 response.write(""),向客戶端返回了一個字符串。
總結:通過這兩步,Content也好,ContentResult也好,原理就很清晰了,下面一樣按照這個套路,詳細的分析各類Result的內部原理,以及測試其使用狀況。
二. 逐個分析
1. ActionResult
①:它是一個抽象類,且包含一個抽象方法ExecuteResult,各類Result都直接或間接繼承ActionResult,並實現ExecuteResult方法。
②:應用於Action方法前面的類型,它是Action的返回值,表明Action的執行結果。
源碼以下:
代碼測試:指向頁面
1 public ActionResult Index() 2 { 3 return View(); 4 }
2. JsonResult
①:繼承了ActionResult,實現了ExecuteResult方法。
②:解讀源碼可知,JsonResult內部實現原理是調用了JavaScriptSerializer對象中的Serialize方法,將Json對象轉換成了Json字符串,
經過:response.Write(javaScriptSerializer.Serialize(this.Data)); 傳遞給前臺。
③:默認是進制Get請求訪問的. JsonRequestBehavior.DenyGet。
④:在MVC的Action中,return Json(),這裏的Json經過源碼可知,即new了一個JsonResult對象而已,而且MVC中封裝了不少重載。
⑤:應用於action充當APP接口的狀況,且返回Json字符串,但ajax接受到後,會自動轉換成Json對象進行使用。
源碼以下:
代碼測試:
1 /// <summary> 2 /// 經過Index1頁面向該方法發送請求 3 /// 前端拿到的是JSON字符串,須要前端轉換 4 /// </summary> 5 /// <returns></returns> 6 public ActionResult GetInfor() 7 { 8 var data =new 9 { 10 id="1", 11 name="mr" 12 }; 13 string data2 = new JavaScriptSerializer().Serialize(data); 14 return Json(data2, JsonRequestBehavior.AllowGet); 15 } 16 /// <summary> 17 /// 前端拿到的是JSON對象,前端能夠直接調用 18 /// </summary> 19 /// <returns></returns> 20 public ActionResult GetInfor2() 21 { 22 var data = new 23 { 24 id = "1", 25 name = "mr" 26 }; 27 return Json(data, JsonRequestBehavior.AllowGet); 28 }
測試結果:一個返回了Json對象,一個返回了Json字符串
3. JavaScriptResult
①:繼承了ActionResult,實現了ExecuteResult方法。
②:解讀源碼可知:JavaScriptResult內部實現原理,設置了返回參數類型 response.ContentType = "application/x-javascript";
而後經過: response.Write(this.Script);將js代碼返回前臺。
能夠觸類旁通:經過ContentResult而且設置其類型爲"application/x-javascript",能夠達到ContentResult一樣的效果。
③:在MVC的Action中,return JavaScript(),這裏的JavaScript經過源碼可知,即new了一個JavaScriptResult對象而已,而且MVC中封裝了不少重載。
④:應用於經過後臺返給前端js代碼。
源碼以下:
代碼測試:下面兩段代碼的結果是一致的
1 public ActionResult GetJs() 2 { 3 return JavaScript("alert('我是js代碼,調用的是JavaScriptResult')"); 4 } 5 public ActionResult GetJs2() 6 { 7 return Content("alert('我是js代碼,調用的是ContentResult,並自定義的返回類型爲js')", "application/x-javascript"); 8 }
4. ContentResult
①:繼承了ActionResult,實現了ExecuteResult方法。
②:解讀源碼可知,ContentResult內部實現原理直接將數據經過這個方法Response.Write(string s)直接返回。
③:在MVC的Action中,return Content(),這裏的Content經過源碼可知,即new了一個ContentResult對象而已,而且MVC中封裝了不少重載,能夠手動設置的返回類型。
④:應用於返回一個簡單的判斷字段,默認表示一個文本內容,以下面的例子。
代碼測試:下面兩段代碼效果原理是一致的。
1 /// <summary> 2 /// 下面這兩種狀況達到的效果是一致的,原理也一致 3 /// </summary> 4 public void GetInfor3() 5 { 6 Response.Write("ok"); 7 } 8 public ActionResult GetInfor4() 9 { 10 return Content("ok"); 11 }
5. EmptyResult
①:繼承了ActionResult,實現了ExecuteResult方法。
②:解讀源碼可知:EmptyResult內部實現原理,實際上它的ExecuteResult方法中爲空,什麼也沒有。
③:在MVC的Action中,return Empty(),這裏的Empty經過源碼可知,即new了一個EmptyResult對象而已,而且MVC中封裝了不少重載。
④:這裏的EmptyResult起到一個適配器做用,一箇中轉的做用,能夠應用於請求不須要顯示頁面的狀況。
6. RedirectResult
①:繼承了ActionResult,實現了ExecuteResult方法。
②:解讀源碼可知:RedirctResult內部實現原理,實際上它的ExecuteResult方法調用的是context.HttpContext.Response.Redirect(text, false)。
③:在MVC的Action中,return Redirct(),這裏的Redirct經過源碼可知,即new了一個RedirctResult對象而已。
④:應用於在後臺進行跨站點跳轉和同站點間action之間進行跳轉。
源碼以下:
代碼測試:同站點和跨站點間的跳轉。
1 /// <summary> 2 /// 跨站點跳轉 3 /// </summary> 4 /// <returns></returns> 5 public ActionResult RedirctToBaidu() 6 { 7 return Redirect("http://www.baidu.com"); 8 } 9 /// <summary> 10 /// 同站點間action之間進行跳轉 11 /// </summary> 12 /// <returns></returns> 13 public ActionResult RedictOtherAction() 14 { 15 return Redirect("/Third/GetInfor4"); 16 }
7. RedirectToRouteResult
①:也是與頁面跳轉相關的。
②:mvc中 return RedirectToAction(""); return RedirectToRoute();都是跳轉的一些變種,這裏再也不詳細分析了。
8. FileResult
①:繼承了ActionResult,實現了ExecuteResult方法。
②:解讀源碼可知:FileResultResult內部實現原理,實際上它的ExecuteResult方法調用的是WriteFile(response)。
③:MVC的Action中,return File(),即new了一個FileResult對象而已.有多個重載。
④:應用於下載文件,驗證碼的例子。
三. 本身擴展
1. 擴展一個高效的json序列化
2. 擴展一個xml序列化
3. 擴展一個ExcelResult,是傳入一組數據,直接下載excel文件
四. 重點研究(ViewResult)(待補充)
第四節:MVC中AOP思想的體現(四種過濾器)並結合項目案例說明過濾器的實際用法
一. 簡介
MVC中的過濾器能夠說是MVC框架中的一種靈魂所在,它是MVC框架中AOP思想的具體體現,因此它以面向切面的形式無侵入式的做用於代碼的業務邏輯,與業務邏輯代碼分離,一經推出,廣受開發者的喜好。
那麼過濾器究竟是什麼呢?它又有什麼做用呢?
用戶經過URL訪問Web系統不必定都能獲得相應的內容,一方面不一樣的用戶權限不一樣,另外一方面是爲了保護系統,防止被攻擊,這就是過濾器的核心所在,咱們總計一下過濾器都有哪些做用:
①:判斷用戶是否登陸以及不一樣用戶對應不一樣的權限問題。
②:防盜鏈、防爬蟲。
③:系統中語言版本的切換(本地化和國際化)。
④:權限管理系統中動態Action。
⑤:決策輸出緩存。
知道到了過濾器的做用,那麼過濾器分哪幾類呢?以下圖1:
二. 執行順序
從上圖①可知,過濾器分四類,總共重寫了六個方法,在這六個方法裏能夠處理相應的業務邏輯,那麼若是四種過濾器的六個重寫方法同時存在,它們的執行順序是什麼呢?
首先要將OnException方法除外,該方法不和其他五個方法參與排序問題,該方法獨立存在,什麼時間報錯,何時調用。
其他三種過濾器中的五個重寫方法的執行順序:
三. 自定義實現形式
1. 直接在控制器中重寫方法或者利用控制器間的繼承
新建任何一個控制器,它均繼承Controller類,F12進入Controller類中,發現Controller類中已經實現了過濾器須要實現的接口,而且提供虛方法供咱們重寫,代碼以下:
基於以上原理,這樣在控制器級別上咱們就有兩種思路來實現過濾器。
方案一:直接在當前控制器重寫相應的過濾器方法,則該過濾器的方法做用於當前控制器的全部Action。
方案二:新建一個父類控制器,在父類控制器中重寫過濾器的方法,而後子類控制器繼承該父類控制器,則該該過濾器做用於子類控制器中的全部Action。
【該方法和接下來以特性的形式做用於控制器的效果是一致的】
1 /// <summary> 2 /// 控制器繼承該控制器,和特性做用在控制器上效果一致 3 /// </summary> 4 public class MyBaseFilterController : Controller 5 { 6 //須要用protected類型,不能用public類型 7 protected override void OnAuthorization(AuthorizationContext filterContext) 8 { 9 //1.若是保留以下代碼,則會運行.net framework定義好的身份驗證,若是但願自定義身份驗證,則刪除以下代碼 10 // base.OnAuthorization(filterContext); 11 12 //2.獲取區域名字 13 // string strAreaName = filterContext.RouteData.DataTokens["area"].ToString().ToLower(); 14 15 //3.獲取控制器做用的Controller和action的名字 16 string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower(); 17 string actionName = filterContext.ActionDescriptor.ActionName.ToLower(); 18 filterContext.HttpContext.Response.Write("身份驗證過濾器做用於" + controllerName + "控制器下的" + actionName + "方法</br>"); 19 } 20 }
2. 自定義類繼承MVC中過濾器實現類或過濾器接口,特性的形式做用於控制器或Action
特別補充:MVC框架中的AuthorizeAttirbute、ActionFilterAttribute、HandleErrorAttribute類已經實現了過濾器對應的接口,因此咱們在自定義過濾器的時候,能夠直接繼承以上三個類;或者實現相應的接口:IAuthorizationFilter、IActionFilter、IResultFilter、IExceptionFilter。(該方案在實現相應接口的同時,須要繼承FilterAttribute,使自定義的類成爲一個特性)。
下面以繼承MVC中實現類的形式來自定義四種過濾器:
A:身份驗證過濾器
1 /// <summary> 2 /// 身份驗證過濾器 3 /// 1. 在非MVC框架項目中使用MVC過濾器,須要經過nuget把MVC的程序集添加進去 4 /// 2. 繼承AuthorizeAttribute類,而後對OnAuthorization方法進行 override 覆寫 5 /// 3. 在Action運行以前首先運行該過濾器 6 /// </summary> 7 public class MyAuthorize : AuthorizeAttribute 8 { 9 public override void OnAuthorization(AuthorizationContext filterContext) 10 { 11 //1.若是保留以下代碼,則會運行.net framework定義好的身份驗證,若是但願自定義身份驗證,則刪除以下代碼 12 // base.OnAuthorization(filterContext); 13 14 //2.獲取區域名字 15 // string strAreaName = filterContext.RouteData.DataTokens["area"].ToString().ToLower(); 16 17 //3.獲取控制器做用的Controller和action的名字 18 string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower(); 19 string actionName = filterContext.ActionDescriptor.ActionName.ToLower(); 20 filterContext.HttpContext.Response.Write("身份驗證過濾器做用於" + controllerName + "控制器下的" + actionName + "方法</br>"); 21 } 22 }
B: 行爲過濾器
1 /// <summary> 2 /// 行爲過濾器 3 /// 1. 在非MVC框架項目中使用MVC過濾器,須要經過nuget把MVC的程序集添加進去 4 /// 2. 繼承ActionFilterAttribute類,而後對OnActionExecuting方法和OnActionExecuted方法進行 override 覆寫 5 /// 3. OnActionExecuting方法:在action方法運行以前,且OnAuthorization過濾器運行以後調用 6 /// OnActionExecuted方法:在action方法運行以後調用 7 /// </summary> 8 public class MyAction: ActionFilterAttribute 9 { 10 11 /// <summary> 12 /// 在action方法運行以前調用 13 /// </summary> 14 /// <param name="filterContext"></param> 15 public override void OnActionExecuting(ActionExecutingContext filterContext) 16 { 17 //1.若是保留以下代碼,則會運行.net framework定義好的行爲驗證,若是但願自定義行爲驗證,則刪除以下代碼 18 // base.OnActionExecuting(filterContext); 19 20 //2.獲取區域名字 21 // string strAreaName = filterContext.RouteData.DataTokens["area"].ToString().ToLower(); 22 23 //3.獲取控制器做用的Controller和action的名字 24 string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower(); 25 string actionName = filterContext.ActionDescriptor.ActionName.ToLower(); 26 filterContext.HttpContext.Response.Write("行爲過濾器OnActionExecuting做用於" + controllerName + "控制器下的" + actionName + "方法運行以前</br>"); 27 } 28 /// <summary> 29 /// 在action方法運行以後調用 30 /// </summary> 31 /// <param name="filterContext"></param> 32 public override void OnActionExecuted(ActionExecutedContext filterContext) 33 { 34 //1.若是保留以下代碼,則會運行.net framework定義好的行爲驗證,若是但願自定義行爲驗證,則刪除以下代碼 35 // base.OnActionExecuted(filterContext); 36 37 //2.獲取區域名字 38 // string strAreaName = filterContext.RouteData.DataTokens["area"].ToString().ToLower(); 39 40 //3.獲取控制器做用的Controller和action的名字 41 string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower(); 42 string actionName = filterContext.ActionDescriptor.ActionName.ToLower(); 43 filterContext.HttpContext.Response.Write("行爲過濾器OnActionExecuted做用於" + controllerName + "控制器下的" + actionName + "方法運行以後</br>"); 44 } 45 }
C:結果過濾器
1 /// <summary> 2 /// 結果過濾器 3 /// 1. 在非MVC框架項目中使用MVC過濾器,須要經過nuget把MVC的程序集添加進去 4 /// 2. 繼承ActionFilterAttribute類,而後對OnResultExecuting方法和OnResultExecuted方法進行 override 覆寫 5 /// 3. OnResultExecuting方法:在執行結果以後(action以後),頁面渲染以前調用 6 /// OnResultExecuted方法:在頁面渲染以後調用 7 /// </summary> 8 public class MyResult : ActionFilterAttribute 9 { 10 11 /// <summary> 12 /// action執行以後(OnActionExecuting以後),頁面渲染以前調用 13 /// </summary> 14 /// <param name="filterContext"></param> 15 public override void OnResultExecuting(ResultExecutingContext filterContext) 16 { 17 //1.若是保留以下代碼,則會運行.net framework定義好的結果驗證,若是但願自定義結果驗證,則刪除以下代碼 18 // base.OnResultExecuting(filterContext); 19 20 //該方法中沒法獲取是哪一個控制器後 21 filterContext.HttpContext.Response.Write("結果過濾器OnResultExecuting做用於action運行以後,頁面加載以前"); 22 } 23 /// <summary> 24 /// 頁面渲染以後調用 25 /// </summary> 26 /// <param name="filterContext"></param> 27 public override void OnResultExecuted(ResultExecutedContext filterContext) 28 { 29 //1.若是保留以下代碼,則會運行.net framework定義好的結果驗證,若是但願自定義結果驗證,則刪除以下代碼 30 // base.OnResultExecuted(filterContext); 31 32 //該方法中沒法獲取是哪一個控制器後 33 filterContext.HttpContext.Response.Write("結果過濾器OnResultExecuted做用於頁面渲染以後"); 34 } 35 }
D:異常過濾器
使用自定義異常處理,須要在web.config中爲system.web添加<customErrors mode="On" />節點
1 /// <summary> 2 /// 異常過濾器 3 /// 須要注意的點: 4 /// ①:若是自定義異常過濾器且須要有做用於全局,須要把FilterConfig中的 filters.Add(new HandleErrorAttribute());註釋掉, 5 /// 而後把自定義的異常過濾器添加到FilterConfig中。 6 /// ②:使用自定義異常處理,須要在web.config中爲system.web添加<customErrors mode="On" />節點 7 /// </summary> 8 public class MyException: HandleErrorAttribute 9 { 10 public override void OnException(ExceptionContext filterContext) 11 { 12 //調用框架自己異常處理器的方法 13 base.OnException(filterContext); 14 15 //獲取異常信息(能夠根據實際須要寫到本地或數據庫中) 16 var errorMsg = filterContext.Exception; 17 18 //跳轉指定的錯誤頁面 19 filterContext.Result = new RedirectResult("/error.html"); 20 } 21 }
下面展現以特性的形式做用於控制器或控制器中的Action:
3. 自定義類繼承MVC中實現類或接口,全局註冊,做用於所有控制器
若是以上兩種方式均不能知足你的過濾器的使用範圍,你能夠在App_Start文件夾下的FilterConfig類中進行全局註冊,使該過濾器做用於全部控制器中全部Action方法。
特別注意的一點是:自定義異常過濾器,須要把系統默認的filters.Add(new HandleErrorAttribute());註釋掉。
全局註冊的代碼以下:
1 public class FilterConfig 2 { 3 public static void RegisterGlobalFilters(GlobalFilterCollection filters) 4 { 5 //若是自定義異常過濾器,須要把默認的異常過濾器給註釋掉 6 //filters.Add(new HandleErrorAttribute()); 7 8 //自定義異常過濾器 9 filters.Add(new MyException()); 10 11 //全局註冊身份驗證、行爲、結果過濾器 12 //filters.Add(new MyAuthorize()); 13 //filters.Add(new MyAction()); 14 //filters.Add(new MyResult()); 15 16 //全局註冊登陸驗證(暫時註釋,使用的時候要打開) 17 //filters.Add(new CheckLogin()); 18 } 19 }
四. 結合實際案例進行代碼測試
1. 測試過濾器的執行順序
將上面的身份驗證過濾器、行爲過濾器、結果過濾器以特性的形式做用於Action上,經過斷點監控或者查看最後的輸出結果:
結果:
符合:OnAuthorization→OnActionExecuting-> Action方法執行 ->OnActionExecuted->OnResultExecuting/ -> Render View() (頁面渲染加載)->OnResultExecuted() 這一順序。
2. 全局捕獲異常,記錄錯誤日誌案例
步驟1:編寫異常過濾器,經過 var errorMsg = filterContext.Exception; 獲取異常信息,能夠寫入文本、存入數據庫、或者是Log4Net錯誤日誌框架進行處理。代碼在上面。
步驟2:在web.config中爲system.web添加<customErrors mode="On" />節點。
步驟3:添加到全局註冊文件中進行捕獲。
步驟4:在自定義的異常過濾器中添加斷點,而且本身製造一個錯誤。
捕獲到錯誤,進行頁面跳轉。
3. 登陸驗證案例
業務背景:在90%以上的Web系統中,不少頁面都是登陸成功之後才能看到的,固然也有不少頁面不須要登陸,對於須要登陸才能看到的頁面,即便你知道了訪問地址,也是不能訪問的,會退出到登陸頁面提示讓你登陸,對於不須要登陸的頁面經過URL地址能夠直接訪問。
分析:針對以上背景,過濾器對於大部分Action是須要過濾的,須要作登陸驗證,對於一小部分是不須要過濾的。
解決思路:
①:自定義一個身份驗證過濾器,進行全局註冊。
②:自定義一個Skip特性,將該特性加到不須要身份驗證的Action上。
③:重點說一下身份證過濾器中的邏輯:
a. 先判斷該Action上是否又Skip特性,若是有,中止不繼續執行;若是沒有,繼續下面的驗證邏輯。
b. 從Session中或Redis中讀取當前用戶,查看是否爲空,若是爲空,代表沒有登陸,返回到登陸頁面;若是不爲空,驗證經過,進行後面的業務邏輯。
代碼段以下:
1 /// <summary> 2 /// 校驗系統是否登陸的過濾器 3 /// 使用身份驗證過濾器進行編寫 4 /// </summary> 5 public class CheckLogin: AuthorizeAttribute 6 { 7 public override void OnAuthorization(AuthorizationContext filterContext) 8 { 9 //1. 校驗是否標記跨過登陸驗證 10 if (filterContext.ActionDescriptor.IsDefined(typeof(skipAttribute), true) 11 || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(skipAttribute), true)) 12 { 13 //表示該方法或控制器跨過登陸驗證 14 return; 15 } 16 //2. 校驗是否登陸 17 //可使Session或數據庫或nosql 18 //這裏只是測試,全部通通當作沒有登陸來處理 19 var sessionUser = HttpContext.Current.Session["CurrentUser"];//使用session 20 if (sessionUser == null) 21 { 22 HttpContext.Current.Session["CurrentUrl"] = filterContext.RequestContext.HttpContext.Request.RawUrl; 23 //若是沒有登陸,則跳轉到錯誤頁面 24 filterContext.Result = new RedirectResult("/error.html"); 25 } 26 } 27 }
第三節:Action向View傳值的四種方式(ViewData、ViewBag、TempData、Model)
簡 介
在前面的章節中,咱們已經很清楚,MVC工做模型的流程,Controller中的Action接收到客戶端的請求,處理後要將數據返回給View,那麼Action中是如何將數據返回給View的,兩者之間打通的橋樑又是什麼呢?
這些問題正是本章節須要解決的,下面先複習一下MVC的請求模型,以下圖:
(一). 先解決第一個問題,Action向View中傳值有四種方式:ViewData、ViewBag、TempData、Model,隨意選中一個點擊F12查看源碼,源碼以下:
分析:ViewData和TempData分別是ViewDataDictionary類型和TempDataDictionary類型,而這兩種類型均實現了IDictionary接口,因此ViewData和TemData均爲字典類型。
咱們再看一下ViewDataDictionary和TempDataDictionary兩個類型源碼,以ViewDataDictionary爲例,代碼以下:
分析可知賦值方式有兩種: ViewData[" "]=XXX 和 ViewData.Add("key", value) ,TempData賦值方式與此相似。
(二). 接下來咱們解決第二個問題,在前端頁面選擇ViewData點擊F12,查看WebViewPage類源碼,源碼以下:這時候,應該都很清楚了。
(三). 總結一下結論:
A. ViewData:字典類型,在前端頁面使用的時候,須要進行類型轉換。
B. ViewBag:動態類型,運行時自動進行類型轉換,不須要進行任何類型轉換。
C:Model: 實質就是ViewData.Model,前端頁面經過Model.XXX進行調用,頁面須要using引入程序集。
D:TempData:字典類型,前端頁面使用時候,須要進行類型轉換,但該類型更多的是做爲臨時變量應用於後臺Action直接的傳值,它內部是基於Session實現的,它能夠存儲一次,可是隻能讀取一次,再次使用,將爲空。
1. 測試四種方式向頁面傳值
1 public ActionResult PassValueIndex() 2 { 3 ViewData["num"] = 2; 4 ViewData.Add("num2", 2); 5 6 ViewBag.myNum = 2; 7 TempData["myNum2"] = 2; 8 9 Student stu = new Student() 10 { 11 id="123456", 12 name="ypf", 13 sex="男" 14 }; 15 return View(stu); 16 }
1 @*使用Model賦值,須要引入下面的命名空間*@ 2 @using Ypf.MVC5.Models; 3 @{ 4 Layout = null; 5 } 6 7 <!DOCTYPE html> 8 9 <html> 10 <head> 11 <meta name="viewport" content="width=device-width" /> 12 <title>PassValueIndex</title> 13 </head> 14 <body> 15 <div> 16 <p>Model賦值: 17 @Model.id 18 @Model.name 19 @Model.sex 20 </p> 21 <p>ViewData(須要進行類型轉換):@((int)ViewData["num"]+1) </p> 22 <p>ViewData(須要進行類型轉換):@((int)ViewData["num2"] + 1) </p> 23 24 <p>ViewBag(不須要進行類型轉換):@(ViewBag.myNum+1)</p> 25 <p>TempData(須要進行類型轉換):@((int)TempData["myNum2"]+1)</p> 26 <a href="TestTempData1">第二次調用TempData</a> 27 </div> 28 </body> 29 </html>
2. 測試TempData的時效性
從上面頁面前端代碼中點擊,a標籤,進入下面代碼:
1 public ActionResult TestTempData1() 2 { 3 //第二次次測試使用TempData,由於PassValueIndex頁面已經使用了一次, 4 //因此此處data1爲空,很好的印證了TempData只能調用一次的結論 5 var data1 = TempData["myNum2"]; 6 return Content(""); 7 }
分析發現,這裏的data1爲null,印證了TempData使用一次後清空的結論。
第二節:各類路由約束(動態路由、靜態路由、組合路由、正則約束、命名空間約束、區域內路由)
一. 什麼是路由
路由是約束URL的一組規範,那麼什麼是URL呢?通俗的來講URL是一個地址,經過該地址,用戶能夠訪問Web網站或者下載服務器上的文件。
好比下面就是兩組URL:
http://www.cnblogs.com/yaopengfei/p/7828441.html
http://www.cnblogs.com/yaopengfei/p/7828441
顯然咱們喜歡第二組,省略.html,會使該地址看起來更加簡潔,更加友好,利於SEO優化。
那麼咱們怎麼實現這個簡單的需求呢?
答案是:經過【路由】配置,因此如今咱們彷佛有點清晰了,路由能夠規定URL的特殊格式,使其達到特殊效果。
在ASP.NET MVC框架中,經過路由配置URL,使用戶的URL請求能夠映射到Controller下的action方法中,執行相應操做,並接受URL中傳過來的參數,在MVC5框架中,在【RouteConfig.cs】類中進行路由規則的配置,以下圖:
二. 從源碼的角度分析路由
咱們進入【RouteConfig】類中,發現核心代碼是調用【RouteCollection】類下的MapRoute方法,F12看源碼得知,MapRoute方法是RouteCollectionExtensions類的給【RouteCollection】類擴展方法的方法,而且有多個重載,以下圖:
下面分析一下參數的含義:
(1) name: 要映射的路由的名稱。
(2) url: 路由的 URL 模式,能夠自定義路由的格式,能夠寫靜態路由,也能夠寫動態路由、組合路由等。
(3) defaults: 一個包含默認路由值的對象,書寫路由的默認值。
(4) constraints: 一組表達式,可使用正則指定 url 參數值的約束。
(5) namespaces: 應用程序的一組命名空間,能夠縮小檢索路由對象匹配的範圍。
底層源碼,有興趣能夠看一下:
1 public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) 2 { 3 if (routes == null) 4 { 5 throw new ArgumentNullException("routes"); 6 } 7 if (url == null) 8 { 9 throw new ArgumentNullException("url"); 10 } 11 Route route = new Route(url, new MvcRouteHandler()) { 12 Defaults = CreateRouteValueDictionaryUncached(defaults), 13 Constraints = CreateRouteValueDictionaryUncached(constraints), 14 DataTokens = new RouteValueDictionary() 15 }; 16 ConstraintValidation.Validate(route); 17 if ((namespaces != null) && (namespaces.Length > 0)) 18 { 19 route.DataTokens["Namespaces"] = namespaces; 20 } 21 routes.Add(name, route); 22 return route;
三. MVC中的幾類路由及其規則
1. 動態路由
1 routes.MapRoute( 2 name: "Default", //路由名稱 3 url: "{controller}/{action}/{id}", //路由規則 4 defaults: new { controller = "First", action = "Index1", id = UrlParameter.Optional } //默認值,當Controller或action爲空(省略)的時候調用 5 );
分析:路由規則爲 {controller}/{action}/{id} ,其中 {controller}、{action}、{id}爲三個參數,/ 爲格式分割符號,defaults中聲明的是默認路由,因此下面的測試結果:
http://localhost:7559/
http://localhost:7559/First
http://localhost:7559/First/index1
http://localhost:7559/First/Index1/2
都會跳轉到Index1的頁面。
變種:將上面的代碼的URL分割符號稍微調整一下
1 routes.MapRoute( 2 name: "Default9", //路由名稱 3 url: "{controller}/{action}-{id}", //路由規則 4 defaults: new { controller = "First", action = "Index1", id = UrlParameter.Optional } //默認值,當Controller或action爲空(省略)的時候調用 5 );
請求地址就變成了:
http://localhost:7559/ 【沒法訪問】
http://localhost:7559/First 【404找不到】
http://localhost:7559/First/index1 【404找不到】
http://localhost:7559/First/Index1-1 【能夠訪問】
2. 靜態路由
1 routes.MapRoute( 2 name: "Default2", //路由名稱 3 url: "Ypf", //路由規則,不區分大小寫,當輸入「ypf」時,會自動跳轉到下面的地址 4 defaults: new { controller = "First", action = "Index1", id = UrlParameter.Optional } //默認值,當Controller或action爲空的時候調用 5 );
靜態路由:url中是一個靜態值,訪問的URL只有輸入這個靜態值,才能訪問下面default中的默認值。
測試地址以下:
http://localhost:7559/ 【沒法訪問】
http://localhost:7559/ypf 【 跳轉到index1頁面】
http://localhost:7559/First/index1 【沒法訪問】
補充一下:MapRoute方法是能夠不須要寫參數名的,就像正常的調用方法同樣,因此上面的代碼能夠改寫:
1 routes.MapRoute( 2 "Default3", //路由名稱 3 "Ypf", //路由規則,不區分大小寫,當輸入「Ypf」時,會自動跳轉到下面的地址 4 new { controller = "First", action = "Index1", id = UrlParameter.Optional } //默認值,當Controller或action爲空的時候調用 5 );
3. 組合路由
1 routes.MapRoute( 2 "Default4", //路由名稱 3 "Ypf/{action}", //路由規則,不區分大小寫,規則相符的時候,會自動跳轉到下面的地址 4 new { controller = "First", action = "Index1" } 5 );
所謂的組合路由,就是靜態路由和動態路由相互組合使用,測試地址以下:
http://localhost:7559/ 【沒法訪問】 (分析:由於不知足路由規則,沒有輸入ypf)
http://localhost:7559/ypf 【 跳轉到index1頁面】
http://localhost:7559/ypf/index1 【 跳轉到index1頁面】
http://localhost:7559/Ypf/hh 【404找不到】 (知足路由規則,可是沒有hh這個action,因此404)
http://localhost:7559/ypf/First/index1 【404找不到】 (知足路由規則,但這裏把First當作action,並無這個action,因此404)
4. 正則約束
1 routes.MapRoute( 2 "Default5", 3 "{controller}/{action}_{Year}_{Month}_{Day}", 4 new { controller = "First", action = "Index1", id = UrlParameter.Optional }, 5 new { Year = @"^\d{4}", Month = @"\d{2}", Day = @"\d{2}" } 6 );//正則路由
所謂的正則約束,是指能夠對URL中的參數使用正則表達式進行約束,上述代碼約束了Year必須是四位數字,Month和Day必須是兩位數字。
測試地址:
http://localhost:7559/first/index1_2018_09_01 【跳轉到index1頁面】
http://localhost:7559/first/index1 【沒法訪問】 (分析:由於不知足路由規則,沒有輸入{Year}_{Month}_{Day} 的參數)
http://localhost:7559/first/ 【沒法訪問】 (分析:由於不知足路由規則,沒有輸入{Year}_{Month}_{Day} 的參數)
http://localhost:7559/ 【沒法訪問】 (分析:由於不知足路由規則,沒有輸入{Year}_{Month}_{Day} 的參數)
5. 命名空間約束
1 routes.MapRoute( 2 name: "Default6", 3 url: "{controller}/{action}/{id}", 4 defaults: new { controller = "Third", action = "Index", id = UrlParameter.Optional }, 5 namespaces: new string[] { "Ypf.MVC5" } 6 );
所謂的命名空間約束,即限定匹配範圍路由的檢索範圍,提升檢索速度。
特別注意:不能從外層控制器直接跳轉到內層Area內的控制器!!
測試地址:如下三個訪問地址,都會跳轉到index1頁面
http://localhost:7559/
http://localhost:7559/First
http://localhost:7559/First/index1
6. Area區域內的路由
1 public override void RegisterArea(AreaRegistrationContext context) 2 { 3 //原路由 4 //context.MapRoute( 5 // "TestOne_default", 6 // "TestOne/{controller}/{action}/{id}", 7 // new { action = "Index", id = UrlParameter.Optional } 8 //); 9 10 //結合命名空間進行路由改造 11 context.MapRoute( 12 this.AreaName + "_default", 13 this.AreaName + "/{controller}/{action}/{id}", 14 new { area = this.AreaName, controller = "Sys_Admin", action = "Index", id = UrlParameter.Optional }, 15 new string[] { "Ypf.MVC5.Areas." + this.AreaName + ".Controllers" } 16 ); 17 18 }
指MVC5框架中Area區域單獨的一套路由規則,咱們能夠結合區域內的原路由,進行改造一番,如上述代碼。