ASP.NET Core 6框架揭秘实例演示[30]:利用路由开发REST API

借助路由系统提供的请求URL模式与对应终结点之间的映射关系,我们可以将具有相同URL模式的请求分发给与之匹配的终结点进行处理。ASP.NET的路由是通过EndpointRoutingMiddleware和EndpointMiddleware这两个中间件协作完成的,它们在ASP.NET平台上具有举足轻重的地位,MVC和gRPC框架,Dapr的Actor和发布订阅编程模式都建立在路由系统之上。Minimal API更是将提升到了前所未有的高度,是我们直接在路由系统基础上定义REST API。[本文节选《ASP.NET Core 6框架揭秘》第20章]

[S2001]注册路由终结点 (源代码)
[S2002]以内联方式设置路由参数的约束(源代码)
[S2003]定义可缺省的路由参数(源代码)
[S2004]为路由参数指定默认值(源代码)
[S2005]一个路径分段定义多个路由参数(源代码)
[S2006]一个路由参数跨越多个路径分段(源代码)
[S2007]主机名绑定(源代码)
[S2008]将终结点处理定义为任意类型的委托(源代码)
[S2009]IResult 的应用(源代码)

[S2001]注册路由终结点

我们演示的这个ASP.NET应用是一个简易版的天气预报站点。服务端利用注册的一个终结点来提供某个城市在未来N天之内的天气信息,对应城市(采用电话区号表示)和天数直接至于请求URL的路径中。如图1所示,为了得到成都未来两天的天气信息,我们将发送请求的路径设置为“weather/028/2”。路径为“weather/0512/4”的请求返回就是苏州未来4天的天气信息。

455bf8579efd669317c00f4f6d57f47f.png

图1 获取天气预报信息

演示程序定义了如下这个WeatherReport记录类型来表示某个城市在某段时间范围内的天气报告。如代码片段所示,某一天的天气体现为一个WeatherInfo记录。简单起见,我们让WeatherInfo记录只携带基本天气状况和气温区间的信息。

public readonly record struct WeatherInfo(string Condition, double HighTemperature, double LowTemperature);
public readonly record struct WeatherReport(string CityCode, string CityName,IDictionary<DateTime, WeatherInfo> WeatherInfos);

我们定义了如下这个工具类型WeatherReportUtility,两个Generate方法会根据指定的城市代码和天数/日期生成一份由WeatherReport对象表示的天气报告。为了将这份报告呈现在网页上,我们定义了另一个RenderAsync方法将指定的WeatherReport转换成HTML,并利用指定的HttpContext上下文将它作为响应内容,具体的HTML内容由AsHtml方法生成。

public static class WeatherReportUtility
{private static readonly Random _random = new();private static readonly Dictionary<string, string> _cities = new(){["010"] = "北京",["028"] = "成都",["0512"] = "苏州"};private static readonly string[] _conditions = new string[] { "晴", "多云", "小雨" };public static WeatherReport Generate(string city, int days){var report = new WeatherReport(city, _cities[city],  new Dictionary<DateTime, WeatherInfo>());for (int i = 0; i < days; i++){report.WeatherInfos[DateTime.Today.AddDays(i + 1)] = new WeatherInfo(_conditions[_random.Next(0, 2)], _random.Next(20, 30), _random.Next(10, 20));}return report;}public static WeatherReport Generate(string city, DateTime date){var report = new WeatherReport(city, _cities[city],  new Dictionary<DateTime, WeatherInfo>());report.WeatherInfos[date] = new WeatherInfo(_conditions[_random.Next(0, 2)], _random.Next(20, 30), _random.Next(10, 20));return report;}public static Task RenderAsync(HttpContext context, WeatherReport report){context.Response.ContentType = "text/html;charset=utf-8";return context.Response.WriteAsync(AsHtml(report));}public static string AsHtml(WeatherReport report){return @$"
<html>
<head><title>Weather</title></head>
<body>
<h3>{report.CityName}</h3>
{AsHtml(report.WeatherInfos)}
</body>
</html>
";static string AsHtml(IDictionary<DateTime, WeatherInfo> dictionary){var builder = new StringBuilder();foreach (var kv in dictionary){var date = kv.Key.ToString("yyyy-MM-dd");var tempFrom = $"{kv.Value.LowTemperature}℃ ";var tempTo = $"{kv.Value.HighTemperature}℃ ";builder.Append( $"{date}: {kv.Value.Condition} ({tempFrom}~{tempTo})<br/></br>");}return builder.ToString();}}
}

Minimal API会默认添加针对路由的服务注册,完成路由的两个中间件(RoutingMiddleware和EndpointRoutingMiddleware)也会在自动注册到创建的WebApplication对象上。WebApplication类型同时实现了IEndpointRouteBuilder接口,我们只需要利用它注册相应的终结点就可以了。如下的演示程序调用了WebApplication对象的MapGet方法注册了一个仅针对GET请求的终结点,终结点采用的路径模板为“weather/{city}/{days}”,携带的两个路由参数({city}和{days})分别代表目标城市代码(区号)和天数。

using App;
var app = WebApplication.Create();
app.MapGet("weather/{city}/{days}", ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context)
{var routeValues = context.GetRouteData().Values;var city = routeValues["city"]!.ToString();var days = int.Parse(routeValues["days"]!.ToString()!);var report = WeatherReportUtility.Generate(city!, days);return WeatherReportUtility.RenderAsync(context, report);
}

注册中间件采用的处理器是一个RequestDelegate委托,我们将它指向ForecastAsync方法。该方法调用HttpContext上下文的GetRouteData方法得到承载“路由数据”的RouteData对象,后者的Values属性返回路由参数字典。我们从中提取出代表城市代码和天数的路由参数,并创建出对应的天气报告,最后将其转换成HTML作为响应内容。

[S2002]以内联方式设置路由参数的约束

上面的演示实例注册的路由模板中定义了两个参数({city}和{days}),分别表示获取天气预报的目标城市对应的区号和天数。区号应该具有一定的格式(以零开始的3~4位数字),而天数除了必须是一个整数,还应该具有一定的范围。由于没有对这两个路由参数坐任何约束,所以请求URL携带的任何字符都是有效的。ForecastAsync方法也并没有对提取的路由参数做任何验证,所以在执行过程中面对不合法的输入会直接抛出异常。

为了确保路由参数值的有效性,在进行中间件注册时可以采用内联(Inline)的方式直接将相应的约束规则定义在路由模板中。ASP.NET为常用的验证规则定义了相应的约束表达式,我们可以根据需要为某个路由参数指定一个或者多个约束表达式。如下面的代码片段所示,我们为路由参数“{city}”指定了一个基于“区号”的正则表达式(“:regex(^0[1-9]{{2,3}}$)”)。另一个路由参数{days}则应用了两个约束,一个是针对数据类型的约束(“:int”),另一个是针对区间的约束(“:range(1,4)”)。

using App;
var template = @"weather/{city:regex(^0\d{{2,3}}$)}/{days:int:range(1,4)}";
var app = WebApplication.Create();
app.MapGet(template, ForecastAsync);
app.Run();

如果在注册路由时应用了约束,那么RoutingMiddleware中间件在进行路由解析时除了要求请求路径必须与路由模板具有相同的模式,还要求携带的数据满足对应路由参数的约束条件。如果不能同时满足这两个条件,RoutingMiddleware中间件将无法选择一个终结点来处理当前请求。对于我们演示的这个实例来说,如果提供的是一个不合法的区号(1014)和预报天数(5),那么客户端都将得到图2所示的状态码为“404 Not Found”的响应。

020d9452e4dceb8560faa1b1475bfa59.png

图2 不满足路由约束而返回的“404 Not Found”响应

[S2003]定义可缺省的路由参数

路由模板(如“weather/{city}/{days}”)可以包含静态的字符(如“weather”),也可以包含动态的参数(如{city}和{days}),我们将后者称为路由参数。并非每个路由参数都必须有请求URL对应的部分来指定,如果赋予路由参数一个默认值,那么它在请求URL中就是可以缺省的。对上面演示的实例来说,我们可以采用如下方式在路由参数名后面添加一个问号(“?”)将原本必需的路由参数变成可以缺省的默认参数的。可以缺省的路由参数与在方法中定义可缺省的(Optional)params参数一样,只能出现在路由模板尾部。

using App;var template = "weather/{city?}/{days?}";
var app = WebApplication.Create();
app.MapGet(template, ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context)
{var routeValues = context.GetRouteData().Values;var city = routeValues.TryGetValue("city", out var v1) ? v1!.ToString() : "010";var days = routeValues.TryGetValue("days", out var v2) ? v1!.ToString() : "4";var report = WeatherReportUtility.Generate(city!, int.Parse(days!));return WeatherReportUtility.RenderAsync(context, report);
}

既然路由变量占据的部分路径是可以缺省的,那么即使请求的URL不具有对应的值(如“weather”和“weather/010”),它与路由规则也是匹配的,但此时在路由参数字典中是找不到它们的。此时我们不得不对处理请求的ForecastAsync方法进行相应的改动。针对上述改动,如果希望获取北京未来4天的天气状况,我们可以采用图3所示的三种URL(“weather”、“weather/010”和“weather/010/4”),这三个请求的URL本质上是完全等效的。

b772b03f9469c87cfd89e50843c36477.png

图3 不同URL针对默认路由参数的等效性

[S2004]为路由参数指定默认值

实际上可缺省路由参数默认值的设置还有一种更简单的方式,那就是按照如下所示的方式直接将默认值定义在路由模板中。这样针对ForecastAsync方法的改动就完全没有必要。

using App;var template = @"weather/{city=010}/{days=4}";
var app = WebApplication.Create();
app.MapGet(template, ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context)
{var routeValues = context.GetRouteData().Values;var city = routeValues["city"]!.ToString();var days = int.Parse(routeValues["days"]!.ToString()!);var report = WeatherReportUtility.Generate(city!, days);return WeatherReportUtility.RenderAsync(context, report);
}

[S2005]一个路径分段定义多个路由参数

一个URL可以通过分隔符“/”划分为多个路径分段(Segment),路由参数一般来说会占据某个独立的分段(如“weather/{city}/{days}”)。但也有例外情况,我们既可以在一个单独的路径分段中定义多个路由参数,也可以让一个路由参数跨越多个连续的路径分段。以我们的演示程序为例,我们需要设计一种路径模式来获取某个城市某一天的天气信息,如使用“/weather/010/2019.11.11”这样URL获取北京在2019年11月11日的天气,对应模板为“/weather/{city}/{year}.{month}.{day}”。

using App;var template = "weather/{city}/{year}.{month}.{day}";
var app = WebApplication.Create();
app.MapGet(template, ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context)
{var routeValues = context.GetRouteData().Values;var city = routeValues["city"]!.ToString();var year = int.Parse(routeValues["year"]!.ToString()!);var month = int.Parse(routeValues["month"]!.ToString()!);var day = int.Parse(routeValues["day"]!.ToString()!);var report = WeatherReportUtility.Generate(city!, new DateTime(year,month,day));return WeatherReportUtility.RenderAsync(context, report);
}

对于修改后的程序,如果采用“/weather/{city}/{yyyy}.{mm}.{dd}”这样的URL,我们就可以获取某个城市指定日期的天气。如图4所示,我们采用请求路径“/weather/010/2019.11.11”可以获取北京在2019年11月11日的天气。

d5c0816c5e39c04f057add3b1587a79d.png

图4 一个路径分段定义多个路由参数

[S2006]一个路由参数跨越多个路径分段

上面设计的路由模板采用“.”作为日期分隔符,如果采用“/”作为日期分隔符(如2019/11/11),这个路由默认应该如何定义呢?由于“/”同时也是路径分隔符,就意味着同一个路由参数跨越了多个路径分段,这种情况只能采用“通配符”的形式才能达成我们的目标。通配符路由参数采用{*variable}或者{**variable}的形式,星号(*)表示路径“余下的部分”,所以这样的路由参数也只能出现在模板的尾端。演示程序的路由模板可以定义成“/weather/{city}/{*date}”。

using App;
using System.Globalization;var template = "weather/{city}/{*date}";
var app = WebApplication.Create();
app.MapGet(template, ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context)
{var routeValues = context.GetRouteData().Values;var city = routeValues["city"]!.ToString();var date = DateTime.ParseExact(routeValues["date"]?.ToString()!,"yyyy/MM/dd",CultureInfo.InvariantCulture);var report = WeatherReportUtility.Generate(city!, date);return WeatherReportUtility.RenderAsync(context, report);
}

我们可以对程序做如上修改来使用新的URL模板(“/weather/{city}/{*date}”)。为了得到北京在2019年11月11日的天气,请求的URL可以替换成“/weather/010/2019/11/11”,返回的天气信息如图5所示。

b547f3872f28804b5230253e42113c75.png

图5 一个路由参数跨越多个路径分段

[S2007]主机名绑定

一般来说,在利用某路由终结点与待路由的请求进行匹配的时候只需要考虑请求地址的路径部分,并忽略主机(Host)名称和端口号,但是一定要加上针对主机名称(含端口)的匹配策略也未尝不可。在如下这个演示程序中,我们通过调用MapGet扩展方法为根路径“/”添加了三个路由终结点,并调用该方法返回的IEndpointConventionBuilder对象的RequireHost扩展方法绑定了对应的主机名(“*.artech.com”、“www.foo.artech.com”和“www.foo.artech.com:9999”)。指定的第一个主机名包含一个前置通配符“*”,最后一个则指定了端口号。注册的这三个终结点会直接将指定的主机名作为响应内容。

var app = WebApplication.Create();
app.Urls.Add("http://0.0.0.0:6666");
app.Urls.Add("http://0.0.0.0:9999");
app.MapHost("*.artech.com").MapHost("www.foo.artech.com").MapHost("www.foo.artech.com:9999");
app.Run();internal static class Extensions
{public static IEndpointRouteBuilder MapHost(this IEndpointRouteBuilder endpoints,string host){endpoints.MapGet("/", context => context.Response.WriteAsync(host)).RequireHost(host);return endpoints;}
}

为了能够在本机采用不同的域名对演示应用发起请求,我们通过修改Hosts文件的方式将本地地址(“127.0.0.1”)映射为多个不同的域名。我们以管理员(Administrator)身份打开文件Hosts “%windir%\System32\drivers\etc\hosts”,并以如下所示的方式添加了针对两个域名的映射。

127.0.0.1 www.foo.artech.com
127.0.0.1 www.bar.artech.com

应用启动之后,我们利用浏览器使用不同的域名和端口对其发起请求,并得到如图6所示的输出结果。输出的内容不仅仅体现了终结点选择过程中针对主机名的过滤,还体现了终结点选择策略的一个重要的特性,那就是路由系统总是试图选择一个与当前请求匹配度最高的终结点,而不是选择第一个匹配的终结点。

c041832c6e4b0efc28699af6a53f153a.png

图6 主机名绑定

[S2008]将终结点处理定义为任意类型的委托

上面的例子都直接使用一个RequestDelegate委托作为终结点的处理器,实际上我们在注册终结点时可以将处理器设置为任何类型的委托都可以。当路由请求分发给注册的委托进行处理器时,会尽可能地从当前HttpContext上下文中提取相应的数据对委托的输入参数进行绑定。对于委托的执行结果,路由系统也会按照预定义的规则“智能”地将它应用到针对请求的响应中。按照这个规则,我们演示程序中用来处理请求的ForecastAsync方法可以简写成如下形式。第一个参数会自动绑定为当前HttpContext上下文,后面的两个参数则自动与同名的路由参数进行绑定。

using App;var app = WebApplication.Create();
app.MapGet("weather/{city}/{days}", ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context, string city, int days){var report = WeatherReportUtility.Generate(city,days);return WeatherReportUtility.RenderAsync(context, report);
}

[S2009]IResult 的应用

不论终结点处理器的委托返回何种类型的对象,路由系统总能做出对应的处理。比如对于返回的字符串会直接作为响应的主体内容,并将Content-Type报头设置为“text/plain”。如果希望对返回对象具有明确的控制,最好返回一个IResult对象(或者Task<IResult>和ValueTask<IResult>),IResult相当ASP.NET MVC中的IActionResult。我们演示程序中的ForecastAsync方法也可以改写成如下这个返回类型为IResult的Forecast方法,该方法通过调用Results类型的静态Content方法返回一个ContentResult对象,它将天气报告转换成的HTML作为响应类型,Content-Type报头设置为 “text/html” 。

using App;var app = WebApplication.Create();
app.MapGet("weather/{city}/{days}", Forecast);
app.Run();static IResult Forecast(HttpContext context, string city, int days)
{var report = WeatherReportUtility.Generate(city,days);return Results.Content(WeatherReportUtility.AsHtml(report), "text/html");
}

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

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

相关文章

数据库(二)tab补全功能,使数据库支持简体中文,日志管理,备份脚本

一、如何在MySQL数据库中使用tab键补全功能 1.修改主配置文件/etc/my.cnf vim /etc/my.cnf [mysql] #no-auto-rehash auto-rehash 2.重启MySQL服务&#xff0c;登录测试 /etc/init.d/mysqld restart mysql -uroot -p (可在数据库中使用tab键&#xff09; 3.&#xff1b;临时支持…

企业数字化转型服务+方案

前言&#xff1a;本文的阅读对象是公司老板、或企业高层管理者&#xff01;1【背景介绍】数字经济与实体经济深度融合是助推我国经济高质量发展的重要环节。为加快数字中国建设&#xff0c;中央和地方政府都出台各类政策扶植数字化转型相关项目。马云在接受采访时也提到&#x…

.NET性能优化-使用ValueStringBuilder拼接字符串

前言这一次要和大家分享的一个Tips是在字符串拼接场景使用的&#xff0c;我们经常会遇到有很多短小的字符串需要拼接的场景&#xff0c;在这种场景下及其的不推荐使用String.Concat也就是使用运算符。 目前来说官方最推荐的方案就是使用StringBuilder来构建这些字符串&#xff…

STOLUCK:经济下行的当下 ,STO或将帮助中小企业度过寒冬

2018年被称为创业阵亡率特别高的一年&#xff0c;相关报道称有近20%的创业团队面临“后续融资跟不上&#xff0c;可能死在春天来临之前”的窘境。经济不景气的当下&#xff0c;上下游资金不足&#xff0c;信贷机构没钱&#xff0c;风投业捉襟见肘。实际今年3月份开始&#xff0…

浅析C# Dictionary实现原理

一、前言二、理论知识1、Hash 算法2、Hash 桶算法3、解决冲突算法三、Dictionary 实现1. Entry 结构体2. 其它关键私有变量3. Dictionary - Add 操作4. Dictionary - Find 操作5. Dictionary - Remove 操作6. Dictionary - Resize 操作(扩容)7. Dictionary - 再谈 Add 操作8. C…

猫晚流量再创记录,阿里云直播方案护航优酷2500万用户体验

2019独角兽企业重金招聘Python工程师标准>>> 对“剁手党而言&#xff0c;天猫双11早已经超越了简单的“买买买”&#xff0c;更是一场边看边玩的狂欢盛宴。今年的天猫双11狂欢夜晚会&#xff08;简称“猫晚”&#xff09;在上海举办&#xff0c;这台兼具年轻潮流与国…

python实现二叉树和它的七种遍历

介绍&#xff1a; 树是数据结构中非常重要的一种&#xff0c;主要的用途是用来提高查找效率&#xff0c;对于要重复查找的情况效果更佳&#xff0c;如二叉排序树、FP-树。另外可以用来提高编码效率&#xff0c;如哈弗曼树。 代码&#xff1a; 用python实现树的构造和几种遍历算…

.NET性能系列文章二:Newtonsoft.Json vs System.Text.Json

微软终于追上了&#xff1f;图片来自 Glenn Carstens-Peters[1]Unsplash[2]欢迎来到.NET 性能系列的另一章。这个系列的特点是对.NET 世界中许多不同的主题进行研究、基准和比较。正如标题所说的那样&#xff0c;重点在于使用最新的.NET7 的性能。你将看到哪种方法是实现特定主…

android gpu平板 推荐,性能强的不像话,最强安卓平板华为平板M6上手

原标题&#xff1a;性能强的不像话&#xff0c;最强安卓平板华为平板M6上手你为什么买平板电脑&#xff1f;当这一问题问出以后&#xff0c;许多朋友的表情都很微妙&#xff0c;随后大概率的回答则相当统一&#xff1a;"我买平板干嘛&#xff1f;"。其实得到这样一个…

企业应用“数据优先”革命的下一个主战场:安全与运营

根据IDC发布的2015年全球CIO日程预测&#xff0c;80%的CIO将提供一个实现创新和改善业务决策的新体系架构。 大数据时代&#xff0c;企业软件市场正在经历一次大迁移&#xff0c;数以十亿计的企业IT支出预算将投向“数据优先”应用&#xff0c;而不是长久以来以业务流程和工作流…

给Web开发人员的以太坊入坑指南

以太坊现在各种学习资料数不胜数&#xff0c;但由于以太坊正处于飞速发展阶段&#xff0c;有些学习资料很快就过时了。所以想找到有价值的资料无异于大海捞针。我费了很大功夫&#xff0c;才建立起对以太坊的整体认识&#xff0c;搞清楚它的工作机制。我相信很多跃跃欲试的开发…

一款简单的缩放拖拽图片控件

本文介绍一个针对 .NET 桌面应用程序的独立图片缩放拖拽显示控件 SQPhoto[1]。SQPhoto 是一个 Windows 桌面应用的组件&#xff0c;支持 .NET6 和 .NET Framework 4.6 。基于 PictureBox 的图片展示工具&#xff0c;增加了拖动和缩放功能&#xff0c;便于在某些场景下的图片展…

HTML怎么让div全透明,设置div为透明 怎样才让div里面的div不透明?

#a{ background:#FFCC33; filter:alpha(opacity:0); width: 300px; heig#a{background:#FFCC33; filter:alpha(opacity50); /*支持 IE 浏览器*/-moz-opacity:0.50; /*支持 FireFox 浏览器*/opacity:0.50; /*支持 Chrome, Opera, Safari 等浏览器*/width: 300px;height:300px;}还…

html overflow 样式,css样式之overflow-x属性样式

overflow-x是overflow子花样&#xff0c;平日也很少用的。overflow-x设置匿伏溢出过宽模式(比如过宽图片)、设置对象底部转折条等重要。overflow-x语法与根本懂得1、overflow-x可设置值overflow-x : visible | auto | hidden| scroll值与解释引见&#xff1a;visible :  不剪切…

C# WPF GridControl用法举例

概述GridControl是Dev中的表格控件&#xff0c;类似于Winfrom中的DataGridView&#xff0c;以及WPF中的DataGrid&#xff0c;但是这个控件功能比原生的功能要强大很多&#xff0c;下面用实例举例说明此控件的用法.代码前台XAML&#xff1a;<UserControl x:Class"Calibu…

js中关于Blob对象的介绍与使用

js中关于Blob对象的介绍与使用 blob对象介绍 一个 Blob对象表示一个不可变的, 原始数据的类似文件对象。Blob表示的数据不一定是一个JavaScript原生格式 blob对象本质上是js中的一个对象&#xff0c;里面可以储存大量的二进制编码格式的数据。 创建blob对象 创建blob对象本质上…

20170102-文件处理

文件处理 正常文件处理 python 文件处理 编码 f open(file"兼职白领学生空姐模特护士练习方式.txt",mode"r",encoding"utf-8")#把值附给变量f &#xff08;路径file"文件是兼职白领学生空姐模特护士练习方式.txt"&#xff0c;文本模式…

创建.NET程序Dump的几种姿势

当一个应用程序运行的有问题时&#xff0c;生成一个 Dump 文件来调试它可能会很有用。在 Windows、Linux 或 Azure 上有许多方法可以生成转储文件。Windows 平台dotnet-dump (Windows)dotnet-dump 全局工具[1]是一种收集和分析.NET 核心应用程序 Dump 的方法。安装 dotnet-dump…

自然语言处理怎么最快入门?

2019独角兽企业重金招聘Python工程师标准>>> 本文整理自知乎上的一个问答&#xff0c;分享给正在学习自然然语言处理的朋友们&#xff01; 一、自然语言处理是什么&#xff1f; 自然语言处理说白了&#xff0c;就是让机器去帮助我们完成一些语言层面的事情&#xff…

dotnet-exec 0.8.0 released

dotnet-exec 0.8.0 releasedIntrodotnet-exec 是一个 C# 程序的小工具&#xff0c;可以用来运行一些简单的 C# 程序而无需创建项目文件&#xff0c;而且可以自定义项目的入口方法&#xff0c;支持但不限于 Main 方法Install/Updatedotnet-exec 是一个 dotnet tool&#xff0c;可…