张赐荣,视障者,信息无障碍专家
深耕Web/PC/移动端可访问性研究与实践工作多年,对跨平台无障碍解决方案拥有深刻的独特理论和丰富的实战经验。
精通视障用户软件交互设计,致力于用专业的能力改善、提升产品可及性体验。

張賜榮

张赐荣的技术博客

博客园 首页 新随笔 联系 订阅 管理

C# 控制台/桌面程序利用 EmbedIO 嵌入式微型Web框架提供 HTTP 服务

在日常使用c# .NET Framework开发过程中,我们经常会和WinForms、WPF或控制台程序打交道。它们是构建桌面工具、后台服务的利器。但你有没有想过,如果能让你桌面上那个安静运行的小程序,摇身一变,成为一个能被手机、浏览器、甚至其他程序访问的Web服务器呢?

听起来是不是很酷?今天,我们就来聊聊,如何在C#控制台/Winforms程序中,使用 EmbedIO 嵌入一个 Web服务器,提供HTTP访问。

一、 为什么要在WinForms里提供Web服务?

我们知道,通常Web服务是由ASP.NET这样的专业框架,运行在IIS或Kestrel上的。为什么要在一个桌面程序里做这件事呢?

1. 传统方式的困境

如果你尝试过用.NET Framework内置的HttpListener来实现,你可能会深有体会:

  • 代码繁琐:需要手动编写循环来监听请求,处理请求流和响应流,代码量不小。
  • 线程阻塞:监听操作是阻塞的,必须手动开启一个后台线程,否则你的WinForms界面会直接卡死。
  • 功能原始:路由、参数绑定、JSON序列化这些现代Web框架的标配功能,统统没有,全得自己造轮子。

2. 实际的应用场景

其实,在很多场景下,这种“嵌入式”Web服务非常实用:

  • 远程控制:做一个“手机遥控器”,躺在床上用手机浏览器就能控制电脑关机、调节音量。
  • 状态面板:你的程序可能在执行一个复杂的长时间任务(比如数据处理),可以通过Web页面,让其他设备实时查看任务进度。
  • 数据接口(API):为你的桌面应用提供一个简单的API,让手机App或者其他脚本能与之交互。
  • 跨设备桥梁:做一个“文件中转站”,手机扫码就能把文件快速传到电脑上。

面对这些需求,我们就需要一个简单易用、功能全面的嵌入式Web框架。于是,今天的主角——EmbedIO——闪亮登场!

二、 EmbedIO 简介

1. 什么是EmbedIO?

EmbedIO 是一个基于.NET Standard开发的,轻量、跨平台、模块化的嵌入式微型Web框架

这个定义有点长,我们拆解一下:

  • 嵌入式:它的设计初衷就是被“嵌入”到你的现有应用中,而不是作为一个大而全的Web后端程序独立运行。
  • 微型框架:它的核心非常小,只提供Web开发的核心基础功能,但扩展性极强。

2. 它和ASP.NET有什么不同?

如果说ASP.NET Core是一个豪华餐厅的完整后厨,拥有从切菜、烹饪到摆盘的全套顶级设备,那么EmbedIO就是一个精致、便携的瑞士军刀式料理工具箱

  • ASP.NET:专为构建大型、高性能、复杂的Web应用和网站而生,功能全面但体量也大且相对复杂。
  • EmbedIO:专注于为非Web应用快速、简单地添加Web服务能力,轻量、易用、上手快。

对于我们上面提到的那些“小而美”的应用场景,EmbedIO无疑是最合适的选择。

三、 快速上手EmbedIO

废话不多说,我们直接上代码!让我们来创建一个WinForms应用,提供几个简单的WebAPI。

第一步:安装EmbedIO库

在WinForms项目上,右键 -> “管理NuGet程序包”,搜索EmbedIO并安装。
或者,在程序包管理器控制台中输入:

Install-Package EmbedIO

编写我们的第一个API控制器

新建一个C#类,比如叫MyApiController.cs。这个类将负责处理我们所有的API请求。

using EmbedIO;
using EmbedIO.Routing;
using EmbedIO.WebApi;
using System.Threading.Tasks;

// 定义一个用于POST请求的数据模型(一个简单的C#类)
public class EchoRequest
{
    public string Message { get; set; }
    public int SenderId { get; set; }
}

// 我们的核心API控制器,必须继承自 WebApiController
public class MyApiController : WebApiController
{
    // 1. 最简单的GET请求
    // [Route(HttpVerbs.Get, "/hello")] 定义了一个路由规则:
    // 当一个GET请求访问 "/hello" 路径时,执行此方法。
    [Route(HttpVerbs.Get, "/hello")]
    public string SayHello()
    {
        // 直接返回一个字符串,EmbedIO会自动处理响应。
        return "Hello from EmbedIO in WinForms!";
    }

    // 2. 带路由参数的GET请求
    // 这里的 {id} 是一个占位符,它会捕获URL中这个位置的值。
    // 例如,访问 /users/123, id参数就会被赋值为123。
    [Route(HttpVerbs.Get, "/users/{id}")]
    public object GetUserById(int id) // 注意:这个id参数名必须和路由里的{id}占位符同名!
    {
        // 返回一个匿名对象,EmbedIO会自动将其序列化为JSON字符串!
        // {"userId":123,"name":"User-123"}
        return new { userId = id, name = $"User-{id}" };
    }
    
    // 3. 带查询参数的GET请求
    // 这种路由不包含占位符,参数从URL的查询字符串中获取。
    // 例如, 访问 /search?keyword=embedio
    [Route(HttpVerbs.Get, "/search")]
    public object Search([QueryField]string keyword) // 参数名'keyword'与查询字符串的键名对应
    {
        return new { searchingFor = keyword, resultCount = new System.Random().Next(100) };
    }

    // 4. 处理POST请求,并接收JSON数据
    // 当一个POST请求访问 "/echo" 时,执行此方法。
    [Route(HttpVerbs.Post, "/echo")]
    public async Task<object> EchoMessage()
    {
        // 从请求体中异步读取数据,并自动反序列化为我们定义的EchoRequest对象。
        var requestData = await HttpContext.GetRequestDataAsync<EchoRequest>();

        // 将收到的数据加上一个状态,再返回给客户端。
        return new { status = "OK", received = requestData };
    }
}

第三步:在WinForms中启动Web服务器

打开你的主窗体(比如Form1.cs)的代码。添加一个启动按钮和一个停止按钮。

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using EmbedIO;

public partial class Form1 : Form
{
    private WebServer server;
    private CancellationTokenSource cts;

    public Form1()
    {
        InitializeComponent();
    }

    // “启动服务器”按钮的点击事件
    private void btnStart_Click(object sender, EventArgs e)
    {
        if (server != null)
        {
            MessageBox.Show("服务器已经启动!");
            return;
        }

        // 启动服务器
        StartWebServer();
    }

    private void StartWebServer()
    {
        // 定义服务器监听的URL和端口
        const string url = "http://localhost:9696";

        // 创建CancellationTokenSource,用于之后优雅地停止服务器
        cts = new CancellationTokenSource();

        // 使用 Task.Run 在后台线程中启动服务器,避免阻塞UI线程!
        Task.Run(() =>
        {
            // 创建并配置WebServer实例
            // 这种 o => ... 的写法是一种现代C#的配置方式,非常方便
            server = new WebServer(o => o
                .WithUrl(url) // 设置监听URL
                .WithMode(HttpListenerMode.EmbedIO)) // 设置工作模式
            // .WithWebApi(...) 是用来注册我们的API控制器的模块
            // 第一个参数"/api"是基础路径,意味着所有API的URL都以 /api 开头
            // 第二个参数 m => ... 是对API模块的配置,我们告诉它去加载 MyApiController
            .WithWebApi("/api", m => m.WithController(typeof(MyApiController)));
            
            // 启动服务器,并传入取消令牌
            server.RunAsync(cts.Token);
        }, cts.Token);

        MessageBox.Show($"服务器已启动,监听地址: {url}");
    }

    // “停止服务器”按钮的点击事件
    private void btnStop_Click(object sender, EventArgs e)
    {
        // 通过CancellationToken发出取消信号
        cts?.Cancel();
        server?.Dispose(); // 释放资源
        server = null;
        MessageBox.Show("服务器已停止。");
    }

    // 确保窗体关闭时,服务器也能被正确停止
    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        cts?.Cancel();
        server?.Dispose();
    }
}

现在,运行这个WinForms程序,点击“启动服务器”按钮。然后就可以通过浏览器或Postman等工具来测试你的API了!

  • GET http://localhost:9696/api/hello
  • GET http://localhost:9696/api/users/123
  • GET http://localhost:9696/api/search?keyword=winforms
  • POST http://localhost:9696/api/echo (请求体为JSON: {"message":"Hi","senderId":101})

四、 总结

通过上面的演示,相信你已经感受到了EmbedIO的简洁与强大。我们只用了很少的代码,就为一个WinForms应用添加了功能完善的Web API,并且涵盖了GET、POST、路由参数、查询参数等核心功能。

当然,EmbedIO的功能远不止于此,它还提供了非常丰富的功能模块,比如:

  • 静态文件服务 (WithStaticFolder):可以直接托管一个前端网站。
  • WebSocket支持 (WithWebSocket):实现实时双向通信。
  • 认证与授权:通过自定义模块保护你的API。
  • Swagger API文档:自动生成漂亮的API文档页面。

纸上得来终觉浅,绝知此事要躬行。希望这篇入门教程能为你打开一扇新的大门。如果你想深入学习更多进阶用法,官方的GitHub仓库和文档是最好的老师:

官方文档:https://github.com/unosquare/embedio

posted on 2025-08-15 09:57  张赐荣  阅读(139)  评论(0)    收藏  举报

感谢您访问张赐荣的技术分享博客!
博客地址:https://cnblogs.com/netlog/
知乎主页:https://www.zhihu.com/people/tzujung-chang
个人网站:https://prc.cx/