Merge branch 'rel/2.0.0-preview2' into dev

This commit is contained in:
Steve Sanderson
2017-06-07 15:02:29 +01:00
29 changed files with 66 additions and 115 deletions

View File

@@ -16,7 +16,8 @@
<PackageReference Include="Internal.AspNetCore.Sdk" Version="$(InternalAspNetCoreSdkVersion)" PrivateAssets="All" /> <PackageReference Include="Internal.AspNetCore.Sdk" Version="$(InternalAspNetCoreSdkVersion)" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)'=='.NETFramework' AND '$(OutputType)'=='library'"> <ItemGroup Condition="'$(TargetFrameworkIdentifier)'=='.NETFramework'">
<PackageReference Include="NETStandard.Library" Version="$(BundledNETStandardPackageVersion)" /> <PackageReference Include="NETStandard.Library" Version="$(BundledNETStandardPackageVersion)" />
<PackageReference Include="NETStandard.Library.NETFramework" Version="$(NETStandardLibraryNETFrameworkVersion)" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -2,10 +2,10 @@
<PropertyGroup> <PropertyGroup>
<AspNetCoreVersion>2.0.0-*</AspNetCoreVersion> <AspNetCoreVersion>2.0.0-*</AspNetCoreVersion>
<AutoMapperVersion>5.0.2</AutoMapperVersion> <AutoMapperVersion>5.0.2</AutoMapperVersion>
<CoreFxVersion>4.3.0</CoreFxVersion>
<InternalAspNetCoreSdkVersion>2.1.0-*</InternalAspNetCoreSdkVersion> <InternalAspNetCoreSdkVersion>2.1.0-*</InternalAspNetCoreSdkVersion>
<JsonNetVersion>10.0.1</JsonNetVersion> <JsonNetVersion>10.0.1</JsonNetVersion>
<NETStandardImplicitPackageVersion>$(BundledNETStandardPackageVersion)</NETStandardImplicitPackageVersion> <NETStandardImplicitPackageVersion>$(BundledNETStandardPackageVersion)</NETStandardImplicitPackageVersion>
<NETStandardLibraryNETFrameworkVersion>2.0.0-*</NETStandardLibraryNETFrameworkVersion>
<RuntimeFrameworkVersion Condition="'$(TargetFramework)'=='netcoreapp2.0'">2.0.0-*</RuntimeFrameworkVersion> <RuntimeFrameworkVersion Condition="'$(TargetFramework)'=='netcoreapp2.0'">2.0.0-*</RuntimeFrameworkVersion>
<ThreadingDataflowVersion>4.7.0</ThreadingDataflowVersion> <ThreadingDataflowVersion>4.7.0</ThreadingDataflowVersion>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,12 +0,0 @@
<Project>
<Target Name="NpmRestore" AfterTargets="Restore" Condition="'$(PreflightRestore)' != 'true'">
<ItemGroup>
<NpmModules Include="$(RepositoryRoot)**\package.json"
Exclude="$(RepositoryRoot)**\node_modules\**\*" />
</ItemGroup>
<Message Text="Restoring NPM modules for: %0A - @(NpmModules -> '%(FullPath)','%0A - ')" Importance="high" />
<Exec Command="npm install --silent" WorkingDirectory="%(NpmModules.RootDir)%(Directory)" />
</Target>
</Project>

View File

@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\build\dependencies.props" /> <Import Project="..\..\..\build\common.props" />
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\build\dependencies.props" /> <Import Project="..\..\..\build\common.props" />
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>

View File

@@ -27,6 +27,7 @@ namespace Webpack.ActionResults
{ {
var nodeServices = context.HttpContext.RequestServices.GetRequiredService<INodeServices>(); var nodeServices = context.HttpContext.RequestServices.GetRequiredService<INodeServices>();
var hostEnv = context.HttpContext.RequestServices.GetRequiredService<IHostingEnvironment>(); var hostEnv = context.HttpContext.RequestServices.GetRequiredService<IHostingEnvironment>();
var applicationLifetime = context.HttpContext.RequestServices.GetRequiredService<IApplicationLifetime>();
var applicationBasePath = hostEnv.ContentRootPath; var applicationBasePath = hostEnv.ContentRootPath;
var request = context.HttpContext.Request; var request = context.HttpContext.Request;
var response = context.HttpContext.Response; var response = context.HttpContext.Response;
@@ -34,6 +35,7 @@ namespace Webpack.ActionResults
var prerenderedHtml = await Prerenderer.RenderToString( var prerenderedHtml = await Prerenderer.RenderToString(
applicationBasePath, applicationBasePath,
nodeServices, nodeServices,
applicationLifetime.ApplicationStopping,
_moduleExport, _moduleExport,
request.GetEncodedUrl(), request.GetEncodedUrl(),
request.Path + request.QueryString.Value, request.Path + request.QueryString.Value,

View File

@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\build\dependencies.props" /> <Import Project="..\..\..\build\common.props" />
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>

View File

@@ -4,7 +4,7 @@
<PropertyGroup> <PropertyGroup>
<Description>Socket-based RPC for Microsoft.AspNetCore.NodeServices.</Description> <Description>Socket-based RPC for Microsoft.AspNetCore.NodeServices.</Description>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags> <PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup> </PropertyGroup>
@@ -15,14 +15,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" /> <ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
<PackageReference Include="Microsoft.Tpl.Dataflow" Version="$(MicrosoftDataflowVersion)" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">
<PackageReference Include="System.IO.Pipes" Version="$(CoreFxVersion)" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="$(ThreadingDataflowVersion)" /> <PackageReference Include="System.Threading.Tasks.Dataflow" Version="$(ThreadingDataflowVersion)" />
</ItemGroup> </ItemGroup>

View File

@@ -18,11 +18,7 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
PipeDirection.InOut, PipeDirection.InOut,
PipeOptions.Asynchronous); PipeOptions.Asynchronous);
#if NET451
_namedPipeClientStream.Connect();
#else
await _namedPipeClientStream.ConnectAsync().ConfigureAwait(false); await _namedPipeClientStream.ConnectAsync().ConfigureAwait(false);
#endif
return _namedPipeClientStream; return _namedPipeClientStream;
} }

View File

@@ -11,9 +11,6 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
public static StreamConnection Create() public static StreamConnection Create()
{ {
#if NET451
return new NamedPipeConnection();
#else
var useNamedPipes = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( var useNamedPipes = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
System.Runtime.InteropServices.OSPlatform.Windows); System.Runtime.InteropServices.OSPlatform.Windows);
if (useNamedPipes) if (useNamedPipes)
@@ -24,7 +21,6 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
{ {
return new UnixDomainSocketConnection(); return new UnixDomainSocketConnection();
} }
#endif
} }
} }
} }

View File

@@ -10,16 +10,6 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
private NetworkStream _networkStream; private NetworkStream _networkStream;
private Socket _socket; private Socket _socket;
#if NET451
public override Task<Stream> Open(string address)
{
// The 'null' assignments avoid the compiler warnings about unassigned fields.
// Note that this whole class isn't supported on .NET 4.5.1, since that's not cross-platform.
_networkStream = null;
_socket = null;
throw new System.PlatformNotSupportedException();
}
#else
public override async Task<Stream> Open(string address) public override async Task<Stream> Open(string address)
{ {
var endPoint = new UnixDomainSocketEndPoint("/tmp/" + address); var endPoint = new UnixDomainSocketEndPoint("/tmp/" + address);
@@ -28,7 +18,6 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
_networkStream = new NetworkStream(_socket); _networkStream = new NetworkStream(_socket);
return _networkStream; return _networkStream;
} }
#endif
public override void Dispose() public override void Dispose()
{ {

View File

@@ -59,11 +59,7 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
} }
else else
{ {
#if NET451
_encodedPath = new byte[0];
#else
_encodedPath = Array.Empty<byte>(); _encodedPath = Array.Empty<byte>();
#endif
_path = string.Empty; _path = string.Empty;
} }
} }

View File

@@ -49,6 +49,7 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets
options.ProjectPath, options.ProjectPath,
options.WatchFileExtensions, options.WatchFileExtensions,
MakeNewCommandLineOptions(socketAddress), MakeNewCommandLineOptions(socketAddress),
options.ApplicationStoppingToken,
options.NodeInstanceOutputLogger, options.NodeInstanceOutputLogger,
options.EnvironmentVariables, options.EnvironmentVariables,
options.InvocationTimeoutMilliseconds, options.InvocationTimeoutMilliseconds,

View File

@@ -12,11 +12,7 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections
/// </summary> /// </summary>
internal class VirtualConnection : Stream internal class VirtualConnection : Stream
{ {
#if NET451
private readonly static Task CompletedTask = Task.FromResult((object)null);
#else
private readonly static Task CompletedTask = Task.CompletedTask; private readonly static Task CompletedTask = Task.CompletedTask;
#endif
private VirtualConnectionClient _host; private VirtualConnectionClient _host;
private readonly BufferBlock<byte[]> _receivedDataQueue = new BufferBlock<byte[]>(); private readonly BufferBlock<byte[]> _receivedDataQueue = new BufferBlock<byte[]>();
private ArraySegment<byte> _receivedDataNotYetUsed; private ArraySegment<byte> _receivedDataNotYetUsed;

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using Microsoft.AspNetCore.NodeServices.HostingModels; using Microsoft.AspNetCore.NodeServices.HostingModels;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -42,6 +43,12 @@ namespace Microsoft.AspNetCore.NodeServices
EnvironmentVariables["NODE_ENV"] = hostEnv.IsDevelopment() ? "development" : "production"; // De-facto standard values for Node EnvironmentVariables["NODE_ENV"] = hostEnv.IsDevelopment() ? "development" : "production"; // De-facto standard values for Node
} }
var applicationLifetime = serviceProvider.GetService<IApplicationLifetime>();
if (applicationLifetime != null)
{
ApplicationStoppingToken = applicationLifetime.ApplicationStopping;
}
// If the DI system gives us a logger, use it. Otherwise, set up a default one. // If the DI system gives us a logger, use it. Otherwise, set up a default one.
var loggerFactory = serviceProvider.GetService<ILoggerFactory>(); var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
NodeInstanceOutputLogger = loggerFactory != null NodeInstanceOutputLogger = loggerFactory != null
@@ -93,5 +100,10 @@ namespace Microsoft.AspNetCore.NodeServices
/// Specifies the maximum duration, in milliseconds, that your .NET code should wait for Node.js RPC calls to return. /// Specifies the maximum duration, in milliseconds, that your .NET code should wait for Node.js RPC calls to return.
/// </summary> /// </summary>
public int InvocationTimeoutMilliseconds { get; set; } public int InvocationTimeoutMilliseconds { get; set; }
/// <summary>
/// A token that indicates when the host application is stopping.
/// </summary>
public CancellationToken ApplicationStoppingToken { get; set; }
} }
} }

View File

@@ -42,6 +42,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
options.ProjectPath, options.ProjectPath,
options.WatchFileExtensions, options.WatchFileExtensions,
MakeCommandLineOptions(port), MakeCommandLineOptions(port),
options.ApplicationStoppingToken,
options.NodeInstanceOutputLogger, options.NodeInstanceOutputLogger,
options.EnvironmentVariables, options.EnvironmentVariables,
options.InvocationTimeoutMilliseconds, options.InvocationTimeoutMilliseconds,

View File

@@ -45,6 +45,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
/// <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> /// <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>
/// <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> /// <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>
/// <param name="commandLineArguments">Additional command-line arguments to be passed to the Node.js instance.</param> /// <param name="commandLineArguments">Additional command-line arguments to be passed to the Node.js instance.</param>
/// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
/// <param name="nodeOutputLogger">The <see cref="ILogger"/> to which the Node.js instance's stdout/stderr (and other log information) should be written.</param> /// <param name="nodeOutputLogger">The <see cref="ILogger"/> to which the Node.js instance's stdout/stderr (and other log information) should be written.</param>
/// <param name="environmentVars">Environment variables to be set on the Node.js process.</param> /// <param name="environmentVars">Environment variables to be set on the Node.js process.</param>
/// <param name="invocationTimeoutMilliseconds">The maximum duration, in milliseconds, to wait for RPC calls to complete.</param> /// <param name="invocationTimeoutMilliseconds">The maximum duration, in milliseconds, to wait for RPC calls to complete.</param>
@@ -55,6 +56,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
string projectPath, string projectPath,
string[] watchFileExtensions, string[] watchFileExtensions,
string commandLineArguments, string commandLineArguments,
CancellationToken applicationStoppingToken,
ILogger nodeOutputLogger, ILogger nodeOutputLogger,
IDictionary<string, string> environmentVars, IDictionary<string, string> environmentVars,
int invocationTimeoutMilliseconds, int invocationTimeoutMilliseconds,
@@ -67,7 +69,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
} }
OutputLogger = nodeOutputLogger; OutputLogger = nodeOutputLogger;
_entryPointScript = new StringAsTempFile(entryPointScript); _entryPointScript = new StringAsTempFile(entryPointScript, applicationStoppingToken);
_invocationTimeoutMilliseconds = invocationTimeoutMilliseconds; _invocationTimeoutMilliseconds = invocationTimeoutMilliseconds;
_launchWithDebugging = launchWithDebugging; _launchWithDebugging = launchWithDebugging;
@@ -315,11 +317,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
private static void SetEnvironmentVariable(ProcessStartInfo startInfo, string name, string value) private static void SetEnvironmentVariable(ProcessStartInfo startInfo, string name, string value)
{ {
#if NET451
startInfo.EnvironmentVariables[name] = value;
#else
startInfo.Environment[name] = value; startInfo.Environment[name] = value;
#endif
} }
private static Process LaunchNodeProcess(ProcessStartInfo startInfo) private static Process LaunchNodeProcess(ProcessStartInfo startInfo)

View File

@@ -4,7 +4,7 @@
<PropertyGroup> <PropertyGroup>
<Description>Invoke Node.js modules at runtime in ASP.NET Core applications.</Description> <Description>Invoke Node.js modules at runtime in ASP.NET Core applications.</Description>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags> <PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
@@ -20,12 +20,6 @@
<PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" /> <PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">
<PackageReference Include="System.Diagnostics.Process" Version="$(CoreFxVersion)" />
<PackageReference Include="System.IO.FileSystem.Watcher" Version="$(CoreFxVersion)" />
<PackageReference Include="System.Runtime.Loader" Version="$(CoreFxVersion)" />
</ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' "> <Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">
<Exec Command="npm install" /> <Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js" /> <Exec Command="node node_modules/webpack/bin/webpack.js" />

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
namespace Microsoft.AspNetCore.NodeServices namespace Microsoft.AspNetCore.NodeServices
{ {
@@ -11,27 +12,21 @@ namespace Microsoft.AspNetCore.NodeServices
private bool _disposedValue; private bool _disposedValue;
private bool _hasDeletedTempFile; private bool _hasDeletedTempFile;
private object _fileDeletionLock = new object(); private object _fileDeletionLock = new object();
private IDisposable _applicationLifetimeRegistration;
/// <summary> /// <summary>
/// Create a new instance of <see cref="StringAsTempFile"/>. /// Create a new instance of <see cref="StringAsTempFile"/>.
/// </summary> /// </summary>
/// <param name="content">The contents of the temporary file to be created.</param> /// <param name="content">The contents of the temporary file to be created.</param>
public StringAsTempFile(string content) /// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
public StringAsTempFile(string content, CancellationToken applicationStoppingToken)
{ {
FileName = Path.GetTempFileName(); FileName = Path.GetTempFileName();
File.WriteAllText(FileName, content); File.WriteAllText(FileName, content);
// Because .NET finalizers don't reliably run when the process is terminating, also // Because .NET finalizers don't reliably run when the process is terminating, also
// add event handlers for other shutdown scenarios. // add event handlers for other shutdown scenarios.
#if NET451 _applicationLifetimeRegistration = applicationStoppingToken.Register(EnsureTempFileDeleted);
AppDomain.CurrentDomain.ProcessExit += HandleProcessExit;
AppDomain.CurrentDomain.DomainUnload += HandleProcessExit;
#else
// Note that this still doesn't capture SIGKILL (at least on macOS) - there doesn't
// appear to be a way of doing that. So in that case, the temporary file will be
// left behind.
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += HandleAssemblyUnloading;
#endif
} }
/// <summary> /// <summary>
@@ -55,12 +50,7 @@ namespace Microsoft.AspNetCore.NodeServices
if (disposing) if (disposing)
{ {
// Dispose managed state // Dispose managed state
#if NET451 _applicationLifetimeRegistration.Dispose();
AppDomain.CurrentDomain.ProcessExit -= HandleProcessExit;
AppDomain.CurrentDomain.DomainUnload -= HandleProcessExit;
#else
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading -= HandleAssemblyUnloading;
#endif
} }
EnsureTempFileDeleted(); EnsureTempFileDeleted();
@@ -81,18 +71,6 @@ namespace Microsoft.AspNetCore.NodeServices
} }
} }
#if NET451
private void HandleProcessExit(object sender, EventArgs args)
{
EnsureTempFileDeleted();
}
#else
private void HandleAssemblyUnloading(System.Runtime.Loader.AssemblyLoadContext context)
{
EnsureTempFileDeleted();
}
#endif
/// <summary> /// <summary>
/// Implements the finalization part of the IDisposable pattern by calling Dispose(false). /// Implements the finalization part of the IDisposable pattern by calling Dispose(false).
/// </summary> /// </summary>

View File

@@ -4,7 +4,7 @@
<PropertyGroup> <PropertyGroup>
<Description>Helpers for building single-page applications on ASP.NET MVC Core.</Description> <Description>Helpers for building single-page applications on ASP.NET MVC Core.</Description>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags> <PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
@@ -13,7 +13,8 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Content\**\*" /> <EmbeddedResource Include="Content\**\*" />
<ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" /> <ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.TagHelpers" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.ViewFeatures" Version="$(AspNetCoreVersion)" />
</ItemGroup> </ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' "> <Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
@@ -24,6 +25,7 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
private static INodeServices _fallbackNodeServices; // Used only if no INodeServices was registered with DI private static INodeServices _fallbackNodeServices; // Used only if no INodeServices was registered with DI
private readonly string _applicationBasePath; private readonly string _applicationBasePath;
private readonly CancellationToken _applicationStoppingToken;
private readonly INodeServices _nodeServices; private readonly INodeServices _nodeServices;
/// <summary> /// <summary>
@@ -35,6 +37,9 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
var hostEnv = (IHostingEnvironment) serviceProvider.GetService(typeof(IHostingEnvironment)); var hostEnv = (IHostingEnvironment) serviceProvider.GetService(typeof(IHostingEnvironment));
_nodeServices = (INodeServices) serviceProvider.GetService(typeof(INodeServices)) ?? _fallbackNodeServices; _nodeServices = (INodeServices) serviceProvider.GetService(typeof(INodeServices)) ?? _fallbackNodeServices;
_applicationBasePath = hostEnv.ContentRootPath; _applicationBasePath = hostEnv.ContentRootPath;
var applicationLifetime = (IApplicationLifetime) serviceProvider.GetService(typeof(IApplicationLifetime));
_applicationStoppingToken = applicationLifetime.ApplicationStopping;
// Consider removing the following. Having it means you can get away with not putting app.AddNodeServices() // Consider removing the following. Having it means you can get away with not putting app.AddNodeServices()
// in your startup file, but then again it might be confusing that you don't need to. // in your startup file, but then again it might be confusing that you don't need to.
@@ -101,6 +106,7 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
var result = await Prerenderer.RenderToString( var result = await Prerenderer.RenderToString(
_applicationBasePath, _applicationBasePath,
_nodeServices, _nodeServices,
_applicationStoppingToken,
new JavaScriptModuleExport(ModuleName) new JavaScriptModuleExport(ModuleName)
{ {
ExportName = ExportName ExportName = ExportName

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.NodeServices; using Microsoft.AspNetCore.NodeServices;
@@ -9,22 +10,16 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
/// </summary> /// </summary>
public static class Prerenderer public static class Prerenderer
{ {
private static readonly Lazy<StringAsTempFile> NodeScript; private static readonly object CreateNodeScriptLock = new object();
static Prerenderer() private static StringAsTempFile NodeScript;
{
NodeScript = new Lazy<StringAsTempFile>(() =>
{
var script = EmbeddedResourceReader.Read(typeof(Prerenderer), "/Content/Node/prerenderer.js");
return new StringAsTempFile(script); // Will be cleaned up on process exit
});
}
/// <summary> /// <summary>
/// Performs server-side prerendering by invoking code in Node.js. /// Performs server-side prerendering by invoking code in Node.js.
/// </summary> /// </summary>
/// <param name="applicationBasePath">The root path to your application. This is used when resolving project-relative paths.</param> /// <param name="applicationBasePath">The root path to your application. This is used when resolving project-relative paths.</param>
/// <param name="nodeServices">The instance of <see cref="INodeServices"/> that will be used to invoke JavaScript code.</param> /// <param name="nodeServices">The instance of <see cref="INodeServices"/> that will be used to invoke JavaScript code.</param>
/// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
/// <param name="bootModule">The path to the JavaScript file containing the prerendering logic.</param> /// <param name="bootModule">The path to the JavaScript file containing the prerendering logic.</param>
/// <param name="requestAbsoluteUrl">The URL of the currently-executing HTTP request. This is supplied to the prerendering code.</param> /// <param name="requestAbsoluteUrl">The URL of the currently-executing HTTP request. This is supplied to the prerendering code.</param>
/// <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> /// <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 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
public static Task<RenderToStringResult> RenderToString( public static Task<RenderToStringResult> RenderToString(
string applicationBasePath, string applicationBasePath,
INodeServices nodeServices, INodeServices nodeServices,
CancellationToken applicationStoppingToken,
JavaScriptModuleExport bootModule, JavaScriptModuleExport bootModule,
string requestAbsoluteUrl, string requestAbsoluteUrl,
string requestPathAndQuery, string requestPathAndQuery,
@@ -43,7 +39,7 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
string requestPathBase) string requestPathBase)
{ {
return nodeServices.InvokeExportAsync<RenderToStringResult>( return nodeServices.InvokeExportAsync<RenderToStringResult>(
NodeScript.Value.FileName, GetNodeScriptFilename(applicationStoppingToken),
"renderToString", "renderToString",
applicationBasePath, applicationBasePath,
bootModule, bootModule,
@@ -53,5 +49,19 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
timeoutMilliseconds, timeoutMilliseconds,
requestPathBase); requestPathBase);
} }
private static string GetNodeScriptFilename(CancellationToken applicationStoppingToken)
{
lock(CreateNodeScriptLock)
{
if (NodeScript == null)
{
var script = EmbeddedResourceReader.Read(typeof(Prerenderer), "/Content/Node/prerenderer.js");
NodeScript = new StringAsTempFile(script, applicationStoppingToken); // Will be cleaned up on process exit
}
}
return NodeScript.FileName;
}
} }
} }

View File

@@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Builder
// Get a filename matching the middleware Node script // Get a filename matching the middleware Node script
var script = EmbeddedResourceReader.Read(typeof(WebpackDevMiddleware), var script = EmbeddedResourceReader.Read(typeof(WebpackDevMiddleware),
"/Content/Node/webpack-dev-middleware.js"); "/Content/Node/webpack-dev-middleware.js");
var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit var nodeScript = new StringAsTempFile(script, nodeServicesOptions.ApplicationStoppingToken); // Will be cleaned up on process exit
// Ideally, this would be relative to the application's PathBase (so it could work in virtual directories) // Ideally, this would be relative to the application's PathBase (so it could work in virtual directories)
// but it's not clear that such information exists during application startup, as opposed to within the context // but it's not clear that such information exists during application startup, as opposed to within the context

View File

@@ -9,7 +9,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -9,7 +9,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -9,7 +9,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -9,7 +9,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -9,7 +9,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -9,7 +9,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>