<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andreas Nägeli</title>
    <description>The latest articles on DEV Community by Andreas Nägeli (@kaliumhexacyanoferrat).</description>
    <link>https://dev.to/kaliumhexacyanoferrat</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2315728%2Fbdc60a04-0520-415e-9bff-456a728f198e.png</url>
      <title>DEV Community: Andreas Nägeli</title>
      <link>https://dev.to/kaliumhexacyanoferrat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kaliumhexacyanoferrat"/>
    <language>en</language>
    <item>
      <title>TechEmpower Framework Benchmarks are now Archived - What's next?</title>
      <dc:creator>Andreas Nägeli</dc:creator>
      <pubDate>Tue, 24 Mar 2026 11:48:56 +0000</pubDate>
      <link>https://dev.to/kaliumhexacyanoferrat/techempower-framework-benchmarks-are-now-archived-whats-next-3l0a</link>
      <guid>https://dev.to/kaliumhexacyanoferrat/techempower-framework-benchmarks-are-now-archived-whats-next-3l0a</guid>
      <description>&lt;p&gt;On 24th of March 2026, the &lt;a href="https://www.techempower.com/benchmarks/#section=data-r23" rel="noopener noreferrer"&gt;TechEmpower Framework Bechmarks&lt;/a&gt; have been discontinued, with their GitHub repository now &lt;a href="https://github.com/TechEmpower/FrameworkBenchmarks/issues/10932" rel="noopener noreferrer"&gt;in archived mode&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Start
&lt;/h1&gt;

&lt;p&gt;The first round of the TechEmpower Framework Benchmarks dates back to &lt;a href="https://www.techempower.com/blog/2013/03/28/framework-benchmarks/" rel="noopener noreferrer"&gt;March 2013&lt;/a&gt;, covering only as much as 25 different frameworks. Within the following years, all major HTTP web framework implementations where added through contributions to their public GitHub repository, making the project an important litmus test for the performance characteristics of HTTP webservers and application development frameworks. In the most recent  &lt;a href="https://www.techempower.com/benchmarks/#section=data-r23&amp;amp;test=json" rel="noopener noreferrer"&gt;Round 23&lt;/a&gt; from February 2025, more than 330 framework implementations were covered.&lt;/p&gt;

&lt;p&gt;As it got more and more traction, having good results became a top priority for framework developers for their new software releases. In &lt;a href="https://www.techempower.com/blog/2016/11/16/framework-benchmarks-round-13/" rel="noopener noreferrer"&gt;round 13&lt;/a&gt;, Microsoft started to sponsor the project with hardware and used their now first position in the benchmarks to fuel their marketing for ASP.NET Core. Overall, the benchmark's sole existence lead to way faster frameworks for all of us.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Decline
&lt;/h1&gt;

&lt;p&gt;Although results have continued to be published once a year, there have been virtually no changes or updates to the underlying test platform in recent years. High performance frameworks were capped in plaintext at roughly 30 milion requests per second, due to a combination of &lt;code&gt;wrk&lt;/code&gt; and large request headers being sent by the tests. Even though framework developers adressed such topics, the maintainers of TechEmpower did not show much effort to improve those points.&lt;/p&gt;

&lt;p&gt;Additionally, the maintainers allowed frameworks to use low-level tricks to get the most out of their framework, causing the results to reflect more a socket benchmark then actual real-life implementations used by end-users. In this regard, the results currently shown are highly missleading and do not reflect typical use cases such as providing a high performance REST API. A large number of "frameworks" listed in the current round would probably even fail basic &lt;a href="https://mda2av.github.io/Http11Probe/probe-results/" rel="noopener noreferrer"&gt;compliance tests&lt;/a&gt;, so they only exist to be shown within the framework results in the first place.&lt;/p&gt;

&lt;p&gt;After all, people interested in improving the platform got the impression that the benchmarks are no longer a priority by TechEmpower, and rightly so as the project is now archived.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Future
&lt;/h1&gt;

&lt;p&gt;Besides TechEmpower FrameworkBenchmarks, there was not much competition - there are the &lt;a href="https://web-frameworks-benchmark.netlify.app/result" rel="noopener noreferrer"&gt;Web Framework Benchmarks&lt;/a&gt;, but they are nowhere comparable to the level of detail shown by the FrameworkBenchmarks from TechEmpower.&lt;/p&gt;

&lt;p&gt;A few months ago, a new project, &lt;a href="https://mda2av.github.io/HttpArena/leaderboard/" rel="noopener noreferrer"&gt;HttpArena&lt;/a&gt;, was started to adress the pain points with TechEmpower FrameworkBenchmarks. They show a large variety of benchmarks, include new technologies such as HTTP/2 or websockets and have most of the major frameworks already covered. In contrast to FrameworkBenchmarks, they run benchmarks within the created PRs as well as when PRs are merged, giving developers feedback way faster. On top, they promise to keep the framework implementations realistic, so the results should be more credible than those of the FrameworkBenchmarks, making them a better tool for developers and architects deciding on technology.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zqb4l5hp2hr88ponhmj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zqb4l5hp2hr88ponhmj.png" alt="Leaderboard in HttpArena" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;TechEmpower FrameworkBenchmarks did an amazing job in making performance of web frameworks visible, and caused the overall performance of those frameworks to be greatly improved through their transparent results.&lt;/p&gt;

&lt;p&gt;We will see, whether &lt;a href="https://mda2av.github.io/HttpArena/leaderboard/" rel="noopener noreferrer"&gt;HttpArena&lt;/a&gt; or any other platform will continue this work, important for the web today and in the future.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover picture by &lt;a href="https://unsplash.com/de/@julianhochgesang" rel="noopener noreferrer"&gt;Julian Hochgesang&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>web</category>
      <category>benchmark</category>
      <category>http</category>
    </item>
    <item>
      <title>Beyond ASP.NET: Lightweight Alternatives for C# Web Development</title>
      <dc:creator>Andreas Nägeli</dc:creator>
      <pubDate>Mon, 22 Dec 2025 13:44:50 +0000</pubDate>
      <link>https://dev.to/kaliumhexacyanoferrat/beyond-aspnet-lightweight-alternatives-for-c-web-development-2ohi</link>
      <guid>https://dev.to/kaliumhexacyanoferrat/beyond-aspnet-lightweight-alternatives-for-c-web-development-2ohi</guid>
      <description>&lt;p&gt;Many programming languages offer a wide variety of web servers and development frameworks for different target groups and project types. In the C# world, ASP.NET Core is generally considered the default choice. To broaden this perspective, this article provides an overview of all currently available web servers and development frameworks in the .NET ecosystem.&lt;/p&gt;

&lt;p&gt;We will highlight active development projects that enable the hosting of web applications. Add-ons for ASP.NET Core (such as FastEndpoints) are not covered in this overview. Furthermore, no overall rating or ranking is given - the frameworks are described in alphabetical order. Nevertheless, there is an overview table for easy comparison of the projects presented here.&lt;/p&gt;

&lt;p&gt;For clarity, we group the projects according to their activity into actively developed and no longer active projects. We will only examine the group of actively developed projects in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Actively Developed Servers&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ASP.NET Core&lt;/li&gt;
&lt;li&gt;GenHTTP&lt;/li&gt;
&lt;li&gt;Sisk&lt;/li&gt;
&lt;li&gt;SimpleW&lt;/li&gt;
&lt;li&gt;Unhinged&lt;/li&gt;
&lt;li&gt;Watson&lt;/li&gt;
&lt;li&gt;Wired.IO&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Summary&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Legacy Servers&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EmbedIO&lt;/li&gt;
&lt;li&gt;NetCoreServer&lt;/li&gt;
&lt;li&gt;Nancy&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Conclusion&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a id="active"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Actively Developed Servers
&lt;/h2&gt;

&lt;p&gt;This section covers all actively maintained and supported web server frameworks for .NET.&lt;/p&gt;

&lt;p&gt;&lt;a id="aspnetcore"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ASP.NET Core
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5n5wu9xrzmx1024rb1j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5n5wu9xrzmx1024rb1j.png" alt="ASP.NET Core" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on libuv, the library that significantly influenced Node.js, Microsoft modernized the aging ASP.NET with &lt;a href="https://dotnet.microsoft.com/en-us/apps/aspnet" rel="noopener noreferrer"&gt;ASP.NET Core&lt;/a&gt; starting in 2014. Later, Kestrel, a .NET-based engine, was added as a modern foundation. Minimal APIs marked ASP.NET Core’s arrival in modern web development in 2021.&lt;/p&gt;

&lt;p&gt;From both a functional and technical perspective, ASP.NET Core leaves little to be desired: it supports various API styles, common middleware such as compression and caching, and implements all common authentication mechanisms. ASP.NET Core is the only framework in this list that offers native support for HTTP/2 and HTTP/3. Native, and therefore first-class, support for features sometimes depends on Microsoft's positioning of its own solutions – for example, ASP.NET Core does not provide native support for server sent events (SSE) because Microsoft promotes its in-house solution, SignalR, instead. Since there are many dependencies to consider for such a widely used framework, new technologies are sometimes only supported after a delay, as can be seen in the example of Minimal APIs. In the end, almost all use cases can be implemented with ASP.NET Core, even if this often requires the use of community guides and tutorials.&lt;/p&gt;

&lt;p&gt;Microsoft sees ASP.NET Core less as a framework that can simply be added to a .NET project and more as a standalone ecosystem. This prevents use cases such as embedding web servers in other applications such as MAUI or WPF apps. It also links the use of ASP.NET Core with other dependencies such as configuration handling, which steepens the learning curve.&lt;/p&gt;

&lt;p&gt;In 2016, Microsoft focused on optimizing performance, resulting in ASP.NET Core being the fastest web server framework in TechEmpower Framework Benchmarks Round 13. While other frameworks have been further optimized, Microsoft has rested on its laurels, with the platform achieving 79% and Minimal APIs 64% of maximum performance in TechEmpower's &lt;a href="https://www.techempower.com/benchmarks/#section=test&amp;amp;runid=21202626-cf55-4d6b-8c15-6d3ed285a7b4&amp;amp;test=json" rel="noopener noreferrer"&gt;latest JSON test&lt;/a&gt;. This means that ASP.NET Core is still very fast, but no longer the leader.&lt;/p&gt;

&lt;p&gt;In essence, ASP.NET Core is the best choice for teams building production-grade web services that require a mature ecosystem, long-term support, and seamless integration with cloud and enterprise infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;a id="genhttp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  GenHTTP
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fli8w9hykmi3bnx88wo1w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fli8w9hykmi3bnx88wo1w.png" alt="GenHTTP" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://genhttp.org/" rel="noopener noreferrer"&gt;GenHTTP&lt;/a&gt; was originally developed as an application server in 2008 and underwent a fundamental modernization in 2019. Similar to most frameworks on this list, it focuses on providing a simple and fast developer experience.&lt;/p&gt;

&lt;p&gt;GenHTTP can be used to host REST web services in various styles, web sockets, server-sent events, static files, and single-page applications. After ASP.NET Core, this framework offers the widest range of functions and also includes, for example, a module for reverse proxies, server-side cached content, or mTLS authentication.&lt;/p&gt;

&lt;p&gt;GenHTTP aims to make project development as easy as possible for developers, while also offering extensive customization options. For example, the framework allows the addition of further serializers or compression algorithms that are permanently integrated into other frameworks. This results in greater complexity and a slightly steeper learning curve, but GenHTTP generally hides this well from its users. There is support for dependency injection, but built on top of the framework and not as native as in Wired.IO.&lt;/p&gt;

&lt;p&gt;Similar to ASP.NET Core, GenHTTP allows the underlying engine to be exchanged – projects can also be hosted on Kestrel and, conversely, GenHTTP functionalities can be used in ASP.NET Core via adapters. This enables support for HTTP/2 and HTTP/3 via Kestrel, even if the internal engine itself only supports HTTP/1.1. In addition, there is an adapter for Wired.IO.&lt;/p&gt;

&lt;p&gt;GenHTTP requires .NET 8 or higher. Support is provided via GitHub or a Discord community, but the website also offers commercial support options.&lt;/p&gt;

&lt;p&gt;In TechEmpower's FrameworkBenchmarks, GenHTTP achieves 46%, one of the lower results on this list.&lt;/p&gt;

&lt;p&gt;To summarize, GenHTTP is well suited for developers who want a flexible, embeddable web server framework with a broad feature set and full control over protocol- and infrastructure-level behavior.&lt;/p&gt;

&lt;p&gt;&lt;a id="sisk"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sisk
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7nvce6dyz9wdml7leqb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7nvce6dyz9wdml7leqb.png" alt="Sisk" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.sisk-framework.org/" rel="noopener noreferrer"&gt;Sisk&lt;/a&gt; was created by Brazilian developer Adão with the first release appearing around 2022. It focuses on a declarative, dependency-free style and thus on a simpler developer experience than ASP.NET Core.&lt;/p&gt;

&lt;p&gt;Sisk has a strong focus on enabling developers to quickly deploy web applications. The documentation is very clear and practical. Those who can live with the range of functions will quickly be able to set up lean and clear projects, including websockets and server sent events. Advanced features such as JWT authentication, caching directives, native dependency injection or SSL headers are missing. The server framework is actively being developed, with several commits per week. Support outside of the GitHub repository is not provided.&lt;/p&gt;

&lt;p&gt;Sisk requires .NET version 8 or higher; .NET Standard and the .NET Framework are not supported. As with the other frameworks, only HTTP/1.1 is implemented.&lt;/p&gt;

&lt;p&gt;There is an ongoing effort to provide a new engine implementation (cadente) with improved performance. For now, Sisk achieves around 5% in TechEmpower's FrameworkBenchmarks, 43% with the new engine, making it the slowest server on this list.&lt;/p&gt;

&lt;p&gt;To sum up: Sisk is a good fit for small, focused web services where simplicity, minimal dependencies, and a declarative programming model are more important than a comprehensive feature set.&lt;/p&gt;

&lt;p&gt;&lt;a id="simplew"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  SimpleW
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feyg9phcje1xg4gu6b7bj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feyg9phcje1xg4gu6b7bj.png" alt="SimpleW" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Started in 2023, &lt;a href="https://stratdev3.github.io/SimpleW/" rel="noopener noreferrer"&gt;SimpleW&lt;/a&gt; provides controller style REST APIs and also supports websockets and server sent events. It shows a lot of activity from the second half of 2025 on, making it an actively developed project.&lt;/p&gt;

&lt;p&gt;Similar to GenHTTP and Sisk, SimpleW aims to enable developers to quickly develop web applications with minimal code. However, in direct comparison to these frameworks, it may provide a less clear API and documentation - which is likey due to its strong integration with the controller pattern. For the functionality being provided, SimpleW has a pretty small code base, alas advanced features such as Open API support or SSL headers are missing. In contrast to the other frameworks in this list, SimpleW has explicit support for telemetry.&lt;/p&gt;

&lt;p&gt;As with GenHTTP and Sisk, SimpleW runs on .NET 8 onwards. Support outside of the GitHub repository is not provided, and only HTTP/1.1 is implemented.&lt;/p&gt;

&lt;p&gt;Originally based on NetCoreServer, SimpleW is currently undergoing efforts to add its own engine, yielding impressive 71% in TechEmpower's FrameworkBenchmark, and therefore being faster than Minimal APIs on ASP.NET Core.&lt;/p&gt;

&lt;p&gt;In essence, SimpleW is suitable for developers who prefer a controller-based programming model and want to build REST APIs quickly while retaining strong performance characteristics.&lt;/p&gt;

&lt;p&gt;&lt;a id="unhinged"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Unhinged
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxapnmgqfi32xn0dh8n5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxapnmgqfi32xn0dh8n5.png" alt="Unhinged" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MDA2AV/unhinged" rel="noopener noreferrer"&gt;Unhinged&lt;/a&gt; is an experimental HTTP/1.1 webserver engine that, while being nowhere near production-ready, shows how .NET web servers could once again be among the fastest servers in the future. In TechEmpower's FrameworkBenchmark, Unhinged achieves 98.4%, allowing it to claim the third place overall. It will be interesting to see how this development will influence other web server frameworks, including ASP.NET Core itself.&lt;/p&gt;

&lt;p&gt;In essence, Unhinged is primarily of interest to developers exploring the performance limits of HTTP servers in .NET and is not intended for building production web applications.&lt;/p&gt;

&lt;p&gt;&lt;a id="watson"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Watson
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ksimsw13rrf9kv4w5c8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ksimsw13rrf9kv4w5c8.png" alt="Watson" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dotnet/WatsonWebserver" rel="noopener noreferrer"&gt;Watson&lt;/a&gt; has been around for several years, is part of the .NET Foundation and receives large commits approximately once a year.&lt;/p&gt;

&lt;p&gt;While providing high-level features such as Open API integration, Watson positions itself more as a low level HTTP server library than a web service development framework, making it more comparable to Unhinged and Wired.IO. For example, Watson expects you to implement chunked transfer encoding of responses yourself, features such as JWT authentication, CORS or compression are not provided and expected to be implemented by the user. Watson uses &lt;code&gt;http.sys&lt;/code&gt; to listen for incoming HTTP requests on Windows, providing a managed wrapper around this OS functionality. For Unix, there is &lt;code&gt;Watson.Lite&lt;/code&gt; with no dependency on &lt;code&gt;http.sys&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In contrast to other frameworks on this list, Watson supports a wide variety of target frameworks, from .NET 4.6.1 to &lt;code&gt;netstandard2.1&lt;/code&gt; and .NET 10, making it a possible choice for legacy projects or exotic use cases such as embedding into a Unity game. As with the other frameworks, Watson only supports HTTP/1.1. Support outside of the GitHub repository is not provided.&lt;/p&gt;

&lt;p&gt;Watson is not included in TechEmpower's FrameworkBenchmark.&lt;/p&gt;

&lt;p&gt;In summary, Watson is a reasonable choice for scenarios that require low-level access to HTTP primitives, broad framework compatibility, or integration into legacy or embedded applications.&lt;/p&gt;

&lt;p&gt;&lt;a id="wiredio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Wired.IO
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flb667zvv4okfhm52c7ku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flb667zvv4okfhm52c7ku.png" alt="Wired.IO" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Started in December 2024, &lt;a href="https://mda2av.github.io/Wired.IO.Docs/" rel="noopener noreferrer"&gt;Wired.IO&lt;/a&gt; is one of the youngest frameworks on this list. It provides full control in handling HTTP requests at a low level, up to replacing the HTTP parser with a custom one. In contrast to the other frameworks on this list, Wired.IO provides first-class support for dependency injection and logging.&lt;/p&gt;

&lt;p&gt;While Wired.IO allows low-level interactions such as directly writing a HTTP response, it provides several mechanisms to achieve routing, middlewares, JSON serialization, hosting static files and single page applications, making it a modern, production-ready framework for webservice development and a potential replacement for Watson. The documentation is clear and up to date. High level features such as Open API integration or CORS are not provided, but can be seamlessly added using an adapter that allows to plug in GenHTTP modules into a Wired.IO application.&lt;/p&gt;

&lt;p&gt;Support is provided via GitHub and a Discord community. Wired.IO requires at least .NET 9, and provides a HTTP/1.1 implementation.&lt;/p&gt;

&lt;p&gt;In TechEmpower's FrameworkBenchmark, Wired.IO achieves 76%, with a marginal difference to ASP.NET Core and way faster than Minimal APIs.&lt;/p&gt;

&lt;p&gt;In conclusion, Wired.IO is well suited for advanced use cases that require fine-grained control over HTTP request handling while still benefiting from modern abstractions such as dependency injection and logging.&lt;/p&gt;

&lt;p&gt;&lt;a id="summary"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The following tables provide a high-level comparison of the frameworks discussed above. They are not intended as a ranking, but as a compact reference to highlight differences in scope, focus, and capabilities.&lt;/p&gt;

&lt;p&gt;The criteria are intentionally broad and reflect typical requirements when building web services in .NET. Not all criteria are equally relevant for every framework, as some projects deliberately focus on low-level control or a narrow set of use cases rather than comprehensive feature coverage.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;&lt;a href="https://dotnet.microsoft.com/en-us/apps/aspnet" rel="noopener noreferrer"&gt;ASP.NET&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://genhttp.org" rel="noopener noreferrer"&gt;GenHTTP&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://www.sisk-framework.org/" rel="noopener noreferrer"&gt;Sisk&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://stratdev3.github.io/SimpleW/" rel="noopener noreferrer"&gt;SimpleW&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Webservices&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Websockets&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server Sent Events&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static Files&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SPA Support&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure Services&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency Injection&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;🔵🔵🔵&lt;/td&gt;
&lt;td&gt;🔵🔵&lt;/td&gt;
&lt;td&gt;🔵&lt;/td&gt;
&lt;td&gt;🔵🔵🔵🔵&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embeddable&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AoT&lt;/td&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP Versions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1.1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1.1&lt;/code&gt; (&lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frameworks&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;= dotnet6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;= dotnet8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;= dotnet8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;= dotnet8&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last Release&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12/2025&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12/2025&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12/2025&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;11/2025&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Stars&lt;/td&gt;
&lt;td&gt;&lt;code&gt;37.5k&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;242&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;198&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;81&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;&lt;a href="https://github.com/MDA2AV/unhinged" rel="noopener noreferrer"&gt;Unhinged&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://github.com/dotnet/WatsonWebserver" rel="noopener noreferrer"&gt;Watson&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://mda2av.github.io/Wired.IO.Docs/" rel="noopener noreferrer"&gt;Wired.IO&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Webservices&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Websockets&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server Sent Events&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static Files&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SPA Support&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure Services&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency Injection&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;🔵🔵🔵🔵🔵&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;🔵🔵🔵🔵&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embeddable&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AoT&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP Versions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frameworks&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;= dotnet9&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;any&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;= dotnet9&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last Release&lt;/td&gt;
&lt;td&gt;&lt;code&gt;11/2025&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12/2025&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12/2025&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Stars&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;457&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;35&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a id="legacy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Legacy Servers
&lt;/h2&gt;

&lt;p&gt;This section covers web server frameworks that are no longer actively developed. Legacy frameworks may still be viable for maintaining existing systems, but are generally not recommended for new projects due to limited development activity and ecosystem support.&lt;/p&gt;

&lt;p&gt;&lt;a id="embedio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  EmbedIO
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/unosquare/embedio" rel="noopener noreferrer"&gt;Embedio&lt;/a&gt; was one of the first lightweight web server frameworks for C#. Accordingly, the library has over 1600 stars on GitHub and is still used in legacy software or even for new, simple projects. The last major changes to the server date back to March 2020, and there are increasing difficulties with its use in current technologies. As the engine uses the deprecated &lt;code&gt;HttpListener&lt;/code&gt; underneath,  EmbedIO ranks 497th out of 520 framework in TechEmpower's FrameworkBenchmark, with only 0.5%.&lt;/p&gt;

&lt;p&gt;&lt;a id="netcoreserver"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  NetCoreServer
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/chronoxor/NetCoreServer" rel="noopener noreferrer"&gt;NetCoreServer&lt;/a&gt; is a socket library that can also be used for serving HTTP requests. Since end of 2022, there has not been any meaningful activity. NetCoreServer achieves 53% in TechEmpower's FrameworkBenchmark.&lt;/p&gt;

&lt;p&gt;&lt;a id="nancy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Nancy
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://nancyfx.org/" rel="noopener noreferrer"&gt;Nancy&lt;/a&gt; was one of the oldest frameworks, with its first release in 2010. It has officially been archived in 2021. In TechEmpower's FrameworkBenchmark, it achieved 14%.&lt;/p&gt;

&lt;p&gt;&lt;a id="conclusion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The .NET ecosystem is often perceived through the lens of ASP.NET Core - and for good reason, given its maturity, breadth, and strong industry backing. However, as this overview shows, ASP.NET Core is only one of several viable approaches to building web-facing applications on .NET.&lt;/p&gt;

&lt;p&gt;Beyond the mainstream, the ecosystem offers a diverse range of web servers and frameworks that address different priorities: from minimalism and embeddability to low-level control and experimental performance research. These alternatives may not aim to replace ASP.NET Core, but they expand the solution space and enable use cases that would otherwise be difficult or impractical to address.&lt;/p&gt;

&lt;p&gt;Choosing the right framework is therefore less about finding a universal “best” option and more about understanding the trade-offs each project makes. By looking beyond the default choice, developers can better align their tools with the specific technical and organizational constraints of their projects - and fully leverage the versatility of the .NET platform.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover picture by &lt;a href="https://unsplash.com/de/@markuswinkler" rel="noopener noreferrer"&gt;Markus Winkler&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>webserver</category>
      <category>restapi</category>
    </item>
    <item>
      <title>An Incomplete Guide to Product Development</title>
      <dc:creator>Andreas Nägeli</dc:creator>
      <pubDate>Wed, 17 Dec 2025 15:11:42 +0000</pubDate>
      <link>https://dev.to/kaliumhexacyanoferrat/an-incomplete-guide-to-product-development-2pb</link>
      <guid>https://dev.to/kaliumhexacyanoferrat/an-incomplete-guide-to-product-development-2pb</guid>
      <description>&lt;p&gt;During the last years, I have seen a lot of companies struggling to setup their processes that allow them to efficiently develop software products and services they can sell to their customers. In this post, we will try to identify the main concerns and responsibilities that need to be adressed for successful product development and provide practical guides on how a possible process structure could look like.&lt;/p&gt;

&lt;h2&gt;
  
  
  But there is SAFe?
&lt;/h2&gt;

&lt;p&gt;You might argue that we do not need to do this exercise as we already have SAFe (or LeSS, Spotify, OKRs ... you name it). Those frameworks work very well if you control the whole problem space and you can either develop internal products or have other companies developing them for you. But as a company having a dozen or even hundreds of customers, your customers will bring their own frameworks with them, expecting you to be integrated in &lt;em&gt;their&lt;/em&gt; processes.&lt;/p&gt;

&lt;p&gt;This creates a world where we have one or multiple product contexts as well as a potentially large number of customer contexts. However you will structure your processes and teams, the different, often conflicting needs the existence of those contexts implies will be there - if you address them or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  So - What are the Needs?
&lt;/h2&gt;

&lt;p&gt;To get a clearer picture of what we actually would like to solve, let us have a look at the different stakeholders in the overall process:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stakeholder&lt;/th&gt;
&lt;th&gt;Needs and Expectations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Customers&lt;/td&gt;
&lt;td&gt;They want a solution to their problem, and they want it cheap and right now. They expect you to understand their internal wording and to align with their processes. They do not care whether you are developing a product or a customized solution for them - in fact, they would prefer a custom solution, as long as it is cheap.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your Management&lt;/td&gt;
&lt;td&gt;They want happy customers that are willing to pay a lot more than the costs &lt;em&gt;you&lt;/em&gt; produce. They want an illusion of stability, so please provide a roadmap for the next ten years. Also they will sabotage this roadmap and the identity of your product on first opportunity to get a new customer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product Owner&lt;/td&gt;
&lt;td&gt;The poor soul that has the responsibility to make customers, the management, the developers, and basically everyone happy. Either they accept that they cannot be perfect or they will quickly burn out.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Team&lt;/td&gt;
&lt;td&gt;They are mainly expected to give a high throughput of well-designed and high quality features. They are often more interested in their software product than the problems the product should solve. They will annoy everyone in the company about technical debt until their throughout falls below a certain threshold were customers become increasingly unhappy, causing management to dictate a whole rewrite that will block your company for the next three years.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Of course there are additional roles such as architects, project managers, product managers, business analysts, scrum masters, testers, devops and security specialists, UX designers and so on, but for our processual solution we will focus on the very core - be assured that they will find their place in our framework as well. &lt;/p&gt;

&lt;h2&gt;
  
  
  Separating Concerns
&lt;/h2&gt;

&lt;p&gt;Now that we know the needs, we can start to shape our requirements for our product development process, already bringing us closer to a possible solution. Let us have a look at what we &lt;em&gt;want&lt;/em&gt; and how we could address those issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Customer Space
&lt;/h3&gt;

&lt;p&gt;We want someone who understands what the customers wants, how they work and what is really important for them (meaning what they are willing to pay for, and if possible, how much). We are willing to make this experience as pleasant as possible for the customer, so they get the feeling they can come to us with new ideas and opportunities. If they are using SaFE, we are happy to send someone to day-long meetings.&lt;/p&gt;

&lt;p&gt;There are a lot of different ways to organize this, usually a combination of responsible sales guys, key accounts and project managers. As you will not have enough resources to flatter all customers in this manner, you will need to categorize and set priorities. This organization is out of scope for this post.&lt;/p&gt;

&lt;p&gt;For our process the main considerations are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide a space where you can organize your collaboration with the customer the best way possible&lt;/li&gt;
&lt;li&gt;Do not expect nor force the customer to understand your company, processes or tooling, it just wastes their time&lt;/li&gt;
&lt;li&gt;Do not allow this problem space to infiltrate your product spaces (for example the wording, but never something like release cycles), but ensure to take away the important requirements, time schedules and constraints (we will see how to do this later) &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Practical examples would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You may want to have a separate Jira project for them, do not allow their input to directly create user stories in your product backlog&lt;/li&gt;
&lt;li&gt;Do not force them to join your reviews (mind you, they do not care about your product)&lt;/li&gt;
&lt;li&gt;If they prefer phone calls, get on the phone, if they want a status meeting, schedule one&lt;/li&gt;
&lt;li&gt;Have your key accounts or project managers join your reviews and they will eagerly tell their customers in a language they understand&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Management Space
&lt;/h3&gt;

&lt;p&gt;This space allows us to align the requirements of our customers with our product strategy and the roadmaps for each product. We want every larger topic to be visible in a unified list, allowing us to prioritize the value the company should provide next. If you have product managers, this is their home base - if you do not, hire some. &lt;/p&gt;

&lt;p&gt;As with the customer space, our considerations for the management space are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide a clear view on the next topics that are important for your organization&lt;/li&gt;
&lt;li&gt;Specify use and cost for each topic (or even better: a business case)&lt;/li&gt;
&lt;li&gt;Keep the topics based on use cases, so do not assign them directly to a product to avoid Conway's Law on this level. Use cases may involve multiple products at once. This also means that there can only be one management space.&lt;/li&gt;
&lt;li&gt;Force decision makers to prioritize this list. Avoid priorities outside of this list as much as possible. Embrace conflict instead of avoiding it.&lt;/li&gt;
&lt;li&gt;Avoid generating too much overhead to describe and estimate the topics. We do not need detailed concepts for things that will never be implemented (or in two years).&lt;/li&gt;
&lt;li&gt;Add some kind of input process that ensures quality of the items as well as a clear communication strategy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This space is crucial for your process to work. Practical considerations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Management will love this process until there is the first conflict where they cannot get everything they want as soon as they want. Defend this process at all costs and do not allow it to be replaced once a year with a new one that will suffer from the same underlying problem.&lt;/li&gt;
&lt;li&gt;Separate the preparation of a topic from the decision. Product managers can prepare cases which then can be decided by management&lt;/li&gt;
&lt;li&gt;Define iteration lengths and schedule regular meeting for prioritization. Anything from one to three months is fine.&lt;/li&gt;
&lt;li&gt;Break down large topics and use cases into smaller ones, allowing to deprioritize or remove parts without meaningful value to your company. Do not allow topics larger than three months, or people will get impatient and the feeling of low performance arises. If needed, introduce a parent-child relation (such as "Epic" and "Feature" borrowed from SAFe).&lt;/li&gt;
&lt;li&gt;Ensure everything that requires large chunks of resources of your development teams to be visible here. Both developers as well as your IT or DevOps platform will try to undermine this process for "technical tasks". Make those topics visible, which requires a clear value to be assigned to the topic. Otherwise management will lose trust in this process, and rightly so.&lt;/li&gt;
&lt;li&gt;Small improvements should be able to by-pass this process. Think about defining some criteria (such as size) to determine when a topic needs to be added.&lt;/li&gt;
&lt;li&gt;Keep the process simple. The more Jira fields you need to maintain, the less accepted this process will be.&lt;/li&gt;
&lt;li&gt;If this process works for you, you can extend it with retros, time tracking, progress indicators etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Product Space
&lt;/h3&gt;

&lt;p&gt;Now that we have customer and management spaces, the life of your poor product owner got way easier. They will still contribute to and communicate with product and project managers, but their team simply can pull the next topics from the unified list, from which the product owner can also forge a product roadmap.&lt;/p&gt;

&lt;p&gt;Besides this, there are not many considerations for this space, as we can apply regular Scrum here. We might question how we map solutions with products and technical services (Conway's Law once again), but this is out of the scope of this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing Everything Together
&lt;/h2&gt;

&lt;p&gt;To summarize the considerations above, we can sketch basic diagram to visualize the overall dependencies. This is not a process diagram but rather the way we separated our concerns.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyqpawbrf1aj6eo7phf1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyqpawbrf1aj6eo7phf1.png" alt="Dependency Overview" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The End
&lt;/h2&gt;

&lt;p&gt;If you found this post useful or would like to add your own experiences, feel free to leave a comment below.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover picture by &lt;a href="https://unsplash.com/de/@lukeheibert" rel="noopener noreferrer"&gt;Luke Heibert&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>scrum</category>
      <category>agile</category>
      <category>management</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Quick HTTP File Server for DevOps with a Simple Script</title>
      <dc:creator>Andreas Nägeli</dc:creator>
      <pubDate>Mon, 15 Dec 2025 11:17:22 +0000</pubDate>
      <link>https://dev.to/kaliumhexacyanoferrat/quick-http-file-server-for-devops-with-a-simple-script-56n2</link>
      <guid>https://dev.to/kaliumhexacyanoferrat/quick-http-file-server-for-devops-with-a-simple-script-56n2</guid>
      <description>&lt;p&gt;As a DevOps engineer or system administrator, you might want to quickly spin up a HTTP server that allows to access files from other machines or to test functionality of programs and apps. Doing so would traditionally require you to install and configure a web server such as nginx or Apache, or to run a Docker image with volumes to make your target files accessible.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/tutorials/file-based-programs" rel="noopener noreferrer"&gt;.NET file-based apps&lt;/a&gt;, we can create script files that are easily capable of using any library hosted on nuget. In this article, we will demonstrate how to use the &lt;a href="https://genhttp.org" rel="noopener noreferrer"&gt;GenHTTP webserver&lt;/a&gt; to serve a local directory via HTTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-Requisites
&lt;/h2&gt;

&lt;p&gt;To run the script, you will need the &lt;a href="https://dotnet.microsoft.com/en-us/download/dotnet" rel="noopener noreferrer"&gt;.NET SDK&lt;/a&gt; (version 10 or higher) to be installed on your device (see &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/install/linux" rel="noopener noreferrer"&gt;this manual&lt;/a&gt; for Linux).&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Script
&lt;/h2&gt;

&lt;p&gt;In a local folder, create a new file named &lt;code&gt;file-server.csx&lt;/code&gt; and paste the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;!/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt; &lt;span class="n"&gt;GenHTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Core&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;10.2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt; &lt;span class="n"&gt;GenHTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DirectoryBrowsing&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;10.2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;GenHTTP.Engine.Internal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;GenHTTP.Modules.IO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;GenHTTP.Modules.Practices&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;GenHTTP.Modules.DirectoryBrowsing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"./"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// adjust as needed&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ResourceTree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Listing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Defaults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script will instruct the server to host a simple HTML-based file browser application that serves the current directory. Adjust the path to your target directory as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Script
&lt;/h2&gt;

&lt;p&gt;On Windows, the script can be started in a terminal window by running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run file-server.csx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Linux, the shebang allows us to directly execute the file. After marking the file as executable via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x file-server.csx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can directly start the server via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./file-server.csx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When started, the server will block until &lt;code&gt;CTRL + C&lt;/code&gt; is pressed. You will now be able to list and download the target files via &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; in your browser or from other apps:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ue5x3lh03yx8i442fgh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ue5x3lh03yx8i442fgh.png" alt="Locally hosted files" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover picture by &lt;a href="https://unsplash.com/de/@iammrcup" rel="noopener noreferrer"&gt;Fabien Barral&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>fileserver</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>Mocking HTTP Services in C# with MockH</title>
      <dc:creator>Andreas Nägeli</dc:creator>
      <pubDate>Tue, 04 Nov 2025 06:30:20 +0000</pubDate>
      <link>https://dev.to/kaliumhexacyanoferrat/mocking-http-services-in-c-with-mockh-2jkj</link>
      <guid>https://dev.to/kaliumhexacyanoferrat/mocking-http-services-in-c-with-mockh-2jkj</guid>
      <description>&lt;p&gt;In acceptance or integration tests of your .NET application, you probably want to mock external HTTP services so you can focus on testing your implemented logic. For this purpose, you might want to mock your &lt;code&gt;HttpClient&lt;/code&gt; or its &lt;code&gt;HttpMessageHandler&lt;/code&gt;, which is only a partial solution as it does not run HTTP over the wire. Or you can spin off Docker containers that will return responses you configured beforehand using some API, which requires a lot of boilerplate code and cannot be parallelized well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Kaliumhexacyanoferrat/MockH" rel="noopener noreferrer"&gt;MockH&lt;/a&gt; is a small library that allows you to do both - run real HTTP requests and configure the responses in code, directly within your test. As it is designed to have no side effects, it can run in parallel and does not mess with ASP.NET Core in any way.&lt;/p&gt;

&lt;p&gt;MockH is powered by &lt;a href="https://genhttp.org" rel="noopener noreferrer"&gt;GenHTTP&lt;/a&gt;, a lightweight and fully managed HTTP server framework written in .NET. This allows you to use high-level features such as JSON serialization to reduce boiler plate code required in your tests. Additionally, it can also be used in combination with any testing framework.&lt;/p&gt;

&lt;p&gt;The following, minimal example will show you how to spin up a server instance and how to consume it in your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;MockH&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;TestSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// start a HTTP server listening on a random, free port&lt;/span&gt;
   &lt;span class="c1"&gt;// servicing the endpoints you define here&lt;/span&gt;
   &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;MockServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/1"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(...)),&lt;/span&gt;
       &lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/2"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ResponseStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="c1"&gt;// obtain the URL from the server instance to use&lt;/span&gt;
   &lt;span class="c1"&gt;// it in your code as needed&lt;/span&gt;
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

   &lt;span class="c1"&gt;// execute your tests here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MockH supports various kind of rules that can be passed to &lt;code&gt;RunAsync()&lt;/code&gt; to generate your responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// return a specific status code&lt;/span&gt;
&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ifail"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ResponseStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InternalServerError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// redirect the client&lt;/span&gt;
&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://github.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// execute logic and return some simple text value&lt;/span&gt;
&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"42"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// execute logic and return some JSON&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;IntValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;StringValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"The answer"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// execute logic asynchronously&lt;/span&gt;
&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;

&lt;span class="c1"&gt;// access query parameters (GET /increment?=1)&lt;/span&gt;
&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/increment"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// access path parameters (GET /increment/1)&lt;/span&gt;
&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/increment/:i"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// access request body&lt;/span&gt;
&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;MyClass&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// access request body as stream&lt;/span&gt;
&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With MockH, you can write expressive, parallelizable, and fully isolated integration tests that use real HTTP without complex infrastructure.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/Kaliumhexacyanoferrat/MockH" rel="noopener noreferrer"&gt;MockH GitHub repository&lt;/a&gt; for more examples and advanced features.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover picture by &lt;a href="https://unsplash.com/de/@philharv3y" rel="noopener noreferrer"&gt;Phil Harvey&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>backend</category>
      <category>testing</category>
      <category>http</category>
    </item>
    <item>
      <title>Loading Certificates on .NET 9</title>
      <dc:creator>Andreas Nägeli</dc:creator>
      <pubDate>Thu, 12 Dec 2024 09:15:49 +0000</pubDate>
      <link>https://dev.to/kaliumhexacyanoferrat/loading-certificates-on-net-9-2ij6</link>
      <guid>https://dev.to/kaliumhexacyanoferrat/loading-certificates-on-net-9-2ij6</guid>
      <description>&lt;p&gt;I've seen a lot of traffic regarding the &lt;code&gt;X509CertificateLoader&lt;/code&gt; on my website lately, so I guess people are struggling with fixing the deprecations on the constructor of &lt;code&gt;X509Certificate2&lt;/code&gt; (as I did, too).&lt;/p&gt;

&lt;p&gt;So basically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from file&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;X509Certificate2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./myfile.pfx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// from byte array&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;X509Certificate2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;has become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from file&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X509CertificateLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadCertificateFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./myfile.pfx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// from byte array&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X509CertificateLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadPkcs12&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are of course more overloads, but you will get the idea.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover picture by &lt;a href="https://unsplash.com/de/@marjan_blan?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Marjan Blan&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Server Sent Events in ASP.NET Core</title>
      <dc:creator>Andreas Nägeli</dc:creator>
      <pubDate>Tue, 03 Dec 2024 06:26:44 +0000</pubDate>
      <link>https://dev.to/kaliumhexacyanoferrat/server-sent-events-in-aspnet-core-48l7</link>
      <guid>https://dev.to/kaliumhexacyanoferrat/server-sent-events-in-aspnet-core-48l7</guid>
      <description>&lt;p&gt;Server Sent Events (SSE) is a &lt;a href="https://html.spec.whatwg.org/multipage/server-sent-events.html" rel="noopener noreferrer"&gt;HTML standard&lt;/a&gt; with good support by all major browsers that allows your server to push events to connected clients as they happen.&lt;/p&gt;

&lt;p&gt;In contrast to websockets, the connection is unidirectional (so only the server can push events), making it easier to be implemented, soley relying on default HTTP mechanisms. On the client side, there is a simple, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events" rel="noopener noreferrer"&gt;comprehensible API&lt;/a&gt; ready to be used.&lt;/p&gt;

&lt;p&gt;ASP.NET Core does not offer an official implementation for Server Sent Events, so there are a lot of tutorials which try to add this functionality by directly writing to the response stream in the format the client expects. This approach works, but adds a lot of clutter to your application code and typically lacks some handy features provided by the standard, such as event types or event stream resumption.&lt;/p&gt;

&lt;p&gt;There are other web server frameworks that have a first-class support for server sent events built in, but you probably do not want to change your server framework just for implementing an event source.&lt;/p&gt;

&lt;p&gt;Gladly, one of those frameworks - namely &lt;a href="https://genhttp.org/" rel="noopener noreferrer"&gt;GenHTTP&lt;/a&gt; - provides &lt;a href="https://genhttp.org/documentation/content/concepts/adapters/" rel="noopener noreferrer"&gt;an adapter&lt;/a&gt; that allows us to plug in &lt;a href="https://genhttp.org/documentation/content/handlers/server-sent-events/" rel="noopener noreferrer"&gt;their implementation&lt;/a&gt; into an ASP.NET Core application. The following example will demonstrate this approach with a simple app that pushes randomly generated stock symbols to interested clients.&lt;/p&gt;

&lt;p&gt;First, we create a new ASP.NET Core minimal API project from the terminal (or in Visual Studio):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet new web -o StockEvents&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the generated project, we then add the nuget packages for the &lt;a href="https://www.nuget.org/packages/GenHTTP.Modules.ServerSentEvents/" rel="noopener noreferrer"&gt;provider&lt;/a&gt; and the &lt;a href="https://www.nuget.org/packages/GenHTTP.Adapters.AspNetCore/" rel="noopener noreferrer"&gt;adapter&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet add package GenHTTP.Modules.ServerSentEvents
dotnet add package GenHTTP.Adapters.AspNetCore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our &lt;code&gt;Program.cs&lt;/code&gt; we can then create an &lt;a href="https://genhttp.org/documentation/content/handlers/server-sent-events/" rel="noopener noreferrer"&gt;Event Source&lt;/a&gt; that will randomly generate our stock symbol updates and push them to connected clients. This source can be mapped to any path using the adapter functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;GenHTTP.Adapters.AspNetCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;GenHTTP.Modules.ServerSentEvents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Generator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GenerateStock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Defaults&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseDefaultFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseStaticFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt; &lt;span class="nf"&gt;GenerateStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEventConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stockSymbols&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"AAPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"GOOGL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MSFT"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CommentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sending stock data"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stockSymbols&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DataAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test our implementation, we create a &lt;code&gt;wwwroot&lt;/code&gt; sub directory in our project and create an &lt;code&gt;index.html&lt;/code&gt; file there with the following content (adjust the port of the endpoint as needed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Stock Tracker&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;#stocks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.stock&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#ddd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f9f9f9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Stock Tracker&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"stocks"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// Establish a connection to the server using Server-Sent Events&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:5011/stock/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Function to display stock updates&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;stockElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;stockElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Create a new element for the stock symbol if it doesn't exist&lt;/span&gt;
            &lt;span class="nx"&gt;stockElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;stockElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;stockElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stocks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stockElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Update stock value&lt;/span&gt;
        &lt;span class="nx"&gt;stockElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&amp;lt;/strong&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Event listener for general updates&lt;/span&gt;
    &lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;updateStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Event listeners for specific stock symbols&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;symbols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AAPL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GOOGL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MSFT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// Example stocks&lt;/span&gt;
    &lt;span class="nx"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;updateStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Error handling&lt;/span&gt;
    &lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection to the server lost.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the server has been started (&lt;code&gt;dotnet run&lt;/code&gt;), you can open the endpoint announced by the launcher (e.g. &lt;a href="http://localhost:5011" rel="noopener noreferrer"&gt;http://localhost:5011&lt;/a&gt;) and will see the stock updates ticking in.&lt;/p&gt;

&lt;p&gt;Please note that the endpoint you specified in the HTML file needs to match the URL you open in your browser, or otherwise CORS will prevent us from fetching events (so for example you cannot mix HTTP and HTTPS).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvr341hh59amoirwylw3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvr341hh59amoirwylw3.png" alt="Stock updates processed via Server Sent Events" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was a brief tutorial to quickly spawn and consume an event source - you can check &lt;a href="https://genhttp.org/documentation/content/handlers/server-sent-events/" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt; of the GenHTTP module to learn more about event types, event IDs, or error handling.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover by: &lt;a href="https://unsplash.com/de/@huntleytography" rel="noopener noreferrer"&gt;Devon Janse van Rensburg&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aspdotnet</category>
      <category>dotnet</category>
      <category>serversentevents</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
