diff --git a/AspNetCore.sln b/AspNetCore.sln
index 3559078a5cda..8395a01cb39f 100644
--- a/AspNetCore.sln
+++ b/AspNetCore.sln
@@ -1756,6 +1756,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "perf", "perf", "{74377D3E-E
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.HttpSys.Microbenchmarks", "src\Servers\HttpSys\perf\Microbenchmarks\Microsoft.AspNetCore.Server.HttpSys.Microbenchmarks.csproj", "{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Transport.NamedPipes", "Transport.NamedPipes", "{F057512B-55BF-4A8B-A027-A0505F8BA10C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes", "src\Servers\Kestrel\Transport.NamedPipes\src\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj", "{10173568-A65E-44E5-8C6F-4AA49D0577A1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests", "src\Servers\Kestrel\Transport.NamedPipes\test\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests.csproj", "{97C7D2A4-87E5-4A4A-A170-D736427D5C21}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -10533,6 +10539,38 @@ Global
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|x64.Build.0 = Release|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|x86.ActiveCfg = Release|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|x86.Build.0 = Release|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|arm64.Build.0 = Debug|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|x64.Build.0 = Debug|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Debug|x86.Build.0 = Debug|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|arm64.ActiveCfg = Release|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|arm64.Build.0 = Release|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|x64.ActiveCfg = Release|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|x64.Build.0 = Release|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|x86.ActiveCfg = Release|Any CPU
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1}.Release|x86.Build.0 = Release|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|arm64.Build.0 = Debug|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|x64.Build.0 = Debug|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Debug|x86.Build.0 = Debug|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|Any CPU.Build.0 = Release|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|arm64.ActiveCfg = Release|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|arm64.Build.0 = Release|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x64.ActiveCfg = Release|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x64.Build.0 = Release|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x86.ActiveCfg = Release|Any CPU
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -11400,6 +11438,9 @@ Global
{F43CC5EA-6032-4A11-A9B2-6D48CB5EB082} = {4DA84F2B-1948-439B-85AB-E99E31331A9C}
{74377D3E-E0C6-41A4-89ED-11A9C00142A9} = {166E48ED-9738-4E13-8618-0D805F6F0F65}
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB} = {74377D3E-E0C6-41A4-89ED-11A9C00142A9}
+ {F057512B-55BF-4A8B-A027-A0505F8BA10C} = {4FDDC525-4E60-4CAF-83A3-261C5B43721F}
+ {10173568-A65E-44E5-8C6F-4AA49D0577A1} = {F057512B-55BF-4A8B-A027-A0505F8BA10C}
+ {97C7D2A4-87E5-4A4A-A170-D736427D5C21} = {F057512B-55BF-4A8B-A027-A0505F8BA10C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
index 9643ae81d3a7..34e46979bfb2 100644
--- a/eng/ProjectReferences.props
+++ b/eng/ProjectReferences.props
@@ -50,6 +50,7 @@
+
diff --git a/eng/SharedFramework.Local.props b/eng/SharedFramework.Local.props
index 986c5e4ef4db..822ae1740c91 100644
--- a/eng/SharedFramework.Local.props
+++ b/eng/SharedFramework.Local.props
@@ -60,6 +60,7 @@
+
diff --git a/eng/TrimmableProjects.props b/eng/TrimmableProjects.props
index 50a1520c5757..ffde1bd5b9bb 100644
--- a/eng/TrimmableProjects.props
+++ b/eng/TrimmableProjects.props
@@ -40,6 +40,7 @@
+
diff --git a/src/Framework/test/TestData.cs b/src/Framework/test/TestData.cs
index 2763c05a5048..dc0de73f0652 100644
--- a/src/Framework/test/TestData.cs
+++ b/src/Framework/test/TestData.cs
@@ -90,6 +90,7 @@ static TestData()
"Microsoft.AspNetCore.Server.Kestrel",
"Microsoft.AspNetCore.Server.Kestrel.Core",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Quic",
+ "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets",
"Microsoft.AspNetCore.Session",
"Microsoft.AspNetCore.SignalR",
@@ -228,6 +229,7 @@ static TestData()
{ "Microsoft.AspNetCore.Server.Kestrel.Core" },
{ "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" },
{ "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" },
+ { "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes" },
{ "Microsoft.AspNetCore.Server.Kestrel" },
{ "Microsoft.AspNetCore.Session" },
{ "Microsoft.AspNetCore.SignalR.Common" },
diff --git a/src/Http/Http/src/BindingAddress.cs b/src/Http/Http/src/BindingAddress.cs
index 398489c84dd0..bc44ae270188 100644
--- a/src/Http/Http/src/BindingAddress.cs
+++ b/src/Http/Http/src/BindingAddress.cs
@@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Http;
public class BindingAddress
{
private const string UnixPipeHostPrefix = "unix:/";
+ private const string NamedPipeHostPrefix = "pipe:/";
private BindingAddress(string host, string pathBase, int port, string scheme)
{
@@ -57,6 +58,14 @@ public BindingAddress()
///
public bool IsUnixPipe => Host.StartsWith(UnixPipeHostPrefix, StringComparison.Ordinal);
+ ///
+ /// Gets a value that determines if this instance represents a named pipe.
+ ///
+ /// Returns if starts with pipe:/ prefix.
+ ///
+ ///
+ public bool IsNamedPipe => Host.StartsWith(NamedPipeHostPrefix, StringComparison.Ordinal);
+
///
/// Gets the unix pipe path if this instance represents a Unix pipe.
///
@@ -73,6 +82,22 @@ public string UnixPipePath
}
}
+ ///
+ /// Gets the named pipe name if this instance represents a named pipe.
+ ///
+ public string NamedPipeName
+ {
+ get
+ {
+ if (!IsNamedPipe)
+ {
+ throw new InvalidOperationException("Binding address is not a named pipe.");
+ }
+
+ return GetNamedPipeName(Host);
+ }
+ }
+
private static string GetUnixPipePath(string host)
{
var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
@@ -84,10 +109,12 @@ private static string GetUnixPipePath(string host)
return host.Substring(unixPipeHostPrefixLength);
}
+ private static string GetNamedPipeName(string host) => host.Substring(NamedPipeHostPrefix.Length);
+
///
public override string ToString()
{
- if (IsUnixPipe)
+ if (IsUnixPipe || IsNamedPipe)
{
return Scheme.ToLowerInvariant() + Uri.SchemeDelimiter + Host.ToLowerInvariant();
}
@@ -135,15 +162,11 @@ public static BindingAddress Parse(string address)
var schemeDelimiterEnd = schemeDelimiterStart + Uri.SchemeDelimiter.Length;
var isUnixPipe = address.IndexOf(UnixPipeHostPrefix, schemeDelimiterEnd, StringComparison.Ordinal) == schemeDelimiterEnd;
+ var isNamedPipe = address.IndexOf(NamedPipeHostPrefix, schemeDelimiterEnd, StringComparison.Ordinal) == schemeDelimiterEnd;
int pathDelimiterStart;
int pathDelimiterEnd;
- if (!isUnixPipe)
- {
- pathDelimiterStart = address.IndexOf("/", schemeDelimiterEnd, StringComparison.Ordinal);
- pathDelimiterEnd = pathDelimiterStart;
- }
- else
+ if (isUnixPipe)
{
var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
if (OperatingSystem.IsWindows())
@@ -159,6 +182,16 @@ public static BindingAddress Parse(string address)
pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + unixPipeHostPrefixLength, StringComparison.Ordinal);
pathDelimiterEnd = pathDelimiterStart + ":".Length;
}
+ else if (isNamedPipe)
+ {
+ pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + NamedPipeHostPrefix.Length, StringComparison.Ordinal);
+ pathDelimiterEnd = pathDelimiterStart + ":".Length;
+ }
+ else
+ {
+ pathDelimiterStart = address.IndexOf("/", schemeDelimiterEnd, StringComparison.Ordinal);
+ pathDelimiterEnd = pathDelimiterStart;
+ }
if (pathDelimiterStart < 0)
{
diff --git a/src/Http/Http/src/PublicAPI.Unshipped.txt b/src/Http/Http/src/PublicAPI.Unshipped.txt
index a158cc48ca22..57e2d5acac44 100644
--- a/src/Http/Http/src/PublicAPI.Unshipped.txt
+++ b/src/Http/Http/src/PublicAPI.Unshipped.txt
@@ -1,3 +1,5 @@
#nullable enable
*REMOVED*Microsoft.AspNetCore.Http.StreamResponseBodyFeature.StreamResponseBodyFeature(System.IO.Stream! stream, Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature! priorFeature) -> void
+Microsoft.AspNetCore.Http.BindingAddress.IsNamedPipe.get -> bool
+Microsoft.AspNetCore.Http.BindingAddress.NamedPipeName.get -> string!
Microsoft.AspNetCore.Http.StreamResponseBodyFeature.StreamResponseBodyFeature(System.IO.Stream! stream, Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature? priorFeature) -> void
diff --git a/src/Servers/Connections.Abstractions/src/Features/IConnectionNamedPipeFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IConnectionNamedPipeFeature.cs
new file mode 100644
index 000000000000..1c54f74390f5
--- /dev/null
+++ b/src/Servers/Connections.Abstractions/src/Features/IConnectionNamedPipeFeature.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.IO.Pipes;
+
+namespace Microsoft.AspNetCore.Connections.Features;
+
+///
+/// Provides access to the connection's underlying .
+///
+public interface IConnectionNamedPipeFeature
+{
+ ///
+ /// Gets the underlying .
+ ///
+ NamedPipeServerStream NamedPipe { get; }
+}
diff --git a/src/Servers/Connections.Abstractions/src/NamedPipeEndPoint.cs b/src/Servers/Connections.Abstractions/src/NamedPipeEndPoint.cs
new file mode 100644
index 000000000000..52d2d803e888
--- /dev/null
+++ b/src/Servers/Connections.Abstractions/src/NamedPipeEndPoint.cs
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Net;
+
+namespace Microsoft.AspNetCore.Connections;
+
+///
+/// Represents a Named Pipe endpoint.
+///
+public sealed class NamedPipeEndPoint : EndPoint
+{
+ internal const string LocalComputerServerName = ".";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the pipe.
+ public NamedPipeEndPoint(string pipeName) : this(pipeName, LocalComputerServerName)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the pipe.
+ /// The name of the remote computer to connect to, or "." to specify the local computer.
+ public NamedPipeEndPoint(string pipeName, string serverName)
+ {
+ ServerName = serverName;
+ PipeName = pipeName;
+ }
+
+ ///
+ /// Gets the name of the remote computer. The server name must be ".", the local computer, when creating a server.
+ ///
+ public string ServerName { get; }
+
+ ///
+ /// Gets the name of the pipe.
+ ///
+ public string PipeName { get; }
+
+ ///
+ /// Gets the pipe name represented by this instance.
+ ///
+ public override string ToString()
+ {
+ // Based on format at https://learn.microsoft.com/windows/win32/ipc/pipe-names
+ return $@"\\{ServerName}\pipe\{PipeName}";
+ }
+
+ ///
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ return obj is NamedPipeEndPoint other && other.ServerName == ServerName && other.PipeName == PipeName;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return ServerName.GetHashCode() ^ PipeName.GetHashCode();
+ }
+}
diff --git a/src/Servers/Connections.Abstractions/src/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Servers/Connections.Abstractions/src/PublicAPI/net462/PublicAPI.Unshipped.txt
index 88184eb7b651..d4e8ab74f791 100644
--- a/src/Servers/Connections.Abstractions/src/PublicAPI/net462/PublicAPI.Unshipped.txt
+++ b/src/Servers/Connections.Abstractions/src/PublicAPI/net462/PublicAPI.Unshipped.txt
@@ -1,5 +1,15 @@
#nullable enable
+Microsoft.AspNetCore.Connections.Features.IConnectionNamedPipeFeature
+Microsoft.AspNetCore.Connections.Features.IConnectionNamedPipeFeature.NamedPipe.get -> System.IO.Pipes.NamedPipeServerStream!
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature.OnClosed(System.Action