ASP.NET Core SignalR的基本使用
文章目录
- 前言
- 一、SignalR
- 是什么?
- 在 ASP.NET Core 中的关键特性:
- SignalR 工作原理简图:
- 二、使用步骤
- 1.创建ASP.NET Core web Api 项目
- 2.添加 SignalR 包
- 3.创建 SignalR Hub
- 4.配置服务与中间件
- 5.创建控制器(模拟服务器向客户端发送消息)
- 6.创建Vue前端项目(模拟客户端发送消息)
- 7.运行使用
- 三、关键配置说明
- 四、故障排查
- 总结
前言
在 ASP.NET Core 中, SignalR 是用于实现实时、双向通信的技术。
一、SignalR
是什么?
一个由微软开发的高级库,构建在 ASP.NET Core 之上,用于简化向应用添加实时 Web 功能。
-
核心目标: 让开发者能够轻松实现服务器到客户端的实时推送(例如:聊天、通知、仪表盘更新、协作编辑)。
-
抽象层: 它在底层自动选择并使用最佳的传输协议来建立实时连接。首选是 WebSocket,但如果 WebSocket 不可用(例如旧浏览器、某些网络限制),它会自动优雅降级到其他技术,如 Server-Sent Events (SSE) 或 Long Polling。开发者无需关心底层使用的是哪种传输方式。
-
基于 Hub 的模型: SignalR 的核心抽象是 Hub。Hub 是一个高级管道,允许客户端和服务器直接相互调用方法(RPC 风格)。
在 ASP.NET Core 中的关键特性:
- 自动传输协商与回退: 无缝处理连接建立和传输选择。
- 连接管理: 内置管理连接的生命周期、连接组(Groups)和用户(Users),方便实现广播(所有客户端)、组播(特定组)、单播(特定客户端或用户)。
- 自动重新连接: 提供客户端 API 在连接意外断开时尝试自动重新连接。
- 简单的编程模型 (RPC):
- 服务器端: 定义继承自 Hub 的类,并在其中声明客户端可以调用的 public 方法。
- 客户端: 提供多种语言的客户端库(JavaScript, .NET, Java 等),调用服务器 Hub 上的方法,并注册处理程序来响应服务器调用的方法。
- 可扩展性: 支持通过 SignalR Backplane(如 Azure SignalR Service, Redis)将消息分发到多个服务器实例,实现横向扩展。
- 与 ASP.NET Core 集成: 深度集成身份认证(如 [Authorize] 特性)、依赖注入等。
SignalR 工作原理简图:
```csharp
[Client] <---(首选 WebSocket, 次选 SSE/Long Polling)---> [ASP.NET Core Server]| ||----(调用) ServerMethod(args) ------------------->| (Hub 方法)|<---(调用) ClientMethod(args) --------------------| (Clients.Caller, Clients.All, etc.)
```
二、使用步骤
1.创建ASP.NET Core web Api 项目
2.添加 SignalR 包
- 执行安装命令
install-package Microsoft.AspNetCore.SignalR
3.创建 SignalR Hub
-
MyHubService.cs
using Microsoft.AspNetCore.SignalR;namespace SignalRDemo.HubService {public class MyHubService:Hub{public Task SendMessageAsync(string user,string content){var connectionId=this.Context.ConnectionId;string msg = $"{connectionId},{DateTime.Now.ToString()}:{user}";return Clients.All.SendAsync("ReceivePubMsg", msg, content);}} }
4.配置服务与中间件
- Program.cs
using SignalRDemo.HubService;var builder = WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // 添加 SignalR 服务 builder.Services.AddSignalR(); //跨域 string[] urls = new[] { "http://localhost:5173" }; builder.Services.AddCors(opt => opt.AddDefaultPolicy(builder => builder.WithOrigins(urls).AllowAnyMethod().AllowAnyHeader().AllowCredentials()));var app = builder.Build();// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) {app.UseSwagger();app.UseSwaggerUI(); } app.UseCors(); app.UseHttpsRedirection();app.UseAuthorization(); // 配置路由 app.MapHub<MyHubService>("/Hubs/MyHubService");// SignalR 终结点 app.MapControllers();app.Run();
5.创建控制器(模拟服务器向客户端发送消息)
- TestController.cs
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using SignalRDemo.Entity; using SignalRDemo.HubService;namespace SignalRDemo.Controllers {[Route("api/[controller]/[action]")][ApiController]public class TestController : ControllerBase{private readonly IHubContext<MyHubService> _hubContext;public TestController(IHubContext<MyHubService> hubContext){_hubContext = hubContext;}[HttpPost("broadcast")]public async Task<IActionResult> BroadcastMessage([FromBody] MessageModel msg){// 从服务端主动推送消息await _hubContext.Clients.All.SendAsync("ReceivePubMsg", msg.User, msg.Content);return Ok();}} }
6.创建Vue前端项目(模拟客户端发送消息)
- 打开文件夹(D:\Project\MyProject\SignalRProject\SignalRDemo)
- 创建文件夹:Front
- 当前文件夹下运行cmd
- 执行命令
- npm create vite@latest SignalRClient1
- 输入y,回车
- 选择JavaScript
- 等待项目创建完成
- npm
- npm run dev
- 进入前端项目文件夹D:\Project\MyProject\SignalRProject\SignalRDemo\Front\SignalClient1\src\components,编辑HelloWorld.vue文件。
- HelloWorld.vue
<template><div style="padding: 20px; max-width: 800px; margin: 0 auto;"><h2 style="color: #2c3e50;">SignalR 聊天室</h2><div style="margin-bottom: 20px; display: flex; align-items: center;"><label style="margin-right: 10px; font-weight: bold; min-width: 80px;">用户:</label><input type="text" v-model="state.userMsg" @keydown.enter="sendMessage"placeholder="输入消息后按回车发送":disabled="!state.isConnected || state.isConnecting"style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; flex: 1;"/><label style="margin-right: 10px; font-weight: bold; min-width: 80px;">消息内容:</label><input type="text" v-model="state.contentMsg" @keydown.enter="sendMessage"placeholder="输入消息后按回车发送":disabled="!state.isConnected || state.isConnecting"style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; flex: 1;"/></div><div style="margin-bottom: 20px; background: #f8f9fa; padding: 15px; border-radius: 4px;"><div style="display: flex; margin-bottom: 10px;"><label style="margin-right: 10px; font-weight: bold; min-width: 80px;">服务器:</label><inputtype="text"v-model="state.serverUrl"placeholder="输入 SignalR Hub URL"style="padding: 8px; border: 1px solid #ddd; border-radius: 4px; flex: 1;"/></div><button @click="reconnect"style="padding: 8px 15px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer;">{{ state.isConnected ? '重新连接' : '连接' }}</button></div><div style="border: 1px solid #e0e0e0; border-radius: 4px; overflow: hidden; margin-bottom: 20px;"><div style="background: #f0f0f0; padding: 10px; font-weight: bold;">消息记录</div><div style="max-height: 300px; overflow-y: auto; padding: 10px; background: white;"><div v-for="(msg, index) in state.messages" :key="index" style="padding: 8px 0; border-bottom: 1px solid #f5f5f5;">{{ msg }}</div><div v-if="state.messages.length === 0" style="text-align: center; color: #999; padding: 20px;">暂无消息</div></div></div><div :style="{padding: '12px',borderRadius: '4px',marginBottom: '15px',backgroundColor: state.connectionStatus.includes('失败') ? '#ffebee' : state.connectionStatus.includes('连接') ? '#e8f5e9' : '#e3f2fd',color: state.connectionStatus.includes('失败') ? '#b71c1c' : state.connectionStatus.includes('连接') ? '#1b5e20' : '#0d47a1',border: state.connectionStatus.includes('失败') ? '1px solid #ffcdd2' : 'none'}"><div style="font-weight: bold; margin-bottom: 5px;">连接状态:</div><div>{{ state.connectionStatus }}</div><div v-if="state.errorDetails" style="margin-top: 10px; font-size: 0.9em; color: #b71c1c;"><div style="font-weight: bold;">错误详情:</div><div style="word-break: break-all;">{{ state.errorDetails }}</div></div></div></div></template><script>import { reactive, onMounted, onUnmounted } from 'vue';import * as signalR from '@microsoft/signalr';export default {setup() {const state = reactive({userMsg: "",contentMsg:"",messages: [],connectionStatus: "正在初始化...",isConnected: false,isConnecting: false,serverUrl: "https://localhost:7183/Hubs/MyHubService",errorDetails: "",connection: null,retryCount: 0});const sendMessage = async () => {if (!state.userMsg.trim()) return;if (!state.isConnected || !state.connection) {state.connectionStatus = "连接尚未建立,无法发送消息";return;}try {// 尝试多种可能的服务端方法名const possibleMethods = ["SendMessage", // 标准命名"SendMessageAsync", // Async后缀命名"BroadcastMessage", // 其他可能命名"SendToAll", // 另一种常见命名"PublishMessage" // 备用命名];let lastError = null;// 依次尝试所有可能的方法名for (const method of possibleMethods) {try {await state.connection.invoke(method, state.userMsg,state.contentMsg);state.userMsg = "";state.contentMsg="";return; // 成功发送则退出} catch (error) {lastError = error;console.log(`尝试调用 ${method} 失败:`, error.message);}}// 所有方法都失败state.connectionStatus = `发送失败: 未找到服务端方法`;state.errorDetails = `尝试的方法: ${possibleMethods.join(", ")}\n错误: ${lastError.message}`;} catch (error) {state.connectionStatus = `发送失败: ${error.message}`;state.errorDetails = error.toString();}};const initSignalRConnection = async () => {state.isConnecting = true;state.connectionStatus = "正在连接...";state.errorDetails = "";try {// 清理现有连接if (state.connection) {await state.connection.stop();state.connection = null;}// 创建新连接state.connection = new signalR.HubConnectionBuilder().withUrl(state.serverUrl, {skipNegotiation: true, // 尝试跳过协商步骤transport: signalR.HttpTransportType.WebSockets // 强制使用 WebSockets}).withAutomaticReconnect({nextRetryDelayInMilliseconds: retryContext => {state.retryCount = retryContext.previousRetryCount + 1;return Math.min(1000 * Math.pow(2, state.retryCount), 30000);}}).configureLogging(signalR.LogLevel.Debug) // 启用详细调试日志.build();// 消息接收处理state.connection.on('ReceiveMessage', rcvMsg => {state.messages.push(rcvMsg);});state.connection.on('ReceivePubMsg', (rcvMsg,rcvContent) => {state.messages.push(rcvMsg,rcvContent);});// 连接状态变化state.connection.onreconnecting(() => {state.isConnected = false;state.isConnecting = true;state.connectionStatus = "连接丢失,正在重连...";});state.connection.onreconnected(connectionId => {state.isConnected = true;state.isConnecting = false;state.retryCount = 0;state.connectionStatus = `已重新连接 (ID: ${connectionId})`;});state.connection.onclose(error => {state.isConnected = false;state.isConnecting = false;state.connectionStatus = error ? `连接关闭: ${error.message}` : "连接已关闭";});// 启动连接await state.connection.start();state.isConnected = true;state.isConnecting = false;state.retryCount = 0;state.connectionStatus = `已连接 (ID: ${state.connection.connectionId})`;console.log("SignalR 连接详情:", state.connection);} catch (error) {console.error("SignalR 连接失败:", error);state.isConnected = false;state.isConnecting = false;state.connectionStatus = `连接失败: ${error.message}`;state.errorDetails = error.toString();// 提供详细的错误诊断if (error.message.includes("Failed to fetch")) {state.errorDetails += "\n\n可能的原因:\n" +"1. CORS 问题 - 确保服务器已启用 CORS\n" +"2. URL 错误 - 检查服务器地址是否正确\n" +"3. 证书问题 - 尝试访问服务器URL查看证书是否有效\n" +"4. 服务器未运行 - 确保后端服务正在运行";}}};const reconnect = async () => {await initSignalRConnection();};onMounted(() => {initSignalRConnection();});onUnmounted(() => {if (state.connection) {state.connection.stop();}});return { state, sendMessage, reconnect };}}</script><style>body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background-color: #f5f7fa;margin: 0;padding: 20px;color: #333;}input {font-size: 1rem;transition: border-color 0.3s;}input:focus {outline: none;border-color: #3498db;box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);}button {font-weight: 500;transition: background-color 0.3s;}button:hover {background-color: #2980b9 !important;}button:disabled {background-color: #bdc3c7 !important;cursor: not-allowed;}</style>
7.运行使用
- 客户端推送消息
- 访问前端地址: http://localhost:5173/ (打开多个浏览器窗口测试消息广播)
- 输入消息,回车
- 服务端推送(多个客户端都可接收到服务端推送的消息)
三、关键配置说明
- 跨域支持 (CORS)
若客户端在不同域,在 Program.cs 添加:..... //跨域 string[] urls = new[] { "http://localhost:5173" }; builder.Services.AddCors(opt => opt.AddDefaultPolicy(builder => builder.WithOrigins(urls).AllowAnyMethod().AllowAnyHeader().AllowCredentials()));....app.UseCors(); app.UseHttpsRedirection(); ......
- 配置路由
在 Program.cs 添加:app.MapHub<MyHubService>("/Hubs/MyHubService");// SignalR 终结点 app.MapControllers();
四、故障排查
-
连接失败 404
检查终结点路由是否匹配 app.MapHub(“/chatHub”) -
跨域问题
确保启用 CORS 并正确配置 -
HTTPS 证书
开发环境下信任本地证书或改用 HTTP
总结
SignalR: 是构建在 WebSocket (和其他传输) 之上的高级框架。它抽象了底层复杂性,提供了极其便利的编程模型(Hub)、内置的连接管理、自动传输回退和重连机制,极大地简化了在 ASP.NET Core 中开发各种实时功能的过程。它是大多数需要服务器主动推送消息的 ASP.NET Core 实时应用的首选。
相关文章:

ASP.NET Core SignalR的基本使用
文章目录 前言一、SignalR是什么?在 ASP.NET Core 中的关键特性:SignalR 工作原理简图: 二、使用步骤1.创建ASP.NET Core web Api 项目2.添加 SignalR 包3.创建 SignalR Hub4.配置服务与中间件5.创建控制器(模拟服务器向客户端发送消息)6.创建…...

【C语言】讲解 程序分配的区域(新手)
目录 代码区 数据区 堆区 栈区 常量区 重点比较一下堆区与 栈区 总结: 前言: C语言程序的内存分配区域是理解其运行机制的重要部分。根据提供的多条证据,我们可以总结出C语言程序在运行时主要涉及以下五个关键内存区域: 代…...
【脚本 完全参数化的通用 APT 源配置方案-Debian/Ubuntu】
通过脚本在 Debian/Ubuntu 系统上一键切换 APT 源 如Dockerfile中 使用某个源(比如 aliyun) 假设你的目录结构是: . ├── Dockerfile └── switch-apt-source.shFROM ubuntu:22.04# 把脚本拷贝到镜像中 COPY switch-apt-source.sh /us…...
数据集笔记:SeekWorld
提出了一项新任务:地理定位推理(Geolocation Reasoning) 该任务要求模型在感知视觉信息的同时,推理出图像中视觉语义所隐含的高级逻辑关系,从而确定图像的拍摄地点 TheEighthDay/SeekWorld at main 构建了一个基于规则…...

LeetCode 算 法 实 战 - - - 移 除 链 表 元 素、反 转 链 表
LeetCode 算 法 实 战 - - - 移 除 链 表 元 素、反 转 链 表 第 一 题 - - - 移 除 链 表 元 素方 法 一 - - - 原 地 删 除方 法 二 - - - 双 指 针方 法 三 - - - 尾 插 第 二 题 - - - 反 转 链 表方 法 一 - - - 迭 代方 法 二 - - - 采 用 头 插 创 建 新 链 表 总 结 &a…...
Jenkins实践(10):pipeline构建历史展示包名和各阶段间传递参数
Jenkins实践(10):构建历史展示包名和pipeline各阶段间传递参数 1、构建历史展示包名 参考:https://blog.csdn.net/fen_fen/article/details/148167868 1.1、方法说明 Jenkins版本:Jenkins2.452 通过修改 currentBuild.displayName 和 currentBuild.description 实现: …...
从头认识AI-----循环神经网络(RNN)
前言 前面我们讲了传统的神经网络,如MLP、CNN,这些网络中的输入都被单独处理,没有上下文之间的信息传递机制,这在处理序列数据(如语音、文本、时间序列)时很鸡肋: 如何理解一句话中“前后文”的…...
配置远程无密登陆ubuntu服务器时无法连接问题排查
配置远程无密登陆ubuntu服务器时无法连接问题排查 登陆端排查服务器端登陆排查 登陆端排查 ssh -v 用户名Ubuntu服务器IP可能日志输出 debug1: Authentications that can continue: publickey,password服务器端登陆排查 sudo tail -f /var/log/auth.log可能日志输出 Authen…...
5.31 数学复习笔记 22
前面的笔记,全部写成一段,有点难以阅读。现在改进一下排版。另外,写笔记实际上就是图一个放松呢,关键还是在于练习。 目前的计划是,把讲义上面的高数例题搞清楚之后,大量刷练习册上面的题。感觉不做几本练…...

kafka学习笔记(三、消费者Consumer使用教程——使用实例及及核心流程源码讲解)
1.核心概念与架构 1.1.消费者与消费者组 Kafka消费者是订阅主题(Topic)并拉取消息的客户端实例,其核心逻辑通过KafkaConsumer类实现。消费者组(Consumer Group)是由多个逻辑关联的消费者组成的集合。 核心规则 同一…...

鸿蒙 Form Kit(卡片开发服务)
Form Kit(卡片开发服务) 鸿蒙应用中,Form / Card / Widget 都翻译为“卡片” Form Kit(卡片开发服务)提供一种界面展示形式,可以将应用的重要信息或操作前置到服务卡片,以达到服务直达、减少跳转…...

算力卡上部署OCR文本识别服务与测试
使用modelscope上的图像文本行检测和文本识别模型进行本地部署并转为API服务。 本地部署时把代码中的检测和识别模型路径改为本地模型的路径。 关于模型和代码原理可以参见modelscope上这两个模型相关的页面: iic/cv_resnet18_ocr-detection-db-line-level_damo iic…...

KWIC—Implicit Invocation
KWIC—Implicit Invocation ✏️ KWIC—Implicit Invocation 文章目录 KWIC—Implicit Invocation📝KWIC—Implicit Invocation🧩KWIC🧩核心组件🧩ImplementationScheme⚖️ 隐式调用 vs 显式调用对比 🌟 总结 &#x…...

Visual Studio 2022 发布独立的 exe 文件
我们在用 Visual Studio 2022 写好一个 exe 程序之后,如果想把这个拿到其他地方运行,需要把 exe 所在的文件夹一起拿过去。 编译出来的 exe 文件需要其他几个文件一同放在同一目录才能运行,原因在于默认情况下,Visual Studio 是把…...

11.4java语言执行浅析4
编译成字节码(.class 文件) 使用 javac 命令将源代码编译为 Java 字节码(bytecode) 它不是机器码,而是 JVM 能理解的中间语言(字节码),具有平台无关性。 编译过程简要࿱…...
Excel 操作 转图片,转pdf等
方式一 spire.xls.free(没找设置分辨率的方法) macOs开发Java GUI程序提示缺少字体问题解决 Spire.XLS:一款Excel处理神器_spire.xls免费版和收费版的区别-CSDN博客 官方文档 Spire.XLS for Java 中文教程 <dependency><groupI…...
说说 Kotlin 中的 Any 与 Java 中的 Object 有何异同?
在 Kotlin 中 Any 类型和 Java 中的 Object 类都是所有类型的根类型。 1 基本定义 Kotlin 中的 Any 和 Any?: Any:是所有非空类型的根类型;Any?:是所有可空类型的根类型; Java 中的 Object: 是所有类…...

python分配方案数 2023年信息素养大赛复赛/决赛真题 小学组/初中组 python编程挑战赛 真题详细解析
python分配方案数 2023全国青少年信息素养大赛Python编程挑战赛复赛真题解析 博主推荐 所有考级比赛学习相关资料合集【推荐收藏】1、Python比赛 信息素养大赛Python编程挑战赛 蓝桥杯python选拔赛真题详解...

《信号与系统》第 5 章 离散时间傅里叶变换
5.0 引言 这一章将介绍并研究离散时间傅里叶变换,这样就完整地建立了傅里叶分析方法。 5.1 非周期信号的表示:离散时间傅里叶变换 5.1.1 离散时间傅里叶变换的导出 在第4章看到:一个连续时间周期方波的傅里叶级数可以看成一个包络函数的采…...

动态IP与区块链:重构网络信任的底层革命
在数字经济蓬勃发展的今天,网络安全与数据隐私正面临前所未有的挑战。动态IP技术与区块链的深度融合,正在构建一个去中心化、高可信的网络基础设施,为Web3.0时代的到来奠定基础。 一、技术碰撞:动态IP与区块链的天然契合 动态I…...
目前主流图像分类模型的详细对比分析
以下是目前主流图像分类模型的详细对比分析,结合性能、架构特点及应用场景进行整理: 一、主流模型架构分类与定量对比 模型名称架构类型核心特点ImageNet Top-1准确率参数量(百万)计算效率典型应用场景ResNetCNN残差连接解决梯度…...

uniapp使用Canvas生成电子名片
uniapp使用Canvas生成电子名片 工作中有生成电子名片的一个需求,刚刚好弄了发一下分享分享 文章目录 uniapp使用Canvas生成电子名片前言一、上代码?总结 前言 先看效果 一、上代码? 不对不对应该是上才艺,哈哈哈 <template…...

世冠科技亮相中汽中心科技周MBDE会议,共探汽车研发数字化转型新路径
近日,中汽中心2025年科技周MBDE前沿应用主题会议在天津成功举办。本次会议以“智汇津门共探MBDE前沿应用新征程”为主题,聚焦基于模型的数字工程(MBDE)方法论在汽车复杂系统研发中的创新实践与跨领域协同,旨在推动行业…...

Linux笔记---线程
1. 线程的介绍 1.1 线程的概念 基本定义: 线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程(Process)之中(或者说是进程的一部分、对进程的划分),是进程中的实际…...

MCP架构深度解析:从基础原理到核心设计
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 持续学习,不断…...
【监控】pushgateway中间服务组件
Pushgateway 是 Prometheus 生态中的一个中间服务组件,以独立工具形式存在,主要用于解决 Prometheus 无法直接获取监控指标的场景,弥补其定时拉取(pull)模式的不足。 其用途如下: 突破网络限制࿱…...

数据库暴露--Get型注入攻击
1.背景知识 1.1Post、Get的对比 特性GET 方法POST 方法HTTP 方法类型GETPOST数据位置URL 查询字符串(?key=value)请求体(Request Body)数据可见性明文显示在 URL 和浏览器历史中不可见(除非开发者工具查看)数据长度限制受 URL 长度限制(通常约 2048 字符)无明确限制(…...

AI炼丹日志-26 - crawl4ai 专为 AI 打造的爬虫爬取库 上手指南
点一下关注吧!!!非常感谢!!持续更新!!! Java篇: MyBatis 更新完毕目前开始更新 Spring,一起深入浅出! 大数据篇 300: Hadoop&…...

ESP32-idf学习(四)esp32C3驱动lcd
一、前言 屏幕是人机交互的重要媒介,而且现在我们产品升级的趋势越来越高大尚,不少产品都会用lcd来做界面,而esp32c3在一些项目上是可以替代主mcu,所以驱动lcd也是必须学会的啦 我新买的这块st7789,突然发现是带触摸…...

【python】uv管理器
uv是一个速度极快的 Python 包和项目管理器,用 Rust 编写。 安装 安装uv之前,确保你的电脑不需要安装了python 在Windows下,可以使用官方的脚本直接安装 powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.…...