ng-alain 與 .net core 搭配

寫(xiě)在前面

鑒于很多使用 ng-alain 都以 .net 為后端,以下我將以一個(gè)示例來(lái)描述 ng-alain 如何同 .net core 一起開(kāi)發(fā)蛔翅。示例以單個(gè)中后臺(tái)項(xiàng)目為基準(zhǔn)瓶殃,對(duì)于多項(xiàng)目的應(yīng)用大體相同充包,但整體目錄結(jié)構(gòu)當(dāng)然不能以單個(gè)項(xiàng)目了,更多應(yīng)該以多人開(kāi)發(fā)為準(zhǔn)遥椿。

所有源碼基矮,從 Github 中獲取。

一冠场、構(gòu)建項(xiàng)目

1家浇、構(gòu)建

分為 ng-alain 和 .net core 兩個(gè)部分,當(dāng)然我推薦二者分開(kāi)創(chuàng)建在一個(gè)根目錄下碴裙,例如以一個(gè) asdf 為項(xiàng)目名钢悲,創(chuàng)建一個(gè) asdf 目錄,然后分別使用以下方式構(gòu)建一個(gè)完整的前后端:

md asdf
cd asdf

ng-alain

ng new asdf --style less
cd asdf
ng add ng-alain

此時(shí)舔株,我們將 asdf/asdf 目錄重命名另一個(gè)名稱(chēng)以便區(qū)分前后端:fe莺琳。

.net core

dotnet new webapi -o asdf
# 這里采用命令行,依然可以使用 vs 創(chuàng)建項(xiàng)目

同樣载慈,我們將 asdf/asdf 目錄重命名另一個(gè)名稱(chēng)以便區(qū)分前后端:be惭等。

示例的命名方式你可以使用你喜歡的風(fēng)格。

最終我們的 asdf 項(xiàng)目的目錄結(jié)構(gòu)如下:

asdf
  - be (后端 .net core)
  - fe (前端 Angular)

2办铡、運(yùn)行

前后端分開(kāi)自然辞做,分別開(kāi)啟兩個(gè)命令行琳要,而 vscode 著實(shí)很方便,分別在 befe 目錄下運(yùn)行:

be: dotnet watch run
fe: npm run hmr

默認(rèn)情況下分開(kāi)可以通過(guò) https://localhost:5001/api/valueshttp://localhost:4200/ 訪問(wèn)前后端秤茅。

IIS

其實(shí)若是使用 vs 創(chuàng)建項(xiàng)目稚补,可以更友好的將后端綁定至某個(gè)域名下,配合修改 host 可以使開(kāi)發(fā)環(huán)境更接近生產(chǎn)環(huán)境嫂伞。當(dāng)然啦孔厉,這一部分百度或博客園可以得到信息支持,這里不再贅述帖努。

二撰豺、編寫(xiě)后端

我在淺談Angular網(wǎng)絡(luò)請(qǐng)求 描述過(guò)網(wǎng)絡(luò)請(qǐng)求與用戶認(rèn)證相關(guān)的,以此為擴(kuò)展拼余,我們來(lái)嘗試怎么實(shí)現(xiàn)這些細(xì)節(jié)污桦。

.net core 也有中間件的概念,如同 Angular 攔截器匙监。依然以一個(gè)網(wǎng)絡(luò)請(qǐng)求流程式來(lái)描述這一過(guò)程凡橱,其大概如下:

  • 使用攔截器,從 Header 獲取用戶 Token亭姥,并檢查 Token 有效性
  • 使用過(guò)濾器稼钩,檢查請(qǐng)求體參數(shù)有效性
  • 執(zhí)行方法,并響應(yīng)結(jié)果
  • 使用攔截器處理統(tǒng)一異常處理

當(dāng)然达罗,這只是一個(gè)大概性坝撑,細(xì)節(jié)上可能需要處理得更多。

1粮揉、校驗(yàn) Token

Startup.cs 增加一個(gè)簡(jiǎn)單通過(guò) header 來(lái)獲取 token 屬性值巡李,并進(jìn)行校驗(yàn)有效性,最后再結(jié)果寫(xiě)入至 context.Items 里扶认,這樣整個(gè)請(qǐng)求只需要一次用戶信息侨拦,后續(xù)直接使用 context.Items 來(lái)獲取用戶信息。

app.Use(async (context, next) => {
  if (!context.Request.Path.ToString().StartsWith("/api/passport")) {
    var _token = "";
    if (context.Request.Headers.TryGetValue("token", out var tokens) && tokens.Count > 0) {
      _token = tokens[0];
    }
    if (_token != "asdf") {
      context.Response.StatusCode = 401;
      return ;
    }
    
    var user = new User();
    user.Id = 1;
    user.Name = "cipchk";
    context.Items.Add("token", _token);
    context.Items.Add("user", user);
  }
  await next();
});

整段代碼實(shí)現(xiàn)幾個(gè)細(xì)節(jié):

  • 忽略所有 /api/passport 開(kāi)頭用戶 Token 校驗(yàn)
  • 從 headers 獲取 token 值辐宾,并校驗(yàn)值
    • 若錯(cuò)誤則直接返回 401 不再執(zhí)行后續(xù)動(dòng)作
  • 獲取 User 信息并寫(xiě)入 context.Items 中狱从,以后后續(xù)請(qǐng)求直接讀取

此時(shí),若再一次訪問(wèn) https://localhost:5001/api/values 后端接收到的是一個(gè) 401 狀態(tài)碼叠纹。

2矫夯、統(tǒng)一異常處理

依然可以使用中間件的寫(xiě)法,但為了區(qū)分不同吊洼,這里采用過(guò)濾器的寫(xiě)法。

繼承 ExceptionFilterAttribute 并重寫(xiě) OnException 就可以簡(jiǎn)單的完成制肮,ExceptionFilterAttribute 異常過(guò)濾器會(huì)在執(zhí)行過(guò)程中若遇到 throw 時(shí)會(huì)被觸發(fā)冒窍。

public class ExceptionAttribute : ExceptionFilterAttribute {
  public override void OnException(ExceptionContext context) {
    var res = context.HttpContext.Response;

    res.StatusCode = 200;
    res.ContentType = "application/json; charset=utf-8";
    context.Result = new JsonResult(new {
      msg = context.Exception.Message,
      code = 503
    });
  }
}

最后递沪,需要注冊(cè)到整個(gè)應(yīng)用里。

services.AddMvc(options => {
  options.Filters.Add(new ExceptionAttribute());
})

3综液、PassportController

新建一個(gè) PassportController.cs 文件款慨,內(nèi)容如下:

[Route("api/[controller]")]
[ApiController]
public class PassportController : ControllerBase
{
  [HttpGet("{id}")]
  public JsonResult Get(int id)
  {
    if (id != 1) throw new Exception("無(wú)效用戶");
    return new JsonResult(new { msg = "ok", data = "asdf" });
  }
}

前面我們已經(jīng)忽略對(duì)所有 /api/passport 開(kāi)頭的 URL 的用戶 Token 校驗(yàn),它是一個(gè)授權(quán)頁(yè)理當(dāng)如此谬莹。這里體現(xiàn)了兩個(gè)細(xì)節(jié):

  • 若無(wú)效用戶拋出一個(gè)錯(cuò)誤
    • ExceptionAttribute 會(huì)捕獲到這個(gè)錯(cuò)誤檩奠,并重新指定變更響應(yīng)體內(nèi)容
    • 當(dāng)然你依然可以使用 return new JsonResult(new { msg = "無(wú)效用戶" }); 這種方式
  • 若有效用戶返回用戶 Token 值

這里都是手工創(chuàng)建統(tǒng)一響應(yīng)體,你依然可以利用過(guò)濾器或中間件來(lái)統(tǒng)一處理響應(yīng)體為統(tǒng)一風(fēng)格附帽,而對(duì)于方法內(nèi)永遠(yuǎn)都只返回一個(gè) data 對(duì)應(yīng)值埠戳。上述單純只是一個(gè)示例,需要自行更進(jìn)一步封裝蕉扮。

4整胃、跨域問(wèn)題

本文描述是前后端分開(kāi)開(kāi)發(fā),因此開(kāi)發(fā)過(guò)程中勢(shì)必存在跨域請(qǐng)求的問(wèn)題喳钟,一個(gè)簡(jiǎn)單的辦法在 Startup.cs 里增加跨域代碼:

#if DEBUG
app.UseCors(builder =>
{
  builder.AllowAnyMethod()
          .AllowAnyHeader()
          .AllowAnyOrigin();
});
#endif

條件編譯可以很好的解決生產(chǎn)和開(kāi)發(fā)環(huán)境的不同屁使,因?yàn)椴渴饡r(shí)我們將不存在跨域問(wèn)題,后續(xù)會(huì)描述奔则。

5蛮寂、小結(jié)

到此,我們已經(jīng)實(shí)現(xiàn)大部分 淺談Angular網(wǎng)絡(luò)請(qǐng)求 描述的功能易茬,哪怕都是一個(gè)簡(jiǎn)化酬蹋,但我們可以訪問(wèn):

三、編寫(xiě)前端

ng-alain 默認(rèn)是以盡可能接收生產(chǎn)環(huán)境項(xiàng)目的腳手架疾呻,誠(chéng)如我在 淺談Angular網(wǎng)絡(luò)請(qǐng)求 描述的那樣除嘹,不應(yīng)該編寫(xiě)一個(gè)簡(jiǎn)單的 Hello World 請(qǐng)求來(lái)校驗(yàn)是否可用。

在開(kāi)始之前岸蜗,需要先了解 Angular 環(huán)境變更配置尉咕,它位于 fe/src/environments 目錄中,每一個(gè)文件表示一種環(huán)境璃岳,他們有者相同參數(shù)年缎,但其值可能各不同。

若是你跟著本文來(lái)做的話铃慷,那么相對(duì)應(yīng)的是 environment.hmr.ts单芜,我們修改這里的 SERVER_URL 值為 https://localhost:5001/api/。這是表示所有請(qǐng)求都會(huì)在請(qǐng)求URL前自動(dòng)加上該地址犁柜。

1洲鸠、APP_INITIALIZER

Angular 啟用前我們能做的事只有這里,腳手架默認(rèn)實(shí)現(xiàn)了 StartupService(位于:fe/src/app/core/startup/ 下),當(dāng)然默認(rèn)代碼并不可用扒腕,我們將其修改為:

load(): Promise<any> {
  // only works with promises
  // https://github.com/angular/angular/issues/15088
  return new Promise((resolve, reject) => {
    this.httpClient.get('values').subscribe(
      (res: any) => {
        this.injector.get(NzMessageService).success(JSON.stringify(res));
      },
      () => {},
      () => {
        resolve(null);
      },
    );
  });
}

這里請(qǐng)求是 api/values绢淀,但由于我們給 Angular 環(huán)境變量統(tǒng)一配置 URL 前綴,因此只需要一個(gè)簡(jiǎn)單的 values 為請(qǐng)求 URL瘾腰。(注:若請(qǐng)求URL地址不是期望結(jié)果皆的,需要重新運(yùn)行 npm run hmr

此時(shí),你訪問(wèn)前端時(shí)會(huì)自動(dòng)跳轉(zhuǎn)至 /passport/login 登錄頁(yè)蹋盆,這一切都是由于 @delon/auth 用戶認(rèn)證模塊在管理的费薄,我們沒(méi)有寫(xiě)任何一行關(guān)于前端校驗(yàn)的代碼。

2栖雾、登錄頁(yè)

登錄示例頁(yè)的大部分代碼是可用的楞抡,但本文并不關(guān)心這一些,我們修改其中發(fā)送請(qǐng)求部分如下:

// mock http
this.loading = true;
this.http.get('passport/1').subscribe((res: any) => {
  this.loading = false;
  // 清空路由復(fù)用信息
  this.reuseTabService.clear();
  // 設(shè)置Token信息
  this.tokenService.set({
    token: res.data,
  });
  // 重新獲取 StartupService 內(nèi)容岩灭,若其包括 User 有關(guān)的信息的話
  this.startupSrv.load().then(() => this.router.navigate(['/']));
  // 否則直接跳轉(zhuǎn)
  // this.router.navigate(['/']);
});

請(qǐng)求 passport/1 返回用戶 Token拌倍,并把 Token 值寫(xiě)入 TokenService 中,最后跳轉(zhuǎn)至儀表盤(pán)頁(yè)噪径。

登錄成功后柱恤,你還會(huì)接收到一個(gè)條 [ "value1", "value2" ] 的消息,這是來(lái)自 APP_INITIALIZER 產(chǎn)生的找爱,至少表明我們能正常訪問(wèn)到 values梗顺,因?yàn)榇藭r(shí)你會(huì)發(fā)現(xiàn)該主體的 Header 已經(jīng)包含了 token: asdf 字樣。

3车摄、小結(jié)

關(guān)于前端部分我簡(jiǎn)略的描述寺谤,這里還有更多細(xì)節(jié),例如:響應(yīng)體直接返回 data 的內(nèi)容吮播,更多細(xì)節(jié)自行挖掘变屁。

四、部署

示例中的 .net core 部分是單純的 Web API 項(xiàng)目意狠,由此不存在任何頁(yè)面之說(shuō)粟关。而如何讓 .net core 項(xiàng)目直接以 Angular 項(xiàng)目來(lái)訪問(wèn)呢?這需要解決兩個(gè)問(wèn)題环戈。

  • 前端打包至 wwwroot 目錄里
  • .net core 默認(rèn)以 index.html 默認(rèn)訪問(wèn)頁(yè)

1闷板、打包前端

這里一個(gè)簡(jiǎn)單的辦法是修改 angular.json 的輸出路徑(當(dāng)然也可以直接命令行里直接指定):

"outputPath": "../be/wwwroot",

最后,執(zhí)行:

npm run bash

打包后的文件會(huì)直接存放至 be/wwwroot 目錄下院塞。

2遮晚、打包后端

默認(rèn)情況下需要開(kāi)啟靜態(tài)資源訪問(wèn)能力并且指定默認(rèn)頁(yè)為 index.html,在 Startup.cs 增加:

var options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("index.html");
app.UseDefaultFiles(options);

app.UseStaticFiles();

此時(shí)拦止,若你再訪問(wèn) https://localhost:5001/ 會(huì)發(fā)現(xiàn)我們啟動(dòng)的是一個(gè) ng-alain 項(xiàng)目县遣。

而對(duì)于后端的打包,也非常簡(jiǎn)單:

dotnet publish -o ../dist

最終,直接將根目錄下的 dist 部署至 Web 服務(wù)器上艺玲。

五括蝠、總結(jié)

本文算是對(duì) 淺談Angular網(wǎng)絡(luò)請(qǐng)求 進(jìn)一步實(shí)踐,雖然一切都采用簡(jiǎn)化的代碼來(lái)解釋?zhuān)傮w的流程是等同的饭聚。

基于 .net core 為后端是出于群里反應(yīng)很多項(xiàng)目都是使用 .net,可能是 .net framework 版本搁拙,但本質(zhì)上是相同秒梳。示例我是以 vscode 編寫(xiě),可能后端的格式會(huì)有些同 vs 不同箕速。

(完)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末酪碘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子盐茎,更是在濱河造成了極大的恐慌兴垦,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件字柠,死亡現(xiàn)場(chǎng)離奇詭異探越,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)窑业,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)钦幔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人常柄,你說(shuō)我怎么就攤上這事鲤氢。” “怎么了西潘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵卷玉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我喷市,道長(zhǎng)相种,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任东抹,我火速辦了婚禮蚂子,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缭黔。我一直安慰自己食茎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布馏谨。 她就那樣靜靜地躺著别渔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哎媚,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天喇伯,我揣著相機(jī)與錄音,去河邊找鬼拨与。 笑死稻据,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的买喧。 我是一名探鬼主播捻悯,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼淤毛!你這毒婦竟也來(lái)了今缚?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤低淡,失蹤者是張志新(化名)和其女友劉穎姓言,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蔗蹋,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡何荚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纸颜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兽泣。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖胁孙,靈堂內(nèi)的尸體忽然破棺而出唠倦,到底是詐尸還是另有隱情,我是刑警寧澤涮较,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布稠鼻,位于F島的核電站,受9級(jí)特大地震影響狂票,放射性物質(zhì)發(fā)生泄漏候齿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一闺属、第九天 我趴在偏房一處隱蔽的房頂上張望慌盯。 院中可真熱鬧,春花似錦掂器、人聲如沸亚皂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)灭必。三九已至狞谱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間禁漓,已是汗流浹背跟衅。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留播歼,地道東北人伶跷。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像秘狞,于是被迫代替她去往敵國(guó)和親撩穿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容