nanoFramework.WebServer 1.2.56
前缀保留
dotnet add package nanoFramework.WebServer --version 1.2.56
NuGet\Install-Package nanoFramework.WebServer -Version 1.2.56
<PackageReference Include="nanoFramework.WebServer" Version="1.2.56" />
paket add nanoFramework.WebServer --version 1.2.56
#r "nuget: nanoFramework.WebServer, 1.2.56"
// Install nanoFramework.WebServer as a Cake Addin #addin nuget:?package=nanoFramework.WebServer&version=1.2.56 // Install nanoFramework.WebServer as a Cake Tool #tool nuget:?package=nanoFramework.WebServer&version=1.2.56
欢迎使用 .NET nanoFramework WebServer 代码库
构建状态
组件 | 构建状态 | NuGet 包 |
---|---|---|
nanoFramework.WebServer | ||
nanoFramework.WebServer.FileSystem |
.NET nanoFramework WebServer
此库由 Laurent Ellerbach 编写,他慷慨地将其提供给 .NET nanoFramework 项目。
这是一个简单的 nanoFramework WebServer。功能包括
- 处理多线程请求
- 使用
nanoFramework.WebServer.FileSystem
NuGet 从任何存储空间中提供服务。需要一个支持存储(具有System.IO.FileSystem
功能)的目标设备。 - 处理 URL 中的参数
- 可行同时运行多个 WebServer
- 支持 GET/PUT 和其他任何命令
- 支持任何类型的头部
- 支持 POST 中的内容
- 反射以方便使用控制器和路由概念
- 辅助函数以直接返回错误代码,方便 REST API 使用
- 支持 HTTPS
- URL 解码/编码
限制
- 不支持请求或响应流中的任何 zip 文件
用法
您只需要为查询指定端口和超时时间,并添加一个当请求到来时进行事件处理的操作。用这种方法,每次接收到请求时都会触发一个事件。
using (WebServer server = new WebServer(80, HttpProtocol.Http)
{
// Add a handler for commands that are received by the server.
server.CommandReceived += ServerCommandReceived;
// Start the server.
server.Start();
Thread.Sleep(Timeout.Infinite);
}
您还可以传递一个控制器,您可以在其中对路由和方法使用装饰。
using (WebServer server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(ControllerPerson), typeof(ControllerTest) }))
{
// Start the server.
server.Start();
Thread.Sleep(Timeout.Infinite);
}
在这种情况下,您正在传递两个具有公开方法的类,这些方法将在找到路由时被调用。
使用前面的例子,一个非常简单直接测试控制器看起来是这样的
public class ControllerTest
{
[Route("test"), Route("Test2"), Route("tEst42"), Route("TEST")]
[CaseSensitive]
[Method("GET")]
public void RoutePostTest(WebServerEventArgs e)
{
string route = $"The route asked is {e.Context.Request.RawUrl.TrimStart('/').Split('/')[0]}";
e.Context.Response.ContentType = "text/plain";
WebServer.OutPutStream(e.Context.Response, route);
}
[Route("test/any")]
public void RouteAnyTest(WebServerEventArgs e)
{
WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK);
}
}
在这个例子中,当请求的URL是 test
或 Test2
或 tEst42
或 TEST
时,将调用 RoutePostTest
,URL可以带参数,且方法是GET。请注意,Test
不会调用该函数,也不会调用 test/
。
RouteAnyTest
在URL是 test/any
且不区分方法时会被调用。
更高级的例子是通过简单的REST API获取人员列表并添加人员。请查看示例。
[!Important]
默认情况下,路由不区分大小写,并且属性必须全部小写。如果您想使用区分大小写的路由,如前面的例子所示,请使用属性
CaseSensitive
。同样,您必须按照您想要的方式响应路由来编写路由。
一个简单的GPIO控制器REST API
您将在简单的 GPIO控制器示例 REST API中找到。控制器不区分大小写,工作方式如下
- 打开2号引脚作为输出:http://你的链接/open/2/output
- 打开4号引脚作为输入:http://你的链接/open/4/input
- 将2号引脚写入高电平:http://你的链接/write/2/high
- 您可以使用high或1,它们具有相同的效果,并将引脚放置在高电平位置
- 您可以使用low或0,它们具有相同的效果,并将引脚放置在低电平位置
- 读取4号引脚:http://你的链接/read/4,您将得到纯文本形式的
high
或low
,取决于状态
控制器上的身份验证
控制器支持身份验证。目前控制器上只有三种身份验证类型已实施
- 基本身份验证:遵循HTTP标准的经典用户和密码。用法:
[Authentication("Basic")]
将使用Web服务器的默认凭证[Authentication("Basic:myuser mypassword")]
将使用myuser作为用户名,使用mypassword作为密码。注意:用户名不能包含空格。
- 在标题中添加ApiKey:在标题中添加带有关键API的ApiKey。用法:
[Authentication("ApiKey")]
将使用Web服务器的默认凭证[Authentication("ApiKey:akey")]
将使用akey作为ApiKey。
- 无:无需身份验证。用法:
[Authentication("None")]
将使用Web服务器的默认凭证
Authentication属性适用于公开类和公开方法。
至于控制器中的其余部分,您可以为它们添加属性来定义或覆盖它们。以下示例显示了可以做什么
[Authentication("Basic")]
class ControllerAuth
{
[Route("authbasic")]
public void Basic(WebServerEventArgs e)
{
WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK);
}
[Route("authbasicspecial")]
[Authentication("Basic:user2 password")]
public void Special(WebServerEventArgs e)
{
WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK);
}
[Authentication("ApiKey:superKey1234")]
[Route("authapi")]
public void Key(WebServerEventArgs e)
{
WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK);
}
[Route("authnone")]
[Authentication("None")]
public void None(WebServerEventArgs e)
{
WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK);
}
[Authentication("ApiKey")]
[Route("authdefaultapi")]
public void DefaultApi(WebServerEventArgs e)
{
WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK);
}
}
并且您可以向服务器传递默认凭证
using (WebServer server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(ControllerPerson), typeof(ControllerTest), typeof(ControllerAuth) }))
{
// To test authentication with various scenarios
server.ApiKey = "ATopSecretAPIKey1234";
server.Credential = new NetworkCredential("topuser", "topPassword");
// Start the server.
server.Start();
Thread.Sleep(Timeout.Infinite);
}
使用前面的示例发生以下情况:
- 默认情况下,所有控制器,即使未指定任何内容,也将使用控件凭证。在我们的案例中,使用默认用户(topuser)和密码(topPassword)的基本身份验证将被使用。
- 当通过浏览器调用http://你的链接/authbasic时,将提示您输入用户名和密码,使用默认的用户topuser和密码topPassword以获取访问权限
- 当调用http://你的链接/authnone时,因为没有身份验证被覆盖为无,所以不会提示您。
- 当调用 http://yoururl/authbasicspecial 时,用户和密码与默认的 user2 和密码不同,这里正确的用户密码对是 user2 和 password
- 如果您在控制器中定义了特定的用户和密码,例如
[Authentication("Basic:myuser mypassword")]
,那么所有控制器都将使用 myuser 和 mypassword 作为默认密码 - 当调用 http://yoururl/authapi 时,您必须传递名为
ApiKey
(区分大小写)的头部,其值应为superKey1234
以获得授权,这将覆盖默认的 Basic 认证 - 当调用 http://yoururl/authdefaultapi 时,将使用默认密钥
ATopSecretAPIKey1234
,因此您需要在请求头部中传递它
总的来说,这是一个展示如何使用认证的示例,它被定义为允许灵活性
通过事件管理传入的查询
基本用法如下
private static void ServerCommandReceived(object source, WebServerEventArgs e)
{
var url = e.Context.Request.RawUrl;
Debug.WriteLine($"Command received: {url}, Method: {e.Context.Request.HttpMethod}");
if (url.ToLower() == "/sayhello")
{
// This is simple raw text returned
WebServer.OutPutStream(e.Context.Response, "It's working, url is empty, this is just raw text, /sayhello is just returning a raw text");
}
else
{
WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.NotFound);
}
}
您可以进行更复杂的场景,例如 返回一个完整的 HTML 页面
WebServer.OutPutStream(e.Context.Response, "<html><head>" +
"<title>Hi from nanoFramework Server</title></head><body>You want me to say hello in a real HTML page!<br/><a href='/useinternal'>Generate an internal text.txt file</a><br />" +
"<a href='/Text.txt'>Download the Text.txt file</a><br>" +
"Try this url with parameters: <a href='/param.htm?param1=42&second=24&NAme=Ellerbach'>/param.htm?param1=42&second=24&NAme=Ellerbach</a></body></html>");
并且可以从 URL 获取参数,例如从之前的 param.html 页面中的链接示例
if (url.ToLower().IndexOf("/param.htm") == 0)
{
// Test with parameters
var parameters = WebServer.decryptParam(url);
string toOutput = "<html><head>" +
"<title>Hi from nanoFramework Server</title></head><body>Here are the parameters of this URL: <br />";
foreach (var par in parameters)
{
toOutput += $"Parameter name: {par.Name}, Value: {par.Value}<br />";
}
toOutput += "</body></html>";
WebServer.OutPutStream(e.Context.Response, toOutput);
}
并且服务静态文件
// E = USB storage
// D = SD Card
// I = Internal storage
// Adjust this based on your configuration
const string DirectoryPath = "I:\\";
string[] _listFiles;
// Gets the list of all files in a specific directory
// See the MountExample for more details if you need to mount an SD card and adjust here
// https://github.com/nanoframework/Samples/blob/main/samples/System.IO.FileSystem/MountExample/Program.cs
_listFiles = Directory.GetFiles(DirectoryPath);
// Remove the root directory
for (int i = 0; i < _listFiles.Length; i++)
{
_listFiles[i] = _listFiles[i].Substring(DirectoryPath.Length);
}
var fileName = url.Substring(1);
// Note that the file name is case sensitive
// Very simple example serving a static file on an SD card
foreach (var file in _listFiles)
{
if (file == fileName)
{
WebServer.SendFileOverHTTP(e.Context.Response, DirectoryPath + file);
return;
}
}
WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.NotFound);
[!Important]
服务文件需要
nanoFramework.WebServer.FileSystem
nuget AND 设备支持存储,因此需要System.IO.FileSystem
。
此外,也支持 REST API,这里有一个全面的示例
if (url.ToLower().IndexOf("/api/") == 0)
{
string ret = $"Your request type is: {e.Context.Request.HttpMethod}\r\n";
ret += $"The request URL is: {e.Context.Request.RawUrl}\r\n";
var parameters = WebServer.DecodeParam(e.Context.Request.RawUrl);
if (parameters != null)
{
ret += "List of url parameters:\r\n";
foreach (var param in parameters)
{
ret += $" Parameter name: {param.Name}, value: {param.Value}\r\n";
}
}
if (e.Context.Request.Headers != null)
{
ret += $"Number of headers: {e.Context.Request.Headers.Count}\r\n";
}
else
{
ret += "There is no header in this request\r\n";
}
foreach (var head in e.Context.Request.Headers?.AllKeys)
{
ret += $" Header name: {head}, Values:";
var vals = e.Context.Request.Headers.GetValues(head);
foreach (var val in vals)
{
ret += $"{val} ";
}
ret += "\r\n";
}
if (e.Context.Request.ContentLength64 > 0)
{
ret += $"Size of content: {e.Context.Request.ContentLength64}\r\n";
byte[] buff = new byte[e.Context.Request.ContentLength64];
e.Context.Request.InputStream.Read(buff, 0, buff.Length);
ret += $"Hex string representation:\r\n";
for (int i = 0; i < buff.Length; i++)
{
ret += buff[i].ToString("X") + " ";
}
}
WebServer.OutPutStream(e.Context.Response, ret);
}
这个 API 示例很简单,但如您所知,您可以选择做什么。
当您获得 URL 时,您可以检查是否调用了特定的控制器。您还有参数和内容有效载荷!
以下是一个调用示例的结果
还有更多!查看完整的示例了解有关此 WebServer 的更多信息!
使用 HTTPS
您需要生成证书和密钥
X509Certificate _myWebServerCertificate509 = new X509Certificate2(_myWebServerCrt, _myWebServerPrivateKey, "1234");
// X509 RSA key PEM format 2048 bytes
// generate with openssl:
// > openssl req -newkey rsa:2048 -nodes -keyout selfcert.key -x509 -days 365 -out selfcert.crt
// and paste selfcert.crt content below:
private const string _myWebServerCrt =
@"-----BEGIN CERTIFICATE-----
MORETEXT
-----END CERTIFICATE-----";
// this one is generated with the command below. We need a password.
// > openssl rsa -des3 -in selfcert.key -out selfcertenc.key
// the one below was encoded with '1234' as the password.
private const string _myWebServerPrivateKey =
@"-----BEGIN RSA PRIVATE KEY-----
MORETEXTANDENCRYPTED
-----END RSA PRIVATE KEY-----";
using (WebServer server = new WebServer(443, HttpProtocol.Https)
{
// Add a handler for commands that are received by the server.
server.CommandReceived += ServerCommandReceived;
server.HttpsCert = _myWebServerCertificate509;
server.SslProtocols = System.Net.Security.SslProtocols.Tls | System.Net.Security.SslProtocols.Tls11 | System.Net.Security.SslProtocols.Tls12;
// Start the server.
server.Start();
Thread.Sleep(Timeout.Infinite);
}
[!重要] 由于上述证书不是由证书颁发机构签发的,因此它不会被认可为有效证书。如果您想通过浏览器访问 nanoFramework 设备,例如,您必须将 CRT 文件添加为可信对象。在 Windows 上,您只需双击 CRT 文件,然后单击“安装证书...”。
当然,您可以使用前面定义的路径。两种方法都有效,即使路由或具有控制器概念的路径。
WebServer 状态
可以通过订阅事件来获取 WebServer 的状态。这可以很有用,例如重新启动服务器,设置重试机制或类似机制。
server.WebServerStatusChanged += WebServerStatusChanged;
private static void WebServerStatusChanged(object obj, WebServerStatusEventArgs e)
{
// Do whatever you need like restarting the server
Debug.WriteLine($"The web server is now {(e.Status == WebServerStatus.Running ? "running" : "stopped" )}");
}
端到端测试
在 WebServerE2ETests 中有一个名为 nanoFramework WebServer E2E Tests.postman_collection.json
的 Postman 测试集合,它应该在真实世界场景中测试 WebServer。使用方法简单
- 将 json 文件导入 Postman
- 将 WebServerE2ETests 部署到您的设备上 - 复制 IP 地址
- 将
base_url
变量设置为与您的设备 IP 地址匹配 - 选择要测试的请求或运行整个集合并检查测试结果。
反馈和文档
有关文档、提供反馈、问题以及了解如何贡献,请参阅 Home 仓库。
加入我们的 Discord 社区 这里。
鸣谢
此项目的贡献者名单可以在 CONTRIBUTORS 中找到。
许可
nanoFramework WebServer 库 licensed under the MIT license。
行为准则
此项目已采用贡献者公约中定义的行为准则,以阐明我们在社区中期望的行为。有关更多信息,请参阅 .NET Foundation Code of Conduct。
.NET Foundation
此项目由 .NET Foundation 支持。
产品 | 版本 兼容和额外的目标框架版本。 |
---|---|
.NET 框架 | net 兼容。 |
-
- nanoFramework.CoreLibrary (>= 1.15.5)
- nanoFramework.System.Net.Http.Client (>= 1.5.145)
NuGet 包
此包没有被任何 NuGet 包使用。
GitHub 仓库 (1)
显示依赖 nanoFramework.WebServer 的最受欢迎的前 1 个 GitHub 仓库
仓库 | 星标 |
---|---|
nanoframework/Samples
🍬 nanoFramework 团队用于测试、概念证明和其他探索性活动的代码示例
|
版本 | 下载 | 最后更新 |
---|---|---|
1.2.56 | 63 | 7/30/2024 |
1.2.55 | 103 | 7/24/2024 |
1.2.52 | 241 | 6/3/2024 |
1.2.48 | 148 | 5/17/2024 |
1.2.45 | 90 | 5/13/2024 |
1.2.43 | 112 | 5/10/2024 |
1.2.40 | 206 | 4/12/2024 |
1.2.38 | 105 | 4/9/2024 |
1.2.36 | 95 | 4/8/2024 |
1.2.34 | 112 | 4/5/2024 |
1.2.32 | 103 | 4/3/2024 |
1.2.30 | 88 | 4/3/2024 |
1.2.27 | 304 | 2/14/2024 |
1.2.25 | 96 | 2/12/2024 |
1.2.23 | 192 | 1/26/2024 |
1.2.21 | 87 | 1/26/2024 |
1.2.19 | 82 | 1/26/2024 |
1.2.17 | 118 | 1/24/2024 |
1.2.14 | 423 | 11/17/2023 |
1.2.12 | 133 | 11/10/2023 |
1.2.9 | 115 | 11/9/2023 |
1.2.7 | 136 | 11/8/2023 |
1.2.6 | 106 | 11/8/2023 |
1.2.3 | 223 | 10/27/2023 |
1.2.1 | 138 | 10/25/2023 |
1.1.79 | 193 | 10/10/2023 |
1.1.77 | 178 | 10/4/2023 |
1.1.75 | 401 | 8/8/2023 |
1.1.73 | 233 | 7/27/2023 |
1.1.71 | 119 | 7/27/2023 |
1.1.65 | 652 | 2/17/2023 |
1.1.63 | 361 | 1/24/2023 |
1.1.61 | 258 | 1/24/2023 |
1.1.59 | 293 | 1/24/2023 |
1.1.56 | 380 | 12/30/2022 |
1.1.54 | 304 | 12/28/2022 |
1.1.51 | 316 | 12/27/2022 |
1.1.47 | 685 | 10/26/2022 |
1.1.44 | 378 | 10/25/2022 |
1.1.41 | 365 | 10/24/2022 |
1.1.39 | 400 | 10/23/2022 |
1.1.36 | 400 | 10/10/2022 |
1.1.32 | 402 | 10/8/2022 |
1.1.29 | 446 | 9/22/2022 |
1.1.27 | 413 | 9/22/2022 |
1.1.25 | 426 | 9/22/2022 |
1.1.23 | 483 | 9/16/2022 |
1.1.21 | 458 | 9/15/2022 |
1.1.19 | 500 | 8/29/2022 |
1.1.17 | 525 | 8/6/2022 |
1.1.14 | 423 | 8/4/2022 |
1.1.12 | 389 | 8/3/2022 |
1.1.10 | 421 | 8/3/2022 |
1.1.8 | 389 | 8/3/2022 |
1.1.6 | 583 | 6/13/2022 |
1.1.4 | 452 | 6/8/2022 |
1.1.2 | 406 | 6/8/2022 |
1.1.1 | 450 | 5/30/2022 |
1.0.0 | 676 | 3/30/2022 |
1.0.0-preview.260 | 138 | 3/29/2022 |
1.0.0-preview.258 | 122 | 3/28/2022 |
1.0.0-preview.256 | 122 | 3/28/2022 |
1.0.0-preview.254 | 128 | 3/28/2022 |
1.0.0-preview.252 | 115 | 3/28/2022 |
1.0.0-preview.250 | 123 | 3/28/2022 |
1.0.0-preview.248 | 139 | 3/17/2022 |
1.0.0-preview.246 | 127 | 3/14/2022 |
1.0.0-preview.244 | 120 | 3/14/2022 |
1.0.0-preview.242 | 120 | 3/14/2022 |
1.0.0-preview.240 | 118 | 3/14/2022 |
1.0.0-preview.238 | 127 | 3/8/2022 |
1.0.0-preview.236 | 126 | 3/8/2022 |
1.0.0-preview.234 | 118 | 3/4/2022 |
1.0.0-preview.232 | 115 | 3/3/2022 |
1.0.0-preview.230 | 127 | 3/2/2022 |
1.0.0-preview.228 | 125 | 2/28/2022 |
1.0.0-preview.226 | 159 | 2/24/2022 |
1.0.0-preview.222 | 136 | 2/17/2022 |
1.0.0-preview.220 | 132 | 2/17/2022 |
1.0.0-preview.218 | 163 | 2/6/2022 |
1.0.0-preview.216 | 122 | 2/4/2022 |
1.0.0-preview.214 | 141 | 2/4/2022 |
1.0.0-preview.212 | 149 | 1/28/2022 |
1.0.0-preview.210 | 142 | 1/28/2022 |
1.0.0-preview.208 | 138 | 1/28/2022 |
1.0.0-preview.206 | 136 | 1/25/2022 |
1.0.0-preview.204 | 133 | 1/21/2022 |
1.0.0-preview.202 | 126 | 1/21/2022 |
1.0.0-preview.200 | 133 | 1/21/2022 |
1.0.0-preview.198 | 133 | 1/21/2022 |
1.0.0-preview.196 | 135 | 1/21/2022 |
1.0.0-preview.194 | 146 | 1/13/2022 |
1.0.0-preview.192 | 142 | 1/12/2022 |
1.0.0-preview.190 | 137 | 1/12/2022 |
1.0.0-preview.188 | 131 | 1/11/2022 |
1.0.0-preview.186 | 140 | 1/11/2022 |
1.0.0-preview.183 | 150 | 1/6/2022 |
1.0.0-preview.181 | 139 | 1/5/2022 |
1.0.0-preview.180 | 146 | 1/3/2022 |
1.0.0-preview.179 | 137 | 1/3/2022 |
1.0.0-preview.178 | 136 | 1/3/2022 |
1.0.0-preview.177 | 139 | 12/30/2021 |
1.0.0-preview.176 | 143 | 12/28/2021 |
1.0.0-preview.174 | 183 | 12/3/2021 |
1.0.0-preview.172 | 155 | 12/3/2021 |
1.0.0-preview.170 | 144 | 12/3/2021 |
1.0.0-preview.168 | 145 | 12/3/2021 |
1.0.0-preview.166 | 145 | 12/3/2021 |
1.0.0-preview.164 | 148 | 12/2/2021 |
1.0.0-preview.162 | 147 | 12/2/2021 |
1.0.0-preview.160 | 144 | 12/2/2021 |
1.0.0-preview.158 | 144 | 12/2/2021 |
1.0.0-preview.156 | 147 | 12/2/2021 |
1.0.0-preview.154 | 142 | 12/2/2021 |
1.0.0-preview.152 | 152 | 12/1/2021 |
1.0.0-preview.150 | 139 | 12/1/2021 |
1.0.0-preview.148 | 149 | 12/1/2021 |
1.0.0-preview.145 | 194 | 11/11/2021 |
1.0.0-preview.143 | 189 | 10/22/2021 |
1.0.0-preview.141 | 176 | 10/18/2021 |
1.0.0-preview.138 | 198 | 10/18/2021 |
1.0.0-preview.136 | 283 | 7/17/2021 |
1.0.0-preview.134 | 150 | 7/16/2021 |
1.0.0-preview.132 | 161 | 7/16/2021 |
1.0.0-preview.130 | 168 | 7/15/2021 |
1.0.0-preview.128 | 176 | 7/14/2021 |
1.0.0-preview.126 | 261 | 6/19/2021 |
1.0.0-preview.124 | 257 | 6/19/2021 |
1.0.0-preview.122 | 157 | 6/17/2021 |
1.0.0-preview.119 | 160 | 6/7/2021 |
1.0.0-preview.117 | 154 | 6/7/2021 |
1.0.0-preview.115 | 194 | 6/7/2021 |
1.0.0-preview.113 | 193 | 6/7/2021 |
1.0.0-preview.111 | 208 | 6/6/2021 |
1.0.0-preview.109 | 905 | 6/5/2021 |
1.0.0-preview.107 | 168 | 6/3/2021 |
1.0.0-preview.105 | 156 | 6/2/2021 |
1.0.0-preview.103 | 158 | 6/2/2021 |
1.0.0-preview.101 | 169 | 6/1/2021 |
1.0.0-preview.99 | 186 | 6/1/2021 |
1.0.0-preview.96 | 184 | 6/1/2021 |
1.0.0-preview.94 | 191 | 5/31/2021 |
1.0.0-preview.92 | 199 | 5/30/2021 |
1.0.0-preview.90 | 166 | 5/27/2021 |
1.0.0-preview.88 | 171 | 5/26/2021 |
1.0.0-preview.86 | 282 | 5/23/2021 |
1.0.0-preview.84 | 187 | 5/22/2021 |
1.0.0-preview.82 | 225 | 5/21/2021 |
1.0.0-preview.80 | 178 | 5/19/2021 |
1.0.0-preview.78 | 165 | 5/19/2021 |
1.0.0-preview.76 | 187 | 5/19/2021 |
1.0.0-preview.71 | 170 | 5/15/2021 |
1.0.0-preview.69 | 144 | 5/14/2021 |
1.0.0-preview.66 | 168 | 5/13/2021 |
1.0.0-preview.64 | 178 | 5/11/2021 |
1.0.0-preview.62 | 165 | 5/11/2021 |
1.0.0-preview.59 | 226 | 5/6/2021 |
1.0.0-preview.57 | 153 | 5/5/2021 |
1.0.0-preview.51 | 174 | 4/12/2021 |
1.0.0-preview.49 | 170 | 4/12/2021 |
1.0.0-preview.47 | 183 | 4/10/2021 |
1.0.0-preview.44 | 180 | 4/6/2021 |
1.0.0-preview.41 | 159 | 4/5/2021 |
1.0.0-preview.32 | 197 | 3/21/2021 |
1.0.0-preview.30 | 216 | 3/20/2021 |
1.0.0-preview.28 | 203 | 3/19/2021 |
1.0.0-preview.26 | 190 | 3/18/2021 |
1.0.0-preview.24 | 152 | 3/17/2021 |
1.0.0-preview.22 | 163 | 3/17/2021 |
1.0.0-preview.20 | 186 | 3/5/2021 |
1.0.0-preview.18 | 162 | 3/2/2021 |
1.0.0-preview.15 | 407 | 1/19/2021 |
1.0.0-preview.13 | 175 | 1/19/2021 |
1.0.0-preview.11 | 233 | 1/7/2021 |
1.0.0-preview.10 | 196 | 12/22/2020 |
1.0.0-preview.6 | 262 | 12/1/2020 |
1.0.0-preview.3 | 263 | 11/6/2020 |