Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit b444831

Browse files
Remove System.Runtime.Loader dependency to enable net461 support. Use IApplicationLifetime instead of AssemblyLoadContext.Default.Unloading.
1 parent 2aaceaa commit b444831

File tree

9 files changed

+50
-24
lines changed

9 files changed

+50
-24
lines changed

src/Microsoft.AspNetCore.NodeServices.Sockets/SocketNodeInstance.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public SocketNodeInstance(NodeServicesOptions options, string socketAddress)
4949
options.ProjectPath,
5050
options.WatchFileExtensions,
5151
MakeNewCommandLineOptions(socketAddress),
52+
options.ApplicationStoppingToken,
5253
options.NodeInstanceOutputLogger,
5354
options.EnvironmentVariables,
5455
options.InvocationTimeoutMilliseconds,

src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Threading;
34
using Microsoft.AspNetCore.NodeServices.HostingModels;
45
using Microsoft.Extensions.Logging;
56
using Microsoft.Extensions.DependencyInjection;
@@ -42,6 +43,12 @@ public NodeServicesOptions(IServiceProvider serviceProvider)
4243
EnvironmentVariables["NODE_ENV"] = hostEnv.IsDevelopment() ? "development" : "production"; // De-facto standard values for Node
4344
}
4445

46+
var applicationLifetime = serviceProvider.GetService<IApplicationLifetime>();
47+
if (applicationLifetime != null)
48+
{
49+
ApplicationStoppingToken = applicationLifetime.ApplicationStopping;
50+
}
51+
4552
// If the DI system gives us a logger, use it. Otherwise, set up a default one.
4653
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
4754
NodeInstanceOutputLogger = loggerFactory != null
@@ -93,5 +100,10 @@ public NodeServicesOptions(IServiceProvider serviceProvider)
93100
/// Specifies the maximum duration, in milliseconds, that your .NET code should wait for Node.js RPC calls to return.
94101
/// </summary>
95102
public int InvocationTimeoutMilliseconds { get; set; }
103+
104+
/// <summary>
105+
/// A token that indicates when the host application is stopping.
106+
/// </summary>
107+
public CancellationToken ApplicationStoppingToken { get; set; }
96108
}
97109
}

src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public HttpNodeInstance(NodeServicesOptions options, int port = 0)
4242
options.ProjectPath,
4343
options.WatchFileExtensions,
4444
MakeCommandLineOptions(port),
45+
options.ApplicationStoppingToken,
4546
options.NodeInstanceOutputLogger,
4647
options.EnvironmentVariables,
4748
options.InvocationTimeoutMilliseconds,

src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public abstract class OutOfProcessNodeInstance : INodeInstance
4545
/// <param name="projectPath">The root path of the current project. This is used when resolving Node.js module paths relative to the project root.</param>
4646
/// <param name="watchFileExtensions">The filename extensions that should be watched within the project root. The Node instance will automatically shut itself down if any matching file changes.</param>
4747
/// <param name="commandLineArguments">Additional command-line arguments to be passed to the Node.js instance.</param>
48+
/// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
4849
/// <param name="nodeOutputLogger">The <see cref="ILogger"/> to which the Node.js instance's stdout/stderr (and other log information) should be written.</param>
4950
/// <param name="environmentVars">Environment variables to be set on the Node.js process.</param>
5051
/// <param name="invocationTimeoutMilliseconds">The maximum duration, in milliseconds, to wait for RPC calls to complete.</param>
@@ -55,6 +56,7 @@ public OutOfProcessNodeInstance(
5556
string projectPath,
5657
string[] watchFileExtensions,
5758
string commandLineArguments,
59+
CancellationToken applicationStoppingToken,
5860
ILogger nodeOutputLogger,
5961
IDictionary<string, string> environmentVars,
6062
int invocationTimeoutMilliseconds,
@@ -67,7 +69,7 @@ public OutOfProcessNodeInstance(
6769
}
6870

6971
OutputLogger = nodeOutputLogger;
70-
_entryPointScript = new StringAsTempFile(entryPointScript);
72+
_entryPointScript = new StringAsTempFile(entryPointScript, applicationStoppingToken);
7173
_invocationTimeoutMilliseconds = invocationTimeoutMilliseconds;
7274
_launchWithDebugging = launchWithDebugging;
7375

src/Microsoft.AspNetCore.NodeServices/Microsoft.AspNetCore.NodeServices.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(AspNetCoreVersion)" />
1919
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreVersion)" />
2020
<PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" />
21-
<PackageReference Include="System.Runtime.Loader" Version="$(CoreFxVersion)" />
2221
</ItemGroup>
2322

2423
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">

src/Microsoft.AspNetCore.NodeServices/Util/StringAsTempFile.cs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using System.Threading;
34

45
namespace Microsoft.AspNetCore.NodeServices
56
{
@@ -11,22 +12,21 @@ public sealed class StringAsTempFile : IDisposable
1112
private bool _disposedValue;
1213
private bool _hasDeletedTempFile;
1314
private object _fileDeletionLock = new object();
15+
private IDisposable _applicationLifetimeRegistration;
1416

1517
/// <summary>
1618
/// Create a new instance of <see cref="StringAsTempFile"/>.
1719
/// </summary>
1820
/// <param name="content">The contents of the temporary file to be created.</param>
19-
public StringAsTempFile(string content)
21+
/// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
22+
public StringAsTempFile(string content, CancellationToken applicationStoppingToken)
2023
{
2124
FileName = Path.GetTempFileName();
2225
File.WriteAllText(FileName, content);
2326

2427
// Because .NET finalizers don't reliably run when the process is terminating, also
2528
// add event handlers for other shutdown scenarios.
26-
// Note that this still doesn't capture SIGKILL (at least on macOS) - there doesn't
27-
// appear to be a way of doing that. So in that case, the temporary file will be
28-
// left behind.
29-
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += HandleAssemblyUnloading;
29+
_applicationLifetimeRegistration = applicationStoppingToken.Register(EnsureTempFileDeleted);
3030
}
3131

3232
/// <summary>
@@ -50,7 +50,7 @@ private void DisposeImpl(bool disposing)
5050
if (disposing)
5151
{
5252
// Dispose managed state
53-
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading -= HandleAssemblyUnloading;
53+
_applicationLifetimeRegistration.Dispose();
5454
}
5555

5656
EnsureTempFileDeleted();
@@ -71,11 +71,6 @@ private void EnsureTempFileDeleted()
7171
}
7272
}
7373

74-
private void HandleAssemblyUnloading(System.Runtime.Loader.AssemblyLoadContext context)
75-
{
76-
EnsureTempFileDeleted();
77-
}
78-
7974
/// <summary>
8075
/// Implements the finalization part of the IDisposable pattern by calling Dispose(false).
8176
/// </summary>

src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Text;
3+
using System.Threading;
34
using System.Threading.Tasks;
45
using Microsoft.AspNetCore.Hosting;
56
using Microsoft.AspNetCore.Http.Features;
@@ -24,6 +25,7 @@ public class PrerenderTagHelper : TagHelper
2425
private static INodeServices _fallbackNodeServices; // Used only if no INodeServices was registered with DI
2526

2627
private readonly string _applicationBasePath;
28+
private readonly CancellationToken _applicationStoppingToken;
2729
private readonly INodeServices _nodeServices;
2830

2931
/// <summary>
@@ -35,6 +37,9 @@ public PrerenderTagHelper(IServiceProvider serviceProvider)
3537
var hostEnv = (IHostingEnvironment) serviceProvider.GetService(typeof(IHostingEnvironment));
3638
_nodeServices = (INodeServices) serviceProvider.GetService(typeof(INodeServices)) ?? _fallbackNodeServices;
3739
_applicationBasePath = hostEnv.ContentRootPath;
40+
41+
var applicationLifetime = (IApplicationLifetime) serviceProvider.GetService(typeof(IApplicationLifetime));
42+
_applicationStoppingToken = applicationLifetime.ApplicationStopping;
3843

3944
// Consider removing the following. Having it means you can get away with not putting app.AddNodeServices()
4045
// in your startup file, but then again it might be confusing that you don't need to.
@@ -101,6 +106,7 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
101106
var result = await Prerenderer.RenderToString(
102107
_applicationBasePath,
103108
_nodeServices,
109+
_applicationStoppingToken,
104110
new JavaScriptModuleExport(ModuleName)
105111
{
106112
ExportName = ExportName
Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading;
23
using System.Threading.Tasks;
34
using Microsoft.AspNetCore.NodeServices;
45

@@ -9,22 +10,16 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
910
/// </summary>
1011
public static class Prerenderer
1112
{
12-
private static readonly Lazy<StringAsTempFile> NodeScript;
13+
private static readonly object CreateNodeScriptLock = new object();
1314

14-
static Prerenderer()
15-
{
16-
NodeScript = new Lazy<StringAsTempFile>(() =>
17-
{
18-
var script = EmbeddedResourceReader.Read(typeof(Prerenderer), "/Content/Node/prerenderer.js");
19-
return new StringAsTempFile(script); // Will be cleaned up on process exit
20-
});
21-
}
15+
private static StringAsTempFile NodeScript;
2216

2317
/// <summary>
2418
/// Performs server-side prerendering by invoking code in Node.js.
2519
/// </summary>
2620
/// <param name="applicationBasePath">The root path to your application. This is used when resolving project-relative paths.</param>
2721
/// <param name="nodeServices">The instance of <see cref="INodeServices"/> that will be used to invoke JavaScript code.</param>
22+
/// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
2823
/// <param name="bootModule">The path to the JavaScript file containing the prerendering logic.</param>
2924
/// <param name="requestAbsoluteUrl">The URL of the currently-executing HTTP request. This is supplied to the prerendering code.</param>
3025
/// <param name="requestPathAndQuery">The path and query part of the URL of the currently-executing HTTP request. This is supplied to the prerendering code.</param>
@@ -35,6 +30,7 @@ static Prerenderer()
3530
public static Task<RenderToStringResult> RenderToString(
3631
string applicationBasePath,
3732
INodeServices nodeServices,
33+
CancellationToken applicationStoppingToken,
3834
JavaScriptModuleExport bootModule,
3935
string requestAbsoluteUrl,
4036
string requestPathAndQuery,
@@ -43,7 +39,7 @@ public static Task<RenderToStringResult> RenderToString(
4339
string requestPathBase)
4440
{
4541
return nodeServices.InvokeExportAsync<RenderToStringResult>(
46-
NodeScript.Value.FileName,
42+
GetNodeScriptFilename(applicationStoppingToken),
4743
"renderToString",
4844
applicationBasePath,
4945
bootModule,
@@ -53,5 +49,19 @@ public static Task<RenderToStringResult> RenderToString(
5349
timeoutMilliseconds,
5450
requestPathBase);
5551
}
52+
53+
private static string GetNodeScriptFilename(CancellationToken applicationStoppingToken)
54+
{
55+
lock(CreateNodeScriptLock)
56+
{
57+
if (NodeScript == null)
58+
{
59+
var script = EmbeddedResourceReader.Read(typeof(Prerenderer), "/Content/Node/prerenderer.js");
60+
NodeScript = new StringAsTempFile(script, applicationStoppingToken); // Will be cleaned up on process exit
61+
}
62+
}
63+
64+
return NodeScript.FileName;
65+
}
5666
}
5767
}

src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public static void UseWebpackDevMiddleware(
6868
// Get a filename matching the middleware Node script
6969
var script = EmbeddedResourceReader.Read(typeof(WebpackDevMiddleware),
7070
"/Content/Node/webpack-dev-middleware.js");
71-
var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit
71+
var nodeScript = new StringAsTempFile(script, nodeServicesOptions.ApplicationStoppingToken); // Will be cleaned up on process exit
7272

7373
// Ideally, this would be relative to the application's PathBase (so it could work in virtual directories)
7474
// but it's not clear that such information exists during application startup, as opposed to within the context

0 commit comments

Comments
 (0)