代碼已上傳Github+Gitee,文末有地址
今天忙著給小伙伴們提出的問題解答,時間上沒把握好,都快下班了,感覺發(fā)布:書說上文《從壹開始前后端分離【 .NET Core2.0 +Vue2.0 】框架之十一 || AOP自定義篩選,Redis入門 11.1》,昨天咱們說到了分布式緩存鍵值數(shù)據(jù)庫,主要講解了如何安裝,使用,最后遺留了一個問題,同步+Redis緩存還是比較簡單,如何使用異步泛型存取Redis,還是一直我的心結,希望大家有會的,可以不吝賜教,本系列教程已經(jīng)基本到了尾聲,今天就說兩個小的知識點,既然本系列是講解前后端分離的,那一定會遇到跨域的問題,沒錯,今天將說下跨域!然后順便說一下DTOs(數(shù)據(jù)傳輸對象),這些東西大家都用過,比如,在MVC中定義一個ViewModel,是基于Model實體類的,然后做了相應的變化,以適應前端需求,沒錯,就是這個,如果大型的實體類,一個個復雜的話會稍顯費力,今天就是用一個自動映射工具——AutoMapper。
零、今天完成左下角的深紫色部分

一、為什么會出現(xiàn)跨域的問題
跨域問題由來已久,主要是來源于瀏覽器的”同源策略”。
? 何為同源?只有當協(xié)議、端口、和域名都相同的頁面,則兩個頁面具有相同的源。只要網(wǎng)站的 協(xié)議名protocol、 主機host、 端口號port 這三個中的任意一個不同,網(wǎng)站間的數(shù)據(jù)請求與傳輸便構成了跨域調用,會受到同源策略的限制。 ? 同源策略限制從一個源加載的文檔或腳本如何與來自另一個源的資源進行交互。這是一個用于隔離潛在惡意文件的關鍵的安全機制。瀏覽器的同源策略,出于防范跨站腳本的攻擊,禁止客戶端腳本(如 JavaScript)對不同域的服務進行跨站調用(通常指使用XMLHttpRequest請求)。
所以說我們在web中,我們無法去獲取跨域的請求,常見的就是無法通過js獲取接口(這里要說下我的以前使用的經(jīng)驗:在同源系統(tǒng)下,前端js去調用后端接口,然后后端C#去調取跨域接口,這是我以前采用的辦法,但是前后端分離,這個辦法肯定就是不行了,因為那時候已經(jīng)沒有了前后端之分,是兩個項目),所以我們只要合理使用同源策略,就可以達到跨域訪問的目的。
二、如何達到跨域的目的——三種跨域方式 之JsonP
我自己建立了一個一個靜態(tài)頁面,用來模擬前端訪問,具體如下步驟:
1、新建一個Html頁面,使用Jquery來發(fā)送請求(文件在項目的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("數(shù)據(jù): " + data.value);
});
});
$("#cors").click(function () {
$.get("http://localhost:58427/api/Login/Token", function (data, status) {
$("#status-cors").html("狀態(tài): " + status);
$("#data-cors").html("數(shù)據(jù): " + data);
});
});
}); </script>
</head>
<body>
<h3>通過JsonP實現(xiàn)跨域請求</h3>
<button id="jsonp">發(fā)送一個 GET </button>
<div id="status-jsonp"></div>
<div id="data-jsonp"></div>
<hr />
<h3>添加請求頭實現(xiàn)跨域</h3>
<hr />
<h3>通過CORS實現(xiàn)跨域請求,另需要在服務器段配置CORE</h3>
<button id="cors">發(fā)送一個 GET </button>
<div id="status-cors"></div>
<div id="data-cors"></div>
<hr />
</body>
</html>
注意:這里一定要注意jsonp的前端頁面請求寫法,要求很嚴謹
2、將這個頁面部署到自己的IIS中(拷貝到文件里,直接在iis添加該文件,訪問剛剛的Html文件目錄就行)

3、在我們的項目 LoginController 中,設計Jsonp接口,Core調用的接口我們已經(jīng)有了,就是之前獲取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實現(xiàn)跨域請求“按鈕,發(fā)現(xiàn)已經(jīng)有數(shù)據(jù)了,證明Jsonp跨域已經(jīng)成功,你可以換成自己的域名試一試,但是Cors的還不行

三、如何達到跨域的目的——三種跨域方式 之添加請求頭實現(xiàn)跨域
這里我沒有寫到代碼里,是在一般處理程序里之前用到的
** 后端**
public void ProcessRequest(HttpContext context)
{ //接收參數(shù)
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")
}
大家感興趣可以自己實驗下。有問題請留言
四、如何達到跨域的目的——三種跨域方式 之 高效CROS
1、前端的代碼在jsonp的時候已經(jīng)寫好,請往上看第二節(jié),后端接口也是Token接口
剩下的就是配置跨域了,很簡單!
2、在ConfigureServices中添加
#region CORS services.AddCors(c => { //↓↓↓↓↓↓↓注意正式環(huán)境不要使用這種全開放的處理↓↓↓↓↓↓↓↓↓↓
c.AddPolicy("AllRequests", policy => {
policy
.AllowAnyOrigin()//允許任何源
.AllowAnyMethod()//允許任何方式
.AllowAnyHeader()//允許任何頭
.AllowCredentials();//允許cookie
}); //↑↑↑↑↑↑↑注意正式環(huán)境不要使用這種全開放的處理↑↑↑↑↑↑↑↑↑↑ //一般采用這種方法
c.AddPolicy("LimitRequests", policy => {
policy
.WithOrigins("http://localhost:8020", "http://blog.core.xxx.com","")//支持多個域名端口
.WithMethods("GET", "POST", "PUT", "DELETE")//請求方法添加到策略
.WithHeaders("authorization");//標頭添加到策略
});
}); #endregion
基本注釋都有,大家都能看的懂,就這么簡單!
3、在需要跨域的controller上,增加特性(本文因為在LoginController,所以在這個控制器里),注意名稱要寫對 LimitRequests
[Produces("application/json")]
[Route("api/Login")]
[EnableCors("LimitRequests")]//就是這里 public class LoginController : Controller
{ //....
}

** 4、好啦運行調試,一切正常**

至此,跨域的問題已經(jīng)完成辣
五、結語
三種辦法其實都能達到目的,但是優(yōu)缺點也很明顯
1、手動創(chuàng)建JSONP跨域
優(yōu)點:無瀏覽器要求,可以在任何瀏覽器中使用此方式
缺點:格式要求很嚴格,只支持get請求方式,請求的后端出錯不會有提示,造成不能處理異常
2、添加請求頭實現(xiàn)跨域
優(yōu)點:支持任意請求方式,并且后端出錯會像非跨域那樣有報錯,可以對異常進行處理
缺點:兼容性不是很好,IE的話 <IE10 都不支持此方式
雖然CORS的方法有點兒類似請求頭,但是封裝,兼容性,靈活性都要好的很多,強烈推薦。
六、初探DTOs
請看以下實體類
//數(shù)據(jù)庫實體類
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
}
上面的例子相當?shù)闹庇^了,我們平時也是這么用的基本,但是問題也隨之而來了,我們可以看到在上面的代碼中,如果一旦在Book對象里添加了一個額外的字段,而后想在前臺頁面輸出這個字段,那么就需要去在項目里找到每一處有這樣BookViewModel轉換字段的地方,這是非常繁瑣的。另外,BookViewModel.Author是一個string類型的字段,但是Book.Author屬性卻是Author對象類型的,我們用的解決方法是通過Book.Auther對象來取得Author的Name屬性值,然后再賦值給BookViewModel的Author屬性,這樣看起行的通,但是想一想,如果打算在以后的開發(fā)中把Name拆分成兩個-FisrtName和LastName,我的天吶!我們得去把原來的ViewModel對象也拆分成對應的兩個字段,然后在項目中找到所有的轉換,然后替換。
那么有什么辦法或者工具來幫助我們能夠避免這樣的情況發(fā)生呢?AutoMapper正是符合要求的一款插件。只需一鍵操作,就能一勞永逸,解決所有問題,然后通過依賴注入,快速使用:
//AutoMapper自動映射 //Mapper.Initialize(cfg => cfg.CreateMap<BlogArticle, BlogViewModels>()); //BlogViewModels models = Mapper.Map<BlogArticle, BlogViewModels>(blogArticle);
BlogViewModels models = IMapper.Map<BlogViewModels>(blogArticle);//就這一句話完全搞定所有轉換
今天因為時間的關系,沒有說到Automapper,明天再見吧~
七、CODE
https://github.com/anjoy8/Blog.Core
https://gitee.com/laozhangIsPhi/Blog.Core
QQ群:
867095512 (blod.core)