日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

【源码解读】Vue与ASP.NET Core WebAPI的集成

發(fā)布時間:2023/12/4 asp.net 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【源码解读】Vue与ASP.NET Core WebAPI的集成 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在前面博文【Vue】Vue 與 ASP.NET Core WebAPI 的集成中,介紹了集成原理:在中間件管道中注冊SPA終端中間件,整個注冊過程中,終端中間件會調(diào)用node,執(zhí)行npm start命令啟動vue開發(fā)服務(wù)器,向中間件管道添加路由匹配,即非 api 請求(請求靜態(tài)文件,js css html)都代理轉(zhuǎn)發(fā)至SPA開發(fā)服務(wù)器。

注冊代碼如下:

public?void?Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder?app,?IWebHostEnvironment?env) {#region?+Endpoints//?Execute?the?matched?endpoint.app.UseEndpoints(endpoints?=>{endpoints.MapControllers();});app.UseSpa(spa?=>{spa.Options.SourcePath?=?"ClientApp";if?(env.IsDevelopment()){//spa.UseReactDevelopmentServer(npmScript:?"start");spa.UseVueCliServer(npmScript:?"start");//spa.UseProxyToSpaDevelopmentServer("http://localhost:8080");}});#endregion } “

可以看到先注冊了能夠匹配API請求的屬性路由。

如果上面的屬性路由無法匹配,請求就會在中間件管道中傳遞,至下一個中間件:SPA的終端中間件

以上便是集成原理。接下來我們對其中間件源碼進(jìn)行解讀。整體還是有蠻多值得解讀學(xué)習(xí)的知識點:

  • 異步編程

  • 內(nèi)聯(lián)中間件

  • 啟動進(jìn)程

  • 事件驅(qū)動

1.異步編程-ContinueWith

我們先忽略調(diào)用npm start命令執(zhí)行等細(xì)節(jié)。映入我們眼簾的便是異步編程。眾所周知,vue執(zhí)行npm start(npm run dev)的一個比較花費時間的過程。要達(dá)成我們完美集成的目的:我們注冊中間件,就需要等待vue前端開發(fā)服務(wù)器啟動后,正常使用,接收代理請求至這個開發(fā)服務(wù)器。這個等待后一個操作完成后再做其他操作,這就是一個異步編程。

  • 建立需要返回npm run dev結(jié)果的類:

class?VueCliServerInfo {public?int?Port?{?get;?set;?} }
  • 編寫異步代碼,啟動前端開發(fā)服務(wù)器

private?static?async?Task<VueCliServerInfo>?StartVueCliServerAsync(string?sourcePath,?string?npmScriptName,?ILogger?logger) {//省略代碼 }

1.1 ContinueWith

  • 編寫繼續(xù)體

ContinueWith本身就會返回一個Task

var?vueCliServerInfoTask?=?StartVueCliServerAsync(sourcePath,?npmScriptName,?logger);//繼續(xù)體 var?targetUriTask?=?vueCliServerInfoTask.ContinueWith(task?=>{return?new?UriBuilder("http",?"localhost",?task.Result.Port).Uri;});

1.2 內(nèi)聯(lián)中間件

  • 繼續(xù)使用這個繼續(xù)體返回的 task,并applicationBuilder.Use()配置一個內(nèi)聯(lián)中間件,即所有請求都代理至開發(fā)服務(wù)器

SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder,?()?=>{var?timeout?=?spaBuilder.Options.StartupTimeout;return?targetUriTask.WithTimeout(timeout,$"The?Vue?CLI?process?did?not?start?listening?for?requests?"?+$"within?the?timeout?period?of?{timeout.Seconds}?seconds.?"?+$"Check?the?log?output?for?error?information.");}); public?static?void?UseProxyToSpaDevelopmentServer(this?ISpaBuilder?spaBuilder,Func<Task<Uri>>?baseUriTaskFactory) {var?applicationBuilder?=?spaBuilder.ApplicationBuilder;var?applicationStoppingToken?=?GetStoppingToken(applicationBuilder);//省略部分代碼//?Proxy?all?requests?to?the?SPA?development?serverapplicationBuilder.Use(async?(context,?next)?=>{var?didProxyRequest?=await?SpaProxy.PerformProxyRequest(context,?neverTimeOutHttpClient,?baseUriTaskFactory(),?applicationStoppingToken,proxy404s:?true);}); }
  • 所有的后續(xù)請求,都會類似 nginx 一樣的操作:

public?static?async?Task<bool>?PerformProxyRequest(HttpContext?context,HttpClient?httpClient,Task<Uri>?baseUriTask,CancellationToken?applicationStoppingToken,bool?proxy404s) {//省略部分代碼...//獲取task的結(jié)果,即開發(fā)服務(wù)器urivar?baseUri?=?await?baseUriTask;//把請求代理至開發(fā)服務(wù)器//接收開發(fā)服務(wù)器的響應(yīng)?給到?context,由asp.net?core響應(yīng) }

2.啟動進(jìn)程-ProcessStartInfo

接下來進(jìn)入StartVueCliServerAsync的內(nèi)部,執(zhí)行node進(jìn)程,執(zhí)行npm start命令。

2.1 確定 vue 開發(fā)服務(wù)器的端口

確定一個隨機(jī)的、可用的開發(fā)服務(wù)器端口,代碼如下:

internal?static?class?TcpPortFinder {public?static?int?FindAvailablePort(){var?listener?=?new?TcpListener(IPAddress.Loopback,?0);listener.Start();try{return?((IPEndPoint)listener.LocalEndpoint).Port;}finally{listener.Stop();}} }

2.2 執(zhí)行 npm 命令

確定好可用的端口,根據(jù)前端項目目錄spa.Options.SourcePath = "ClientApp";

private?static?async?Task<VueCliServerInfo>?StartVueCliServerAsync(string?sourcePath,?string?npmScriptName,?ILogger?logger) {var?portNumber?=?TcpPortFinder.FindAvailablePort();logger.LogInformation($"Starting?Vue/dev-server?on?port?{portNumber}...");//執(zhí)行命令var?npmScriptRunner?=?new?NpmScriptRunner(//sourcePath,?npmScriptName,?$"--port?{portNumber}");sourcePath,?npmScriptName,?$"{portNumber}"); }

NpmScriptRunner內(nèi)部便在開始調(diào)用 node 執(zhí)行 cmd 命令:

internal?class?NpmScriptRunner {public?EventedStreamReader?StdOut?{?get;?}public?EventedStreamReader?StdErr?{?get;?}public?NpmScriptRunner(string?workingDirectory,?string?scriptName,?string?arguments){var?npmExe?=?"npm";var?completeArguments?=?$"run?{scriptName}?{arguments????string.Empty}";if?(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){npmExe?=?"cmd";completeArguments?=?$"/c?npm?{completeArguments}";}var?processStartInfo?=?new?ProcessStartInfo(npmExe){Arguments?=?completeArguments,UseShellExecute?=?false,RedirectStandardInput?=?true,RedirectStandardOutput?=?true,RedirectStandardError?=?true,WorkingDirectory?=?workingDirectory};var?process?=?LaunchNodeProcess(processStartInfo);//讀取文本輸出流StdOut?=?new?EventedStreamReader(process.StandardOutput);//讀取錯誤輸出流StdErr?=?new?EventedStreamReader(process.StandardError);} } private?static?Process?LaunchNodeProcess(ProcessStartInfo?startInfo) {try{var?process?=?Process.Start(startInfo);process.EnableRaisingEvents?=?true;return?process;}catch?(Exception?ex){var?message?=?$"Failed?to?start?'npm'.?To?resolve?this:.\n\n"+?"[1]?Ensure?that?'npm'?is?installed?and?can?be?found?in?one?of?the?PATH?directories.\n"+?$"????Current?PATH?enviroment?variable?is:?{?Environment.GetEnvironmentVariable("PATH")?}\n"+?"????Make?sure?the?executable?is?in?one?of?those?directories,?or?update?your?PATH.\n\n"+?"[2]?See?the?InnerException?for?further?details?of?the?cause.";throw?new?InvalidOperationException(message,?ex);} } internal?class?EventedStreamReader {public?delegate?void?OnReceivedChunkHandler(ArraySegment<char>?chunk);public?delegate?void?OnReceivedLineHandler(string?line);public?delegate?void?OnStreamClosedHandler();public?event?OnReceivedChunkHandler?OnReceivedChunk;public?event?OnReceivedLineHandler?OnReceivedLine;public?event?OnStreamClosedHandler?OnStreamClosed;private?readonly?StreamReader?_streamReader;private?readonly?StringBuilder?_linesBuffer;//構(gòu)造函數(shù)中啟動線程讀流public?EventedStreamReader(StreamReader?streamReader){_streamReader?=?streamReader????throw?new?ArgumentNullException(nameof(streamReader));_linesBuffer?=?new?StringBuilder();Task.Factory.StartNew(Run);}private?async?Task?Run(){var?buf?=?new?char[8?*?1024];while?(true){var?chunkLength?=?await?_streamReader.ReadAsync(buf,?0,?buf.Length);if?(chunkLength?==?0){//觸發(fā)事件的方法OnClosed();break;}//觸發(fā)事件的方法OnChunk(new?ArraySegment<char>(buf,?0,?chunkLength));var?lineBreakPos?=?Array.IndexOf(buf,?'\n',?0,?chunkLength);if?(lineBreakPos?<?0){_linesBuffer.Append(buf,?0,?chunkLength);}else{_linesBuffer.Append(buf,?0,?lineBreakPos?+?1);//觸發(fā)事件的方法OnCompleteLine(_linesBuffer.ToString());_linesBuffer.Clear();_linesBuffer.Append(buf,?lineBreakPos?+?1,?chunkLength?-?(lineBreakPos?+?1));}}}private?void?OnChunk(ArraySegment<char>?chunk){var?dlg?=?OnReceivedChunk;dlg?.Invoke(chunk);}private?void?OnCompleteLine(string?line){var?dlg?=?OnReceivedLine;dlg?.Invoke(line);}private?void?OnClosed(){var?dlg?=?OnStreamClosed;dlg?.Invoke();} }

2.3 讀取并輸出 npm 命令執(zhí)行的日志

npmScriptRunner.AttachToLogger(logger);

注冊O(shè)nReceivedLine與OnReceivedChunk事件,由讀文本流和錯誤流觸發(fā):

internal?class?EventedStreamReader {public?void?AttachToLogger(ILogger?logger){StdOut.OnReceivedLine?+=?line?=>{if?(!string.IsNullOrWhiteSpace(line)){logger.LogInformation(StripAnsiColors(line));}};StdErr.OnReceivedLine?+=?line?=>{if?(!string.IsNullOrWhiteSpace(line)){logger.LogError(StripAnsiColors(line));}};StdErr.OnReceivedChunk?+=?chunk?=>{var?containsNewline?=?Array.IndexOf(chunk.Array,?'\n',?chunk.Offset,?chunk.Count)?>=?0;if?(!containsNewline){Console.Write(chunk.Array,?chunk.Offset,?chunk.Count);}};} }

2.4 讀取輸出流至開發(fā)服務(wù)器啟動成功

正常情況下,Vue開發(fā)服務(wù)器啟動成功后,如下圖:

所以代碼中只需要讀取輸入流中的http://localhost:port,這里使用了正則匹配:

Match?openBrowserLine; openBrowserLine?=?await?npmScriptRunner.StdOut.WaitForMatch(new?Regex("-?Local:???(http:\\S+/)",?RegexOptions.None,?RegexMatchTimeout));

2.5 異步編程-TaskCompletionSource

**TaskCompletionSource也是一種創(chuàng)建Task的方式。**這里的異步方法WaitForMatch便使用了TaskCompletionSource,會持續(xù)讀取流,每一行文本輸出流,進(jìn)行正則匹配:

  • 匹配成功便調(diào)用SetResult()給Task完成信號

  • 匹配失敗便調(diào)用SetException()給Task異常信號

internal?class?EventedStreamReader {public?Task<Match>?WaitForMatch(Regex?regex){var?tcs?=?new?TaskCompletionSource<Match>();var?completionLock?=?new?object();OnReceivedLineHandler?onReceivedLineHandler?=?null;OnStreamClosedHandler?onStreamClosedHandler?=?null;//C#7.0?本地函數(shù)void?ResolveIfStillPending(Action?applyResolution){lock?(completionLock){if?(!tcs.Task.IsCompleted){OnReceivedLine?-=?onReceivedLineHandler;OnStreamClosed?-=?onStreamClosedHandler;applyResolution();}}}onReceivedLineHandler?=?line?=>{var?match?=?regex.Match(line);//匹配成功if?(match.Success){ResolveIfStillPending(()?=>?tcs.SetResult(match));}};onStreamClosedHandler?=?()?=>{//一直到文本流結(jié)束ResolveIfStillPending(()?=>?tcs.SetException(new?EndOfStreamException()));};OnReceivedLine?+=?onReceivedLineHandler;OnStreamClosed?+=?onStreamClosedHandler;return?tcs.Task;} }

2.6 確保開發(fā)服務(wù)器訪問正常

并從正則匹配結(jié)果獲取uri,即使在Vue CLI提示正在監(jiān)聽請求之后,如果過快地發(fā)出請求,在很短的一段時間內(nèi)它也會給出錯誤(可能就是代碼層級才會出現(xiàn))。所以還得繼續(xù)添加異步方法WaitForVueCliServerToAcceptRequests()確保開發(fā)服務(wù)器的的確確準(zhǔn)備好了。

private?static?async?Task<VueCliServerInfo>?StartVueCliServerAsync(string?sourcePath,?string?npmScriptName,?ILogger?logger) {var?portNumber?=?TcpPortFinder.FindAvailablePort();logger.LogInformation($"Starting?Vue/dev-server?on?port?{portNumber}...");//執(zhí)行命令var?npmScriptRunner?=?new?NpmScriptRunner(//sourcePath,?npmScriptName,?$"--port?{portNumber}");sourcePath,?npmScriptName,?$"{portNumber}");npmScriptRunner.AttachToLogger(logger);Match?openBrowserLine;//省略部分代碼openBrowserLine?=?await?npmScriptRunner.StdOut.WaitForMatch(new?Regex("-?Local:???(http:\\S+/)",?RegexOptions.None,?RegexMatchTimeout));var?uri?=?new?Uri(openBrowserLine.Groups[1].Value);var?serverInfo?=?new?VueCliServerInfo?{?Port?=?uri.Port?};await?WaitForVueCliServerToAcceptRequests(uri);return?serverInfo; } private?static?async?Task?WaitForVueCliServerToAcceptRequests(Uri?cliServerUri) {var?timeoutMilliseconds?=?1000;using?(var?client?=?new?HttpClient()){while?(true){try{await?client.SendAsync(new?HttpRequestMessage(HttpMethod.Head,?cliServerUri),new?CancellationTokenSource(timeoutMilliseconds).Token);return;}catch?(Exception){//它創(chuàng)建Task,但并不占用線程await?Task.Delay(500);if?(timeoutMilliseconds?<?10000){timeoutMilliseconds?+=?3000;}}}} } “

Task.Delay()的魔力:創(chuàng)建 Task,但并不占用線程,相當(dāng)于異步版本的Thread.Sleep,且可以在后面編寫繼續(xù)體:ContinueWith

3.總結(jié)

3.1 異步編程

  • 通過ContinueWiht繼續(xù)體返回Task的特性創(chuàng)建Task,并在后續(xù)配置內(nèi)聯(lián)中間件時使用這個Task

app.Use(async?(context,?next)=>{});

使ASP.NET Core的啟動與中間件注冊順滑。

  • 通過TaskCompletionSource可以在稍后開始和結(jié)束的任意操作中創(chuàng)建Task,這個Task,可以手動指示操作何時結(jié)束(SetResult),何時發(fā)生故障(SetException),這兩種狀態(tài)都意味著Task完成tcs.Task.IsCompleted,對經(jīng)常需要等 IO-Bound 類工作比較理想。

總結(jié)

以上是生活随笔為你收集整理的【源码解读】Vue与ASP.NET Core WebAPI的集成的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。