Eliminate the NodeHostingModel enum because it isn't extensible. Instead, use extension methods on NodeServicesOptions to configure a NodeInstanceFactory.

This commit is contained in:
SteveSandersonMS
2016-11-30 11:17:17 +00:00
parent d865e1f28b
commit ebf5a18344
8 changed files with 54 additions and 81 deletions

View File

@@ -16,7 +16,6 @@ namespace ConsoleApplication
// Set up the DI system // Set up the DI system
var services = new ServiceCollection(); var services = new ServiceCollection();
services.AddNodeServices(options => { services.AddNodeServices(options => {
options.HostingModel = NodeServicesOptions.DefaultNodeHostingModel;
options.ProjectPath = Directory.GetCurrentDirectory(); options.ProjectPath = Directory.GetCurrentDirectory();
options.WatchFileExtensions = new string[] {}; // Don't watch anything options.WatchFileExtensions = new string[] {}; // Don't watch anything
}); });

View File

@@ -1,18 +0,0 @@
namespace Microsoft.AspNetCore.NodeServices
{
/// <summary>
/// Represents a way of creating and invoking code in a Node.js environment.
/// </summary>
public enum NodeHostingModel
{
/// <summary>
/// An out-of-process Node.js instance where RPC calls are made via HTTP.
/// </summary>
Http,
/// <summary>
/// An out-of-process Node.js instance where RPC calls are made over binary sockets.
/// </summary>
Socket,
}
}

View File

@@ -1,5 +1,4 @@
using System; using System;
using Microsoft.AspNetCore.NodeServices.HostingModels;
namespace Microsoft.AspNetCore.NodeServices namespace Microsoft.AspNetCore.NodeServices
{ {
@@ -20,32 +19,7 @@ namespace Microsoft.AspNetCore.NodeServices
throw new ArgumentNullException(nameof (options)); throw new ArgumentNullException(nameof (options));
} }
return new NodeServicesImpl(() => CreateNodeInstance(options)); return new NodeServicesImpl(options.NodeInstanceFactory);
}
private static INodeInstance CreateNodeInstance(NodeServicesOptions options)
{
if (options.NodeInstanceFactory != null)
{
// If you've explicitly supplied an INodeInstance factory, we'll use that. This is useful for
// custom INodeInstance implementations.
return options.NodeInstanceFactory();
}
else
{
switch (options.HostingModel)
{
case NodeHostingModel.Http:
return new HttpNodeInstance(options.ProjectPath, options.WatchFileExtensions, options.NodeInstanceOutputLogger,
options.EnvironmentVariables, options.InvocationTimeoutMilliseconds, options.LaunchWithDebugging, options.DebuggingPort, /* port */ 0);
case NodeHostingModel.Socket:
var pipeName = "pni-" + Guid.NewGuid().ToString("D"); // Arbitrary non-clashing string
return new SocketNodeInstance(options.ProjectPath, options.WatchFileExtensions, pipeName, options.NodeInstanceOutputLogger,
options.EnvironmentVariables, options.InvocationTimeoutMilliseconds, options.LaunchWithDebugging, options.DebuggingPort);
default:
throw new ArgumentException("Unknown hosting model: " + options.HostingModel);
}
}
} }
} }
} }

View File

@@ -13,11 +13,6 @@ namespace Microsoft.AspNetCore.NodeServices
/// </summary> /// </summary>
public class NodeServicesOptions public class NodeServicesOptions
{ {
/// <summary>
/// Defines the default <see cref="NodeHostingModel"/>.
/// </summary>
public const NodeHostingModel DefaultNodeHostingModel = NodeHostingModel.Http;
internal const string TimeoutConfigPropertyName = nameof(InvocationTimeoutMilliseconds); internal const string TimeoutConfigPropertyName = nameof(InvocationTimeoutMilliseconds);
private const int DefaultInvocationTimeoutMilliseconds = 60 * 1000; private const int DefaultInvocationTimeoutMilliseconds = 60 * 1000;
private const string LogCategoryName = "Microsoft.AspNetCore.NodeServices"; private const string LogCategoryName = "Microsoft.AspNetCore.NodeServices";
@@ -36,7 +31,6 @@ namespace Microsoft.AspNetCore.NodeServices
EnvironmentVariables = new Dictionary<string, string>(); EnvironmentVariables = new Dictionary<string, string>();
InvocationTimeoutMilliseconds = DefaultInvocationTimeoutMilliseconds; InvocationTimeoutMilliseconds = DefaultInvocationTimeoutMilliseconds;
HostingModel = DefaultNodeHostingModel;
WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone(); WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone();
// In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few // In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few
@@ -53,15 +47,15 @@ namespace Microsoft.AspNetCore.NodeServices
NodeInstanceOutputLogger = loggerFactory != null NodeInstanceOutputLogger = loggerFactory != null
? loggerFactory.CreateLogger(LogCategoryName) ? loggerFactory.CreateLogger(LogCategoryName)
: new ConsoleLogger(LogCategoryName, null, false); : new ConsoleLogger(LogCategoryName, null, false);
// By default, we use this package's built-in out-of-process-via-HTTP hosting/transport
this.UseHttpHosting();
} }
/// <summary> /// <summary>
/// Specifies which <see cref="NodeHostingModel"/> should be used. /// Specifies how to construct Node.js instances. An <see cref="INodeInstance"/> encapsulates all details about
/// </summary> /// how Node.js instances are launched and communicated with. A new <see cref="INodeInstance"/> will be created
public NodeHostingModel HostingModel { get; set; } /// automatically if the previous instance has terminated (e.g., because a source file changed).
/// <summary>
/// If set, this callback function will be invoked to supply the <see cref="INodeServices"/> instance.
/// </summary> /// </summary>
public Func<INodeInstance> NodeInstanceFactory { get; set; } public Func<INodeInstance> NodeInstanceFactory { get; set; }

View File

@@ -1,12 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
@@ -36,21 +34,19 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
private bool _disposed; private bool _disposed;
private int _portNumber; private int _portNumber;
public HttpNodeInstance(string projectPath, string[] watchFileExtensions, ILogger nodeInstanceOutputLogger, public HttpNodeInstance(NodeServicesOptions options, int port = 0)
IDictionary<string, string> environmentVars, int invocationTimeoutMilliseconds, bool launchWithDebugging,
int debuggingPort, int port = 0)
: base( : base(
EmbeddedResourceReader.Read( EmbeddedResourceReader.Read(
typeof(HttpNodeInstance), typeof(HttpNodeInstance),
"/Content/Node/entrypoint-http.js"), "/Content/Node/entrypoint-http.js"),
projectPath, options.ProjectPath,
watchFileExtensions, options.WatchFileExtensions,
MakeCommandLineOptions(port), MakeCommandLineOptions(port),
nodeInstanceOutputLogger, options.NodeInstanceOutputLogger,
environmentVars, options.EnvironmentVariables,
invocationTimeoutMilliseconds, options.InvocationTimeoutMilliseconds,
launchWithDebugging, options.LaunchWithDebugging,
debuggingPort) options.DebuggingPort)
{ {
_client = new HttpClient(); _client = new HttpClient();
} }

View File

@@ -0,0 +1,30 @@
using System;
namespace Microsoft.AspNetCore.NodeServices.HostingModels
{
/// <summary>
/// Extension methods that help with populating a <see cref="NodeServicesOptions"/> object.
/// </summary>
public static class NodeServicesOptionsExtensions
{
/// <summary>
/// Configures the <see cref="INodeServices"/> service so that it will use out-of-process
/// Node.js instances and perform RPC calls over HTTP.
/// </summary>
public static void UseHttpHosting(this NodeServicesOptions options)
{
options.NodeInstanceFactory = () => new HttpNodeInstance(options);
}
/// <summary>
/// Configures the <see cref="INodeServices"/> service so that it will use out-of-process
/// Node.js instances and perform RPC calls over binary sockets (on Windows, this is
/// implemented as named pipes; on other platforms it uses domain sockets).
/// </summary>
public static void UseSocketHosting(this NodeServicesOptions options)
{
var pipeName = "pni-" + Guid.NewGuid().ToString("D"); // Arbitrary non-clashing string
options.NodeInstanceFactory = () => new SocketNodeInstance(options, pipeName);
}
}
}

View File

@@ -42,21 +42,19 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
private string _socketAddress; private string _socketAddress;
private VirtualConnectionClient _virtualConnectionClient; private VirtualConnectionClient _virtualConnectionClient;
public SocketNodeInstance(string projectPath, string[] watchFileExtensions, string socketAddress, public SocketNodeInstance(NodeServicesOptions options, string socketAddress)
ILogger nodeInstanceOutputLogger, IDictionary<string, string> environmentVars,
int invocationTimeoutMilliseconds, bool launchWithDebugging, int debuggingPort)
: base( : base(
EmbeddedResourceReader.Read( EmbeddedResourceReader.Read(
typeof(SocketNodeInstance), typeof(SocketNodeInstance),
"/Content/Node/entrypoint-socket.js"), "/Content/Node/entrypoint-socket.js"),
projectPath, options.ProjectPath,
watchFileExtensions, options.WatchFileExtensions,
MakeNewCommandLineOptions(socketAddress), MakeNewCommandLineOptions(socketAddress),
nodeInstanceOutputLogger, options.NodeInstanceOutputLogger,
environmentVars, options.EnvironmentVariables,
invocationTimeoutMilliseconds, options.InvocationTimeoutMilliseconds,
launchWithDebugging, options.LaunchWithDebugging,
debuggingPort) options.DebuggingPort)
{ {
_socketAddress = socketAddress; _socketAddress = socketAddress;
} }

View File

@@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.NodeServices
return InvokeExportWithPossibleRetryAsync<T>(moduleName, exportedFunctionName, args, /* allowRetry */ true, cancellationToken); return InvokeExportWithPossibleRetryAsync<T>(moduleName, exportedFunctionName, args, /* allowRetry */ true, cancellationToken);
} }
public async Task<T> InvokeExportWithPossibleRetryAsync<T>(string moduleName, string exportedFunctionName, object[] args, bool allowRetry, CancellationToken cancellationToken) private async Task<T> InvokeExportWithPossibleRetryAsync<T>(string moduleName, string exportedFunctionName, object[] args, bool allowRetry, CancellationToken cancellationToken)
{ {
ThrowAnyOutstandingDelayedDisposalException(); ThrowAnyOutstandingDelayedDisposalException();
var nodeInstance = GetOrCreateCurrentNodeInstance(); var nodeInstance = GetOrCreateCurrentNodeInstance();