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

在前面博文【Vue】Vue 与 ASP.NET Core WebAPI 的集成中,介绍了集成原理:在中间件管道中注册SPA终端中间件,整个注册过程中,终端中间件会调用node,执行npm start命令启动vue开发服务器,向中间件管道添加路由匹配,即非 api 请求(请求静态文件,js css html)都代理转发至SPA开发服务器。

注册代码如下:

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的终端中间件

以上便是集成原理。接下来我们对其中间件源码进行解读。整体还是有蛮多值得解读学习的知识点:

  • 异步编程

  • 内联中间件

  • 启动进程

  • 事件驱动

1.异步编程-ContinueWith

我们先忽略调用npm start命令执行等细节。映入我们眼帘的便是异步编程。众所周知,vue执行npm start(npm run dev)的一个比较花费时间的过程。要达成我们完美集成的目的:我们注册中间件,就需要等待vue前端开发服务器启动后,正常使用,接收代理请求至这个开发服务器。这个等待后一个操作完成后再做其他操作,这就是一个异步编程。

  • 建立需要返回npm run dev结果的类:

class VueCliServerInfo
{public int Port { get; set; }
}
  • 编写异步代码,启动前端开发服务器

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

1.1 ContinueWith

  • 编写继续体

ContinueWith本身就会返回一个Task

var vueCliServerInfoTask = StartVueCliServerAsync(sourcePath, npmScriptName, logger);//继续体
var targetUriTask = vueCliServerInfoTask.ContinueWith(task =>{return new UriBuilder("http", "localhost", task.Result.Port).Uri;});

1.2 内联中间件

  • 继续使用这个继续体返回的 task,并applicationBuilder.Use()配置一个内联中间件,即所有请求都代理至开发服务器

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);});
}
  • 所有的后续请求,都会类似 nginx 一样的操作:

public static async Task<bool> PerformProxyRequest(HttpContext context,HttpClient httpClient,Task<Uri> baseUriTask,CancellationToken applicationStoppingToken,bool proxy404s)
{//省略部分代码...//获取task的结果,即开发服务器urivar baseUri = await baseUriTask;//把请求代理至开发服务器//接收开发服务器的响应 给到 context,由asp.net core响应
}

2.启动进程-ProcessStartInfo

接下来进入StartVueCliServerAsync的内部,执行node进程,执行npm start命令。

2.1 确定 vue 开发服务器的端口

确定一个随机的、可用的开发服务器端口,代码如下:

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 执行 npm 命令

确定好可用的端口,根据前端项目目录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}...");//执行命令var npmScriptRunner = new NpmScriptRunner(//sourcePath, npmScriptName, $"--port {portNumber}");sourcePath, npmScriptName, $"{portNumber}");
}

NpmScriptRunner内部便在开始调用 node 执行 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;//构造函数中启动线程读流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){//触发事件的方法OnClosed();break;}//触发事件的方法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);//触发事件的方法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 命令执行的日志

npmScriptRunner.AttachToLogger(logger);

注册OnReceivedLineOnReceivedChunk事件,由读文本流和错误流触发:

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 读取输出流至开发服务器启动成功

正常情况下,Vue开发服务器启动成功后,如下图:

所以代码中只需要读取输入流中的http://localhost:port,这里使用了正则匹配:

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

2.5 异步编程-TaskCompletionSource

**TaskCompletionSource也是一种创建Task的方式。**这里的异步方法WaitForMatch便使用了TaskCompletionSource,会持续读取流,每一行文本输出流,进行正则匹配:

  • 匹配成功便调用SetResult()Task完成信号

  • 匹配失败便调用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 本地函数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 = () =>{//一直到文本流结束ResolveIfStillPending(() => tcs.SetException(new EndOfStreamException()));};OnReceivedLine += onReceivedLineHandler;OnStreamClosed += onStreamClosedHandler;return tcs.Task;}
}

2.6 确保开发服务器访问正常

并从正则匹配结果获取uri,即使在Vue CLI提示正在监听请求之后,如果过快地发出请求,在很短的一段时间内它也会给出错误(可能就是代码层级才会出现)。所以还得继续添加异步方法WaitForVueCliServerToAcceptRequests()确保开发服务器的的确确准备好了。

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}...");//执行命令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){//它创建Task,但并不占用线程await Task.Delay(500);if (timeoutMilliseconds < 10000){timeoutMilliseconds += 3000;}}}}
}

Task.Delay()的魔力:创建 Task,但并不占用线程,相当于异步版本的Thread.Sleep,且可以在后面编写继续体:ContinueWith

3.总结

3.1 异步编程

  • 通过ContinueWiht继续体返回Task的特性创建Task,并在后续配置内联中间件时使用这个Task

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

使ASP.NET Core的启动与中间件注册顺滑。

  • 通过TaskCompletionSource可以在稍后开始和结束的任意操作中创建Task,这个Task,可以手动指示操作何时结束(SetResult),何时发生故障(SetException),这两种状态都意味着Task完成tcs.Task.IsCompleted,对经常需要等 IO-Bound 类工作比较理想。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306060.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android开发环境 比较好,android 开发环境比较(Android开发环境比较).doc

android 开发环境比较(Android开发环境比较)android 开发环境比较(Android开发环境比较)Android build up various development environmentsThis article describes some of the things you need to know about the Android development environment configuration. The Andro…

ios采用什么技术_app软件公司开发宠物别APP采用什么技术?

app软件公司开发宠物别APP采用什么技术&#xff1f;随着经济的发展&#xff0c;人们生活水平的提高&#xff0c;养宠的家庭越来越多&#xff0c;宠物也逐渐成为主人家庭成员的重要组成部分&#xff0c;宠物识别APP在市场上也是很热门的手机软件&#xff0c;那么它是根据什么原理…

Dapr微服务应用开发系列3:服务调用构件块

题记&#xff1a;这篇开始逐一深入介绍各个构件块&#xff0c;从服务调用开始原理所谓服务调用&#xff0c;就是通过这个构件块让你方便的通过HTTP或者gRPC协议同步调用其他服务的方法&#xff0c;这些方法也是通过HTTP或者gRPC来暴露的。而方便的含义在于&#xff0c;你无需担…

Android开发p图软件,媲美大神P图效果 Android软件抠图神手

媲美大神P图效果 Android软件抠图神手2013年02月20日 01:50作者&#xff1a;杨霏霏编辑&#xff1a;杨霏霏文章出处&#xff1a;泡泡网原创分享泡泡网手机频道2月20日 PS的功能大家耳熟能详&#xff0c;其中抠图便是各位PS用户普遍会用到的一个功能。然而手机上抠图大家想过吗&…

腾讯公测云开发低码!实战评测

听说腾讯的新产品『 云开发低码 』即将开放公测了&#xff0c;怀着无比激动的心情&#xff0c;鱼皮立刻去官网申请并成功拿到了公测资格&#xff0c;然后使用它开发了一个小程序&#xff0c;并且通过 2020 Techo Park 开发者大会加深了对这项技术的了解。而就在 2020 年的最后一…

gitee 从 拉取新分支到本地_Hexo博客详细教程(一)| 建立本地站点

点上方蓝字关注我们每天都有好玩的东西等着你博客炫酷效果展示安装Hexo安装Git参考文章&#xff1a;Git实用教程(二) | Git简介及安装详解。安装NodejsNodejs可以从官网( https://nodejs.org/en )下载LTS版本&#xff1a;安装之后检查一下是否正常输出版本信息&#xff1a;安装…

索尼android 怎么截屏快捷键,索尼XZ Premium怎么截屏 2种索尼XZ Premium截图方法

截屏作为手机常用功能之一&#xff0c;我们经常在分享朋友圈或微博的时候经常需要用到屏幕截屏。今天本文主要分享一下索尼XZ Premium怎么截屏&#xff0c;作为一款相对冷门的非国产骁龙835旗舰机&#xff0c;在使用中难免出现一些不太熟悉的问题&#xff0c;下面小编分享2种索…

IdentityServer4 之Client Credentials走起来

前言API裸奔是绝对不允许滴&#xff0c;之前专门针对这块分享了jwt的解决方案(WebApi接口裸奔有风险)&#xff1b;那如果是微服务&#xff0c;又怎么解决呢&#xff1f;每一个服务都加认证授权也可以解决问题&#xff0c;只是显得认证授权这块冗余&#xff0c;重复在搞事情&…

读数据库遇到空就进行不下去_如何保证缓存与数据库的双写一致性?

作者&#xff1a;你是我的海啸来源&#xff1a;https://blog.csdn.net/chang384915878分布式缓存是现在很多分布式应用中必不可少的组件&#xff0c;但是用到了分布式缓存&#xff0c;就可能会涉及到缓存与数据库双存储双写&#xff0c;你只要是双写&#xff0c;就一定会有数据…

获取html滚动条位置,pc和移动端获取滚动条的位置

html5碰撞小球模拟这里根据动量守恒和能量守恒定理来计算小球的位置,从而模拟完全弹性碰撞下的小球运行轨迹. html代码: /p>spring jdbctemplate源码跟踪闲着没事,看看源码也是一种乐趣! java操作数据库的基本步骤都是类似的: 1. 建立数据库连接 2. 创建Connection 3. 创建s…

Windows Terminal 新手入门

翻译自 Kayla Cinnamon 2020年12月17日的文章《Getting Started with Windows Terminal》[1]安装Windows Terminal&#xff08;Windows 终端&#xff09;有两个不同的版本&#xff1a;Windows Terminal[2] 和 Windows Terminal 预览版[3]。两个版本都可以从 Microsoft Store 和…

html绑定按键图片移动,如何使用JS实现用键盘控制图片移动呢?

html xmlnshttp://www。w3。org/1999/xhtml headtitle标题页-学无忧(www。xue51。com)/title/headscript languageJavaScriptvar key0var timerfunction setObj(){ ietype (document。layers) ? 1 : 0; //判断浏览器类型 divObj (ietype)? document。mydiv : mydiv。style …

5120v2怎么配置web登陆_阿里企业邮箱如何配置和添加到第三个电子邮件客户端中?...

我们常见的第三方邮箱客户端有&#xff1a;Outlook、Thunderbird、Live mail、Web客户端、畅邮&#xff08;DM Pro&#xff09;客户端等&#xff0c;下面用畅邮&#xff08;DM Pro&#xff09;为例。流程一.配置信息企业邮箱POP、SMTP、IMAP地址列表如下&#xff1a;&#xff0…

赛尔号什么时候支持html5,赛尔号三全能时代即将来临 你准备好了吗

大新闻&#xff01;赛尔号刻印系统即将迎来改版升级&#xff01;更简单的刻印强化方式&#xff0c;更清晰的刻印等级机制&#xff0c;强化满级还有额外属性加成&#xff01;想知道具体情况如何&#xff1f;下面柚子姐姐就为大家介绍本次刻印系统改版升级后的全新变化吧&#xf…

银河麒麟V10入选2020中国十大科技新闻

日前&#xff0c;中央电视台、中央人民广播电台、中国国际广播电台、中国国际电视台联合评选了“2020十大国内科技新闻”&#xff0c;“银河麒麟操作系统V10”与其他国家科技领域重大成榜上有名。8月13日&#xff0c;银河麒麟操作系统V10发布后&#xff0c;央视新闻、人民日报、…

vue 调用mutation方法_Vuex白话教程第三讲:Vuex旗下的Mutation

文 | 大宏写在前面上一讲「Vuex 旗下的 State 和 Getter」&#xff0c;告诉了我们怎么去使用仓库 store 中的状态数据。当然&#xff0c;光会用肯定还不够&#xff0c;大部分的应用场景还得对这些状态进行操控&#xff0c;那么具体如何操控呢&#xff0c;这就是这一讲要说的重点…

微型计算机硬件采用什么,微型计算机的硬件系统包括什么?

微型计算机的硬件系统包括cpu、存储器、输入设备、输出设备四大部分。CPU是计算机硬件的核心&#xff0c;控制着整个计算机系统的工作&#xff1b;存储器是计算机中的记忆存储部件&#xff1b;输入设备是计算机与用户或其他设备通信的桥梁&#xff1b;输出设备是计算机硬件系统…

如何在 C# 中使用 反射

C# 中的 反射 常用于在程序的运行时获取 类型 的元数据&#xff0c;可获取的信息包括已加载到进程中的 程序集 和 类型 信息&#xff0c;它和 C 中的 RTTI&#xff08;Runtime Type Information&#xff09; 的作用是差不多的。为了能够使用反射&#xff0c;需要在项目中引用 S…

echarts迁徙图 vue_如何快速在Vue中实现流向图或迁徙图?

原标题&#xff1a;如何快速在Vue中实现流向图或迁徙图&#xff1f;我们经常在一些新闻报道和商业杂志上看到运用地图来展示商业现象的做法。这样利用地图来反映和分析数据的形式&#xff0c;叫数据地图&#xff0c;它可以直观的表达出数据之间的空间关系。在数据地图中&#x…

霍纳法树形流图中处理机p个数_2009系统结构试卷答案

一&#xff0e;单项选择题(共10分&#xff0c;每选1分)1.与流水线最大吞吐率高低有关的是(C)A.各个子过程的时间B.最快子过程的时间C.最慢子过程的时间D.最后子过程的时间2.在流水机器中&#xff0c;全局相关是指(D)A.先写后读相关B.先读后写相关C.指令相关D.由转移指令引起的相…