19
7

几个月前,SignalR Core 团队发布了一个非官方版本的 ASP.NET Core SignalR。为此,开发人员有机会了解其工作原理以及 ASP.NET SignalR 与 Signal Core 新架构之间的区别。

SignalR Core 中移除了哪些特性
通过对比两个版本的 SignalR 可以发现,新版本不再支持一些重要的特性。首先是移除了对 jQuery 和其他第三方类库的依赖,因为新版本的 JavaScript 客户端是使用 TypeScript 开发的。其次是自动连接后的消息重放功能,移除该功能主要是出于性能方面的考虑。服务器需要为每一个连接维护一个缓冲区,用于保存消息,以便后续重新发送。当客户端断开连接,可以尝试重新恢复连接,然后将未发送的消息发送给客户端。可以想象,如果有很多客户端断开连接,而且每个客户端都发送大量的消息,对于服务器来说是个很大的负担。另一个被 SignalR 团队移除特性是多 Hub 端点,所以,在新版本里,每个连接只有一个 Hub。

新版本的 SignalR Core 不再支持横向扩展(Scale Out)模型,原因是 MessageBus 被当成了横向扩展的“万灵丹”,但它实际上只支持Azure Service Bus、Redis 和 SQL Server。在实际的协作场景当中(客户端到客户端),随着客户端和消息数量的增长,通过以上三种方式进行横向扩展会有瓶颈问题。

不过,我认为,移除横向扩展功能这一决定有点太过激进,因为在某些场景下,MessageBus 仍然十分有用。例如,在将 SignalR 作为一个广播服务器时,它可以控制发送消息的数量。而在 SignalR Core 的 alpha 版本中,开发者可以根据实际情况选择是否进行横向扩展,如业务需求、系统约束或基础设施,这种设计更加“可插拔”。SignalR Core 团队提供了一个使用 Redis 进行横向扩展的示例。其他扩展方式可能会被包含在 SignalR Core 的最终版中。

最后一个被移除的功能是多服务器间的双向复制(backplane),因为这个功能会在服务器场生成太多的流量。ASP.NET SignalR 通过 MessageBus 在服务器间复制每一个消息,因为客户端无法直接连接到服务器场,而现在,SignalR 使用粘性会话来避免在所有服务器间复制消息。这样一来,SignalR Core 就可以知道哪个客户端连接到了哪台服务器上。

SignalR Core 中增加了哪些新特性
现在让我们来看一下 SignalR Core 带来了哪些新的特性。首先是使用了二进制协议来发送和接收消息。在 ASP.NET SignalR 中只能使用 JSON 格式的文本来发送和接收消息,而现在则可以使用二进制协议,该二进制协议基于MessagePack序列化格式,比 JSON 更快、体积更小。

主机无关性是另一个非常重要的特性,有了这个特性,就可以移除对 HTTP 的依赖。现在,我们可以在 HTTP 或 TCP 之上使用 SignalR。端点 API 也是非常重要的一个特性,它是基础的构建块,用于支持主机无关性。因为新版本是基于 Microsoft.AspNetCore.Sockets 这一底层的网络抽象层,所以可以直接使用 Socket。这么说来,SignalR Hub 其实也就是一个端点。

多格式也是一个很酷的特性,有了这个特性,我们可以处理任意格式的消息。我们可以使用多种不同的客户端连接到同一个端点,这些客户端可以使用不同的消息格式。也就是说,SignalR Core 已经实现了消息的格式无关性。这个示例在同一个端点上使用了三种格式(JSON、PIPE 和 Protobuf)来读写消息,因为使用了自定义的处理器,可以无缝地处理各种格式的消息。正如之前提到的那样,可能是因为使用了 Microsoft.AspNetCore.Sockets,从底层来看,消息都只是简单的二进制字节。

新版本还支持 WebSocket 原生客户端,所以开发者也可以使用除 SignalR Web 客户端之外的其他客户端。之前,开发者必须使用基于 JavaScript 的 Web 客户端连接到 SignalR 服务器上。现在,开发者可以自己开发客户端,充分利用浏览器 API 提供的优势。当然,开发者也可以使用最新的 TypeScript 客户端,因为 TypeScript 提供了很多有用的特性。另外,客户端是通过 NPM 包管理器进行发布的,这样依赖管理也就变得更简单了。

最后一点是,横向扩展变得更灵活,提供了更高的可扩展性。SignalR Core 团队简化并改进了横向扩展模型,并提供了一个基于 Redis 的横向扩展示例,帮助开发者了解如何进行横向扩展。

去年 9 月 14 号,SignalR Core 团队发布了第一个 alpha 版,10 月 9 号发布了第二个 alpha 版,也就是 SignalR Core 2.0 官方预览版。现在,我们即将探讨这一版本中包含的主要变化。

在得知有新版本后,我在第一时间去拉取代码,并试着去构建最新的代码。不过,正如预期的那样,因为代码还在开发当中,无法立马构建成功。尽管如此,我们还是能够在第一时间看到正在发生的变更,这有助于我们了解为什么要做出这些变更。接下来,我将列出我在构建过程中遇到的问题,并告诉大家我做了哪些事情来修复这些问题。

要在项目中使用 SignalR Core,必须引用 Microsoft.AspNetCore.SignalR,最新版本是 1.0.0-alpha2-final。

HubConnectionBuilder
在之前的版本中,如果要在服务器端连接到一个 Hub,我们会使用 HubConnection 类,比如:

var connection = new HubConnection(new Uri(baseUrl), loggerFactory);

而现在,我们需要使用 HubConnectionBuilder 类(实现了 Builder 设计模式)来连接 SignalR Core Hub,这也是第一个导致代码构建失败的变化。这一变化让建立连接变得更具可扩展性,不需要使用满是参数或带有 null 参数的构造函数。我很喜欢这个变化,因为它简化了建立连接的过程。

var connection = new HubConnectionBuilder()

.WithUrl(baseUrl)
.WithConsoleLogger()
.Build();

在服务器端处理连接
在之前的版本中,客户端在“On”方法中处理由 SignalR Hub 广播过来的数据,这个时候需要处理一大堆参数:

connection.On("UpdateCatalog", new[] { typeof(IEnumerable<Product>) }, data =>

{
var products = data[0] as List<Product>;
foreach (var item in products)
{
Console.WriteLine($"{item.name}: {item.quantity}");
}
});

可以看出,这个方法有点累赘,因为不管用不用得到方法里的参数,都必须指定这些参数和它们的类型,即使用不到,也要指定一个空数组。但问题是,处理器的参数是无类型的,所以,即使在数组中指定了类型,仍然需要遍历数组,对它们进行类型转换。

而新版本的 SignalR Core 提供了最新的泛型方法重载机制,通过这种方式指定参数类型后就不需要再进行类型转换。泛型方法对原始方法进行了包装,从而简化了开发者的工作。最终的代码更简单、可读性更好。

connection.On<List<Product>>("UpdateCatalog", products =>

{
// now, “products” parameter is a List<Product> type.
foreach (var item in products)
{
Console.WriteLine($"{item.name}: {item.quantity}");
}
});

命名约定
我发现 Invoke 方法名发生了变化(这个变化也导致代码构建失败):

await connection.Invoke("RegisterProduct", cts.Token, product, quanity);

这是一个异步方法,为了遵循命名约定,方法名被改成了 InvokeAsync,方法参数的顺序也发生了改变,令牌参数被放在了最后:

await connection.InvokeAsync("RegisterProduct", product, quanity, cts.Token);

因为遵循了命名约定和标准,开发者在使用 SignalR Core API(包括 SignalR Core 的开发者团队)时就更加直观,因为它带来了代码的统一性。例如,如果开发者在他们的 IDE 中使用了 Intellisense,就可以提前知道这个方法是异步的。

另一个与命名约定有关的变化是 MapEndpoint 方法,这个方法被改成了 MapEndPoint,遵循了Pascal 的大小写风格。

之前:

app.UseSockets(routes =>

{
routes.MapEndpoint<MessagesEndPoint>("/message");
});

现在:

app.UseSockets(routes =>

{
routes.MapEndPoint<MessagesEndPoint>("message");
});

可以看到,现在不使用“/”符号了。MapHub 方法也一样。不过,我们发现这里存在一个问题,这些方法没有使用 PathString API。不过,在下一个版本中会继续使用“/”,与其他的.Net Core API 保持一致。

命名变更
命名方面发生了很多变更,其中一个是与 Connection 类有关的 ConnectionContext。ConnectionContext 包含了与连接相关的信息,如元数据、通道等。

之前:

public override async Task OnConnectedAsync(Connection connection)

现在:

public override async Task OnConnectedAsync(ConnectionContext connection)

另一个命名方面的变更与 ConnectionContext 中的 Transport 有关。之前,用于管理输入和输出的属性分别叫作 Input 和 Output,而现在它们被改为 In 和 Out。

之前:

connection.Transport.Input.WaitToReadAsync()
connection.Transport.Output.WriteAsync()

现在:

connection.Transport.In.WaitToReadAsync()
connection.Transport.Out.WriteAsync()
TryRead 和 WriteAsync

TryRead 和 WriteAsync 方法得到了简化。之前,它们接收一个 Message 对象作为参数。

之前:

Message message;
if (connection.Transport.Input.TryRead(out message))
{
...
}
connection.Transport.Output.WriteAsync(new Message(payload, format, endOfMessage));

现在:

// message is byte[]
if (connection.Transport.In.TryRead(out var message))
{
...
}
// payload is byte[]
connection.Transport.Out.WriteAsync(payload);

现在他们使用字节数组作为参数,因为底层的 Socket 使用了 Channel<byte[]>。SignalR Core 团队认为,将字节数据移到上层可以让 Socket 层的逻辑更清晰。之前,SignalR Core 团队在字节数据之上使用了一个底层的数据帧协议(不过 WebSocket 已经有数据帧,所以没有在 WebSocket 上使用该协议)。

因此,Microsoft.AspNetCore.Sockets 层得到了“净化”,只允许端点处理二进制数据,而端点就可以使用任何一种协议,比如 TCP 或 HTTP。

底层的数据帧协议是在 Microsoft.AspNetCore.SignalR 层实现的,所以消息类型、数据帧都是在实现了 IHubProtocol 接口的类中处理的,比如JsonHubProtocol和 MessagPackHubProtocol。这种设计提供了一种可扩展的方式用于实现其他的 Hub 协议。

其他变更
我们可以直接通过 NPM 管理器来安装 signalr-client,比如,我在 package.json 文件里将它作为客户端依赖:

{
"version": "1.0.0",
"name": "asp.net",
"private": true,
"dependencies": {
"@aspnet/signalr-client": "^1.0.0-alpha2-final",
"jQuery.tabulator": "^1.12.0"
}
}

Visual Studio 会在构建解决方案时自动安装这个包。当然,我们也可以使用.NET Core 内置的新特性,它会自动把 signalr-client 文件拷贝到 wwwroot 目录,这样就不需要再使用 gulp、grunt 或其他任务执行器了。

[

{
"outputFileName": "wwwroot/lib/signalr/signalr-clientES5-1.0.0-alpha2-final.min.js",
"inputFiles": [
"node_modules/@aspnet/signalr-client/dist/browser/signalr-clientES5-1.0.0-alpha2-final.min.js"
],
"minify": {
"enabled": false
}
},
{
"outputFileName": "wwwroot/lib/signalr/signalr-client-1.0.0-alpha2-final.min.js",
"inputFiles": [
"node_modules/@aspnet/signalr-client/dist/browser/signalr-client-1.0.0-alpha2-final.min.js"
],
"minify": {
"enabled": false
}
}
]

默认情况下,.NET Core 启用 minify 选项,而我引用的文件已经被 minify 过,当它尝试再次 minify 这些文件时就会报错,于是我就把 minify 选项禁用了。

结论
以上就是我在升级到最新版 SignalR Core 时发现的一些变化。我把它们分享出来,让其他开发者也知道这些变更以及为什么要做出这些变更。我希望这些信息对大家有用,也鼓励大家在自己的项目中测试最新的 SignalR Core。

我花了几个小时解决在构建新版本代码时遇到的问题,而查看代码和理解这些变更又额外花了我几个小时时间,不过这些都是值得的。

关于作者
Geovanny Alzate Sandoval 是一名来自哥伦比亚麦德林的系统工程师,他喜欢所有与软件开发、新技术、设计模式和软件架构相关的事物。他已经在该领域工作了十多年,做过开发者、技术负责人和软件架构师。他乐于向社区做贡献,喜欢在博客上写与微软新技术有关的东西。另外,他还是麦德林.NET 开发者社区MDE.NET的联合组织者。

17
7

关于.NET Core情况

0
归档:2019年7月分类:C#和.NET

这几天终于抽出空啦看微软的Build 2019,看到.NET Core 3.0及相关技术的详细介绍,并且了解.NET的未来规划,感叹微软的伟大。

微信图片_20190717093004

上图是.NET Core的web开发部分。

微信图片_20190717092956

 

上图是.NET 平台大统一设计。

微信图片_20190717093000

上图是.NET未来的Roadmap。

 

这就叫做技术领域的一统江湖。二十年前很多人嘲笑Windows的垄断和Microsoft的不开源(现在依然有老古董如此认为),如今,Sun寄人篱下并且靠着Java专利法残喘苟活。我一直觉得微软汇聚的是软件世界最优秀的工程师,这些人要是投身开源代码,那是很恐怖的,短短不到5年时间,微软现在成为开源代码贡献最大的公司。

如今.NET Core 在海外若日中天,当国内经济持续下滑,人力成本持续提高,你们就会想到用.NET Core了,一个公司只需要三名码农就可以搞定后端、前端、移动开发。不过很可惜,公司的人力部门都听CTO的,而这群Java养出来的没落贵族是不会自己变革的。 ​​​​

12
7

本文要点
WebAssembly 是一种新的客户端技术,可以在所有现代浏览器(包括移动浏览器)中实现近乎原生的性能,而且不需要插件。
许多语言,包括 C、C#、Go 和 Rust,都可以编译成面向基于栈的 WebAssembly 虚拟机的代码。
.NET 代码可以在任何地方运行,包括浏览器内部。
Blazor 是一个客户端库,它在 WebAssembly 上使用.NET 来支持借助 Razor 模板使用 C# 编写的单页应用程序。
Blazor 支持代码重用和将遗留代码移植到现代 Web 应用程序的能力。
在 2019 年 4 月中旬,微软悄悄地推出了一个年轻的框架,从“一切皆有可能”的实验阶段过渡到“我们致力于实现这一目标”的预览版。这个框架名为Blazor,因为它在浏览器中运行,并利用了一个名为 Razor 的模板系统或“视图引擎”,促成了这个.NET 开发人员几乎放弃了的场景。它不仅允许开发人员使用 C# 构建客户端代码(不需要 JavaScript),还允许开发人员在没有插件的情况下在浏览器中运行现有的.NET 标准 DLL。

Blazor 有两种托管模式。本文主要关注客户端版本。你可以阅读“Blazor 服务器端托管模型”了解更多关于服务器端版本的信息。

Silverlight 的希望
在任何地方运行.NET 的梦想始于 2006 年,当时有一个名为“Windows Presentation Foundation/Everywhere(WPF/E)”的应用程序框架以 Silverlight 的形式向公众发布。第一个版本支持通过 WPF 引入的声明性用户界面,即可扩展应用程序标记语言(Extensible Application Markup Language,简称 XAML)。该平台提供了对 UI 元素的细粒度控制,并提供了自己的文档对象模型(DOM),可以通过 JavaScript 访问。

当 Silverlight 2 在 2008 年发布时,它通过一个作为浏览器插件运行的公共语言运行时(CLR)实现.NET 的完全支持,从而加快了采用速度。开发人员可以使用任何.NET 语言来构建 Web 应用程序,利用成熟的数据绑定模式,如 Model-View-ViewMode(MVVM),并使用 REST 或 Windows Communication Foundation(WCF)客户端与 Web API 通信。看起来,.NET 开发人员可以摆脱 JavaScript 的束缚,不用再担心跨浏览器测试,而是专注于一个具有公共代码库的平台来交付他们的应用程序。

Silverlight 开发者不知道的是,2007 年对于这个平台来说是艰难的一年。两个看似不相干的事件发生了,最终导致了它的灭亡。首先,Web 超文本应用技术工作组(WHATWG)和万维网联盟(W3C)之间开始着手合作编写将于 2008 年发布的 HTML5 规范初稿。

第二,2007 年 6 月 29 日,苹果发布了 iPhone。

每隔一段时间,我们就会有一件革命性的产品横空出世,并彻底改变一切。
——史蒂夫•乔布斯

比赛开始了。手机几乎是在一夜之间从带有联系人列表的翻盖手机发展到带有游戏和内置网络浏览器的便携式电脑。在很短的一段时间内,Silverlight 的未来似乎充满了希望。微软对 iPhone 的回应是 Windows Phone 7,支持以 Silverlight 作为开发平台。Chrome 支持即将到来。如果微软能够找到一种将 Silverlight 应用到 iPhone 和 Android 手机上的方法,那么“一次编写,到处运行”的圣杯将最终被发现。

只是,它没有找到。

出于许多原因,包括运行“浏览器中的虚拟机”的安全性考虑,以及潜在的电池消耗,通向浏览器插件的大门砰地关上了,特别是在移动设备上。业界开始期待 HTML5 在打造移动体验方面的前景。微软改变了自己的关注点,到 2011 年 Silverlight 5 发布时,大多数开发人员已经看到了不祥之兆:不会再有新版本了。

HTML5 和 JavaScript 继续赢得 Web 开发人员的青睐。jQuery 等工具对 DOM 进行了标准化,使构建多浏览器应用程序变得更加容易,同时,浏览器引擎开始采用通用的 DOM 标准,使“一次构建,到处运行”变得更加容易。Angular、React 和 Vue.js 等前端框架的爆炸式增长使单页应用程序(SPA)成为主流,并巩固了 JavaScript 作为浏览器操作系统首选语言的地位。

JavaScript 即平台
2013 年 3 月,asm.js正式推出。其文档把它描述为JavaScript 的一个严格子集,可以用作编译器的一种低级、高效的目标语言。该规范本质上定义了一组 JavaScript 约定,这些约定使通过提前编译优化代码成为可能,并提供了强类型(JavaScript 本身是一种动态语言)和基于堆的内存模型。

Asm.js 的推出使将 C/C++ 代码编译成 JavaScript 成为可能,从而开启了一个新的可能性领域。对约定的限制使得“支持 asm.js”的引擎可以有效地将 JavaScript 编译成高性能的本地代码。为了更好地理解这是如何实现的,请考虑下面的 C 代码片段:

int find(char *buf, char test) {
char *cur = buf;
while (*cur != 0 && *cur != test) {
cur++;
}
if (*cur == 0) {
return -1;
}
return (cur - buf);
}
该代码有效地扫描一个字符串,寻找标记其结束的测试字符或零字节,并计算偏移量。C++ 已经可以使用一个名为Clang的工具编译成字节码,该工具与LLVM 工具链兼容。LLVM 是一组支持快速跨平台编译代码的技术。一个名为Emscripten的项目利用该工具链来生成 asm.js。

使用 Emscripten 编译 C++ 代码可以生成几十行高度优化的 JavaScript。以下代码经过简化,用于说明生成的内容:

function find(buf, test) {
buf = buf|0;
var cur = buf|0;
var result = -1|0;
while (1) {
var check = HEAP8[cur>>0]|0;
var foundZero = (check) === (0);
if (foundZero) {
break;
}
var foundTest = (check) === (test|0);
if (foundTest) {
result = (cur - buf)|0;
break;
}
}
return result|0;
}
生成的 JavaScript 与所有浏览器兼容并且运行良好。带零操作的异或(|0)可以很容易地将任何数字转换为带符号整数。在较老的浏览器中,这可以确保数字没有小数部分。在现代浏览器中,该约定可以提前通知编译器使用 32 位整数(使数学运算更快),而不是默认的 64 位浮点值。0 右移(>>0)可以防止溢出,它还声明了一个“索引”整数类型,该类型在 HEAP8 上迭代,而 HEAP8 是一个可以在 asm.js 中使用的类型化的字节缓冲区。

在 asm.js 中没有定义 for 循环。所有内容都被转换为 while(1) 循环。这使得应用编译器优化变得更容易。这些优化非常有效,以至于一个团队能够将 Unreal 4 引擎移植到 Web 浏览器中,以近乎原生的性能直接运行 3D 第一人称视角游戏。

WebAssembly:新希望
时间很快来到 2017 年,WebAssembly发布了,这是一种基于栈的虚拟机的二进制指令格式。WebAssembly 提供了一个可移植的编译目标(简称 Wasm),与 asm.js 相比,它有几个优点:

作为字节码格式,不需要解析脚本和预编译来进行优化。代码可以直接翻译成本机指令。与 asm.js 相比,加载和开始执行代码的启动时间要快几个数量级。
字节码格式是一种更紧凑的代码交付方式。
Wasm 实现了自己的指令集,因此不受 JavaScript 语言的限制。
任何编译成 asm.js 的代码都可以把 WebAssembly 作为目标。对于前面的示例,编译器标志的一个简单修改就会生成一个扩展名为.wasm 的文件。该文件只有 116 字节长。尽管该文件包含字节码,但也存在代码的标准化文本表示形式,名为WebAssembly 文本格式。这是 WebAssembly 中 find 模块的文本表示:

(module
(type $t0 (func (param i32 i32) (result i32)))
(import "env" "memory" (memory $env.memory 256 256))
(func $a (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
(local $l0 i32) (local $l1 i32) (local $l2 i32) (local $l3 i32)
get_local $p0
set_local $l0
loop $L0
get_local $l0
i32.load8_s
tee_local $l2
i32.eqz
set_local $l1
get_local $l0
i32.const 1
i32.add
set_local $l3
get_local $l1
i32.const 1
i32.xor
get_local $p1
i32.const 24
i32.shl
i32.const 24
i32.shr_s
get_local $l2
i32.ne
i32.and
if $I1
get_local $l3
set_local $l0
br $L0
end
end
i32.const -1
get_local $l0
get_local $p0
i32.sub
get_local $l1
select)
代码大小进行了优化,因此函数被重命名为了 a。

WebAssembly 现在是 1.x 稳定版,支持所有的现代浏览器,包括手机。若干语言都以 Wasm 作为有效的编译目标。你可以使用 C、C++、Go、Rust、TypeScript 和许多其他语言来构建 WebAssembly 程序。它已经应用于计算机视觉、音频混合、视频编解码器支持、数字信号处理、医学成像、物理模拟、加密、压缩等解决方案中。

但是 C# 呢?

在 WebAssembly 推出之后,将. NET 框架的工作版本(包括它的公共语言运行时)移植到 WebAssembly 上运行的工作就立即开始了。

这一努力取得了成功。

浏览器和 Razor 视图引擎
2017 年底,微软软件工程师 Steve Sanderson 在他的个人博客上宣布了 Blazor 的消息。当时,它“只是一个实验”,并不是正式产品。它始于这样一个问题:“我们如何让.NET 在 WebAssembly 中运行?”第一个答案是比较老的简化版.NET 运行时,他能够在几个小时内将其编译成 Wasm 二进制文件。.NET 本身在浏览器中并不是非常有用:你需要一个 UI 和某种与用户交互的方式。Razor 文件将标记和 C# 结合起来创建 Web 模板,基于这项扎实的工作,Blazor 添加了大量的服务,从数据绑定和依赖注入到可重用组件、布局,以及调用 JavaScript 和从 JavaScript 调用。所有这些服务结合使得使用.NET 和 C# 构建单页面应用程序(SPA)成为可能。

为什么会有人在意呢?开发人员最初对 Blazor 的反应非常积极,这主要是因为:

它允许开发人员使用他们已经熟悉的语言(C#)和框架(.NET)来构建以前深深扎根在 JavaScript 中的客户端应用程序。
它可以在所有现代浏览器中运行,包括移动浏览器,而且不需要插件。
它使开发人员能够进入.NET 生态系统并“按原样”使用现有的库。例如,如果你正在构建一个使用 Markdown 的博客引擎,那么你可以为现有的 Markdown 引擎安装 NuGet 包,并将 Markdown 直接转换为 HTML,以便在浏览器中预览。
.NET 的性能会随着时间的推移不断提高,因此在浏览器的 Wasm 上运行已经足够了。
Blazor 是一个真正的单页应用程序,它从一组静态资产运行,可以使用Azure Storage 静态网站等服务以非常低的成本托管这些静态资产。
现在,你已经了解了 Blazor 背后的历史和动机,让我们来研究一些技术细节。

本文中的所有代码示例都可以在Blazor WebAssembly GitHub 存储库中找到。

安装 Blazor 并开始使用所需要了解的所有内容都可以在Blazor 入门这篇文章中找到。在安装了 Blazor 之后,你可以选择只创建客户端或带有ASP.NET Core后端的客户端。对于现有的基于 MVC 的服务器端项目来说,该项目看起来非常熟悉。但是,生成的 DLL 直接加载到浏览器中,并由.NET 的 WebAssembly 版本运行。

 

Mono.js JavaScript 动态加载 mono.wasm 并开始在浏览器中运行.NET。其余的加载是组成应用程序的实际 DLL 文件。

浏览器中的 C#(带依赖注入)
默认模板包含一个获取模拟天气信息的页面。这是 Razor 视图,完全由 Wasm 在客户端上渲染。

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<tableclass="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
在模板的上部,一组指令决定了页面的路径,声明了一个 using 指令,并使用依赖注入来获取.NET Framework 的 HttpClient 副本,该副本可以在浏览器中使用。
@page "/fetchdata"
@using GetStarted.Shared
@inject HttpClient Http
最后,页面上的一小段代码嵌入到 @functions 块中。这里需要注意的是,代码完全是 C# 的。你可以使用熟悉的 HttpClient 执行网络操作,并且支持 async/wait。

WeatherForecast[] forecasts;

protected override async Task OnInitAsync()
{
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
}
视图模板渲染一个页面,但是控件呢?

可重用组件
Blazor 基于分层组件的可组合 UI。天气预报组件与其他组件的唯一区别是提供路由的页面指令。下面是一个名为 LabelSlider.razor 的组件的模板,使用一个显示当前值的 span 扩展内置的 HTML Input Range。

<inputtype="range"min="@Min"max="@Max"bind-value-oninput="@CurrentValue"/>
<span>@CurrentValue</span>
绑定语法的格式为 bind-{property}-{event}。事件是可选的,触发事件时绑定会更新。如果没有这个,滑块只会在用户停止移动滚动条时更新 span。通过连接到 oninput,该值将随着滑块的移动而刷新。

关联的代码暴露了参数,这些参数允许父组件设置最小和最大范围值,并将数据绑定到当前值。Action 属性暴露一个与 CurrentValue 关联的事件,并按照约定将其命名为 CurrentValueChanged,以方便双向数据绑定(父组件可以“侦听”更改事件并相应更新绑定值)。

[Parameter]
int Min { get; set; }

[Parameter]
int Max { get; set; }

private int _currentValue;

[Parameter]
int CurrentValue
{
get => _currentValue;
set
{
if (value != _currentValue)
{
_currentValue = value;
CurrentValueChanged?.Invoke(value);
}
}
}

[Parameter]
Action<int> CurrentValueChanged { get; set; }
注意,没有使用 Parameter 属性标记的属性只对组件可见。组件重用非常简单,只需把组件名置入标签并提供必要的参数,用法如下:

<LabelSlider Min="0" Max="99" bind-CurrentValue="@currentCount"/>
在本例中,一个与父组件上的 currentCount 属性之间的双向绑定建立起来了。

使用现有的库
Blazor 的一个非常强大的优点是能够“按原样”集成现有的类库。例如,考虑一个使用Markdown的博客引擎,它能够在浏览器中预览生成的 HTML。在 Blazor 中构建它就像安装一个 NuGet 包一样简单,在本例中是开源的MarkDig处理器。然后,可以直接调用库:

var html = Markdig.Markdown.ToHtml(SourceText);
NuGet DLL 像其他项目引用一样被导入浏览器,并且可以从客户端应用程序调用。

调用 JavaScript/ 从 JavaScript 调用
Blazor 提供的一个重要服务是能够从.NET 调用 JavaScript,反之亦然。你希望从 Blazor 调用的任何 JavaScript 方法都必须能够从全局 window 对象访问,调用方法如下:
window.jsAlert = msg => alert(msg);
该互操作功能的使用方式如下:
await JsRuntime.InvokeAsync<object>("jsAlert", "Wow!");
InvokeAsync 方法支持传递和返回值,这些值将自动在 JavaScript 与.NET 之间转换并由 Blazor 运行时编组。使用 JsInvokable 属性来暴露 C# 方法,以便可以从 JavaScript 调用它。下面是一个例子,它封装了 Markdown 转换调用:
public static class Markdown
{
[JSInvokable]
public static string Convert(string src)
{
return Markdig.Markdown.ToHtml(src);
}
}
从 JavaScript 调用 DotNet.invoke 方法并传递程序集名称、暴露的方法名称和任何参数。

这使得扩展遗留应用程序和使用现有的 JavaScript 库成为可能。你甚至可以从 Blazor 应用程序调用其他 WebAssembly 模块。

Blazor 在发展
微软将 Blazor 移出了实验阶段,进入了官方预览版。使用组件模型进行服务器端渲染的 Blazor 版本将与.NET Core 3 的最终版本一起发布(请参阅.NET Core 路线图),客户端版本将在随后不久发布。还有工作要完成:调试体验极其有限,必须改进;有机会通过提前编译生成本机 Wasm 来优化代码性能;在将未使用的代码库发送到浏览器之前,需要从库中删除未使用的代码,从而降低总体大小(这个过程称为树抖动)。对 WebAsssembly 的兴趣和采用与日俱增,借助 Blazor,编写可以在任何地方运行的 C# 和.NET 代码的梦想终于实现了。

关于作者
Jeremy Likness是微软 Azure 的云推广专员。Jeremy 在 1982 年编写了他的第一个程序,并且已经开发企业应用程序 25 年了。他是四本科技书籍的作者,曾做过 8 年微软 MVP,是国际性的主题演讲家。Jeremy 的饮食以植物为主,在大部分空闲时间里,他都在太平洋西北部他的家附近跑步、徒步旅行和露营。关注Jeremy 的博客。

28
5

.NET Core 3.0 Preview 5发布

0
归档:2019年5月分类:C#和.NET

今天我们非常高兴发布 .NET Core 3.0 Preview 5,它包含了新的Json serializer,支持发布单执行文件,并且更新到新的runtime roll-forward,BCL也做了一些改动。

目前可以下载.NET Core 3.0 Preview 5安装到Windows, macOS and Linux.另外,ASP.NET Core和EF Core也发布了。

具体英文地址:https://devblogs.microsoft.com/dotnet/announcing-net-core-3-0-preview-5/

20
5

领域驱动设计实现之路

0
归档:2019年5月分类:C#和.NET

2004年,Eric Evans出版《领域驱动设计——软件核心复杂性应对之道》(简称《领域驱动设计》),10年之后,我们有了《实现领域驱动设计》。DDD将一个软件系统的核心业务功能集中在一个核心域里面,其中包含了实体、值对象、领域服务、资源库和聚合等概念。在此基础上,DDD提出了一套完整的支撑这样的核心领域的基础设施。此时,DDD已经不再是“面向对象进阶”那么简单了,而是演变成了一个系统工程。

领域,即是一个组织的业务开展方式,业务价值便体现在其中。长久以来,我们程序员都是很好的技术型思考者,我们总是擅长从技术的角度来解决项目问题。但是,一个软件系统是否真正可用是通过它所提供的业务价值体现出来的。因此,与其每天钻在那些永远也学不完的技术中,何不将我们的关注点向软件系统所提供的业务价值方向思考思考,这也正是DDD所试图解决的问题。在DDD中,代码就是设计本身,你不再需要那些繁文缛节的并且永远也无法得到实时更新的设计文档。编码者与领域专家再也不需要翻译才能理解对方所表达的意思。DDD有战略设计和战术设计之分。战略设计主要从高层“俯视”我们的软件系统,帮助我们精准地划分领域以及处理各个领域之间的关系;而战术设计则从技术实现的层面教会我们如何具体地实施DDD。

DDD之战略设计

DDD的战略设计主要包括领域/子域、通用语言、限界上下文和架构风格等概念。

1、领域和子域(Domain/Subdomain)

领域驱动设计关注点应该放在如何设计领域模型上,以及对领域模型的划分。

领域的概念:一个电商网站的领域包含了产品名录、订单、发票、库存和物流的概念。领域的划分是将一个大的领域划分成若干个子域。通常会将一个大型的软件系统拆分成若干个子系统。这种划分有可能是基于架构方面的考虑,也有可能是基于基础设施的。DDD对系统的划分是基于领域的,即基于业务。第一个问题,哪些概念应该建模在哪些子系统里面?第二个问题是,各个子系统之间的应该如何集成?如何解决?答案是:限界上下文和上下文映射图。

2、限界上下文(Bounded Context)

在一个领域/子域中,我们会创建一个概念上的领域边界,在这个边界中,任何领域对象都只表示特定于该边界内部的确切含义。这样边界便称为限界上下文。限界上下文和领域具有一对一的关系。将一个限界上下文中的所有概念,包括名词、动词和形容词全部集中在一起,我们便为该限界上下文创建了一套通用语言。通用语言是一个团队所有成员交流时所使用的语言,业务分析人员、编码人员和测试人员都应该直接通过通用语言进行交流。

上文中的各个子域之间的集成问题,是限界上下文之间的集成问题。防腐层负责与外部服务提供方打交道,还负责将外部概念翻译成自己的核心领域能够理解的概念。当然,防腐层只是限界上下文之间众多集成方式的一种,另外还有共享内核、开放主机服务等,具体细节请参考 《实现领域驱动设计》原书。限界上下文之间的集成关系也可以理解为是领域概念在不同上下文之间的映射关系,因此,限界上下文之间的集成也称为上下文映射图。

3、架构风格(Architecture)

DDD并不要求采用特定的架构风格,因为它是对架构中立的。可以采用传统的三层式架构,也可以采用REST架构和事件驱动架构等。但是在《实现领域驱动设计》中,作者比较推崇事件驱动架构和六边形(Hexagonal)架构。在六边形架构中,已经不存在分层的概念,所有组件都是平等的。这主要得益于软件抽象的好处,即各个组件的之间的交互完全通过接口完成,而不是具体的实现细节。Robert C. Martin:抽象不应该依赖于细节,细节应该依赖于抽象。

采用六边形架构的系统中存在着很多端口和适配器的组合。端口表示的是一个软件系统的输入和输出,而适配器则是对每一个端口的访问方式。

DDD之战术设计

战略设计为我们提供一种高层视野来审视我们的软件系统,而战术设计则将战略设计进行具体化和细节化,它主要关注的是技术层面的实施,也是对我们程序员来得最实在的地方。

1、领域对象

领域对象能够准确地表达出业务意图,但多数时候却是充满getter和setter的领域对象,此时的领域对象已经不是领域对象了,而是Martin Fowler所称之为的贫血对象。.NET里面很好地支持非贫血对象。

2、实体vs值对象(Entity vs Value Object)

软件系统中实体表示那些具有生命周期并且会在其生命周期中发生改变的东西;而值对象则表示起描述性作用的并且可以相互替换的概念。同一个概念,在一个软件系统中被建模成了实体,但是在另一个系统中则有可能是值对象。

3、聚合(Aggregate)

聚合是DDD中最难理解的概念 ,聚合中所包含的对象之间具有密不可分的联系,他们是内聚在一起的。比如一辆汽车(Car)包含了引擎(Engine)、车轮 (Wheel)和油箱(Tank)等组件,缺一不可。一个聚合中可以包含多个实体和值对象,因此聚合也被称为根实体。聚合是持久化的基本单位,它和资源库具有一一对应的关系。

4、领域服务(Domain Service)

领域概念放在实体上不合适,放在值对象上也不合适,领域服务本来就是来处理这种场景的。比如对密码进行加密,可以创建一个 PasswordEncryptService来专门负责此事。

5、资源库(Repository)

资源库用于保存和获取聚合对象,资源库与DAO相似。但资源库和DAO存在显著区别。DAO只是对数据库的一层很薄的封装,而资源库则更加具有领域特征。另外,所有的实体都可以有相应的DAO,但并不是所有的实体都有资源库,只有聚合才有相应的资源库。资源库分为两种,一种是基于集合的,一种是基于持久化的。顾名思义,基于集合的资源库具有编程语言中集合的特征。

6、领域事件(Domain Event)

《领域驱动设计》中并没有提到领域事件,领域事件是最近几年才加入DDD生态系统的。微服务(Micro Service)的架构中,整个系统被分成了很多个轻量的程序模块,他们之间的数据一致性并不容易通过事务一致性完成,领域事件便可以用于处理上述问题,此时最终一致性取代了事务一致性,通过领域事件的方式达到各个组件之间的数据一致性。

06
3

.NET Core 3.0-preview3 发布

0
归档:2019年3月分类:C#和.NET

.NET Core 3.0 Preview 3已经发布,框架和ASP.NET Core有许多有趣的更新。这是最重要的更新列表。 下载地址 :https://aka.ms/netcore3download 。

.NET Core 3.0的更新:

C#中对索引和范围的更多支持

支持.NET Standard 2.1。以.NET Standard项目文件为目标,并将netstandard2.1指定为目标框架。完整的.NET Framework不支持.NET Standard 2.1。

F#4.6和dotnet fsi命令。可以使用F#4.6和dotnet fsi命令的预览。FSI代表F#互动。

AssemblyDependencyResolver和resolver事件。从给定路径加载依赖程序集(之前不可能),解析程序事件可帮助我们更好地处理动态加载的本机依赖项。

Windows Forms应用程序的高DPI。最后,微软将Windows Forms应用程序推向了当今时代。96DPI不再适用,并且可以构建高DP Windows窗体应用程序。

ASP.NET Core 3.0的更新:

Razor组件的改进。现在2个项目合并成单个项目模板,Razor组件支持端点路由和预渲染,Razor组件可以托管在Razor类库中。还改进了事件处理和表单和验证支持。

运行时编译。它在ASP.NET Core 3.0模板中被禁用,但现在可以通过向项目添加特殊的NuGet包来打开它。

Worker Service 模板。需要编写Windows服务还是Linux守护进程?现在我们有了Worker Service 模板。

gRPC模板。与谷歌一起构建的gRPC是一种流行的远程过程调用(RPC)框架。此版本的ASP.NET Core在ASP.NET Core上引入了第一等的gRPC支持。

Angular模板使用Angular 7. Angular SPA模板现在使用Angular 7,在第一次稳定释放之前,它将被Angular 8替换。

SPA-s的身份验证。Microsoft通过此预览为单页应用程序添加了现成的身份验证支持。

SignalR与端点路由集成。小变化 - 现在使用端点路由定义SingalR路由。

SignalR Java客户端支持长轮询。即使在不支持或不允许WebSocket的环境中,SignalR Java客户端现在也可以使用。

 

14
11

昨天.NET已经宣布正式开源,而我的信念更加坚定了:将业余的大部分时间投入给C#开源世界。

之前一直诟病于C#和.NET只属于微软的产品,现在完全不一样了,我自己的下一步计划:

一、学习计划

1、重新把前几年的项目进行一次全面梳理,把核心和精华的部分留下;

2、重新全面系统完整地把最新的.NET框架学习一边,并且结合大的开源项目研究,目标是基于国外开源系统改造出非常好用的系统;

3、将手头上关于.NET和C#的图书做一遍梳理,包括电子版的;

4、持续跟进最新的关于Mono和.NET开源方面的资讯;

二、工作计划

1、将目前公司的项目做好梳理、未未来的跨平台打好基础;

2、专心研究nopcommerce(电商)项目,并且基于它搭建一个云、端、移动app、网店一体化的电商项目(时间可能会很长),这个项目会为未来二次开源做准备;

3、专心研究CSLA .NET,这个是.NET的一个典范,而且包含了所有平台的样例,这是跨平台的先驱;

4、专心研究Orchard(cms)项目,并且基于它搭建一个云、端、移动app的公司企业网站系统,这个项目也会为未来开源做准备。

将慢慢地把cms和电商项目融入公司,打造一支技术能力强的团队。

13
11

微软正式MIT协议将开源.NET框

0
归档:2014年11月分类:C#和.NET

今天是C#程序员狂欢的日子,这真的是一门非常非常优雅的很棒的编程语言。

“Scott Guthrie宣布微软正式将开源.NET框架,使用MIT协议开源,让它在Linux和OS X系统上也能够运行。开发商们将能够在全球三个最大的操作系统上使用.NET框架了。”

这将是计算机软件行业载入史册的一天,.NET平台终于兑现它对世界的承诺,全面支持所有平台,我坚信这是安德鲁和mono老大的英明推动下、当然也是在时代的发展驱动下的结果,太棒了!

2000

公告栏

欢迎大家来到我的博客,我是dodoro,希望我的博客能给你带来帮助。