方法/工具 | 发布时间 | 所属框架 | 命名空间/依赖项 | 编码标准 | 空格处理 | 严格性 | 适用场景 | 现代项目支持(.NET 6+) |
---|---|---|---|---|---|---|---|---|
HttpUtility.UrlEncode | 2002 | .NET Framework 1.0+ | System.Web(需引用 DLL) | x-www-form-urlencoded | + | 宽松 | 传统 ASP.NET WebForms | ❌ |
Server.UrlEncode | 2002 | .NET Framework 1.0+ | System.Web(ASP.NET 页面内) | x-www-form-urlencoded | + | 宽松 | ASP.NET WebForms 页面内编码 | ❌ |
Uri.EscapeDataString | 2005 | .NET Framework 2.0+ | System(核心库) | RFC 3986 | %20 | 严格 | 构造 URI 组件(路径、查询参数) | ✔️ |
WebUtility.UrlEncode | 2012 | .NET Framework 4.5+ | System.Net(跨平台) | x-www-form-urlencoded | + | 宽松 | 非 Web 环境或兼容旧代码 | ✔️ |
UrlEncoder.Default.Encode | 2016 | .NET Core 1.0+ | System.Text.Encodings.Web | RFC 3986 | %20 | 严格 | 现代应用,严格 URI 编码 | ✔️ |
关键选择原则
兼容旧代码 → HttpUtility.UrlEncode 或 WebUtility.UrlEncode。
严格 URI 规范 → Uri.EscapeDataString 或 UrlEncoder。
ASP.NET Core → UrlEncoder。
非 Web 或跨平台 → 弃用 System.Web,选择 System.Net 或 System.Text.Encodings.Web 中的方法。

如果用后端代码实现,必须替换 \r\n、\r、\n 这些换行符为 <br />。
这时,如果前端绑定时是自动编码的,那么 <br /> 会直接显示出来,起不到换行效果。
如果前端绑定时是原文输出的,那么当数据源不可信时会引发 XSS 漏洞。
推荐一个既简单又安全的方法,用前端的 CSS 就能轻松实现:
<div style="white-space: pre-line">内容</div>
原理:CSS 的 white-space: pre-line 属性会保留换行符(\r\n或\n),自动渲染为可视换行,无需修改数据源且无安全风险。


尝试捕获 InnerException 的 Message:
catch (Exception ex)
{
return BadRequest(ex.InnerException?.Message ?? ex.Message);
}
得到具体的错误信息:
Failed to find library "leptonica-1.82.0.dll" for platform x64.
GitHub 上有人提出了同样的问题,去看看。
解决方法:
安装 VC Redist。
其实 Tesseract 的 GitHub 已经提到需要安装 Visual Studio 2019 运行时。

SignalR 是一个开源的实时通信库,用于构建实时 Web 应用程序。它提供了一个简单的 API,可以在客户端和服务器之间建立持久连接,以便实时地推送数据。
与传统的 WebSocket 相比,SignalR 提供了更高级的功能和更简单的开发体验。下面是一些主要区别:
支持多种传输方式:SignalR 可以使用多种传输方式,包括 WebSocket、Server-Sent Events(SSE)、长轮询和 Forever Frame。这使得 SignalR 在不同的环境中都能提供实时通信的能力,即使某些浏览器不支持 WebSocket,也可以使用其他传输方式。
自动处理连接管理:SignalR 管理连接的生命周期,包括连接的建立、断开和重新连接。它会自动处理连接的失败和重新连接的逻辑,简化了开发人员的工作。
服务器端推送:SignalR 允许服务器端主动推送消息给客户端,而不需要客户端发起请求。这使得实时通信变得更加高效和实时,适用于聊天应用、实时监控等场景。
跨平台支持:SignalR 可以在多个平台上使用,包括 .NET、Java、JavaScript 等。这使得开发人员可以使用自己熟悉的语言和框架来构建实时应用程序。
微软官方提供了针对 ASP.NET Core Web 应用(Razor 页面)的详细教程,这里给出 MVC 版本入门教程。
最终将创建一个正常运行的聊天应用:
创建 Web 应用项目
添加 SignalR 客户端库
在“解决方案资源管理器”>中,右键单击项目,然后选择“添加”“客户端库”。
在“添加客户端库”对话框中:
“提供程序”选择“unpkg”
“库”,请输入 @microsoft/signalr@latest。
选择“选择特定文件”,展开“dist/browser”文件夹,然后选择 signalr.js 和 signalr.min.js。
点击“安装” 。
创建 SignalR Hubs 类
using Microsoft.AspNetCore.SignalR;
/// <summary>
/// Hub 类管理连接、组和消息
/// </summary>
public class ChatHub : Hub
{
/// <summary>
/// 可通过已连接客户端调用 SendMessage,以向所有客户端发送消息
/// </summary>
public async Task SendMessage(string user, string message)
{
// Clients.All 向所有的客户端发送消息
// ReceiveMessage 是客户端监听的方法
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
其父类 Hub 可管理连接、组和消息。这里演示的是向所有客户端发送消息。
配置 SignalR
打开 Program.cs,添加注入:
builder.Services.AddSignalR();
添加路由:
app.MapHub<ChatHub>("/chatHub");
添加 SignalR 客户端代码
视图页面:
<div class="container">
<div class="row p-1">
<div class="col-1">用户</div>
<div class="col-5"><input type="text" id="userInput" /></div>
</div>
<div class="row p-1">
<div class="col-1">消息</div>
<div class="col-5"><input type="text" class="w-100" id="messageInput" /></div>
</div>
<div class="row p-1">
<div class="col-6 text-end">
<input type="button" id="sendButton" value="发送消息" />
</div>
</div>
<div class="row p-1">
<div class="col-6">
<hr />
</div>
</div>
<div class="row p-1">
<div class="col-6">
<ul id="messagesList"></ul>
</div>
</div>
</div>
<script src="~/lib/microsoft/signalr/dist/browser/signalr.min.js"></script>
<script src="~/js/chat.js"></script>
chat.js 文件:
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
// 在连接建立之前禁用发送按钮
document.getElementById("sendButton").disabled = true;
connection.on("ReceiveMessage", function (user, message) {
var li = document.createElement("li");
document.getElementById("messagesList").appendChild(li);
// 修改此处时应注意脚本注入问题
li.textContent = `${user} says ${message}`;
});
connection.start().then(function () {
document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});
document.getElementById("sendButton").addEventListener("click", function (event) {
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
connection.invoke("SendMessage", user, message).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
});
完成。在线示例:https://xoyozo.net/Demo/SignalRDemo

推荐使用 SignalR 来平替 WebSocket,参考教程。
【服务端(.NET)】
一、创建 WebSocketHandler 类,用于处理客户端发送过来的消息,并分发到其它客户端
public class WebSocketHandler
{
private static List<WebSocket> connectedClients = [];
public static async Task Handle(WebSocket webSocket)
{
connectedClients.Add(webSocket);
byte[] buffer = new byte[1024];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
Console.WriteLine($"Received message: {message}");
// 处理接收到的消息,例如广播给其他客户端
await BroadcastMessage(message, webSocket);
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
connectedClients.Remove(webSocket);
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
private static async Task BroadcastMessage(string message, WebSocket sender)
{
foreach (var client in connectedClients)
{
if (client != sender && client.State == WebSocketState.Open)
{
await client.SendAsync(Encoding.UTF8.GetBytes(message), WebSocketMessageType.Text, true, CancellationToken.None);
}
}
}
}
二、在 Program.cs 或 Startup.cs 中插入:
// 添加 WebSocket 路由
app.UseWebSockets();
app.Use(async (context, next) =>
{
if (context.Request.Path == "/chat")
{
if (context.WebSockets.IsWebSocketRequest)
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
await WebSocketHandler.Handle(webSocket);
}
else
{
context.Response.StatusCode = 400;
}
}
else
{
await next();
}
});
这里的路由路径可以更改,此处将会创建一个 WebSocket 连接,并将其传递给 WebSocketHandler.Handle 方法进行处理。
也可以用控制器来接收 WebSocket 消息。
【客户端(JS)】
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>WebSocket 示例(JS + ASP.NET)</title>
<style>
#list { width: 100%; max-width: 500px; }
.tip { color: red; }
</style>
</head>
<body>
<h2>WebSocket 示例(JS + ASP.NET)</h2>
<textarea id="list" readonly rows="20"></textarea>
<div>
<input type="text" id="msg" />
<button onclick="fn_send()">发送</button>
<button onclick="fn_disconnect()">断开</button>
</div>
<p id="tip_required">请输入要发送的内容</p>
<p id="tip_closed">WebSocket 连接未建立</p>
<script>
var wsUrl = "wss://" + window.location.hostname + ":" + window.location.port + "/chat";
var webSocket;
var domList = document.getElementById('list');
// 初始化 WebSocket 并连接
function fn_ConnectWebSocket() {
webSocket = new WebSocket(wsUrl);
// 向服务端发送连接请求
webSocket.onopen = function (event) {
var content = domList.value;
content += "[ WebSocket 连接已建立 ]" + '\r\n';
domList.innerHTML = content;
document.getElementById('tip_closed').style.display = 'none';
webSocket.send(JSON.stringify({
msg: 'Hello'
}));
};
// 接收服务端发送的消息
webSocket.onmessage = function (event) {
if (event.data) {
var content = domList.value;
content += event.data + '\r\n';
domList.innerHTML = content;
domList.scrollTop = domList.scrollHeight;
}
};
// 各种情况导致的连接关闭或失败
webSocket.onclose = function (event) {
var content = domList.value;
content += "[ WebSocket 连接已关闭,3 秒后自动重连 ]" + '\r\n';
domList.innerHTML = content;
document.getElementById('tip_closed').style.display = 'block';
setTimeout(function () {
var content = domList.value;
content += "[ 正在重连... ]" + '\r\n';
domList.innerHTML = content;
fn_ConnectWebSocket();
}, 3000); // 3 秒后重连
};
}
// 检查连接状态
function fn_IsWebSocketConnected() {
if (webSocket.readyState === WebSocket.OPEN) {
document.getElementById('tip_closed').style.display = 'none';
return true;
} else {
document.getElementById('tip_closed').style.display = 'block';
return false;
}
}
// 发送内容
function fn_send() {
if (fn_IsWebSocketConnected()) {
var message = document.getElementById('msg').value;
document.getElementById('tip_required').style.display = message ? 'none' : 'block';
if (message) {
webSocket.send(JSON.stringify({
msg: message
}));
}
}
}
// 断开连接
function fn_disconnect() {
if (fn_IsWebSocketConnected()) {
// 部分浏览器调用 close() 方法关闭 WebSocket 时不支持传参
// webSocket.close(001, "Reason");
webSocket.close();
}
}
// 执行连接
fn_ConnectWebSocket();
</script>
</body>
</html>
【在线示例】
https://xoyozo.net/Demo/JsNetWebSocket
【其它】
-
服务端以 IIS 作为 Web 服务器,那么需要安装 WebSocket 协议
-
一个连接对应一个客户端(即 JS 中的 WebSocket 对象),注意与会话 Session 的区别
-
在实际项目中使用应考虑兼容性问题
-
程序设计应避免 XSS、CSRF 等安全隐患
-
参考文档:https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/websockets

如果手机开启了自动横屏,那么网页就会跟着旋转,导致网页宽度变大,高度变小,页面布局不友好,我们可以通过判断屏幕方向 api,用 css 设置旋转。
HTML:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>保持竖屏示例</title>
</head>
<body>
<div id="app">
<div class="page">
<div>把手机横过来试试</div>
<div>页面永远保持竖屏方向</div>
</div>
</div>
</body>
</html>
CSS:
body { margin: 0; }
.page { text-align: center; padding-top: 100px; }
@media screen and (orientation: landscape) {
body { width: 100vh; height: 100vw; overflow: hidden; }
.page.landscape-primary { transform: rotate(-90deg); transform-origin: top left; width: 100vh; height: 100vw; position: absolute; top: 100%; left: 0; }
.page.landscape-secondary { transform: rotate(90deg); transform-origin: top left; width: 100vh; height: 100vw; position: absolute; top: 0; left: 100%; }
}
JS:
function fn_set_orientation() {
var orientation = window.screen.orientation || window.screen.mozOrientation || window.screen.msOrientation;
const div = document.querySelector('.page');
console.log(orientation.type)
switch (orientation.type) {
case 'landscape-primary': {
div.classList.remove('landscape-secondary');
div.classList.add('landscape-primary');
break;
}
case 'landscape-secondary': {
div.classList.remove('landscape-primary');
div.classList.add('landscape-secondary');
break;
}
default: {
div.classList.remove('landscape-primary');
div.classList.remove('landscape-secondary');
break;
}
}
}
window.addEventListener("orientationchange", function () {
fn_set_orientation();
});
fn_set_orientation();
示例:
https://xoyozo.net/Demo/Orientation

ChatGPT:
当你从 <textarea>
中获取到的数据中包含换行符时,如果直接在 Vue 模板中使用插值表达式 {{}}
显示,是无法保持换行的。因为在 HTML 中,多个连续的空格和换行符都会被视为一个空格,因此如果直接将 \n
显示在模板中,会被渲染成一个空格。
解决这个问题的方法之一是使用 CSS 样式来控制换行的显示。可以为显示数据的元素添加 white-space: pre-wrap;
样式,该样式可以保留换行符并自动换行。
例如,假设你的数据保存在 text
变量中,可以这样在模板中使用:
<pre style="white-space: pre-wrap;">{{ text }}</pre>
在上面的例子中,使用 <pre>
元素来包裹要显示的数据,并添加 white-space: pre-wrap;
样式,这样数据中的换行符就能被保留并自动换行了。
另外,如果你在 Vue 组件中使用了 v-html
指令来显示数据,也可以在数据中使用 <br>
标签来手动添加换行符,然后使用 v-html
来渲染数据。但是,这种方法需要注意 XSS 攻击的问题,因为 v-html
可以渲染任意的 HTML 标签和内容,如果数据中包含恶意代码,很容易被攻击者利用。

您需要将 IWebHostEnvironment 注入到类中才能访问属性值 ApplicationBasePath:阅读有关依赖关系注入的信息。成功注入依赖项后,wwwroot 路径应该可供您使用。例如:
private readonly IWebHostEnvironment _appEnvironment;
public ProductsController(IWebHostEnvironment appEnvironment)
{
_appEnvironment = appEnvironment;
}
用法:
[HttpGet]
public IEnumerable<string> Get()
{
FolderScanner scanner = new FolderScanner(_appEnvironment.WebRootPath);
return scanner.scan();
}
编辑:在 ASP.NET 的更高版本中,IHostingEnvironment 已被替换为 IWebHostEnvironment。
电动汽车就是 EV(Electric vehicle),按照当前的产品模式可分为纯电动的 BEV(Battery Electric vehicle),混合电动汽车 HEV(Hybrid Electric vehicle),插电式混合动力汽车 PHEV(Plug-in Hybrid Electric vehicle),和增程式电动车 EREV(Extended-Range Electric Vehicles)。