代碼已上傳Github+Gitee,文末有地址
今天忙著給小伙伴們提出的問(wèn)題解答冻河,時(shí)間上沒(méi)把握好箍邮,都快下班了,感覺(jué)發(fā)布:書(shū)說(shuō)上文《從壹開(kāi)始前后端分離【 .NET Core2.0 +Vue2.0 】框架之十一 || AOP自定義篩選叨叙,Redis入門(mén) 11.1》锭弊,昨天咱們說(shuō)到了分布式緩存鍵值數(shù)據(jù)庫(kù),主要講解了如何安裝擂错,使用味滞,最后遺留了一個(gè)問(wèn)題,同步+Redis緩存還是比較簡(jiǎn)單钮呀,如何使用異步泛型存取Redis剑鞍,還是一直我的心結(jié),希望大家有會(huì)的爽醋,可以不吝賜教蚁署,本系列教程已經(jīng)基本到了尾聲,今天就說(shuō)兩個(gè)小的知識(shí)點(diǎn)子房,既然本系列是講解前后端分離的形用,那一定會(huì)遇到跨域的問(wèn)題,沒(méi)錯(cuò)证杭,今天將說(shuō)下跨域田度!然后順便說(shuō)一下DTOs(數(shù)據(jù)傳輸對(duì)象),這些東西大家都用過(guò)解愤,比如镇饺,在MVC中定義一個(gè)ViewModel,是基于Model實(shí)體類(lèi)的送讲,然后做了相應(yīng)的變化奸笤,以適應(yīng)前端需求,沒(méi)錯(cuò)哼鬓,就是這個(gè)监右,如果大型的實(shí)體類(lèi),一個(gè)個(gè)復(fù)雜的話會(huì)稍顯費(fèi)力异希,今天就是用一個(gè)自動(dòng)映射工具——AutoMapper健盒。
零、今天完成左下角的深紫色部分
一称簿、為什么會(huì)出現(xiàn)跨域的問(wèn)題
跨域問(wèn)題由來(lái)已久扣癣,主要是來(lái)源于瀏覽器的”同源策略”。
? 何為同源憨降?只有當(dāng)協(xié)議父虑、端口、和域名都相同的頁(yè)面授药,則兩個(gè)頁(yè)面具有相同的源士嚎。只要網(wǎng)站的 協(xié)議名protocol、 主機(jī)host悔叽、 端口號(hào)port 這三個(gè)中的任意一個(gè)不同航邢,網(wǎng)站間的數(shù)據(jù)請(qǐng)求與傳輸便構(gòu)成了跨域調(diào)用,會(huì)受到同源策略的限制骄蝇。 ? 同源策略限制從一個(gè)源加載的文檔或腳本如何與來(lái)自另一個(gè)源的資源進(jìn)行交互膳殷。這是一個(gè)用于隔離潛在惡意文件的關(guān)鍵的安全機(jī)制。瀏覽器的同源策略九火,出于防范跨站腳本的攻擊赚窃,禁止客戶(hù)端腳本(如 JavaScript)對(duì)不同域的服務(wù)進(jìn)行跨站調(diào)用(通常指使用XMLHttpRequest請(qǐng)求)。
所以說(shuō)我們?cè)趙eb中岔激,我們無(wú)法去獲取跨域的請(qǐng)求勒极,常見(jiàn)的就是無(wú)法通過(guò)js獲取接口(這里要說(shuō)下我的以前使用的經(jīng)驗(yàn):在同源系統(tǒng)下,前端js去調(diào)用后端接口虑鼎,然后后端C#去調(diào)取跨域接口辱匿,這是我以前采用的辦法键痛,但是前后端分離,這個(gè)辦法肯定就是不行了匾七,因?yàn)槟菚r(shí)候已經(jīng)沒(méi)有了前后端之分絮短,是兩個(gè)項(xiàng)目),所以我們只要合理使用同源策略昨忆,就可以達(dá)到跨域訪問(wèn)的目的丁频。
二、如何達(dá)到跨域的目的——三種跨域方式 之JsonP
我自己建立了一個(gè)一個(gè)靜態(tài)頁(yè)面邑贴,用來(lái)模擬前端訪問(wèn)席里,具體如下步驟:
1、新建一個(gè)Html頁(yè)面拢驾,使用Jquery來(lái)發(fā)送請(qǐng)求(文件在項(xiàng)目的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>通過(guò)JsonP實(shí)現(xiàn)跨域請(qǐng)求</h3>
<button id="jsonp">發(fā)送一個(gè) GET </button>
<div id="status-jsonp"></div>
<div id="data-jsonp"></div>
<hr />
<h3>添加請(qǐng)求頭實(shí)現(xiàn)跨域</h3>
<hr />
<h3>通過(guò)CORS實(shí)現(xiàn)跨域請(qǐng)求署穗,另需要在服務(wù)器段配置CORE</h3>
<button id="cors">發(fā)送一個(gè) GET </button>
<div id="status-cors"></div>
<div id="data-cors"></div>
<hr />
</body>
</html>
注意:這里一定要注意jsonp的前端頁(yè)面請(qǐng)求寫(xiě)法,要求很?chē)?yán)謹(jǐn)
2嵌洼、將這個(gè)頁(yè)面部署到自己的IIS中(拷貝到文件里案疲,直接在iis添加該文件,訪問(wèn)剛剛的Html文件目錄就行)
3麻养、在我們的項(xiàng)目 LoginController 中褐啡,設(shè)計(jì)Jsonp接口,Core調(diào)用的接口我們已經(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);
//重要备畦,一定要這么寫(xiě) string response = string.Format("\"value\":\"{0}\"", jwtStr); string call = callBack + "({"+response+"})";
Response.WriteAsync(call);
}
注意:這里一定要注意jsonp的接口寫(xiě)法,要求很?chē)?yán)謹(jǐn)
4许昨、點(diǎn)擊”通過(guò)JsonP實(shí)現(xiàn)跨域請(qǐng)求“按鈕懂盐,發(fā)現(xiàn)已經(jīng)有數(shù)據(jù)了,證明Jsonp跨域已經(jīng)成功糕档,你可以換成自己的域名試一試莉恼,但是Cors的還不行
三、如何達(dá)到跨域的目的——三種跨域方式 之添加請(qǐng)求頭實(shí)現(xiàn)跨域
這里我沒(méi)有寫(xiě)到代碼里速那,是在一般處理程序里之前用到的
** 后端**
public void ProcessRequest(HttpContext context)
{ //接收參數(shù)
string uName = context.Request["name"]; string data = "{\"name\":\"" + uName + "\",\"age\":\"18\"}"; //只需在服務(wù)端添加以下兩句
context.Response.AddHeader("Access-Control-Allow-Origin", "*"); //跨域可以請(qǐng)求的方式
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")
}
大家感興趣可以自己實(shí)驗(yàn)下俐银。有問(wèn)題請(qǐng)留言
四、如何達(dá)到跨域的目的——三種跨域方式 之 高效CROS
1端仰、前端的代碼在jsonp的時(shí)候已經(jīng)寫(xiě)好捶惜,請(qǐng)往上看第二節(jié),后端接口也是Token接口
剩下的就是配置跨域了荔烧,很簡(jiǎn)單吱七!
2汽久、在ConfigureServices中添加
#region CORS services.AddCors(c => { //↓↓↓↓↓↓↓注意正式環(huán)境不要使用這種全開(kāi)放的處理↓↓↓↓↓↓↓↓↓↓
c.AddPolicy("AllRequests", policy => {
policy
.AllowAnyOrigin()//允許任何源
.AllowAnyMethod()//允許任何方式
.AllowAnyHeader()//允許任何頭
.AllowCredentials();//允許cookie
}); //↑↑↑↑↑↑↑注意正式環(huán)境不要使用這種全開(kāi)放的處理↑↑↑↑↑↑↑↑↑↑ //一般采用這種方法
c.AddPolicy("LimitRequests", policy => {
policy
.WithOrigins("http://localhost:8020", "http://blog.core.xxx.com","")//支持多個(gè)域名端口
.WithMethods("GET", "POST", "PUT", "DELETE")//請(qǐng)求方法添加到策略
.WithHeaders("authorization");//標(biāo)頭添加到策略
});
}); #endregion
基本注釋都有,大家都能看的懂踊餐,就這么簡(jiǎn)單景醇!
3、在需要跨域的controller上市袖,增加特性(本文因?yàn)樵贚oginController啡直,所以在這個(gè)控制器里)烁涌,注意名稱(chēng)要寫(xiě)對(duì) LimitRequests
[Produces("application/json")]
[Route("api/Login")]
[EnableCors("LimitRequests")]//就是這里 public class LoginController : Controller
{ //....
}
** 4苍碟、好啦運(yùn)行調(diào)試,一切正常**
至此撮执,跨域的問(wèn)題已經(jīng)完成辣
五微峰、結(jié)語(yǔ)
三種辦法其實(shí)都能達(dá)到目的,但是優(yōu)缺點(diǎn)也很明顯
1抒钱、手動(dòng)創(chuàng)建JSONP跨域
優(yōu)點(diǎn):無(wú)瀏覽器要求蜓肆,可以在任何瀏覽器中使用此方式
缺點(diǎn):格式要求很?chē)?yán)格,只支持get請(qǐng)求方式谋币,請(qǐng)求的后端出錯(cuò)不會(huì)有提示,造成不能處理異常
2仗扬、添加請(qǐng)求頭實(shí)現(xiàn)跨域
優(yōu)點(diǎn):支持任意請(qǐng)求方式,并且后端出錯(cuò)會(huì)像非跨域那樣有報(bào)錯(cuò),可以對(duì)異常進(jìn)行處理
缺點(diǎn):兼容性不是很好蕾额,IE的話 <IE10 都不支持此方式
雖然CORS的方法有點(diǎn)兒類(lèi)似請(qǐng)求頭早芭,但是封裝,兼容性诅蝶,靈活性都要好的很多退个,強(qiáng)烈推薦。
六调炬、初探DTOs
請(qǐng)看以下實(shí)體類(lèi)
//數(shù)據(jù)庫(kù)實(shí)體類(lèi)
public class Author
{ public string Name { get; set; }
} public class Book
{ public string Title { get; set; } public Author Author { get; set; }
} //頁(yè)面實(shí)體類(lèi)
public class BookViewModel
{ public string Title { get; set; } public string Author { get; set; }
} //api調(diào)用
BookViewModel model = new BookViewModel
{
Title = book.Title,
Author = book.Author.Name
}
上面的例子相當(dāng)?shù)闹庇^了语盈,我們平時(shí)也是這么用的基本,但是問(wèn)題也隨之而來(lái)了缰泡,我們可以看到在上面的代碼中刀荒,如果一旦在Book對(duì)象里添加了一個(gè)額外的字段,而后想在前臺(tái)頁(yè)面輸出這個(gè)字段棘钞,那么就需要去在項(xiàng)目里找到每一處有這樣BookViewModel轉(zhuǎn)換字段的地方照棋,這是非常繁瑣的。另外武翎,BookViewModel.Author是一個(gè)string類(lèi)型的字段,但是Book.Author屬性卻是Author對(duì)象類(lèi)型的烈炭,我們用的解決方法是通過(guò)Book.Auther對(duì)象來(lái)取得Author的Name屬性值,然后再賦值給BookViewModel的Author屬性宝恶,這樣看起行的通符隙,但是想一想趴捅,如果打算在以后的開(kāi)發(fā)中把Name拆分成兩個(gè)-FisrtName和LastName,我的天吶!我們得去把原來(lái)的ViewModel對(duì)象也拆分成對(duì)應(yīng)的兩個(gè)字段霹疫,然后在項(xiàng)目中找到所有的轉(zhuǎn)換拱绑,然后替換。
那么有什么辦法或者工具來(lái)幫助我們能夠避免這樣的情況發(fā)生呢丽蝎?AutoMapper正是符合要求的一款插件猎拨。只需一鍵操作,就能一勞永逸屠阻,解決所有問(wèn)題红省,然后通過(guò)依賴(lài)注入,快速使用:
//AutoMapper自動(dòng)映射 //Mapper.Initialize(cfg => cfg.CreateMap<BlogArticle, BlogViewModels>()); //BlogViewModels models = Mapper.Map<BlogArticle, BlogViewModels>(blogArticle);
BlogViewModels models = IMapper.Map<BlogViewModels>(blogArticle);//就這一句話完全搞定所有轉(zhuǎn)換
今天因?yàn)闀r(shí)間的關(guān)系国觉,沒(méi)有說(shuō)到Automapper吧恃,明天再見(jiàn)吧~
七、CODE
https://github.com/anjoy8/Blog.Core
https://gitee.com/laozhangIsPhi/Blog.Core
QQ群:
867095512 (blod.core)