從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】框架之十二 || 三種跨域方式比較,DTOs(資料傳輸物件)初探
更新反饋
1、博友@童鞋說到了,Nginx反向代理實現跨域,因為我目前還沒有使用到,給忽略了,這次記錄下,為下次補充。
程式碼已上傳Github+Gitee,文末有地址
今天忙著給小夥伴們提出的問題解答,時間上沒把握好,都快下班了,趕緊釋出:書說上文《從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】框架之十一 || AOP自定義篩選,Redis入門 11.1》,昨天咱們說到了分散式快取鍵值資料庫,主要講解了如何安裝,使用,最後遺留了一個問題,同步+Redis快取還是比較簡單,如何使用非同步泛型存取Redis,還是一直我的心結,希望大家有會的,可以不吝賜教,本系列教程已經基本到了尾聲,今天就說兩個小的知識點,既然本系列是講解前後端分離的,那一定會遇到跨域的問題,沒錯,今天將說下跨域!然後順便說一下DTOs(資料傳輸物件),這些東西大家都用過,比如,在MVC中定義一個ViewModel,是基於Model實體類的,然後做了相應的變化,以適應前端需求,沒錯,就是這個,如果大型的實體類,一個個複雜的話會稍顯費力,今天就是用一個自動對映工具——AutoMapper。
零、今天完成左下角的深紫色部分
一、為什麼會出現跨域的問題
跨域問題由來已久,主要是來源於瀏覽器的”同源策略”。 何為同源?只有當協議、埠、和域名都相同的頁面,則兩個頁面具有相同的源。只要網站的 協議名protocol、 主機host、 埠號port 這三個中的任意一個不同,網站間的資料請求與傳輸便構成了跨域呼叫,會受到同源策略的限制。 同源策略限制從一個源載入的文件或指令碼如何與來自另一個源的資源進行互動。這是一個用於隔離潛在惡意檔案的關鍵的安全機制。瀏覽器的同源策略,出於防範跨站指令碼的攻擊,禁止客戶端指令碼(如 JavaScript)對不同域的服務進行跨站呼叫(通常指使用XMLHttpRequest請求)。
所以說我們在web中,我們無法去獲取跨域的請求,常見的就是無法通過js獲取介面(這裡要說下我的以前使用的經驗:在同源系統下,前端js去呼叫後端介面,然後後端C#去調取跨域介面,這是我以前採用的辦法,但是前後端分離,這個辦法肯定就是不行了,因為那時候已經沒有了前後端之分,是兩個專案),所以我們只要合理使用同源策略,就可以達到跨域訪問的目的。
二、如何達到跨域的目的——三種跨域方式 之JsonP
我自己建立了一個一個靜態頁面,用來模擬前端訪問,具體如下步驟:
1、新建一個Html頁面,使用Jquery來發送請求(檔案在專案的WWW資料夾下,大家可以自己下載,或者Copy下邊程式碼)。
一共三種跨域方法
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Blog.Core</title> <script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script> <style> div { margin: 10px; word-wrap: break-word; } </style> <script> $(document).ready(function () { $("#jsonp").click(function () { $.getJSON("http://localhost:58427/api/Login/jsonp?callBack=?", function (data) { $("#data-jsonp").html("資料: " + data.value); }); }); $("#cors").click(function () { $.get("http://localhost:58427/api/Login/Token", function (data, status) { $("#status-cors").html("狀態: " + status); $("#data-cors").html("資料: " + data); }); }); }); </script> </head> <body> <h3>通過JsonP實現跨域請求</h3> <button id="jsonp">傳送一個 GET </button> <div id="status-jsonp"></div> <div id="data-jsonp"></div> <hr /> <h3>新增請求頭實現跨域</h3> <hr /> <h3>通過CORS實現跨域請求,另需要在伺服器段配置CORE</h3> <button id="cors">傳送一個 GET </button> <div id="status-cors"></div> <div id="data-cors"></div> <hr /> </body> </html>
注意:這裡一定要注意jsonp的前端頁面請求寫法,要求很嚴謹
2、將這個頁面部署到自己的IIS中(拷貝到檔案裡,直接在iis新增該檔案,訪問剛剛的Html檔案目錄就行)
3、在我們的專案 LoginController 中,設計Jsonp介面,Core呼叫的介面我們已經有了,就是之前獲取Token的介面GetJWTStr
[HttpGet] [Route("jsonp")] public void Getjsonp(string callBack, long id = 1, string sub = "Admin", int expiresSliding = 30, int expiresAbsoulute = 30) { TokenModel tokenModel = new TokenModel(); tokenModel.Uid = id; tokenModel.Sub = sub; DateTime d1 = DateTime.Now; DateTime d2 = d1.AddMinutes(expiresSliding); DateTime d3 = d1.AddDays(expiresAbsoulute); TimeSpan sliding = d2 - d1; TimeSpan absoulute = d3 - d1; string jwtStr = BlogCoreToken.IssueJWT(tokenModel, sliding, absoulute); //重要,一定要這麼寫 string response = string.Format("\"value\":\"{0}\"", jwtStr); string call = callBack + "({"+response+"})"; Response.WriteAsync(call); }
注意:這裡一定要注意jsonp的介面寫法,要求很嚴謹
4、點選”通過JsonP實現跨域請求“按鈕,發現已經有資料了,證明Jsonp跨域已經成功,你可以換成自己的域名試一試,但是Cors的還不行
三、如何達到跨域的目的——三種跨域方式 之新增請求頭實現跨域
這裡我沒有寫到程式碼裡,是在一般處理程式裡之前用到的
後端
public void ProcessRequest(HttpContext context) { //接收引數 string uName = context.Request["name"]; string data = "{\"name\":\"" + uName + "\",\"age\":\"18\"}"; //只需在服務端新增以下兩句 context.Response.AddHeader("Access-Control-Allow-Origin", "*"); //跨域可以請求的方式 context.Response.AddHeader("Access-Control-Allow-Methods", "POST,GET"); context.Response.Write(data); }
前端
function ashxRequest() { $.post("http://localhost:5551/ashxRequest.ashx", { name: "halo" }, function (data) { for (var i in data) { alert(data[i]); } }, "json") }
大家感興趣可以自己實驗下。有問題請留言
四、如何達到跨域的目的——三種跨域方式 之 高效CORS
1、前端的程式碼在jsonp的時候已經寫好,請往上看第二節,後端介面也是Token介面
剩下的就是配置跨域了,很簡單!
2、在ConfigureServices中新增
#region CORS services.AddCors(c => { //↓↓↓↓↓↓↓注意正式環境不要使用這種全開放的處理↓↓↓↓↓↓↓↓↓↓ c.AddPolicy("AllRequests", policy => { policy .AllowAnyOrigin()//允許任何源 .AllowAnyMethod()//允許任何方式 .AllowAnyHeader()//允許任何頭 .AllowCredentials();//允許cookie }); //↑↑↑↑↑↑↑注意正式環境不要使用這種全開放的處理↑↑↑↑↑↑↑↑↑↑ //一般採用這種方法 c.AddPolicy("LimitRequests", policy => { policy .WithOrigins("http://localhost:8020", "http://blog.core.xxx.com","")//支援多個域名埠 .WithMethods("GET", "POST", "PUT", "DELETE")//請求方法新增到策略 .WithHeaders("authorization");//標頭新增到策略 }); }); #endregion
基本註釋都有,大家都能看的懂,就這麼簡單!
注意:在定義策略 LimitRequests 的時候,源域名應該是客戶端請求的埠域名,不是當前API的域名埠。
3、在啟動檔案 的Configure 配置方法裡,新增啟用Cors中介軟體服務
4、在需要跨域的controller上,增加特性(本文因為在LoginController,所以在這個控制器裡),注意名稱要寫對 LimitRequests
[Produces("application/json")] [Route("api/Login")] [EnableCors("LimitRequests")]//就是這裡 public class LoginController : Controller { //.... }
4、好啦執行除錯,一切正常
至此,跨域的問題已經完成辣
五、其他跨域方法補充
nginx是一個高效能的web伺服器,常用作反向代理伺服器。nginx作為反向代理伺服器,就是把http請求轉發到另一個或者一些伺服器上。
通過把本地一個url字首對映到要跨域訪問的web伺服器上,就可以實現跨域訪問。
對於瀏覽器來說,訪問的就是同源伺服器上的一個url。而nginx通過檢測url字首,把http請求轉發到後面真實的物理伺服器。並通過rewrite命令把字首再去掉。這樣真實的伺服器就可以正確處理請求,並且並不知道這個請求是來自代理伺服器的。
簡單說,nginx伺服器欺騙了瀏覽器,讓它認為這是同源呼叫,從而解決了瀏覽器的跨域問題。又通過重寫url,欺騙了真實的伺服器,讓它以為這個http請求是直接來自與使用者瀏覽器的。
這樣,為了解決跨域問題,只需要動一下nginx配置檔案即可。
六、結語
三種辦法其實都能達到目的,但是優缺點也很明顯
1、手動建立JSONP跨域
優點:無瀏覽器要求,可以在任何瀏覽器中使用此方式
缺點:格式要求很嚴格,只支援get請求方式,請求的後端出錯不會有提示,造成不能處理異常
2、新增請求頭實現跨域
優點:支援任意請求方式,並且後端出錯會像非跨域那樣有報錯,可以對異常進行處理
缺點:相容性不是很好,IE的話 <IE10 都不支援此方式
雖然CORS的方法有點兒類似請求頭,但是封裝,相容性,靈活性都要好的很多,強烈推薦。
七、初探DTOs
請看以下實體類
//資料庫實體類 public class Author { public string Name { get; set; } } public class Book { public string Title { get; set; } public Author Author { get; set; } } //頁面實體類 public class BookViewModel { public string Title { get; set; } public string Author { get; set; } } //api呼叫 BookViewModel model = new BookViewModel { Title = book.Title, Author = book.Author.Name }
上面的例子相當的直觀了,我們平時也是這麼用的基本,但是問題也隨之而來了,我們可以看到在上面的程式碼中,如果一旦在Book物件裡添加了一個額外的欄位,而後想在前臺頁面輸出這個欄位,那麼就需要去在專案裡找到每一處有這樣BookViewModel轉換欄位的地方,這是非常繁瑣的。另外,BookViewModel.Author是一個string型別的欄位,但是Book.Author屬性卻是Author物件型別的,我們用的解決方法是通過Book.Auther物件來取得Author的Name屬性值,然後再賦值給BookViewModel的Author屬性,這樣看起行的通,但是想一想,如果打算在以後的開發中把Name拆分成兩個-FisrtName和LastName,我的天吶!我們得去把原來的ViewModel物件也拆分成對應的兩個欄位,然後在專案中找到所有的轉換,然後替換。 那麼有什麼辦法或者工具來幫助我們能夠避免這樣的情況發生呢?AutoMapper正是符合要求的一款外掛。
只需一鍵操作,就能一勞永逸,解決所有問題,然後通過依賴注入,快速使用:
//AutoMapper自動對映 //Mapper.Initialize(cfg => cfg.CreateMap<BlogArticle, BlogViewModels>()); //BlogViewModels models = Mapper.Map<BlogArticle, BlogViewModels>(blogArticle); BlogViewModels models = IMapper.Map<BlogViewModels>(blogArticle);//就這一句話完全搞定所有轉換
今天因為時間的關係,沒有說到Automapper,明天再見吧~
八、CODE
QQ群:
867095512