diff --git a/samples/angular/MusicStore/MusicStore.xproj b/samples/angular/MusicStore/MusicStore.xproj
index 2b74858..93efb5e 100644
--- a/samples/angular/MusicStore/MusicStore.xproj
+++ b/samples/angular/MusicStore/MusicStore.xproj
@@ -10,11 +10,11 @@
1a74148f-9dc0-435d-b5ac-7d1b0d3d5e0b
MusicStore
..\artifacts\obj\$(MSBuildProjectName)
- ..\artifacts\bin\$(MSBuildProjectName)\
+ .\bin\
2.0
5068
-
+
\ No newline at end of file
diff --git a/samples/misc/ES2015Transpilation/ES2015Transpilation.xproj b/samples/misc/ES2015Transpilation/ES2015Transpilation.xproj
index 9f87ae0..ba3c063 100644
--- a/samples/misc/ES2015Transpilation/ES2015Transpilation.xproj
+++ b/samples/misc/ES2015Transpilation/ES2015Transpilation.xproj
@@ -10,11 +10,11 @@
6d4bcdd6-7951-449b-be55-cb7f014b7430
ES2015Transpilation
..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName)
- ..\..\..\JavaScriptServices.sln\artifacts\bin\$(MSBuildProjectName)\
+ .\bin\
2.0
2018
-
+
\ No newline at end of file
diff --git a/samples/react/ReactGrid/ReactGrid.xproj b/samples/react/ReactGrid/ReactGrid.xproj
index 7abf60c..0d002a6 100644
--- a/samples/react/ReactGrid/ReactGrid.xproj
+++ b/samples/react/ReactGrid/ReactGrid.xproj
@@ -10,11 +10,11 @@
abf90a5b-f4e0-438c-a6e4-9549fb43690b
ReactGrid
..\..\..\artifacts\obj\$(MSBuildProjectName)
- ..\..\..\artifacts\bin\$(MSBuildProjectName)\
+ .\bin\
2.0
2311
-
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.AngularServices/Microsoft.AspNetCore.AngularServices.xproj b/src/Microsoft.AspNetCore.AngularServices/Microsoft.AspNetCore.AngularServices.xproj
index b037b32..701af9a 100644
--- a/src/Microsoft.AspNetCore.AngularServices/Microsoft.AspNetCore.AngularServices.xproj
+++ b/src/Microsoft.AspNetCore.AngularServices/Microsoft.AspNetCore.AngularServices.xproj
@@ -9,11 +9,10 @@
421807e6-b62c-417b-b901-46c5dedaa8f1
Microsoft.AspNetCore.AngularServices
..\artifacts\obj\$(MSBuildProjectName)
- ..\artifacts\bin\$(MSBuildProjectName)\
+ .\bin\
-
2.0
-
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.AngularServices/PrimeCacheHelper.cs b/src/Microsoft.AspNetCore.AngularServices/PrimeCacheHelper.cs
index 48fdeca..ca9f603 100644
--- a/src/Microsoft.AspNetCore.AngularServices/PrimeCacheHelper.cs
+++ b/src/Microsoft.AspNetCore.AngularServices/PrimeCacheHelper.cs
@@ -3,42 +3,51 @@ using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Html;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
-namespace Microsoft.AspNetCore.AngularServices {
- public static class PrimeCacheHelper {
- public static async Task PrimeCache(this IHtmlHelper html, string url) {
+namespace Microsoft.AspNetCore.AngularServices
+{
+ public static class PrimeCacheHelper
+ {
+ public static async Task PrimeCache(this IHtmlHelper html, string url)
+ {
// TODO: Consider deduplicating the PrimeCache calls (that is, if there are multiple requests to precache
// the same URL, only return nonempty for one of them). This will make it easier to auto-prime-cache any
// HTTP requests made during server-side rendering, without risking unnecessary duplicate requests.
- if (string.IsNullOrEmpty(url)) {
+ if (string.IsNullOrEmpty(url))
+ {
throw new ArgumentException("Value cannot be null or empty", nameof(url));
}
- try {
+ try
+ {
var request = html.ViewContext.HttpContext.Request;
- var baseUri = new Uri(string.Concat(request.Scheme, "://", request.Host.ToUriComponent(), request.PathBase.ToUriComponent(), request.Path.ToUriComponent(), request.QueryString.ToUriComponent()));
+ var baseUri =
+ new Uri(string.Concat(request.Scheme, "://", request.Host.ToUriComponent(),
+ request.PathBase.ToUriComponent(), request.Path.ToUriComponent(),
+ request.QueryString.ToUriComponent()));
var fullUri = new Uri(baseUri, url);
var response = await new HttpClient().GetAsync(fullUri.ToString());
var responseBody = await response.Content.ReadAsStringAsync();
return new HtmlString(FormatAsScript(url, response.StatusCode, responseBody));
- } catch (Exception ex) {
- var logger = (ILogger)html.ViewContext.HttpContext.RequestServices.GetService(typeof (ILogger));
- if (logger != null) {
- logger.LogWarning("Error priming cache for URL: " + url, ex);
- }
+ }
+ catch (Exception ex)
+ {
+ var logger = (ILogger)html.ViewContext.HttpContext.RequestServices.GetService(typeof(ILogger));
+ logger?.LogWarning("Error priming cache for URL: " + url, ex);
return new HtmlString(string.Empty);
}
}
private static string FormatAsScript(string url, HttpStatusCode responseStatusCode, string responseBody)
- {
- return string.Format(@"",
- JsonConvert.SerializeObject(url),
- JsonConvert.SerializeObject(new { statusCode = responseStatusCode, body = responseBody })
- );
- }
+ =>
+ "";
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration.cs
index 5d85d07..d40733a 100644
--- a/src/Microsoft.AspNetCore.NodeServices/Configuration.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/Configuration.cs
@@ -2,31 +2,37 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.PlatformAbstractions;
using Microsoft.AspNetCore.Hosting;
-namespace Microsoft.AspNetCore.NodeServices {
- public static class Configuration {
- private readonly static string[] defaultWatchFileExtensions = new[] { ".js", ".jsx", ".ts", ".tsx", ".json", ".html" };
- private readonly static NodeServicesOptions defaultOptions = new NodeServicesOptions {
+namespace Microsoft.AspNetCore.NodeServices
+{
+ using System;
+
+ public static class Configuration
+ {
+ private static readonly string[] DefaultWatchFileExtensions = {".js", ".jsx", ".ts", ".tsx", ".json", ".html"};
+
+ private static readonly NodeServicesOptions DefaultOptions = new NodeServicesOptions
+ {
HostingModel = NodeHostingModel.Http,
- WatchFileExtensions = defaultWatchFileExtensions
+ WatchFileExtensions = DefaultWatchFileExtensions
};
- public static void AddNodeServices(this IServiceCollection serviceCollection) {
- AddNodeServices(serviceCollection, defaultOptions);
- }
+ public static void AddNodeServices(this IServiceCollection serviceCollection)
+ => AddNodeServices(serviceCollection, DefaultOptions);
- public static void AddNodeServices(this IServiceCollection serviceCollection, NodeServicesOptions options) {
- serviceCollection.AddSingleton(typeof(INodeServices), (serviceProvider) => {
+ public static void AddNodeServices(this IServiceCollection serviceCollection, NodeServicesOptions options)
+ => serviceCollection.AddSingleton(typeof(INodeServices), serviceProvider =>
+ {
var hostEnv = serviceProvider.GetRequiredService();
- if (string.IsNullOrEmpty(options.ProjectPath)) {
+ if (string.IsNullOrEmpty(options.ProjectPath))
+ {
options.ProjectPath = hostEnv.ContentRootPath;
}
return CreateNodeServices(options);
});
- }
public static INodeServices CreateNodeServices(NodeServicesOptions options)
{
- var watchFileExtensions = options.WatchFileExtensions ?? defaultWatchFileExtensions;
+ var watchFileExtensions = options.WatchFileExtensions ?? DefaultWatchFileExtensions;
switch (options.HostingModel)
{
case NodeHostingModel.Http:
@@ -34,18 +40,8 @@ namespace Microsoft.AspNetCore.NodeServices {
case NodeHostingModel.InputOutputStream:
return new InputOutputStreamNodeInstance(options.ProjectPath);
default:
- throw new System.ArgumentException("Unknown hosting model: " + options.HostingModel.ToString());
+ throw new ArgumentException("Unknown hosting model: " + options.HostingModel);
}
}
}
-
- public class NodeServicesOptions {
- public NodeHostingModel HostingModel { get; set; }
- public string ProjectPath { get; set; }
- public string[] WatchFileExtensions { get; set; }
-
- public NodeServicesOptions() {
- this.HostingModel = NodeHostingModel.Http;
- }
- }
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs
index 4671740..8cff883 100644
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs
@@ -7,66 +7,90 @@ using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
-namespace Microsoft.AspNetCore.NodeServices {
- internal class HttpNodeInstance : OutOfProcessNodeInstance {
- private readonly static Regex PortMessageRegex = new Regex(@"^\[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on port (\d+)\]$");
+namespace Microsoft.AspNetCore.NodeServices
+{
+ internal class HttpNodeInstance : OutOfProcessNodeInstance
+ {
+ private static readonly Regex PortMessageRegex =
+ new Regex(@"^\[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on port (\d+)\]$");
- private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings {
+ private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
+ {
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
private int _portNumber;
- public HttpNodeInstance(string projectPath, int port = 0, string[] watchFileExtensions = null)
- : base(EmbeddedResourceReader.Read(typeof(HttpNodeInstance), "/Content/Node/entrypoint-http.js"), projectPath, MakeCommandLineOptions(port, watchFileExtensions))
+ public HttpNodeInstance(string projectPath, int port = 0, string[] watchFileExtensions = null)
+ : base(
+ EmbeddedResourceReader.Read(
+ typeof(HttpNodeInstance),
+ "/Content/Node/entrypoint-http.js"),
+ projectPath,
+ MakeCommandLineOptions(port, watchFileExtensions))
{
- }
+ }
- private static string MakeCommandLineOptions(int port, string[] watchFileExtensions) {
- var result = "--port " + port.ToString();
- if (watchFileExtensions != null && watchFileExtensions.Length > 0) {
+ private static string MakeCommandLineOptions(int port, string[] watchFileExtensions)
+ {
+ var result = "--port " + port;
+ if (watchFileExtensions != null && watchFileExtensions.Length > 0)
+ {
result += " --watch " + string.Join(",", watchFileExtensions);
}
+
return result;
}
- public override async Task Invoke(NodeInvocationInfo invocationInfo) {
- await this.EnsureReady();
+ public override async Task Invoke(NodeInvocationInfo invocationInfo)
+ {
+ await EnsureReady();
- using (var client = new HttpClient()) {
+ using (var client = new HttpClient())
+ {
// TODO: Use System.Net.Http.Formatting (PostAsJsonAsync etc.)
- var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings);
+ var payloadJson = JsonConvert.SerializeObject(invocationInfo, JsonSerializerSettings);
var payload = new StringContent(payloadJson, Encoding.UTF8, "application/json");
- var response = await client.PostAsync("http://localhost:" + this._portNumber, payload);
+ var response = await client.PostAsync("http://localhost:" + _portNumber, payload);
var responseString = await response.Content.ReadAsStringAsync();
- if (!response.IsSuccessStatusCode) {
+ if (!response.IsSuccessStatusCode)
+ {
throw new Exception("Call to Node module failed with error: " + responseString);
}
var responseIsJson = response.Content.Headers.ContentType.MediaType == "application/json";
- if (responseIsJson) {
+ if (responseIsJson)
+ {
return JsonConvert.DeserializeObject(responseString);
- } else if (typeof(T) != typeof(string)) {
- throw new System.ArgumentException("Node module responded with non-JSON string. This cannot be converted to the requested generic type: " + typeof(T).FullName);
- } else {
- return (T)(object)responseString;
}
+ if (typeof(T) != typeof(string))
+ {
+ throw new ArgumentException(
+ "Node module responded with non-JSON string. This cannot be converted to the requested generic type: " +
+ typeof(T).FullName);
+ }
+ return (T)(object)responseString;
}
}
- protected override void OnOutputDataReceived(string outputData) {
- var match = this._portNumber != 0 ? null : PortMessageRegex.Match(outputData);
- if (match != null && match.Success) {
- this._portNumber = int.Parse(match.Groups[1].Captures[0].Value);
- } else {
+ protected override void OnOutputDataReceived(string outputData)
+ {
+ var match = _portNumber != 0 ? null : PortMessageRegex.Match(outputData);
+ if (match != null && match.Success)
+ {
+ _portNumber = int.Parse(match.Groups[1].Captures[0].Value);
+ }
+ else
+ {
base.OnOutputDataReceived(outputData);
}
}
- protected override void OnBeforeLaunchProcess() {
+ protected override void OnBeforeLaunchProcess()
+ {
// Prepare to receive a new port number
- this._portNumber = 0;
+ _portNumber = 0;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/InputOutputStreamNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/InputOutputStreamNodeInstance.cs
index 3f27acd..ffa2cdc 100644
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/InputOutputStreamNodeInstance.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/InputOutputStreamNodeInstance.cs
@@ -4,55 +4,72 @@ using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
-namespace Microsoft.AspNetCore.NodeServices {
- // This is just to demonstrate that other transports are possible. This implementation is extremely
- // dubious - if the Node-side code fails to conform to the expected protocol in any way (e.g., has an
- // error), then it will just hang forever. So don't use this.
- //
- // But it's fast - the communication round-trip time is about 0.2ms (tested on OS X on a recent machine),
- // versus 2-3ms for the HTTP transport.
- //
- // Instead of directly using stdin/stdout, we could use either regular sockets (TCP) or use named pipes
- // on Windows and domain sockets on Linux / OS X, but either way would need a system for framing the
- // requests, associating them with responses, and scheduling use of the comms channel.
+namespace Microsoft.AspNetCore.NodeServices
+{
+ ///
+ /// This is just to demonstrate that other transports are possible. This implementation is extremely
+ /// dubious - if the Node-side code fails to conform to the expected protocol in any way (e.g., has an
+ /// error), then it will just hang forever. So don't use this.
+ ///
+ /// But it's fast - the communication round-trip time is about 0.2ms (tested on OS X on a recent machine),
+ /// versus 2-3ms for the HTTP transport.
+ ///
+ /// Instead of directly using stdin/stdout, we could use either regular sockets (TCP) or use named pipes
+ /// on Windows and domain sockets on Linux / OS X, but either way would need a system for framing the
+ /// requests, associating them with responses, and scheduling use of the comms channel.
+ ///
+ ///
internal class InputOutputStreamNodeInstance : OutOfProcessNodeInstance
{
- private SemaphoreSlim _invocationSemaphore = new SemaphoreSlim(1);
- private TaskCompletionSource _currentInvocationResult;
-
- private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings {
+ private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
+ {
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
- public InputOutputStreamNodeInstance(string projectPath)
- : base(EmbeddedResourceReader.Read(typeof(InputOutputStreamNodeInstance), "/Content/Node/entrypoint-stream.js"), projectPath)
+ private TaskCompletionSource _currentInvocationResult;
+ private readonly SemaphoreSlim _invocationSemaphore = new SemaphoreSlim(1);
+
+ public InputOutputStreamNodeInstance(string projectPath)
+ : base(
+ EmbeddedResourceReader.Read(
+ typeof(InputOutputStreamNodeInstance),
+ "/Content/Node/entrypoint-stream.js"),
+ projectPath)
{
- }
+ }
- public override async Task Invoke(NodeInvocationInfo invocationInfo) {
- await this._invocationSemaphore.WaitAsync();
- try {
- await this.EnsureReady();
+ public override async Task Invoke(NodeInvocationInfo invocationInfo)
+ {
+ await _invocationSemaphore.WaitAsync();
+ try
+ {
+ await EnsureReady();
- var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings);
- var nodeProcess = this.NodeProcess;
- this._currentInvocationResult = new TaskCompletionSource();
+ var payloadJson = JsonConvert.SerializeObject(invocationInfo, JsonSerializerSettings);
+ var nodeProcess = NodeProcess;
+ _currentInvocationResult = new TaskCompletionSource();
nodeProcess.StandardInput.Write("\ninvoke:");
nodeProcess.StandardInput.WriteLine(payloadJson); // WriteLineAsync isn't supported cross-platform
- var resultString = await this._currentInvocationResult.Task;
+ var resultString = await _currentInvocationResult.Task;
return JsonConvert.DeserializeObject(resultString);
- } finally {
- this._invocationSemaphore.Release();
- this._currentInvocationResult = null;
+ }
+ finally
+ {
+ _invocationSemaphore.Release();
+ _currentInvocationResult = null;
}
}
- protected override void OnOutputDataReceived(string outputData) {
- if (this._currentInvocationResult != null) {
- this._currentInvocationResult.SetResult(outputData);
- } else {
+ protected override void OnOutputDataReceived(string outputData)
+ {
+ if (_currentInvocationResult != null)
+ {
+ _currentInvocationResult.SetResult(outputData);
+ }
+ else
+ {
base.OnOutputDataReceived(outputData);
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationInfo.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationInfo.cs
index a4fde64..2e196f6 100644
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationInfo.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationInfo.cs
@@ -1,8 +1,9 @@
-namespace Microsoft.AspNetCore.NodeServices {
+namespace Microsoft.AspNetCore.NodeServices
+{
public class NodeInvocationInfo
{
- public string ModuleName;
- public string ExportedFunctionName;
- public object[] Args;
+ public string ModuleName { get; set; }
+ public string ExportedFunctionName { get; set; }
+ public object[] Args { get; set; }
}
}
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs
index fe3386a..65e21a3 100644
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs
@@ -3,126 +3,42 @@ using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.NodeServices {
- /**
- * Class responsible for launching the Node child process, determining when it is ready to accept invocations,
- * and finally killing it when the parent process exits. Also it restarts the child process if it dies.
- */
- public abstract class OutOfProcessNodeInstance : INodeServices {
- private object _childProcessLauncherLock;
- private bool disposed;
- private StringAsTempFile _entryPointScript;
- private string _projectPath;
- private string _commandLineArguments;
- private Process _nodeProcess;
+namespace Microsoft.AspNetCore.NodeServices
+{
+ ///
+ /// Class responsible for launching the Node child process, determining when it is ready to accept invocations,
+ /// and finally killing it when the parent process exits. Also it restarts the child process if it dies.
+ ///
+ ///
+ public abstract class OutOfProcessNodeInstance : INodeServices
+ {
+ private readonly object _childProcessLauncherLock;
+ private readonly string _commandLineArguments;
+ private readonly StringAsTempFile _entryPointScript;
private TaskCompletionSource _nodeProcessIsReadySource;
-
- protected Process NodeProcess {
- get {
- // This is only exposed to support the unreliable OutOfProcessNodeRunner, which is just to verify that
- // other hosting/transport mechanisms are possible. This shouldn't really be exposed.
- return this._nodeProcess;
- }
- }
+ private readonly string _projectPath;
+ private bool _disposed;
public OutOfProcessNodeInstance(string entryPointScript, string projectPath, string commandLineArguments = null)
{
- this._childProcessLauncherLock = new object();
- this._entryPointScript = new StringAsTempFile(entryPointScript);
- this._projectPath = projectPath;
- this._commandLineArguments = commandLineArguments ?? string.Empty;
+ _childProcessLauncherLock = new object();
+ _entryPointScript = new StringAsTempFile(entryPointScript);
+ _projectPath = projectPath;
+ _commandLineArguments = commandLineArguments ?? string.Empty;
}
- public abstract Task Invoke(NodeInvocationInfo invocationInfo);
+ protected Process NodeProcess { get; private set; }
- public Task Invoke(string moduleName, params object[] args) {
- return this.InvokeExport(moduleName, null, args);
- }
+ public Task Invoke(string moduleName, params object[] args)
+ => InvokeExport(moduleName, null, args);
- public async Task InvokeExport(string moduleName, string exportedFunctionName, params object[] args) {
- return await this.Invoke(new NodeInvocationInfo {
+ public Task InvokeExport(string moduleName, string exportedFunctionName, params object[] args)
+ => Invoke(new NodeInvocationInfo
+ {
ModuleName = moduleName,
ExportedFunctionName = exportedFunctionName,
Args = args
});
- }
-
- protected async Task EnsureReady() {
- lock (this._childProcessLauncherLock) {
- if (this._nodeProcess == null || this._nodeProcess.HasExited) {
- var startInfo = new ProcessStartInfo("node") {
- Arguments = "\"" + this._entryPointScript.FileName + "\" " + this._commandLineArguments,
- UseShellExecute = false,
- RedirectStandardInput = true,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- WorkingDirectory = this._projectPath
- };
-
- // Append projectPath to NODE_PATH so it can locate node_modules
- var existingNodePath = Environment.GetEnvironmentVariable("NODE_PATH") ?? string.Empty;
- if (existingNodePath != string.Empty) {
- existingNodePath += ":";
- }
-
- var nodePathValue = existingNodePath + Path.Combine(this._projectPath, "node_modules");
- #if NET451
- startInfo.EnvironmentVariables.Add("NODE_PATH", nodePathValue);
- #else
- startInfo.Environment.Add("NODE_PATH", nodePathValue);
- #endif
-
- this.OnBeforeLaunchProcess();
- this._nodeProcess = Process.Start(startInfo);
- this.ConnectToInputOutputStreams();
- }
- }
-
- var task = this._nodeProcessIsReadySource.Task;
- var initializationSucceeded = await task;
-
- if (!initializationSucceeded) {
- throw new InvalidOperationException("The Node.js process failed to initialize", task.Exception);
- }
- }
-
- private void ConnectToInputOutputStreams() {
- var initializationIsCompleted = false; // TODO: Make this thread-safe? (Interlocked.Exchange etc.)
- this._nodeProcessIsReadySource = new TaskCompletionSource();
-
- this._nodeProcess.OutputDataReceived += (sender, evt) => {
- if (evt.Data == "[Microsoft.AspNetCore.NodeServices:Listening]" && !initializationIsCompleted) {
- this._nodeProcessIsReadySource.SetResult(true);
- initializationIsCompleted = true;
- } else if (evt.Data != null) {
- this.OnOutputDataReceived(evt.Data);
- }
- };
-
- this._nodeProcess.ErrorDataReceived += (sender, evt) => {
- if (evt.Data != null) {
- this.OnErrorDataReceived(evt.Data);
- if (!initializationIsCompleted) {
- this._nodeProcessIsReadySource.SetResult(false);
- initializationIsCompleted = true;
- }
- }
- };
-
- this._nodeProcess.BeginOutputReadLine();
- this._nodeProcess.BeginErrorReadLine();
- }
-
- protected virtual void OnBeforeLaunchProcess() {
- }
-
- protected virtual void OnOutputDataReceived(string outputData) {
- Console.WriteLine("[Node] " + outputData);
- }
-
- protected virtual void OnErrorDataReceived(string errorData) {
- Console.WriteLine("[Node] " + errorData);
- }
public void Dispose()
{
@@ -130,23 +46,124 @@ namespace Microsoft.AspNetCore.NodeServices {
GC.SuppressFinalize(this);
}
- protected virtual void Dispose(bool disposing)
+ public abstract Task Invoke(NodeInvocationInfo invocationInfo);
+
+ protected async Task EnsureReady()
{
- if (!disposed) {
- if (disposing) {
- this._entryPointScript.Dispose();
- }
+ lock (_childProcessLauncherLock)
+ {
+ if (NodeProcess == null || NodeProcess.HasExited)
+ {
+ var startInfo = new ProcessStartInfo("node")
+ {
+ Arguments = "\"" + _entryPointScript.FileName + "\" " + _commandLineArguments,
+ UseShellExecute = false,
+ RedirectStandardInput = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ WorkingDirectory = _projectPath
+ };
- if (this._nodeProcess != null && !this._nodeProcess.HasExited) {
- this._nodeProcess.Kill(); // TODO: Is there a more graceful way to end it? Or does this still let it perform any cleanup?
- }
+ // Append projectPath to NODE_PATH so it can locate node_modules
+ var existingNodePath = Environment.GetEnvironmentVariable("NODE_PATH") ?? string.Empty;
+ if (existingNodePath != string.Empty)
+ {
+ existingNodePath += ":";
+ }
- disposed = true;
+ var nodePathValue = existingNodePath + Path.Combine(_projectPath, "node_modules");
+#if NET451
+ startInfo.EnvironmentVariables.Add("NODE_PATH", nodePathValue);
+#else
+ startInfo.Environment.Add("NODE_PATH", nodePathValue);
+#endif
+
+ OnBeforeLaunchProcess();
+ NodeProcess = Process.Start(startInfo);
+ ConnectToInputOutputStreams();
+ }
+ }
+
+ var task = _nodeProcessIsReadySource.Task;
+ var initializationSucceeded = await task;
+
+ if (!initializationSucceeded)
+ {
+ throw new InvalidOperationException("The Node.js process failed to initialize", task.Exception);
}
}
- ~OutOfProcessNodeInstance() {
- Dispose (false);
+ private void ConnectToInputOutputStreams()
+ {
+ var initializationIsCompleted = false; // TODO: Make this thread-safe? (Interlocked.Exchange etc.)
+ _nodeProcessIsReadySource = new TaskCompletionSource();
+
+ NodeProcess.OutputDataReceived += (sender, evt) =>
+ {
+ if (evt.Data == "[Microsoft.AspNetCore.NodeServices:Listening]" && !initializationIsCompleted)
+ {
+ _nodeProcessIsReadySource.SetResult(true);
+ initializationIsCompleted = true;
+ }
+ else if (evt.Data != null)
+ {
+ OnOutputDataReceived(evt.Data);
+ }
+ };
+
+ NodeProcess.ErrorDataReceived += (sender, evt) =>
+ {
+ if (evt.Data != null)
+ {
+ OnErrorDataReceived(evt.Data);
+ if (!initializationIsCompleted)
+ {
+ _nodeProcessIsReadySource.SetResult(false);
+ initializationIsCompleted = true;
+ }
+ }
+ };
+
+ NodeProcess.BeginOutputReadLine();
+ NodeProcess.BeginErrorReadLine();
+ }
+
+ protected virtual void OnBeforeLaunchProcess()
+ {
+ }
+
+ protected virtual void OnOutputDataReceived(string outputData)
+ {
+ Console.WriteLine("[Node] " + outputData);
+ }
+
+ protected virtual void OnErrorDataReceived(string errorData)
+ {
+ Console.WriteLine("[Node] " + errorData);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _entryPointScript.Dispose();
+ }
+
+ if (NodeProcess != null && !NodeProcess.HasExited)
+ {
+ NodeProcess.Kill();
+ // TODO: Is there a more graceful way to end it? Or does this still let it perform any cleanup?
+ }
+
+ _disposed = true;
+ }
+ }
+
+ ~OutOfProcessNodeInstance()
+ {
+ Dispose(false);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/INodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/INodeInstance.cs
index fae73db..0c17ea1 100644
--- a/src/Microsoft.AspNetCore.NodeServices/INodeInstance.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/INodeInstance.cs
@@ -1,8 +1,10 @@
using System;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.NodeServices {
- public interface INodeServices : IDisposable {
+namespace Microsoft.AspNetCore.NodeServices
+{
+ public interface INodeServices : IDisposable
+ {
Task Invoke(string moduleName, params object[] args);
Task InvokeExport(string moduleName, string exportedFunctionName, params object[] args);
diff --git a/src/Microsoft.AspNetCore.NodeServices/Microsoft.AspNetCore.NodeServices.xproj b/src/Microsoft.AspNetCore.NodeServices/Microsoft.AspNetCore.NodeServices.xproj
index f03c112..ee1f038 100644
--- a/src/Microsoft.AspNetCore.NodeServices/Microsoft.AspNetCore.NodeServices.xproj
+++ b/src/Microsoft.AspNetCore.NodeServices/Microsoft.AspNetCore.NodeServices.xproj
@@ -9,11 +9,10 @@
b0fa4175-8b29-4904-9780-28b3c24b0567
Microsoft.AspNetCore.NodeServices
..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName)
- ..\JavaScriptServices.sln\artifacts\bin\$(MSBuildProjectName)\
+ .\bin\
-
2.0
-
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/NodeHostingModel.cs b/src/Microsoft.AspNetCore.NodeServices/NodeHostingModel.cs
index b363631..64d9170 100644
--- a/src/Microsoft.AspNetCore.NodeServices/NodeHostingModel.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/NodeHostingModel.cs
@@ -1,5 +1,7 @@
-namespace Microsoft.AspNetCore.NodeServices {
- public enum NodeHostingModel {
+namespace Microsoft.AspNetCore.NodeServices
+{
+ public enum NodeHostingModel
+ {
Http,
InputOutputStream,
}
diff --git a/src/Microsoft.AspNetCore.NodeServices/NodeServicesOptions.cs b/src/Microsoft.AspNetCore.NodeServices/NodeServicesOptions.cs
new file mode 100644
index 0000000..7452c5f
--- /dev/null
+++ b/src/Microsoft.AspNetCore.NodeServices/NodeServicesOptions.cs
@@ -0,0 +1,14 @@
+namespace Microsoft.AspNetCore.NodeServices
+{
+ public class NodeServicesOptions
+ {
+ public NodeServicesOptions()
+ {
+ HostingModel = NodeHostingModel.Http;
+ }
+
+ public NodeHostingModel HostingModel { get; set; }
+ public string ProjectPath { get; set; }
+ public string[] WatchFileExtensions { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/Util/EmbeddedResourceReader.cs b/src/Microsoft.AspNetCore.NodeServices/Util/EmbeddedResourceReader.cs
index 73fe29e..a7147c1 100644
--- a/src/Microsoft.AspNetCore.NodeServices/Util/EmbeddedResourceReader.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/Util/EmbeddedResourceReader.cs
@@ -2,16 +2,20 @@ using System;
using System.IO;
using System.Reflection;
-namespace Microsoft.AspNetCore.NodeServices {
- public static class EmbeddedResourceReader {
- public static string Read(Type assemblyContainingType, string path) {
+namespace Microsoft.AspNetCore.NodeServices
+{
+ public static class EmbeddedResourceReader
+ {
+ public static string Read(Type assemblyContainingType, string path)
+ {
var asm = assemblyContainingType.GetTypeInfo().Assembly;
var embeddedResourceName = asm.GetName().Name + path.Replace("/", ".");
using (var stream = asm.GetManifestResourceStream(embeddedResourceName))
- using (var sr = new StreamReader(stream)) {
+ using (var sr = new StreamReader(stream))
+ {
return sr.ReadToEnd();
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/Util/StringAsTempFile.cs b/src/Microsoft.AspNetCore.NodeServices/Util/StringAsTempFile.cs
index bfcb333..a9f04a9 100644
--- a/src/Microsoft.AspNetCore.NodeServices/Util/StringAsTempFile.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/Util/StringAsTempFile.cs
@@ -1,39 +1,45 @@
using System;
using System.IO;
-namespace Microsoft.AspNetCore.NodeServices {
+namespace Microsoft.AspNetCore.NodeServices
+{
// Makes it easier to pass script files to Node in a way that's sure to clean up after the process exits
- public sealed class StringAsTempFile : IDisposable {
- public string FileName { get; private set; }
-
+ public sealed class StringAsTempFile : IDisposable
+ {
private bool _disposedValue;
- public StringAsTempFile(string content) {
- this.FileName = Path.GetTempFileName();
- File.WriteAllText(this.FileName, content);
- }
-
- private void DisposeImpl(bool disposing)
+ public StringAsTempFile(string content)
{
- if (!_disposedValue) {
- if (disposing) {
- // TODO: dispose managed state (managed objects).
- }
-
- File.Delete(this.FileName);
-
- _disposedValue = true;
- }
+ FileName = Path.GetTempFileName();
+ File.WriteAllText(FileName, content);
}
+ public string FileName { get; }
+
public void Dispose()
{
DisposeImpl(true);
GC.SuppressFinalize(this);
}
- ~StringAsTempFile() {
- DisposeImpl(false);
+ private void DisposeImpl(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects).
+ }
+
+ File.Delete(FileName);
+
+ _disposedValue = true;
+ }
+ }
+
+ ~StringAsTempFile()
+ {
+ DisposeImpl(false);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.ReactServices/Microsoft.AspNetCore.ReactServices.xproj b/src/Microsoft.AspNetCore.ReactServices/Microsoft.AspNetCore.ReactServices.xproj
index 0f54276..ae962e2 100644
--- a/src/Microsoft.AspNetCore.ReactServices/Microsoft.AspNetCore.ReactServices.xproj
+++ b/src/Microsoft.AspNetCore.ReactServices/Microsoft.AspNetCore.ReactServices.xproj
@@ -9,11 +9,10 @@
b04381de-991f-4831-a0b5-fe1bd3ef80c4
Microsoft.AspNetCore.ReactServices
..\artifacts\obj\$(MSBuildProjectName)
- ..\artifacts\bin\$(MSBuildProjectName)\
+ .\bin\
-
2.0
-
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SpaServices/Microsoft.AspNetCore.SpaServices.xproj b/src/Microsoft.AspNetCore.SpaServices/Microsoft.AspNetCore.SpaServices.xproj
index 99a66ba..31f1d76 100644
--- a/src/Microsoft.AspNetCore.SpaServices/Microsoft.AspNetCore.SpaServices.xproj
+++ b/src/Microsoft.AspNetCore.SpaServices/Microsoft.AspNetCore.SpaServices.xproj
@@ -9,11 +9,10 @@
4624f728-6dff-44b6-93b5-3c7d9c94bf3f
Microsoft.AspNetCore.SpaServices
..\artifacts\obj\$(MSBuildProjectName)
- ..\artifacts\bin\$(MSBuildProjectName)\
+ .\bin\
-
2.0
-
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/JavaScriptModuleExport.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/JavaScriptModuleExport.cs
new file mode 100644
index 0000000..054a693
--- /dev/null
+++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/JavaScriptModuleExport.cs
@@ -0,0 +1,14 @@
+namespace Microsoft.AspNetCore.SpaServices.Prerendering
+{
+ public class JavaScriptModuleExport
+ {
+ public JavaScriptModuleExport(string moduleName)
+ {
+ this.ModuleName = moduleName;
+ }
+
+ public string ModuleName { get; private set; }
+ public string ExportName { get; set; }
+ public string WebpackConfig { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs
index ee07d6f..ea95821 100644
--- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs
+++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs
@@ -16,11 +16,31 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
[HtmlTargetElement(Attributes = PrerenderModuleAttributeName)]
public class PrerenderTagHelper : TagHelper
{
- static INodeServices fallbackNodeServices; // Used only if no INodeServices was registered with DI
+ private const string PrerenderModuleAttributeName = "asp-prerender-module";
+ private const string PrerenderExportAttributeName = "asp-prerender-export";
+ private const string PrerenderWebpackConfigAttributeName = "asp-prerender-webpack-config";
+ private static INodeServices _fallbackNodeServices; // Used only if no INodeServices was registered with DI
- const string PrerenderModuleAttributeName = "asp-prerender-module";
- const string PrerenderExportAttributeName = "asp-prerender-export";
- const string PrerenderWebpackConfigAttributeName = "asp-prerender-webpack-config";
+ private readonly string _applicationBasePath;
+ private readonly INodeServices _nodeServices;
+
+ public PrerenderTagHelper(IServiceProvider serviceProvider)
+ {
+ var hostEnv = (IHostingEnvironment) serviceProvider.GetService(typeof(IHostingEnvironment));
+ _nodeServices = (INodeServices) serviceProvider.GetService(typeof(INodeServices)) ?? _fallbackNodeServices;
+ _applicationBasePath = hostEnv.ContentRootPath;
+
+ // 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.
+ if (_nodeServices == null)
+ {
+ _nodeServices = _fallbackNodeServices = Configuration.CreateNodeServices(new NodeServicesOptions
+ {
+ HostingModel = NodeHostingModel.Http,
+ ProjectPath = _applicationBasePath
+ });
+ }
+ }
[HtmlAttributeName(PrerenderModuleAttributeName)]
public string ModuleName { get; set; }
@@ -35,52 +55,37 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
[ViewContext]
public ViewContext ViewContext { get; set; }
- private string applicationBasePath;
- private INodeServices nodeServices;
-
- public PrerenderTagHelper(IServiceProvider serviceProvider)
- {
- var hostEnv = (IHostingEnvironment)serviceProvider.GetService(typeof (IHostingEnvironment));
- this.nodeServices = (INodeServices)serviceProvider.GetService(typeof (INodeServices)) ?? fallbackNodeServices;
- this.applicationBasePath = hostEnv.ContentRootPath;
-
- // 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.
- if (this.nodeServices == null) {
- this.nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(new NodeServicesOptions {
- HostingModel = NodeHostingModel.Http,
- ProjectPath = this.applicationBasePath
- });
- }
- }
-
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
- var request = this.ViewContext.HttpContext.Request;
+ var request = ViewContext.HttpContext.Request;
var result = await Prerenderer.RenderToString(
- applicationBasePath: this.applicationBasePath,
- nodeServices: this.nodeServices,
- bootModule: new JavaScriptModuleExport(this.ModuleName) {
- exportName = this.ExportName,
- webpackConfig = this.WebpackConfigPath
+ _applicationBasePath,
+ _nodeServices,
+ new JavaScriptModuleExport(ModuleName)
+ {
+ ExportName = ExportName,
+ WebpackConfig = WebpackConfigPath
},
- requestAbsoluteUrl: UriHelper.GetEncodedUrl(request),
- requestPathAndQuery: request.Path + request.QueryString.Value);
+ request.GetEncodedUrl(),
+ request.Path + request.QueryString.Value);
output.Content.SetHtmlContent(result.Html);
// Also attach any specified globals to the 'window' object. This is useful for transferring
// general state between server and client.
- if (result.Globals != null) {
+ if (result.Globals != null)
+ {
var stringBuilder = new StringBuilder();
- foreach (var property in result.Globals.Properties()) {
+ foreach (var property in result.Globals.Properties())
+ {
stringBuilder.AppendFormat("window.{0} = {1};",
property.Name,
property.Value.ToString(Formatting.None));
}
- if (stringBuilder.Length > 0) {
- output.PostElement.SetHtmlContent($"");
+ if (stringBuilder.Length > 0)
+ {
+ output.PostElement.SetHtmlContent($"");
}
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs
index 287d547..5ed8552 100644
--- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs
+++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs
@@ -1,42 +1,34 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.NodeServices;
-using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.SpaServices.Prerendering
{
public static class Prerenderer
{
- private static Lazy nodeScript;
+ private static readonly Lazy NodeScript;
- static Prerenderer() {
- nodeScript = new Lazy(() => {
+ static Prerenderer()
+ {
+ NodeScript = new Lazy(() =>
+ {
var script = EmbeddedResourceReader.Read(typeof(Prerenderer), "/Content/Node/prerenderer.js");
return new StringAsTempFile(script); // Will be cleaned up on process exit
});
}
- public static async Task RenderToString(string applicationBasePath, INodeServices nodeServices, JavaScriptModuleExport bootModule, string requestAbsoluteUrl, string requestPathAndQuery) {
- return await nodeServices.InvokeExport(nodeScript.Value.FileName, "renderToString",
+ public static async Task RenderToString(
+ string applicationBasePath,
+ INodeServices nodeServices,
+ JavaScriptModuleExport bootModule,
+ string requestAbsoluteUrl,
+ string requestPathAndQuery)
+ => await nodeServices.InvokeExport(
+ NodeScript.Value.FileName,
+ "renderToString",
applicationBasePath,
bootModule,
requestAbsoluteUrl,
requestPathAndQuery);
- }
}
-
- public class JavaScriptModuleExport {
- public string moduleName { get; private set; }
- public string exportName { get; set; }
- public string webpackConfig { get; set; }
-
- public JavaScriptModuleExport(string moduleName) {
- this.moduleName = moduleName;
- }
- }
-
- public class RenderToStringResult {
- public string Html;
- public JObject Globals;
- }
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/RenderToStringResult.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/RenderToStringResult.cs
new file mode 100644
index 0000000..1d5b482
--- /dev/null
+++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/RenderToStringResult.cs
@@ -0,0 +1,10 @@
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.AspNetCore.SpaServices.Prerendering
+{
+ public class RenderToStringResult
+ {
+ public JObject Globals { get; set; }
+ public string Html { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SpaServices/Routing/SpaRouteConstraint.cs b/src/Microsoft.AspNetCore.SpaServices/Routing/SpaRouteConstraint.cs
index 00243d6..025d11a 100644
--- a/src/Microsoft.AspNetCore.SpaServices/Routing/SpaRouteConstraint.cs
+++ b/src/Microsoft.AspNetCore.SpaServices/Routing/SpaRouteConstraint.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
@@ -7,26 +6,27 @@ namespace Microsoft.AspNetCore.SpaServices
{
internal class SpaRouteConstraint : IRouteConstraint
{
- private readonly string clientRouteTokenName;
+ private readonly string _clientRouteTokenName;
- public SpaRouteConstraint(string clientRouteTokenName) {
- if (string.IsNullOrEmpty(clientRouteTokenName)) {
- throw new ArgumentException("Value cannot be null or empty", "clientRouteTokenName");
+ public SpaRouteConstraint(string clientRouteTokenName)
+ {
+ if (string.IsNullOrEmpty(clientRouteTokenName))
+ {
+ throw new ArgumentException("Value cannot be null or empty", nameof(clientRouteTokenName));
}
- this.clientRouteTokenName = clientRouteTokenName;
+ _clientRouteTokenName = clientRouteTokenName;
}
- public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
- {
- var clientRouteValue = (values[this.clientRouteTokenName] as string) ?? string.Empty;
- return !HasDotInLastSegment(clientRouteValue);
- }
+ public bool Match(
+ HttpContext httpContext,
+ IRouter route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ => !HasDotInLastSegment(values[_clientRouteTokenName] as string ?? string.Empty);
private bool HasDotInLastSegment(string uri)
- {
- var lastSegmentStartPos = uri.LastIndexOf('/');
- return uri.IndexOf('.', lastSegmentStartPos + 1) >= 0;
- }
+ => uri.IndexOf('.', uri.LastIndexOf('/') + 1) >= 0;
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SpaServices/Routing/SpaRouteExtensions.cs b/src/Microsoft.AspNetCore.SpaServices/Routing/SpaRouteExtensions.cs
index 192c839..7bdebc6 100644
--- a/src/Microsoft.AspNetCore.SpaServices/Routing/SpaRouteExtensions.cs
+++ b/src/Microsoft.AspNetCore.SpaServices/Routing/SpaRouteExtensions.cs
@@ -4,18 +4,34 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.SpaServices;
// Putting in this namespace so it's always available whenever MapRoute is
+
namespace Microsoft.AspNetCore.Builder
{
public static class SpaRouteExtensions
{
private const string ClientRouteTokenName = "clientRoute";
- public static void MapSpaFallbackRoute(this IRouteBuilder routeBuilder, string name, object defaults, object constraints = null, object dataTokens = null)
- {
- MapSpaFallbackRoute(routeBuilder, name, /* templatePrefix */ (string)null, defaults, constraints, dataTokens);
- }
+ public static void MapSpaFallbackRoute(
+ this IRouteBuilder routeBuilder,
+ string name,
+ object defaults,
+ object constraints = null,
+ object dataTokens = null)
+ => MapSpaFallbackRoute(
+ routeBuilder,
+ name,
+ /* templatePrefix */ null,
+ defaults,
+ constraints,
+ dataTokens);
- public static void MapSpaFallbackRoute(this IRouteBuilder routeBuilder, string name, string templatePrefix, object defaults, object constraints = null, object dataTokens = null)
+ public static void MapSpaFallbackRoute(
+ this IRouteBuilder routeBuilder,
+ string name,
+ string templatePrefix,
+ object defaults,
+ object constraints = null,
+ object dataTokens = null)
{
var template = CreateRouteTemplate(templatePrefix);
@@ -29,25 +45,27 @@ namespace Microsoft.AspNetCore.Builder
{
templatePrefix = templatePrefix ?? string.Empty;
- if (templatePrefix.Contains("?")) {
+ if (templatePrefix.Contains("?"))
+ {
// TODO: Consider supporting this. The {*clientRoute} part should be added immediately before the '?'
throw new ArgumentException("SPA fallback route templates don't support querystrings");
}
- if (templatePrefix.Contains("#")) {
- throw new ArgumentException("SPA fallback route templates should not include # characters. The hash part of a URI does not get sent to the server.");
+ if (templatePrefix.Contains("#"))
+ {
+ throw new ArgumentException(
+ "SPA fallback route templates should not include # characters. The hash part of a URI does not get sent to the server.");
}
- if (templatePrefix != string.Empty && !templatePrefix.EndsWith("/")) {
+ if (templatePrefix != string.Empty && !templatePrefix.EndsWith("/"))
+ {
templatePrefix += "/";
}
- return templatePrefix + $"{{*{ ClientRouteTokenName }}}";
+ return templatePrefix + $"{{*{ClientRouteTokenName}}}";
}
private static IDictionary ObjectToDictionary(object value)
- {
- return value as IDictionary ?? new RouteValueDictionary(value);
- }
+ => value as IDictionary ?? new RouteValueDictionary(value);
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs
index 8c82a8f..63c4f3e 100644
--- a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs
+++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs
@@ -8,65 +8,85 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.SpaServices.Webpack
{
- // Based on https://github.com/aspnet/Proxy/blob/dev/src/Microsoft.AspNetCore.Proxy/ProxyMiddleware.cs
- // Differs in that, if the proxied request returns a 404, we pass through to the next middleware in the chain
- // This is useful for Webpack middleware, because it lets you fall back on prebuilt files on disk for
- // chunks not exposed by the current Webpack config (e.g., DLL/vendor chunks).
- internal class ConditionalProxyMiddleware {
- private RequestDelegate next;
- private ConditionalProxyMiddlewareOptions options;
- private HttpClient httpClient;
- private string pathPrefix;
+ ///
+ /// Based on https://github.com/aspnet/Proxy/blob/dev/src/Microsoft.AspNetCore.Proxy/ProxyMiddleware.cs
+ /// Differs in that, if the proxied request returns a 404, we pass through to the next middleware in the chain
+ /// This is useful for Webpack middleware, because it lets you fall back on prebuilt files on disk for
+ /// chunks not exposed by the current Webpack config (e.g., DLL/vendor chunks).
+ ///
+ internal class ConditionalProxyMiddleware
+ {
+ private readonly HttpClient _httpClient;
+ private readonly RequestDelegate _next;
+ private readonly ConditionalProxyMiddlewareOptions _options;
+ private readonly string _pathPrefix;
- public ConditionalProxyMiddleware(RequestDelegate next, string pathPrefix, ConditionalProxyMiddlewareOptions options)
+ public ConditionalProxyMiddleware(
+ RequestDelegate next,
+ string pathPrefix,
+ ConditionalProxyMiddlewareOptions options)
{
- this.next = next;
- this.pathPrefix = pathPrefix;
- this.options = options;
- this.httpClient = new HttpClient(new HttpClientHandler());
+ _next = next;
+ _pathPrefix = pathPrefix;
+ _options = options;
+ _httpClient = new HttpClient(new HttpClientHandler());
}
-
+
public async Task Invoke(HttpContext context)
{
- if (context.Request.Path.StartsWithSegments(this.pathPrefix)) {
+ if (context.Request.Path.StartsWithSegments(_pathPrefix))
+ {
var didProxyRequest = await PerformProxyRequest(context);
- if (didProxyRequest) {
+ if (didProxyRequest)
+ {
return;
}
}
-
+
// Not a request we can proxy
- await this.next.Invoke(context);
+ await _next.Invoke(context);
}
-
- private async Task PerformProxyRequest(HttpContext context) {
+
+ private async Task PerformProxyRequest(HttpContext context)
+ {
var requestMessage = new HttpRequestMessage();
// Copy the request headers
- foreach (var header in context.Request.Headers) {
- if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null) {
+ foreach (var header in context.Request.Headers)
+ {
+ if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()))
+ {
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
-
- requestMessage.Headers.Host = options.Host + ":" + options.Port;
- var uriString = $"{options.Scheme}://{options.Host}:{options.Port}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}";
+
+ requestMessage.Headers.Host = _options.Host + ":" + _options.Port;
+ var uriString =
+ $"{_options.Scheme}://{_options.Host}:{_options.Port}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}";
requestMessage.RequestUri = new Uri(uriString);
requestMessage.Method = new HttpMethod(context.Request.Method);
- using (var responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted)) {
- if (responseMessage.StatusCode == HttpStatusCode.NotFound) {
+ using (
+ var responseMessage =
+ await
+ _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead,
+ context.RequestAborted))
+ {
+ if (responseMessage.StatusCode == HttpStatusCode.NotFound)
+ {
// Let some other middleware handle this
return false;
}
-
+
// We can handle this
- context.Response.StatusCode = (int)responseMessage.StatusCode;
- foreach (var header in responseMessage.Headers) {
+ context.Response.StatusCode = (int) responseMessage.StatusCode;
+ foreach (var header in responseMessage.Headers)
+ {
context.Response.Headers[header.Key] = header.Value.ToArray();
}
- foreach (var header in responseMessage.Content.Headers) {
+ foreach (var header in responseMessage.Content.Headers)
+ {
context.Response.Headers[header.Key] = header.Value.ToArray();
}
@@ -77,16 +97,4 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack
}
}
}
-
- internal class ConditionalProxyMiddlewareOptions {
- public string Scheme { get; private set; }
- public string Host { get; private set; }
- public string Port { get; private set; }
-
- public ConditionalProxyMiddlewareOptions(string scheme, string host, string port) {
- this.Scheme = scheme;
- this.Host = host;
- this.Port = port;
- }
- }
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs
new file mode 100644
index 0000000..5654007
--- /dev/null
+++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs
@@ -0,0 +1,16 @@
+namespace Microsoft.AspNetCore.SpaServices.Webpack
+{
+ internal class ConditionalProxyMiddlewareOptions
+ {
+ public ConditionalProxyMiddlewareOptions(string scheme, string host, string port)
+ {
+ Scheme = scheme;
+ Host = host;
+ Port = port;
+ }
+
+ public string Scheme { get; }
+ public string Host { get; }
+ public string Port { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs
index dd8a188..f998c55 100644
--- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs
+++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs
@@ -9,22 +9,31 @@ using Microsoft.Extensions.PlatformAbstractions;
using Newtonsoft.Json;
// Putting in this namespace so it's always available whenever MapRoute is
+
namespace Microsoft.AspNetCore.Builder
{
public static class WebpackDevMiddleware
{
- const string WebpackDevMiddlewareScheme = "http";
- const string WebpackDevMiddlewareHostname = "localhost";
- const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr";
- const string DefaultConfigFile = "webpack.config.js";
+ private const string WebpackDevMiddlewareScheme = "http";
+ private const string WebpackDevMiddlewareHostname = "localhost";
+ private const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr";
+ private const string DefaultConfigFile = "webpack.config.js";
- public static void UseWebpackDevMiddleware(this IApplicationBuilder appBuilder, WebpackDevMiddlewareOptions options = null) {
- // Validate options
- if (options == null) {
+ public static void UseWebpackDevMiddleware(
+ this IApplicationBuilder appBuilder,
+ WebpackDevMiddlewareOptions options = null)
+ {
+ // Prepare options
+ if (options == null)
+ {
options = new WebpackDevMiddlewareOptions();
}
- if (options.ReactHotModuleReplacement && !options.HotModuleReplacement) {
- throw new ArgumentException("To enable ReactHotModuleReplacement, you must also enable HotModuleReplacement.");
+
+ // Validate options
+ if (options.ReactHotModuleReplacement && !options.HotModuleReplacement)
+ {
+ throw new ArgumentException(
+ "To enable ReactHotModuleReplacement, you must also enable HotModuleReplacement.");
}
// Unlike other consumers of NodeServices, WebpackDevMiddleware dosen't share Node instances, nor does it
@@ -32,44 +41,55 @@ namespace Microsoft.AspNetCore.Builder
// because it must *not* restart when files change (if it did, you'd lose all the benefits of Webpack
// middleware). And since this is a dev-time-only feature, it doesn't matter if the default transport isn't
// as fast as some theoretical future alternative.
- var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof (IHostingEnvironment));
- var nodeServices = Configuration.CreateNodeServices(new NodeServicesOptions {
+ var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment));
+ var nodeServices = Configuration.CreateNodeServices(new NodeServicesOptions
+ {
HostingModel = NodeHostingModel.Http,
ProjectPath = hostEnv.ContentRootPath,
- WatchFileExtensions = new string[] {} // Don't watch anything
+ WatchFileExtensions = new string[] { } // Don't watch anything
});
// Get a filename matching the middleware Node script
- var script = EmbeddedResourceReader.Read(typeof (WebpackDevMiddleware), "/Content/Node/webpack-dev-middleware.js");
+ var script = EmbeddedResourceReader.Read(typeof(WebpackDevMiddleware),
+ "/Content/Node/webpack-dev-middleware.js");
var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit
// Tell Node to start the server hosting webpack-dev-middleware
- var devServerOptions = new {
+ var devServerOptions = new
+ {
webpackConfigPath = Path.Combine(hostEnv.ContentRootPath, options.ConfigFile ?? DefaultConfigFile),
suppliedOptions = options
};
- var devServerInfo = nodeServices.InvokeExport(nodeScript.FileName, "createWebpackDevServer", JsonConvert.SerializeObject(devServerOptions)).Result;
+ var devServerInfo =
+ nodeServices.InvokeExport(nodeScript.FileName, "createWebpackDevServer",
+ JsonConvert.SerializeObject(devServerOptions)).Result;
// Proxy the corresponding requests through ASP.NET and into the Node listener
- var proxyOptions = new ConditionalProxyMiddlewareOptions(WebpackDevMiddlewareScheme, WebpackDevMiddlewareHostname, devServerInfo.Port.ToString());
+ var proxyOptions = new ConditionalProxyMiddlewareOptions(WebpackDevMiddlewareScheme,
+ WebpackDevMiddlewareHostname, devServerInfo.Port.ToString());
appBuilder.UseMiddleware(devServerInfo.PublicPath, proxyOptions);
// While it would be nice to proxy the /__webpack_hmr requests too, these return an EventStream,
// and the Microsoft.AspNetCore.Proxy code doesn't handle that entirely - it throws an exception after
// a while. So, just serve a 302 for those.
- appBuilder.Map(WebpackHotMiddlewareEndpoint, builder => {
- builder.Use(next => async ctx => {
- ctx.Response.Redirect($"{ WebpackDevMiddlewareScheme }://{ WebpackDevMiddlewareHostname }:{ devServerInfo.Port.ToString() }{ WebpackHotMiddlewareEndpoint }");
+ appBuilder.Map(WebpackHotMiddlewareEndpoint, builder =>
+ {
+ builder.Use(next => async ctx =>
+ {
+ ctx.Response.Redirect(
+ $"{WebpackDevMiddlewareScheme}://{WebpackDevMiddlewareHostname}:{devServerInfo.Port.ToString()}{WebpackHotMiddlewareEndpoint}");
await Task.Yield();
});
});
}
- #pragma warning disable CS0649
- class WebpackDevServerInfo {
- public int Port;
- public string PublicPath;
+#pragma warning disable CS0649
+ class WebpackDevServerInfo
+ {
+ public int Port { get; set; }
+ public string PublicPath { get; set; }
}
- #pragma warning restore CS0649
}
+#pragma warning restore CS0649
}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs
index 020589a..08a6dbd 100644
--- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs
+++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs
@@ -1,7 +1,9 @@
-namespace Microsoft.AspNetCore.SpaServices.Webpack {
- public class WebpackDevMiddlewareOptions {
+namespace Microsoft.AspNetCore.SpaServices.Webpack
+{
+ public class WebpackDevMiddlewareOptions
+ {
public bool HotModuleReplacement { get; set; }
public bool ReactHotModuleReplacement { get; set; }
public string ConfigFile { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/templates/Angular2Spa/Angular2Spa.xproj b/templates/Angular2Spa/Angular2Spa.xproj
index 455712b..d4c5f07 100644
--- a/templates/Angular2Spa/Angular2Spa.xproj
+++ b/templates/Angular2Spa/Angular2Spa.xproj
@@ -10,11 +10,11 @@
8f5cb8a9-3086-4b49-a1c2-32a9f89bca11
Angular2Spa
..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName)
- ..\..\..\JavaScriptServices.sln\artifacts\bin\$(MSBuildProjectName)\
+ .\bin\
2.0
2018
-
+
\ No newline at end of file
diff --git a/templates/ReactReduxSpa/ReactReduxSpa.xproj b/templates/ReactReduxSpa/ReactReduxSpa.xproj
index 630731a..0996bb6 100644
--- a/templates/ReactReduxSpa/ReactReduxSpa.xproj
+++ b/templates/ReactReduxSpa/ReactReduxSpa.xproj
@@ -10,11 +10,11 @@
dbfc6db0-a6d1-4694-a108-1c604b988da3
ReactReduxSpa
..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName)
- ..\..\..\JavaScriptServices.sln\artifacts\bin\$(MSBuildProjectName)\
+ .\bin\
2.0
2018
-
+
\ No newline at end of file
diff --git a/templates/ReactSpa/ReactSpa.xproj b/templates/ReactSpa/ReactSpa.xproj
index d1b8262..abe5a58 100644
--- a/templates/ReactSpa/ReactSpa.xproj
+++ b/templates/ReactSpa/ReactSpa.xproj
@@ -10,11 +10,11 @@
e9d1a695-f0e6-46f2-b5e3-72f4af805387
ReactSpa
..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName)
- ..\..\..\JavaScriptServices.sln\artifacts\bin\$(MSBuildProjectName)\
+ .\bin\
2.0
2018
-
+
\ No newline at end of file
diff --git a/templates/WebApplicationBasic/WebApplicationBasic.xproj b/templates/WebApplicationBasic/WebApplicationBasic.xproj
index 3845bf2..b0c23a0 100644
--- a/templates/WebApplicationBasic/WebApplicationBasic.xproj
+++ b/templates/WebApplicationBasic/WebApplicationBasic.xproj
@@ -10,11 +10,11 @@
cb4398d6-b7f1-449a-ae02-828769679232
WebApplicationBasic
..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName)
- ..\..\..\JavaScriptServices.sln\artifacts\bin\$(MSBuildProjectName)\
+ .\bin\
2.0
2018
-
+
\ No newline at end of file