Skip to content

Commit ad8a1af

Browse files
committed
Include HotReload web middleware and switch to shared implementation of delta appliers
1 parent 1996607 commit ad8a1af

29 files changed

+676
-101
lines changed

.editorconfig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ csharp_preserve_single_line_blocks = true
213213
csharp_prefer_braces = false:none
214214

215215
# Using statements
216-
csharp_using_directive_placement = outside_namespace:error
216+
csharp_using_directive_placement = outside_namespace_ignoring_aliases:error
217217

218218
# Modifier settings
219219
csharp_prefer_static_local_function = true:warning
@@ -263,7 +263,7 @@ dotnet_diagnostic.CA1055.severity = none # Uri return values should not b
263263
dotnet_diagnostic.CA1056.severity = none # Uri properties should not be strings
264264
dotnet_diagnostic.CA1060.severity = none # Move P/Invokes to NativeMethods class
265265
dotnet_diagnostic.CA1062.severity = none # Validate arguments of public methods
266-
dotnet_diagnostic.CA1063.severity = warning # Implement IDisposable Correctly
266+
dotnet_diagnostic.CA1063.severity = none # Implement IDisposable Correctly: https://github.com/dotnet/roslyn-analyzers/issues/4801
267267
dotnet_diagnostic.CA1064.severity = none # Exceptions should be public
268268
dotnet_diagnostic.CA1065.severity = none # Do not raise exceptions in unexpected locations
269269
dotnet_diagnostic.CA1066.severity = none # Type {0} should implement IEquatable<T> because it overrides Equals

Directory.Packages.props

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@
3030
<PackageVersion Include="Nerdbank.Streams" Version="2.12.87" />
3131
<PackageVersion Include="System.IO.Pipelines" Version="9.0.0" />
3232
<PackageVersion Include="StreamJsonRpc" Version="2.23.32-alpha" />
33+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
34+
<PackageVersion Include="Microsoft.DotNet.HotReload.Agent.Data" Version="10.0.100-rc.2.25464.102"/>
35+
<PackageVersion Include="Microsoft.DotNet.HotReload.Agent.PipeRpc" Version="10.0.100-rc.2.25464.102"/>
36+
<PackageVersion Include="Microsoft.DotNet.HotReload.Agent.Host" Version="10.0.100-rc.2.25464.102"/>
37+
<PackageVersion Include="Microsoft.DotNet.HotReload.Client" Version="10.0.100-rc.2.25464.102"/>
38+
<PackageVersion Include="Microsoft.DotNet.HotReload.Web.Middleware" Version="10.0.100-rc.2.25464.102"/>
3339

3440
<!-- VS SDK -->
3541
<!-- https://dev.azure.com/azure-public/vside/_artifacts/feed/vssdk -->

nuget.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
1+
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
22

33
<configuration>
44
<packageSources>

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/Microsoft.VisualStudio.ProjectSystem.Managed.VS.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<PackageReference Include="Microsoft.VisualStudio.ProjectSystem.Query" />
3030
<PackageReference Include="Microsoft.VisualStudio.ProjectSystem.VS" />
3131
<PackageReference Include="IsExternalInit" PrivateAssets="all" />
32+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
3233
</ItemGroup>
3334

3435
<ItemGroup>

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Debug/ErrorProfileDebugTargetsProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ public Task<IReadOnlyList<IDebugLaunchSettings>> QueryDebugTargetsAsync(DebugLau
5757
activeProfile.OtherSettings.TryGetValue("ErrorString", out object? objErrorString) &&
5858
objErrorString is string errorString)
5959
{
60-
throw new Exception(string.Format(VSResources.ErrorInProfilesFile2, Path.GetFileNameWithoutExtension(_configuredProject.UnconfiguredProject.FullPath), errorString));
60+
throw new Exception(string.Format(VSResources.ErrorInProfilesFile2, _configuredProject.GetProjectName(), errorString));
6161
}
6262

63-
throw new Exception(string.Format(VSResources.ErrorInProfilesFile, Path.GetFileNameWithoutExtension(_configuredProject.UnconfiguredProject.FullPath)));
63+
throw new Exception(string.Format(VSResources.ErrorInProfilesFile, _configuredProject.GetProjectName()));
6464
}
6565
}

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Debug/ProjectLaunchTargetsProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public async Task OnAfterLaunchAsync(DebugLaunchOptions launchOptions, ILaunchPr
114114

115115
var configuredProjectForDebug = await GetConfiguredProjectForDebugAsync();
116116
var hotReloadSessionManager = configuredProjectForDebug.GetExportedService<IProjectHotReloadSessionManager>();
117-
await hotReloadSessionManager.ActivateSessionAsync((int)processInfos[0].dwProcessId, Path.GetFileNameWithoutExtension(_project.UnconfiguredProject.FullPath));
117+
await hotReloadSessionManager.ActivateSessionAsync((int)processInfos[0].dwProcessId, _project.GetProjectName());
118118
}
119119

120120
public async Task<bool> CanBeStartupProjectAsync(DebugLaunchOptions launchOptions, ILaunchProfile profile)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
2+
3+
using Microsoft.VisualStudio.ProjectSystem.HotReload;
4+
using Microsoft.VisualStudio.Shell.Interop;
5+
6+
namespace Microsoft.VisualStudio.ProjectSystem.VS.HotReload;
7+
8+
// TODO: Replace with IDebuggerStateService
9+
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2571211
10+
11+
[Export(typeof(IHotReloadDebugStateProvider))]
12+
[method: ImportingConstructor]
13+
internal sealed class HotReloadDebugStateProvider(
14+
IProjectThreadingService threadingService,
15+
IVsUIService<SVsShellDebugger, IVsDebugger> debugger) : IHotReloadDebugStateProvider
16+
{
17+
public async ValueTask<bool> IsSuspendedAsync(CancellationToken cancellationToken)
18+
{
19+
await threadingService.SwitchToUIThread(cancellationToken);
20+
21+
var dbgmode = new DBGMODE[1];
22+
return ErrorHandler.Succeeded(debugger.Value.GetMode(dbgmode)) &&
23+
(dbgmode[0] & ~DBGMODE.DBGMODE_EncMask) == DBGMODE.DBGMODE_Break;
24+
}
25+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
2+
3+
using System.Net;
4+
using Microsoft.DotNet.HotReload;
5+
using Microsoft.Extensions.Logging;
6+
using Microsoft.VisualStudio.Threading;
7+
8+
namespace Microsoft.VisualStudio.ProjectSystem.HotReload;
9+
10+
internal sealed class VisualStudioBrowserRefreshServer(
11+
ILogger logger,
12+
ILoggerFactory loggerFactory,
13+
string projectName,
14+
int port,
15+
int sslPort,
16+
string virtualDirectory)
17+
: AbstractBrowserRefreshServer(GetMiddlewareAssemblyPath(), logger, loggerFactory)
18+
{
19+
private const string MiddlewareTargetFramework = "net6.0";
20+
21+
private static string GetMiddlewareAssemblyPath()
22+
=> ProjectHotReloadSession.GetInjectedAssemblyPath(MiddlewareTargetFramework, "Microsoft.AspNetCore.Watch.BrowserRefresh");
23+
24+
protected override bool SuppressTimeouts
25+
=> false;
26+
27+
// for testing
28+
internal Task? WebSocketListeningTask { get; private set; }
29+
30+
protected override ValueTask<WebServerHost> CreateAndStartHostAsync(CancellationToken cancellationToken)
31+
{
32+
var httpListener = CreateListener(projectName, port, sslPort);
33+
WebSocketListeningTask = ListenAsync(cancellationToken);
34+
35+
return new(new WebServerHost(httpListener, GetWebSocketUrls(projectName, port, sslPort), virtualDirectory));
36+
37+
async Task ListenAsync(CancellationToken cancellationToken)
38+
{
39+
try
40+
{
41+
httpListener.Start();
42+
43+
while (!cancellationToken.IsCancellationRequested)
44+
{
45+
Logger.LogDebug("Waiting for a browser connection");
46+
47+
// wait for incoming request:
48+
var context = await httpListener.GetContextAsync();
49+
if (!context.Request.IsWebSocketRequest)
50+
{
51+
context.Response.StatusCode = 400;
52+
context.Response.Close();
53+
continue;
54+
}
55+
56+
try
57+
{
58+
// Accepting Socket Next request. If the context has a "Sec-WebSocket-Protocol" header it passes back in the AcceptWebSocket
59+
var protocol = context.Request.Headers["Sec-WebSocket-Protocol"];
60+
var webSocketContext = await context.AcceptWebSocketAsync(subProtocol: protocol).WithCancellation(cancellationToken);
61+
62+
_ = OnBrowserConnected(webSocketContext.WebSocket, webSocketContext.SecWebSocketProtocols.FirstOrDefault());
63+
}
64+
catch (Exception e)
65+
{
66+
Logger.LogError("Accepting web socket exception: {Message}", e.Message);
67+
68+
context.Response.StatusCode = 500;
69+
context.Response.Close();
70+
}
71+
}
72+
}
73+
catch (OperationCanceledException)
74+
{
75+
// nop
76+
}
77+
catch (Exception e)
78+
{
79+
Logger.LogError("HttpListener exception: {Message}", e.Message);
80+
}
81+
}
82+
}
83+
84+
private static HttpListener CreateListener(string projectName, int port, int sslPort)
85+
{
86+
var httpListener = new HttpListener();
87+
88+
httpListener.Prefixes.Add($"https://localhost:{sslPort}/{projectName}/");
89+
if (sslPort >= 0)
90+
{
91+
httpListener.Prefixes.Add($"http://localhost:{port}/{projectName}/");
92+
}
93+
94+
return httpListener;
95+
}
96+
97+
private static ImmutableArray<string> GetWebSocketUrls(string projectName, int port, int sslPort)
98+
{
99+
return sslPort >= 0 ? [GetWebSocketUrl(port, isSecure: false), GetWebSocketUrl(sslPort, isSecure: true)] : [GetWebSocketUrl(port, isSecure: false)];
100+
101+
string GetWebSocketUrl(int port, bool isSecure)
102+
=> $"{(isSecure ? "wss" : "ws")}://localhost:{port}/{projectName}/";
103+
}
104+
}

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Input/Commands/GenerateNuGetPackageTopLevelBuildMenuCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ public GenerateNuGetPackageTopLevelBuildMenuCommand(
2424
protected override bool ShouldHandle(IProjectTree node) => true;
2525

2626
protected override string GetCommandText()
27-
=> string.Format(VSResources.PackSelectedProjectCommand, Path.GetFileNameWithoutExtension(Project.FullPath));
27+
=> string.Format(VSResources.PackSelectedProjectCommand, Project.GetProjectName());
2828
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
2+
3+
using Microsoft.DotNet.HotReload;
4+
using Microsoft.Extensions.Logging;
5+
using Microsoft.VisualStudio.ProjectSystem.HotReload;
6+
using Microsoft.VisualStudio.ProjectSystem.VS.HotReload;
7+
8+
namespace Microsoft.VisualStudio.ProjectSystem.VS.Web;
9+
10+
public sealed class VisualStudioBrowserRefreshServerAccessor(
11+
ILogger logger,
12+
ILoggerFactory loggerFactory,
13+
string projectName,
14+
int port,
15+
int sslPort,
16+
string virtualDirectory)
17+
: AbstractBrowserRefreshServerAccessor
18+
{
19+
internal override AbstractBrowserRefreshServer Server { get; } = new VisualStudioBrowserRefreshServer(
20+
logger,
21+
loggerFactory,
22+
projectName,
23+
port,
24+
sslPort,
25+
virtualDirectory);
26+
}

0 commit comments

Comments
 (0)