{"id":3002,"date":"2025-12-08T18:12:12","date_gmt":"2025-12-08T18:12:12","guid":{"rendered":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2025\/12\/08\/net-10-networking-improvements\/"},"modified":"2025-12-08T18:12:12","modified_gmt":"2025-12-08T18:12:12","slug":"net-10-networking-improvements","status":"publish","type":"post","link":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2025\/12\/08\/net-10-networking-improvements\/","title":{"rendered":".NET 10 Networking Improvements"},"content":{"rendered":"<p>As with every <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-dotnet-10\/\">release<\/a>, we publish a blog post about the new and interesting changes and additions in .NET networking space. This time, we are writing about <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/dotnet-10-networking-improvements\/#http\">HTTP<\/a> improvements, new <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/dotnet-10-networking-improvements\/#websockets\">web sockets<\/a> APIs, <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/dotnet-10-networking-improvements\/#security\">security<\/a> changes and many distinct additions in <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/dotnet-10-networking-improvements\/#networking-primitives\">networking primitives<\/a>.<\/p>\n<h2>HTTP<\/h2>\n<p>The following section introduces improvements and additions in HTTP space. Among them are a performance optimization in <code>WinHttpHandler<\/code>, a new HTTP verb and small addition in cookies.<\/p>\n<h3>WinHttpHandler<\/h3>\n<p>One of the performance improvements in .NET 10 is an optimization of server certificate validation in <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.http.winhttphandler\"><code>WinHttpHandler<\/code><\/a>. Normally, the validation is left to the native <a href=\"https:\/\/learn.microsoft.com\/windows\/win32\/winhttp\"><em>WinHTTP<\/em><\/a> implementation, which is expected to do the right thing. But sometimes, user code needs more control over the process and <code>WinHttpHandler<\/code> offers <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.http.winhttphandler.servercertificatevalidationcallback\"><code>ServerCertificateValidationCallback<\/code><\/a> for exactly that reason. Once the user code registers the callback, <code>WinHttpHandler<\/code> skips the internal <em>WinHTTP<\/em> validation. Unfortunately, there\u2019s no exact event raised by the native <em>WinHTTP<\/em> that would correspond to connection establishment and would also provide the server certificate for validation. As a result, the managed layer within <code>WinHttpHandler<\/code> is forced to invoke the custom <code>ServerCertificateValidationCallback<\/code> on each and every request. To avoid this, a cache of already validated certificates by server IP address was put in place. Every time a new request is being sent, <code>WinHttpHandler<\/code> skips building the whole certificate chain and invoking the custom callback if the certificate was previously validated. On top of that, each new connection clears up the cached certificate for that particular server IP to re-validate on connection recreation. To stay prudent from a security perspective, this feature (<a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/111791\">dotnet\/runtime#111791<\/a>) is opt-in and hidden behind an <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.appcontext\"><code>AppContext<\/code><\/a> switch:<\/p>\n<pre><code class=\"language-c#\">AppContext.SetSwitch(\"System.Net.Http.UseWinHttpCertificateCaching\", true);<\/code><\/pre>\n<p>The following example illustrates the effect of the switch:<\/p>\n<pre><code class=\"language-c#\">using System.Net.Security;\r\n\r\nAppContext.SetSwitch(\"System.Net.Http.UseWinHttpCertificateCaching\", true);\r\n\r\nusing var client = new HttpClient(new WinHttpHandler()\r\n{\r\n    ServerCertificateValidationCallback = static (req, cert, chain, errors) =&gt;\r\n    {\r\n        Console.WriteLine(\"Server certificate validation invoked\");\r\n        return errors == SslPolicyErrors.None;\r\n    }\r\n});\r\n\r\nConsole.WriteLine((await client.GetAsync(\"https:\/\/github.com\")).StatusCode);\r\nConsole.WriteLine((await client.GetAsync(\"https:\/\/github.com\")).StatusCode);\r\nConsole.WriteLine((await client.GetAsync(\"https:\/\/github.com\")).StatusCode);<\/code><\/pre>\n<p>The callback is invoked only once, with output like the following:<\/p>\n<pre><code class=\"language-txt\">Server certificate validation invoked\r\nOK\r\nOK\r\nOK<\/code><\/pre>\n<p>Whereas without the switch, the callback is invoked with each request:<\/p>\n<pre><code class=\"language-txt\">Server certificate validation invoked\r\nOK\r\nServer certificate validation invoked\r\nOK\r\nServer certificate validation invoked\r\nOK<\/code><\/pre>\n<p>The following graph shows the cumulative time saved with the certificate caching per increasing number of requests:<br \/>\n<img data-opt-id=1166232978  fetchpriority=\"high\" decoding=\"async\" title=\"Cumulative time saved with certificate caching\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/12\/winhttphandler.webp\" alt=\"Cumulative time saved with certificate caching\" \/><\/p>\n<h3>QUERY<\/h3>\n<p>Another addition is a new HTTP verb <code>QUERY<\/code>. The purpose of the verb is to allow sending details for the query in the request body while still using a safe, idempotent request. In other words, being able to execute multiple times without affecting server data. One of the use cases is when the details of the query might not fit into URI length limits and using body in <code>GET<\/code> request is not supported by the server. The verb is still in process of standardization in <a href=\"https:\/\/www.ietf.org\/archive\/id\/draft-ietf-httpbis-safe-method-w-body-08.html\">The HTTP QUERY Method<\/a> proposal. For that reason, we have decided to postpone the full implementation of the helper methods in <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/113522\">dotnet\/runtime#113522<\/a> until the RFC is published. And we only added the string constant in <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/114489\">dotnet\/runtime#114489<\/a>, which can be used this way:<\/p>\n<pre><code class=\"language-c#\">using var client = new HttpClient();\r\nvar response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Query, \"https:\/\/api.example.com\/resource\"));<\/code><\/pre>\n<h3>Cookies<\/h3>\n<p>A very small change was requested: making the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.cookieexception\"><code>CookieException<\/code><\/a> constructor public in <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/95965\">dotnet\/runtime#95965<\/a>. The change was made by a community contributor <a href=\"https:\/\/github.com\/deeprobin\">@deeprobin<\/a> in <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/109026\">dotnet\/runtime#109026<\/a>. <code>CookieException<\/code> can now be manually thrown to avoid unexpected visits from <em>Cookie Monster<\/em>:<\/p>\n<pre><code class=\"language-c#\">throw new CookieException(\"\ud83c\udf6a\");<\/code><\/pre>\n<h2>WebSockets<\/h2>\n<p>Working with raw <code>WebSocket<\/code> APIs means dealing with low-level details\u2014buffering, framing, encoding, and custom wrappers to integrate with streams or channels. This complexity makes building transport layers and streaming protocols harder.<\/p>\n<p>.NET 10 introduces <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.websockets.websocketstream\"><code>WebSocketStream<\/code><\/a>, a stream-based abstraction over WebSockets that makes reading, writing, and parsing data for text and binary protocols much simpler.<\/p>\n<p>Key advantages:<\/p>\n<ul>\n<li><strong>Stream-first design<\/strong>: Works seamlessly with <code>StreamReader<\/code>, <code>JsonSerializer<\/code>, compressors, serializers, etc.<\/li>\n<li><strong>No manual plumbing<\/strong>: Eliminates message framing and leftover handling.<\/li>\n<li><strong>Supports multiple scenarios<\/strong>: JSON-based protocols, text-based protocols like STOMP, and binary protocols like AMQP.<\/li>\n<\/ul>\n<h3>Common usage patterns<\/h3>\n<ol>\n<li><strong>Read a complete JSON message (text)<\/strong>\n<p>Use <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.websockets.websocketstream.createreadablemessagestream\"><code>CreateReadableMessageStream<\/code><\/a> with <code>JsonSerializer.DeserializeAsync&lt;T&gt;()<\/code>. You don\u2019t need a manual receive loop or a <code>MemoryStream<\/code> buffer. The stream ends exactly at the message boundary, so <code>DeserializeAsync<\/code> completes naturally when the message is fully read.<\/p>\n<\/li>\n<li><strong>Stream a text protocol (e.g., STOMP-like, line oriented)<\/strong>\n<p>Use <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.websockets.websocketstream.create\"><code>Create<\/code><\/a> to get a transport stream, and layer a <code>StreamReader<\/code> on top. You can parse <strong>line-by-line<\/strong> while the stream stays open across frames. This pattern leverages automatic UTF\u20118 handling and keeps reads composable with standard text parsers.<\/p>\n<\/li>\n<li><strong>Write a single binary message (e.g., AMQP payload)<\/strong>\n<p>Use <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.websockets.websocketstream.createwritablemessagestream\"><code>CreateWritableMessageStream<\/code><\/a> and write chunk-by-chunk; <code>Dispose<\/code> sends end-of-message (EOM) for you. The one\u2011message write semantics avoid manual <code>EndOfMessage<\/code> handling and make your send path look like any other stream write.<\/p>\n<\/li>\n<\/ol>\n<p>In other words,<\/p>\n<ul>\n<li>Prefer <code>CreateReadableMessageStream<\/code> \/ <code>CreateWritableMessageStream<\/code> for message-oriented workflows.<\/li>\n<li>For continuous protocols, use <code>Create<\/code> with appropriate <code>leaveOpen<\/code>\/<code>ownsWebSocket<\/code> semantics in layered readers\/writers.<\/li>\n<li>Dispose streams to complete EOM and close gracefully. Use <code>closeTimeout<\/code> to limit how long Dispose will wait for a graceful close handshake completion.<\/li>\n<\/ul>\n<h3>Before vs After (JSON example)<\/h3>\n<p>See the snippet below for the comparison between using a raw WebSocket loop and using a WebSocketStream to read a complete JSON message.<\/p>\n<pre><code class=\"language-csharp\">\/\/ BEFORE: manual buffering\r\nstatic async Task&lt;AppMessage?&gt; ReceiveJsonManualAsync(WebSocket ws, CancellationToken ct)\r\n{\r\n    var buffer = new byte[8192];\r\n    using var mem = new MemoryStream();\r\n    while (ws.State == WebSocketState.Open)\r\n    {\r\n        var result = await ws.ReceiveAsync(buffer, ct);\r\n        if (result.MessageType == WebSocketMessageType.Close)\r\n        {\r\n            return null;\r\n        }\r\n        await mem.WriteAsync(buffer.AsMemory(0, result.Count), ct);\r\n        if (result.EndOfMessage)\r\n        {\r\n            break;\r\n        }\r\n    }\r\n    mem.Position = 0;\r\n    return await JsonSerializer.DeserializeAsync&lt;AppMessage&gt;(mem, cancellationToken: ct);\r\n}\r\n\r\n\/\/ AFTER: message stream\r\nstatic async Task&lt;AppMessage?&gt; ReceiveJsonAsync(WebSocket ws, CancellationToken ct)\r\n{\r\n    using Stream message = WebSocketStream.CreateReadableMessageStream(ws);\r\n    return await JsonSerializer.DeserializeAsync&lt;AppMessage&gt;(message, cancellationToken: ct);\r\n}<\/code><\/pre>\n<p>The \u201cafter\u201d version eliminates buffering, copies, and <code>EndOfMessage<\/code> checks via the <code>Stream<\/code> abstraction.<\/p>\n<h2>Security<\/h2>\n<p>In this release, there were two changes in the networking security space that are worth calling out. One of them is a long-standing request for TLS 1.3 support on OSX. And the second one is unifying how we expose TLS cipher suite details.<\/p>\n<h3>Client-side TLS 1.3 on OSX<\/h3>\n<p>TLS 1.3 support on OSX was highly requested issue <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/1979\">dotnet\/runtime#1979<\/a> for several releases. It was a non-trivial effort because it required switching to a different set of native OSX APIs. On top of that, the new APIs combine TLS together with TCP whereas .NET exposes these two layers independently from each other as <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.security.sslstream\"><code>SslStream<\/code><\/a> and <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.sockets.socket\"><code>Socket<\/code><\/a>. Furthermore, the new APIs support only TLS 1.2 and TLS 1.3 and, despite other <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.security.authentication.sslprotocols\"><code>SslProtocols<\/code><\/a> being deprecated, .NET still supports them. All of this combined led to a decision to expose this functionality only as an opt-in feature behind an <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.appcontext\"><code>AppContext<\/code><\/a> switch (<a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/117428\">dotnet\/runtime#117428<\/a>). Client application can take advantage of this new support by either setting the switch in their code:<\/p>\n<pre><code class=\"language-c#\">AppContext.SetSwitch(\"System.Net.Security.UseNetworkFramework\", true);<\/code><\/pre>\n<p>or with an environment variable:<\/p>\n<pre><code class=\"language-bash\">export DOTNET_SYSTEM_NET_SECURITY_USENETWORKFRAMEWORK=1<\/code><\/pre>\n<p>Note that this will switch the backend <code>SslStream<\/code> uses for client operations on OSX, so only TLS 1.3 and TLS 1.2 will be supported. But it has no effect on server-side usage of <code>SslStream<\/code>.<\/p>\n<h3>Negotiated Cipher Suite<\/h3>\n<p><a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.security.sslstream\"><code>SslStream<\/code><\/a> provides several properties with details about negotiated cipher suite. Among them belong <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.security.sslstream.keyexchangealgorithm\"><code>KeyExchangeAlgorithm<\/code><\/a>, <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.security.sslstream.hashalgorithm\"><code>HashAlgorithm<\/code><\/a> and <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.security.sslstream.cipheralgorithm\"><code>CipherAlgorithm<\/code><\/a>. However, the underlying enums for these properties weren\u2019t updated for a while and therefore not providing accurate information. For example, mapping multiple algorithms from the same family into the single enum value, losing important information in the process. Instead of expanding the enums, we decided to obsolete them and leave only <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.security.sslstream.negotiatedciphersuite\"><code>NegotiatedCipherSuite<\/code><\/a> as the only source of truth (<a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/100361\">dotnet\/runtime#100361<\/a>). The underlying enum <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.security.tlsciphersuite\"><code>TlsCipherSuite<\/code><\/a> follows IANA specification for <a href=\"https:\/\/www.iana.org\/assignments\/tls-parameters\/tls-parameters.xhtml#tls-parameters-4\">TLS Cipher Suites<\/a> and encompasses all the information needed. The obsoleted properties were only derived from <code>NegotiatedCipherSuite<\/code> and didn\u2019t convey any additional information. The whole change can be examined in <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/105875\">dotnet\/runtime#105875<\/a>.<\/p>\n<p>On top of that, we have introduced the same <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.quic.quicconnection.negotiatedciphersuite\"><code>NegotiatedCipherSuite<\/code><\/a> property to <code>QuicConnection<\/code> in <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/106391\">dotnet\/runtime#106391<\/a>. Also being the only source of truth for the negotiated TLS details for the established connection. Discussion for the API addition can be found in <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/70184\">dotnet\/runtime#70184<\/a>.<\/p>\n<h2>Networking Primitives<\/h2>\n<p>This section introduces changes in <code>System.Net<\/code> namespace which contains several additions to server-sent events helpers, IP address, URI and similar types.<\/p>\n<h3>Server-Sent Events Formatter<\/h3>\n<p>In the previous release of .NET, we have added support for parsing server-sent events, mentioned in the last year\u2019s blog post chapter <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/dotnet-9-networking-improvements\/#server-sent-events-parser\">Server-Sent Events Parser<\/a>. This release introduces the opposite side, formatter for server-sent events (<a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/109294\">dotnet\/runtime#109294<\/a>). In the most simple scenario, with <code>string<\/code> typed data, the new API can be used like in the following example:<\/p>\n<pre><code class=\"language-c#\">using var stream = new MemoryStream();\r\n\r\n\/\/ Only pass in source of items and stream to serialize into.\r\nawait SseFormatter.WriteAsync(GetStringItems(), stream);\r\n\r\nstatic async IAsyncEnumerable&lt;SseItem&lt;string&gt;&gt; GetStringItems()\r\n{\r\n    yield return new SseItem&lt;string&gt;(\"data 1\");\r\n    yield return new SseItem&lt;string&gt;(\"data 2\");\r\n    yield return new SseItem&lt;string&gt;(\"data 3\");\r\n    yield return new SseItem&lt;string&gt;(\"data 4\");\r\n}<\/code><\/pre>\n<p>The content of the stream for this code will look like:<\/p>\n<pre><code class=\"language-text\">data: data 1\r\n\r\ndata: data 2\r\n\r\ndata: data 3\r\n\r\ndata: data 4\r\n<\/code><\/pre>\n<p>And the following example illustrates the case when the data payload is not a simple <code>string<\/code>:<\/p>\n<pre><code class=\"language-c#\">var stream = new MemoryStream();\r\n\r\n\/\/ The third argument is a delegate taking in SseItem and IBufferWriter into which the item is serialized.\r\nawait SseFormatter.WriteAsync&lt;int&gt;(GetItems(), stream, (item, writer) =&gt;\r\n{\r\n    \/\/ The data of the item should be converted to a string and that string then converted to corresponding UTF-8 bytes.\r\n    writer.Write(Encoding.UTF8.GetBytes(item.Data.ToString()));\r\n});\r\n\r\nstatic async IAsyncEnumerable&lt;SseItem&lt;int&gt;&gt; GetItems()\r\n{\r\n    yield return new SseItem&lt;int&gt;(1) { ReconnectionInterval = TimeSpan.FromSeconds(1) };\r\n    yield return new SseItem&lt;int&gt;(2);\r\n    yield return new SseItem&lt;int&gt;(3);\r\n    yield return new SseItem&lt;int&gt;(4);\r\n}<\/code><\/pre>\n<p>For this example, the content of the stream will look like:<\/p>\n<pre><code class=\"language-text\">data: 1\r\nretry: 1000\r\n\r\ndata: 2\r\n\r\ndata: 3\r\n\r\ndata: 4\r\n<\/code><\/pre>\n<p>As the examples show, this addition (<a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/109832\">dotnet\/runtime#109832<\/a>) introduced two overloads for writing the events:<\/p>\n<ul>\n<li>simple one for <code>string<\/code> typed data, without formatting delegate: <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.serversentevents.sseformatter.writeasync#system-net-serversentevents-sseformatter-writeasync(system-collections-generic-iasyncenumerable((system-net-serversentevents-sseitem((system-string)\"><code>WriteAsync(IAsyncEnumerable&lt;SseItem&lt;String&gt;&gt;, Stream, CancellationToken)<\/code><\/a>))-system-io-stream-system-threading-cancellationtoken))<\/li>\n<li>generic one for any data, with formatting delegate: <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.serversentevents.sseformatter.writeasync#system-net-serversentevents-sseformatter-writeasync-1(system-collections-generic-iasyncenumerable((system-net-serversentevents-sseitem((-0)\"><code>WriteAsync&lt;T&gt;(IAsyncEnumerable&lt;SseItem&lt;T&gt;&gt;, Stream, Action&lt;SseItem&lt;T&gt;,IBufferWriter&lt;Byte&gt;&gt;, CancellationToken)<\/code><\/a>))-system-io-stream-system-action((system-net-serversentevents-sseitem((-0))-system-buffers-ibufferwriter((system-byte))))-system-threading-cancellationtoken))<\/li>\n<\/ul>\n<p>On top of that, <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.serversentevents.sseitem-1\"><code>SseItem&lt;T&gt;<\/code><\/a> was expanded with two new properties for writing:<\/p>\n<ul>\n<li><a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.serversentevents.sseitem-1.eventid\"><code>EventId<\/code><\/a>: to send the <code>id<\/code> field<\/li>\n<li><a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.serversentevents.sseitem-1.reconnectioninterval\"><code>ReconnectionInterval<\/code><\/a>: to send the <code>retry<\/code> field<\/li>\n<\/ul>\n<p>Both of these fields control the behavior of the client when the connection needs to be re-established. The last parsed <code>EventId<\/code> will map to <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.serversentevents.sseparser-1.lasteventid\"><code>LastEventId<\/code><\/a> on the parser side and eventually will be used in <a href=\"https:\/\/html.spec.whatwg.org\/multipage\/server-sent-events.html#the-last-event-id-header\"><code>Last-Event-ID<\/code><\/a> header if the connection is re-created. And <code>ReconnectionInterval<\/code> will map to <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.serversentevents.sseparser-1.reconnectioninterval\"><code>ReconnectionInterval<\/code><\/a> and will control the time before a new connection is attempted.<\/p>\n<p>With all of this together, <code>System.Net.ServerSentEvents<\/code> provides a complete set of helpers for both sides of the communication and can be used to round-trip the data back and forth:<\/p>\n<pre><code class=\"language-c#\">var stream = new MemoryStream();\r\n\r\nawait SseFormatter.WriteAsync&lt;int&gt;(GetItems(), stream, (item, writer) =&gt;\r\n{\r\n    writer.Write(Encoding.UTF8.GetBytes(item.Data.ToString()));\r\n});\r\n\r\nstream.Seek(0, SeekOrigin.Begin);\r\n\r\nvar parser = SseParser.Create(stream, (type, data) =&gt;\r\n{\r\n    var str = Encoding.UTF8.GetString(data);\r\n    return Int32.Parse(str);\r\n});\r\n\r\nawait foreach (var item in parser.EnumerateAsync())\r\n{\r\n    Console.WriteLine($\"{item.EventType}: {item.Data} {item.EventId} {item.ReconnectionInterval} [{parser.LastEventId};{parser.ReconnectionInterval}]\");\r\n}\r\n\r\nstatic async IAsyncEnumerable&lt;SseItem&lt;int&gt;&gt; GetItems()\r\n{\r\n    yield return new SseItem&lt;int&gt;(1) { ReconnectionInterval = TimeSpan.FromSeconds(1) };\r\n    yield return new SseItem&lt;int&gt;(2) { EventId = \"2\" };\r\n    yield return new SseItem&lt;int&gt;(3);\r\n    yield return new SseItem&lt;int&gt;(4);\r\n}<\/code><\/pre>\n<h3>IP Address<\/h3>\n<p>There were two new additions for <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.ipaddress\"><code>IPAddress<\/code><\/a> class. The first one is a static method to validate whether a string is a valid IP address (<a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/111282\">dotnet\/runtime#111282<\/a>). It can be used as:<\/p>\n<pre><code class=\"language-c#\">if (IPAddress.IsValid(\"10.0.0.1\"))\r\n{ ... }\r\nif (IPAddress.IsValid(\"::1\"))\r\n{ ... }\r\nif (IPAddress.IsValid(\"10.0.1\"))\r\n{ ... }\r\nif (IPAddress.IsValidUtf8(\"::192.168.0.1\"u8))\r\n{ ... }\r\nif (IPAddress.IsValidUtf8(\"fe80::9656:d028:8652:66b6\"u8))\r\n{ ... }<\/code><\/pre>\n<p>The other change follows up on a change adding <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.iutf8spanformattable\"><code>IUtf8SpanFormattable<\/code><\/a> in .NET 8. Both <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.ipaddress\"><code>IPAddress<\/code><\/a> and <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.ipnetwork\"><code>IPNetwork<\/code><\/a> now also implement <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.iutf8spanparsable-1\"><code>IUtf8SpanParsable&lt;T&gt;<\/code><\/a>. The API proposal <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/103111\">dotnet\/runtime#103111<\/a> as well as the implementation itself <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/102144\">dotnet\/runtime#102144<\/a> were done by a community contributor <a href=\"https:\/\/github.com\/edwardneal\">@edwardneal<\/a>.<\/p>\n<h3>Miscellaneous<\/h3>\n<p>The last changes in <code>System.Net<\/code> namespace worth mentioning are removing the length limit on <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.uri\"><code>Uri<\/code><\/a> in <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/117287\">dotnet\/runtime#117287<\/a>. The main reason for this change was to support <code>data<\/code> URI scheme (<a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/96544\">dotnet\/runtime#96544<\/a>) as specified in <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc2397\">RFC 2397<\/a>. Instead of linking a resource, <code>data<\/code> URI carries the data for the resource in itself. For example as base64 encoded image:<\/p>\n<pre><code class=\"language-text\">data:image\/jpeg;base64,[base64 encoded data of the image]<\/code><\/pre>\n<p>And with the original limit of slightly under 64 KB, this was not enough for many such data URIs.<\/p>\n<p>The last small change was adding a new media type for <code>yml<\/code> files (<a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/105809\">dotnet\/runtime#105809<\/a>). The change adds a new constant <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.mime.mediatypenames.application.yaml\"><code>MediaTypesName.Yaml<\/code><\/a> in <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/117211\">dotnet\/runtime#117211<\/a> and was done by a community contributor <a href=\"https:\/\/github.com\/martincostello\">@martincostello<\/a>.<\/p>\n<h2>Final Notes<\/h2>\n<p>It has become a tradition to publish this article every year and as in the past years, the article cannot cover all the changes that have been made. We pick things that might have a direct impact on our customers, whether it\u2019s a new API, feature or performance improvement. And of course, many of such performance improvements are already covered by Stephen\u2019s great article about <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/performance-improvements-in-net-10\/#networking\">Performance Improvements in .NET 10<\/a>. We\u2019d also like to encourage you to reach out to us in <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\">our GitHub repo<\/a> in case you have any questions, requests for new features or discover any bugs. And lastly, I\u2019d like to thank my co-author <a href=\"https:\/\/github.com\/CarnaViire\">@CarnaViire<\/a> for writing the <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/dotnet-10-networking-improvements\/#websockets\">WebSockets<\/a> section.<\/p>\n<p>The post <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/dotnet-10-networking-improvements\/\">.NET 10 Networking Improvements<\/a> appeared first on <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\">.NET Blog<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>As with every release, we publish a blog post about the new and interesting changes and additions in .NET networking [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3003,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[7],"tags":[],"class_list":["post-3002","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet"],"_links":{"self":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/3002","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/comments?post=3002"}],"version-history":[{"count":0,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/3002\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media\/3003"}],"wp:attachment":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media?parent=3002"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/categories?post=3002"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/tags?post=3002"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}