What if you could build a desktop application using only .NET and a browser UI — bundled inside your executable, with no installer dependencies? EmbedIO is a lightweight, cross-platform .NET library that embeds a full web server inside your application, letting you use HTML, CSS, and JavaScript as your UI layer while keeping all business logic in C#.
What Is EmbedIO?
EmbedIO is a NuGet library maintained by the Unosquare team. It provides a complete embedded HTTP server for .NET 6+, supporting REST API routing with attribute-based controllers, static file serving from embedded resources or the filesystem, WebSocket bi-directional communication, CORS, compression, and custom middleware — all with no IIS, Kestrel, or external server required.
The Architecture: Desktop App with Browser UI
How It Works
Getting Started
1. Install EmbedIO
dotnet add package EmbedIO
2. Create the Web Server
using EmbedIO;
using EmbedIO.WebApi;
public class Program
{
public static async Task Main(string[] args)
{
var url = "http://localhost:8080";
var server = new WebServer(o => o
.WithUrlPrefix(url)
.WithMode(HttpListenerMode.EmbedIO))
.WithLocalSessionManager()
.WithWebApi("/api", m => m.WithController<ApiController>())
.WithStaticFolder("/", "wwwroot", true);
// Open in system browser
System.Diagnostics.Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
await server.RunAsync();
}
}
3. Define API Controller
using EmbedIO.Routing;
using EmbedIO.WebApi;
public class ApiController : WebApiController
{
[Route(HttpVerbs.Get, "/greeting")]
public async Task<string> GetGreeting()
{
return "Hello from EmbedIO!";
}
[Route(HttpVerbs.Post, "/data")]
public async Task<object> PostData()
{
var data = await HttpContext.GetRequestDataAsync<MyDto>();
return new { success = true, received = data };
}
}
4. Frontend (wwwroot/index.html)
<!DOCTYPE html>
<html>
<body>
<h1>My Desktop App</h1>
<button onclick="callApi()">Fetch Greeting</button>
<div id="result"></div>
<script>
async function callApi() {
const resp = await fetch('/api/greeting');
document.getElementById('result').textContent = await resp.text();
}
</script>
</body>
</html>
WebSockets for Real-Time Communication
using EmbedIO.WebSockets;
public class ChatModule : WebSocketModule
{
public ChatModule(string urlPath) : base(urlPath, true) { }
protected override Task OnMessageReceivedAsync(
IWebSocketContext context,
byte[] buffer,
IWebSocketReceiveResult result)
{
var message = Encoding.UTF8.GetString(buffer);
return BroadcastAsync($"Echo: {message}");
}
}
// Register in server setup:
server.WithModule(new ChatModule("/chat"));
EmbedIO vs. Traditional Desktop Frameworks
| Aspect | WinForms / WPF | Electron | EmbedIO + Browser |
|---|---|---|---|
| Platform | Windows only | Cross-platform | Cross-platform |
| UI Language | XAML / WinForms | HTML/CSS/JS | HTML/CSS/JS |
| Runtime Size | ~50–200 MB | ~150–300 MB | ~10–30 MB |
| Backend Language | C# / VB.NET | Node.js / JS | C# / .NET |
| Startup Speed | Fast | Slow | Fast |
| Modern UI | Difficult | Easy | Easy |
| .NET Integration | Native | Via IPC | Native |
Self-Contained Single-File Deployment
To create a truly self-contained single-file executable, embed your HTML/CSS/JS files as resources and serve them from memory:
<!-- In .csproj -->
<EmbeddedResource Include="wwwroot**" />
server.WithEmbeddedResources("/", typeof(Program).Assembly, "MyApp.wwwroot");
Conclusion
EmbedIO is an excellent choice for developers who want to build cross-platform desktop applications with a modern web UI while keeping all logic in .NET/C#. It is far lighter than Electron, more flexible than WinForms, and doesn't require learning a new framework. For internal tools, admin panels, dashboards, and utilities, EmbedIO is a highly practical and underutilized solution in the .NET ecosystem.