C# .NET基于PicoServer的Live Reload实现热重载的完整方案

 更新时间:2026年06月10日 08:30:09   作者:桔子雨  
这篇文章主要为大家详细介绍了如何基于.NET实现的零配置热重载服务器,通过文件监控、防抖处理及WebSocket广播等步实现热重载,文中的示例代码讲解详细,有需要的可以了解下

.NET 是一个开源、跨平台的开发平台,运行稳定,资源消耗低,AOT 编译进一步降低了交付体积。本文基于 .NET 10 实现一个零配置的热重载服务器,核心代码不到 50 行。

依赖安装:dotnet add package PicoServer

一、热重载的本质

热重载解决的问题:文件保存后浏览器自动刷新

  • 传统流程:修改 → 保存 → 切换窗口 → F5(4 步)
  • 热重载后:修改 → 保存(2 步)

核心流程:文件变更 → 服务端检测 → WebSocket 推送 → 浏览器刷新

二、实现

2.1 文件监控与防抖

using PicoServer;
using System.Timers;
using System.IO;

string staticRoot = "wwwroot";
var app = new WebAPIServer();

var watcher = new FileSystemWatcher(staticRoot)
{
    EnableRaisingEvents = true,
    IncludeSubdirectories = true
};

var timer = new System.Timers.Timer(200) { AutoReset = false };
timer.Elapsed += async (_, _) => await app.WsBroadcastAsync("reload");

void OnChange(object s, FileSystemEventArgs e)
{
    var name = Path.GetFileName(e.Name);
    if (string.IsNullOrEmpty(name) || name.StartsWith('.') || name.StartsWith('~'))
        return;  // 过滤 IDE 临时文件
    
    timer.Stop();
    timer.Start();  // 200ms 内无新事件才触发
}

watcher.Changed += OnChange;
watcher.Created += OnChange;
watcher.Deleted += OnChange;
watcher.Renamed += OnChange;

防抖的必要性:一次保存操作可能触发 Changed、Created、Renamed 多个事件,防抖将其合并为一次广播,避免页面重复刷新。

2.2 WebSocket 广播

app.enableWebSocket = true;
app.WsOnConnectionChanged = (_, _) => Task.CompletedTask;

PicoServer 开启 WebSocket 后,通过以下方法广播:

await app.WsBroadcastAsync("reload");

2.3 客户端脚本注入

在返回 HTML 时自动注入 WebSocket 客户端代码,开发者无需手动添加:

if (filePath.EndsWith(".html", StringComparison.OrdinalIgnoreCase))
{
    var content = await File.ReadAllTextAsync(filePath);
    var script = """
        <script>
        new WebSocket('ws://'+location.host+'/ws-reload').onmessage = e => {
            if (e.data === 'reload') location.reload();
        };
        </script>
        """;
    
    content = content.Contains("</body>")
        ? content.Replace("</body>", script + "</body>")
        : content + script;
    
    await res.WriteAsync(content, "text/html");
}

2.4 路径放行

/ws-reload 路径需要交给 WebSocket 处理,静态文件中间件应放行:

if (path == "/ws-reload") return true;

三、完整示例

using PicoServer;
using System.Timers;

var app = new WebAPIServer();
app.enableWebSocket = true;
app.WsOnConnectionChanged = (_, _) => Task.CompletedTask;

string staticRoot = "wwwroot";
var watcher = new FileSystemWatcher(staticRoot) { EnableRaisingEvents = true };
var timer = new System.Timers.Timer(200) { AutoReset = false };
timer.Elapsed += async (_, _) => await app.WsBroadcastAsync("reload");
watcher.Changed += (s, e) => { timer.Stop(); timer.Start(); };

app.AddMiddleware(async (req, res) =>
{
    var path = req.Url?.AbsolutePath ?? "";
    if (path == "/ws-reload") return true;
    
    // 解析文件路径
    var reqPath = path.TrimStart('/');
    if (string.IsNullOrEmpty(reqPath)) reqPath = "index.html";
    var filePath = Path.GetFullPath(Path.Combine(staticRoot, reqPath));
    
    // 安全检查
    if (!filePath.StartsWith(staticRoot) || !File.Exists(filePath))
    {
        res.StatusCode = 404;
        await res.WriteAsync("Not Found");
        return false;
    }
    
    // HTML 文件:注入脚本后返回
    if (filePath.EndsWith(".html", StringComparison.OrdinalIgnoreCase))
    {
        var content = await File.ReadAllTextAsync(filePath);
        var script = """
            <script>
            new WebSocket('ws://'+location.host+'/ws-reload').onmessage = e => {
                if (e.data === 'reload') location.reload();
            };
            </script>
            """;
        content = content.Contains("</body>")
            ? content.Replace("</body>", script + "</body>")
            : content + script;
        await res.WriteAsync(content, "text/html");
    }
    else
    {
        // 非 HTML 文件:直接返回
        await res.SendFileAsync(filePath, false);
    }
    return false;
});

app.StartServer(8080);
Console.WriteLine("热重载已启动: http://localhost:8080");
await Task.Delay(Timeout.Infinite);

四、扩展方向

  • CSS 热更新:广播 css 消息,客户端仅刷新样式链接
  • 多设备同步:局域网内多端同时刷新
  • 差异化刷新:根据文件类型决定刷新策略(如 .css 增量更新,.cshtml 全页刷新)

五、关于 AOT

热重载工具本身也应当保持轻量。Node.js 工具链通常需要安装运行时并下载数百 MB 依赖。

通过 .NET AOT 编译,整个热重载服务器可打包为 4-10 MB 的单文件,无需安装 .NET 运行时,拷贝即用。

六、总结

组件代码量
文件监控 + 防抖15 行
WebSocket 广播1 行
脚本注入10 行
总计26 行

26 行代码,解决开发中最频繁的中断问题。

以上就是C# .NET基于PicoServer的Live Reload实现热重载的完整方案的详细内容,更多关于C#热重载的资料请关注脚本之家其它相关文章!

相关文章

  • C#新手常犯的错误汇总

    C#新手常犯的错误汇总

    这篇文章主要介绍了C#新手常犯的错误汇总,对于经验丰富的C#程序员同样具有很好的参考借鉴价值,需要的朋友可以参考下
    2014-08-08
  • 利用C#开发浏览器扩展的全过程记录

    利用C#开发浏览器扩展的全过程记录

    做web开发的同学,经常会用到各种chrome浏览器插件,那么我们寄几怎么开发一个插件呢(其实是浏览器扩展)?这篇文章主要给大家介绍了关于利用C#开发浏览器扩展的相关资料,需要的朋友可以参考下
    2021-07-07
  • C# Winform 实现控件自适应父容器大小的示例代码

    C# Winform 实现控件自适应父容器大小的示例代码

    这篇文章主要介绍了C# Winform 实现控件自适应父容器大小的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • VS2010写的程序在自己电脑可以运行、其他电脑上不能运行的解决方案

    VS2010写的程序在自己电脑可以运行、其他电脑上不能运行的解决方案

    自己用Visual Studio 2010 旗舰版写了一个软件,在自己电脑上运行完全没有问题,但是拷贝到其他人电脑上之后不管双击还是以管理身份运行,均没有反应,进程管理器中相关进程也只是一闪而过
    2013-04-04
  • C#微信公众平台开发之access_token的获取存储与更新

    C#微信公众平台开发之access_token的获取存储与更新

    这篇文章主要介绍了C#微信公众平台开发之access_token的获取存储与更新的相关资料,需要的朋友可以参考下
    2016-03-03
  • C# wpf嵌入winform控件的示例详解

    C# wpf嵌入winform控件的示例详解

    wpf的强大界面能力,再加上winform的性能以及灵活性,那基本上什么界面都能够做的很好,本文我们就来看看如何在C# wpf中嵌入winform控件吧
    2024-03-03
  • C#实现远程连接ORACLE数据库的方法

    C#实现远程连接ORACLE数据库的方法

    这篇文章主要介绍了C#实现远程连接ORACLE数据库的方法,通过自定义函数db_connection_test实现远程连接Oracle数据库的功能,是非常实用的技巧,需要的朋友可以参考下
    2014-12-12
  • WPF轻松实现进度条的示例代码

    WPF轻松实现进度条的示例代码

    WPF中的ProgressBar控件用于表示任务进度,适用于文件下载、数据处理等场景,本文将通过XAML和C#代码展示如何创建一个基本的WPF进度条,并演示如何通过事件处理程序更新进度条的值来模拟耗时操作,感兴趣的小伙伴跟着小编一起来看看吧
    2024-12-12
  • C#多线程Thread使用示例详解

    C#多线程Thread使用示例详解

    这篇文章主要为大家详细介绍了C#多线程Thread使用示例,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • adonet基础示例分享(adonet连接数据库)

    adonet基础示例分享(adonet连接数据库)

    这篇文章主要介绍了adonet基础示例分享(adonet连接数据库),需要的朋友可以参考下
    2014-04-04

最新评论