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

Commit f04fb8c

Browse files
Design review: Always instantiate via DI
1 parent 61fd900 commit f04fb8c

File tree

9 files changed

+97
-91
lines changed

9 files changed

+97
-91
lines changed

samples/misc/LatencyTest/Program.cs

+12-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO;
44
using System.Threading.Tasks;
55
using Microsoft.AspNetCore.NodeServices;
6+
using Microsoft.Extensions.DependencyInjection;
67

78
namespace ConsoleApplication
89
{
@@ -12,7 +13,17 @@ namespace ConsoleApplication
1213
public class Program
1314
{
1415
public static void Main(string[] args) {
15-
using (var nodeServices = CreateNodeServices(NodeServicesOptions.DefaultNodeHostingModel)) {
16+
// Set up the DI system
17+
var services = new ServiceCollection();
18+
services.AddNodeServices(new NodeServicesOptions {
19+
HostingModel = NodeServicesOptions.DefaultNodeHostingModel,
20+
ProjectPath = Directory.GetCurrentDirectory(),
21+
WatchFileExtensions = new string[] {} // Don't watch anything
22+
});
23+
var serviceProvider = services.BuildServiceProvider();
24+
25+
// Now instantiate an INodeServices and use it
26+
using (var nodeServices = serviceProvider.GetRequiredService<INodeServices>()) {
1627
MeasureLatency(nodeServices).Wait();
1728
}
1829
}
@@ -34,13 +45,5 @@ private static async Task MeasureLatency(INodeServices nodeServices) {
3445
Console.WriteLine("\nTotal time: {0:F2} milliseconds", 1000 * elapsedSeconds);
3546
Console.WriteLine("\nTime per invocation: {0:F2} milliseconds", 1000 * elapsedSeconds / requestCount);
3647
}
37-
38-
private static INodeServices CreateNodeServices(NodeHostingModel hostingModel) {
39-
return Configuration.CreateNodeServices(new NodeServicesOptions {
40-
HostingModel = hostingModel,
41-
ProjectPath = Directory.GetCurrentDirectory(),
42-
WatchFileExtensions = new string[] {} // Don't watch anything
43-
});
44-
}
4548
}
4649
}

samples/misc/LatencyTest/project.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"version": "1.0.0",
99
"type": "platform"
1010
},
11-
"Microsoft.AspNetCore.NodeServices": "1.0.0-*"
11+
"Microsoft.AspNetCore.NodeServices": "1.0.0-*",
12+
"Microsoft.Extensions.DependencyInjection": "1.0.0"
1213
},
1314
"frameworks": {
1415
"netcoreapp1.0": {

samples/misc/Webpack/webpack.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module.exports = merge({
1010
},
1111
module: {
1212
loaders: [
13-
{ test: /\.ts(x?)$/, exclude: /node_modules/, loader: 'ts-loader' }
13+
{ test: /\.ts(x?)$/, exclude: /node_modules/, loader: 'ts-loader?silent' }
1414
],
1515
},
1616
entry: {

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

+47-42
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.AspNetCore.NodeServices.HostingModels;
55
using Microsoft.Extensions.Logging;
66
using Microsoft.Extensions.Logging.Console;
7+
using System.Collections.Generic;
78

89
namespace Microsoft.AspNetCore.NodeServices
910
{
@@ -16,48 +17,18 @@ public static void AddNodeServices(this IServiceCollection serviceCollection)
1617

1718
public static void AddNodeServices(this IServiceCollection serviceCollection, NodeServicesOptions options)
1819
{
19-
serviceCollection.AddSingleton(typeof(INodeServices), serviceProvider =>
20-
{
21-
// Since this instance is being created through DI, we can access the IHostingEnvironment
22-
// to populate options.ProjectPath if it wasn't explicitly specified.
23-
var hostEnv = serviceProvider.GetRequiredService<IHostingEnvironment>();
24-
if (string.IsNullOrEmpty(options.ProjectPath))
25-
{
26-
options.ProjectPath = hostEnv.ContentRootPath;
27-
}
28-
29-
// Similarly, we can determine the 'is development' value from the hosting environment
30-
options.AddDefaultEnvironmentVariables(hostEnv.IsDevelopment());
31-
32-
// Likewise, if no logger was specified explicitly, we should use the one from DI.
33-
// If it doesn't provide one, CreateNodeInstance will set up a default.
34-
if (options.NodeInstanceOutputLogger == null)
35-
{
36-
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
37-
if (loggerFactory != null)
38-
{
39-
options.NodeInstanceOutputLogger = loggerFactory.CreateLogger(LogCategoryName);
40-
}
41-
}
42-
43-
return new NodeServicesImpl(options, () => CreateNodeInstance(options));
44-
});
20+
serviceCollection.AddSingleton(
21+
typeof(INodeServices),
22+
serviceProvider => CreateNodeServices(serviceProvider, options));
4523
}
4624

47-
public static INodeServices CreateNodeServices(NodeServicesOptions options)
25+
public static INodeServices CreateNodeServices(IServiceProvider serviceProvider, NodeServicesOptions options)
4826
{
49-
return new NodeServicesImpl(options, () => CreateNodeInstance(options));
27+
return new NodeServicesImpl(() => CreateNodeInstance(serviceProvider, options));
5028
}
5129

52-
private static INodeInstance CreateNodeInstance(NodeServicesOptions options)
30+
private static INodeInstance CreateNodeInstance(IServiceProvider serviceProvider, NodeServicesOptions options)
5331
{
54-
// If you've specified no logger, fall back on a default console logger
55-
var logger = options.NodeInstanceOutputLogger;
56-
if (logger == null)
57-
{
58-
logger = new ConsoleLogger(LogCategoryName, null, false);
59-
}
60-
6132
if (options.NodeInstanceFactory != null)
6233
{
6334
// If you've explicitly supplied an INodeInstance factory, we'll use that. This is useful for
@@ -66,17 +37,51 @@ private static INodeInstance CreateNodeInstance(NodeServicesOptions options)
6637
}
6738
else
6839
{
69-
// Otherwise we'll construct the type of INodeInstance specified by the HostingModel property,
70-
// which itself has a useful default value.
40+
// Otherwise we'll construct the type of INodeInstance specified by the HostingModel property
41+
// (which itself has a useful default value), plus obtain config information from the DI system.
42+
var projectPath = options.ProjectPath;
43+
var envVars = options.EnvironmentVariables == null
44+
? new Dictionary<string, string>()
45+
: new Dictionary<string, string>(options.EnvironmentVariables);
46+
47+
var hostEnv = serviceProvider.GetService<IHostingEnvironment>();
48+
if (hostEnv != null)
49+
{
50+
// In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few
51+
// things that you'd otherwise have to specify manually
52+
if (string.IsNullOrEmpty(projectPath))
53+
{
54+
projectPath = hostEnv.ContentRootPath;
55+
}
56+
57+
// Similarly, we can determine the 'is development' value from the hosting environment
58+
if (!envVars.ContainsKey("NODE_ENV"))
59+
{
60+
// These strings are a de-facto standard in Node
61+
envVars["NODE_ENV"] = hostEnv.IsDevelopment() ? "development" : "production";
62+
}
63+
}
64+
65+
// If no logger was specified explicitly, we should use the one from DI.
66+
// If it doesn't provide one, we'll set up a default one.
67+
var logger = options.NodeInstanceOutputLogger;
68+
if (logger == null)
69+
{
70+
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
71+
logger = loggerFactory != null
72+
? loggerFactory.CreateLogger(LogCategoryName)
73+
: new ConsoleLogger(LogCategoryName, null, false);
74+
}
75+
7176
switch (options.HostingModel)
7277
{
7378
case NodeHostingModel.Http:
74-
return new HttpNodeInstance(options.ProjectPath, options.WatchFileExtensions, logger,
75-
options.EnvironmentVariables, options.LaunchWithDebugging, options.DebuggingPort, /* port */ 0);
79+
return new HttpNodeInstance(projectPath, options.WatchFileExtensions, logger,
80+
envVars, options.LaunchWithDebugging, options.DebuggingPort, /* port */ 0);
7681
case NodeHostingModel.Socket:
7782
var pipeName = "pni-" + Guid.NewGuid().ToString("D"); // Arbitrary non-clashing string
78-
return new SocketNodeInstance(options.ProjectPath, options.WatchFileExtensions, pipeName, logger,
79-
options.EnvironmentVariables, options.LaunchWithDebugging, options.DebuggingPort);
83+
return new SocketNodeInstance(projectPath, options.WatchFileExtensions, pipeName, logger,
84+
envVars, options.LaunchWithDebugging, options.DebuggingPort);
8085
default:
8186
throw new ArgumentException("Unknown hosting model: " + options.HostingModel);
8287
}

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

-16
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,5 @@ public NodeServicesOptions()
2525
public bool LaunchWithDebugging { get; set; }
2626
public IDictionary<string, string> EnvironmentVariables { get; set; }
2727
public int? DebuggingPort { get; set; }
28-
29-
public NodeServicesOptions AddDefaultEnvironmentVariables(bool isDevelopmentMode)
30-
{
31-
if (EnvironmentVariables == null)
32-
{
33-
EnvironmentVariables = new Dictionary<string, string>();
34-
}
35-
36-
if (!EnvironmentVariables.ContainsKey("NODE_ENV"))
37-
{
38-
// These strings are a de-facto standard in Node
39-
EnvironmentVariables["NODE_ENV"] = isDevelopmentMode ? "development" : "production";
40-
}
41-
42-
return this;
43-
}
4428
}
4529
}

src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs

+1-3
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,13 @@ namespace Microsoft.AspNetCore.NodeServices
1919
internal class NodeServicesImpl : INodeServices
2020
{
2121
private static TimeSpan ConnectionDrainingTimespan = TimeSpan.FromSeconds(15);
22-
private NodeServicesOptions _options;
2322
private Func<INodeInstance> _nodeInstanceFactory;
2423
private INodeInstance _currentNodeInstance;
2524
private object _currentNodeInstanceAccessLock = new object();
2625
private Exception _instanceDelayedDisposalException;
2726

28-
internal NodeServicesImpl(NodeServicesOptions options, Func<INodeInstance> nodeInstanceFactory)
27+
internal NodeServicesImpl(Func<INodeInstance> nodeInstanceFactory)
2928
{
30-
_options = options;
3129
_nodeInstanceFactory = nodeInstanceFactory;
3230
}
3331

src/Microsoft.AspNetCore.NodeServices/README.md

+23-7
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,33 @@ If you want to put `addNumber.js` inside a subfolder rather than the root of you
9090

9191
## For non-ASP.NET apps
9292

93-
In other types of .NET app where you don't have ASP.NET Core's DI system, you can get an instance of `NodeServices` as follows:
93+
In other types of .NET Core app, where you don't have ASP.NET supplying an `IServiceCollection` to you, you'll need to instantiate your own DI container. For example, add a reference to the .NET package `Microsoft.Extensions.DependencyInjection`, and then you can construct an `IServiceCollection`, then register NodeServices as usual:
9494

9595
```csharp
9696
// Remember to add 'using Microsoft.AspNetCore.NodeServices;' at the top of your file
97+
var services = new ServiceCollection();
98+
services.AddNodeServices(new NodeServicesOptions { /* your options here */ });
99+
```
100+
101+
Now you can ask it to supply the shared `INodeServices` instance:
102+
103+
```csharp
104+
var serviceProvider = services.BuildServiceProvider();
105+
var nodeServices = serviceProvider.GetRequiredService<INodeServices>();
106+
```
107+
108+
Or, if you want to obtain a separate (non-shared) `INodeServices` instance:
97109

98-
var nodeServices = Configuration.CreateNodeServices(new NodeServicesOptions());
110+
```csharp
111+
var options = new NodeServicesOptions { /* your options here */ };
112+
var nodeServices = Microsoft.AspNetCore.NodeServices.Configuration.CreateNodeServices(serviceProvider, options);
99113
```
100114

101115
Besides this, the usage is the same as described for ASP.NET above, so you can now call `nodeServices.InvokeAsync<T>(...)` etc.
102116

103-
You can dispose the `nodeServices` object whenever you are done with it (and it will shut down the associated Node.js instance), but because these instances are expensive to create, you should whenever possible retain and reuse instances. They are thread-safe - you can call `InvokeAsync<T>` simultaneously from multiple threads. Also, `NodeServices` instances are smart enough to detect if the associated Node instance has died and will automatically start a new Node instance if needed.
117+
You can dispose the `nodeServices` object whenever you are done with it (and it will shut down the associated Node.js instance), but because these instances are expensive to create, you should whenever possible retain and reuse instances. Don't dispose the shared instance returned from `serviceProvider.GetRequiredService` (except perhaps if you know your application is shutting down, although .NET's finalizers will dispose it anyway if the shutdown is graceful).
104118

119+
NodeServices instances are thread-safe - you can call `InvokeAsync<T>` simultaneously from multiple threads. Also, they are smart enough to detect if the associated Node instance has died and will automatically start a new Node instance if needed.
105120

106121
# API Reference
107122

@@ -158,21 +173,22 @@ If no `options` is passed, the default `WatchFileExtensions` array includes `.js
158173
**Signature:**
159174

160175
```csharp
161-
CreateNodeServices(NodeServicesOptions options)
176+
CreateNodeServices(IServiceProvider serviceProvider, NodeServicesOptions options)
162177
```
163178

164-
Directly supplies an instance of `NodeServices` without using ASP.NET's DI system.
179+
Supplies a new (non-shared) instance of `NodeServices`. It takes configuration from the .NET DI system (hence requiring an `IServiceProvider`), though some aspects of configuration can be overridden via the `options` parameter.
165180

166181
**Example**
167182

168183
```csharp
169-
var nodeServices = Configuration.CreateNodeServices(new NodeServicesOptions {
184+
var nodeServices = Configuration.CreateNodeServices(serviceProvider, new NodeServicesOptions {
170185
HostingModel = NodeHostingModel.Socket
171186
});
172187
```
173188

174189
**Parameters**
175-
190+
* `serviceProvider` - type: `IServiceProvider`
191+
* An instance of .NET's standard DI service provider. You can get an instance of this by calling `BuildServiceProvider` on an `IServiceCollection` object. See the example usage of `CreateNodeServices` earlier in this document.
176192
* `options` - type: `NodeServicesOptions`.
177193
* Configures the returned `NodeServices` instance.
178194
* Properties:

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

+3-4
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@ public PrerenderTagHelper(IServiceProvider serviceProvider)
3333
// in your startup file, but then again it might be confusing that you don't need to.
3434
if (_nodeServices == null)
3535
{
36-
_nodeServices = _fallbackNodeServices = Configuration.CreateNodeServices(new NodeServicesOptions
37-
{
38-
ProjectPath = _applicationBasePath
39-
}.AddDefaultEnvironmentVariables(hostEnv.IsDevelopment()));
36+
_nodeServices = _fallbackNodeServices = Configuration.CreateNodeServices(
37+
serviceProvider,
38+
new NodeServicesOptions());
4039
}
4140
}
4241

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

+8-8
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,26 @@ public static void UseWebpackDevMiddleware(
3535
"To enable ReactHotModuleReplacement, you must also enable HotModuleReplacement.");
3636
}
3737

38-
var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment));
39-
var projectPath = options.ProjectPath ?? hostEnv.ContentRootPath;
40-
4138
// Unlike other consumers of NodeServices, WebpackDevMiddleware dosen't share Node instances, nor does it
4239
// use your DI configuration. It's important for WebpackDevMiddleware to have its own private Node instance
4340
// because it must *not* restart when files change (if it did, you'd lose all the benefits of Webpack
4441
// middleware). And since this is a dev-time-only feature, it doesn't matter if the default transport isn't
4542
// as fast as some theoretical future alternative.
46-
var nodeServices = Configuration.CreateNodeServices(new NodeServicesOptions
47-
{
48-
ProjectPath = projectPath,
49-
WatchFileExtensions = new string[] { } // Don't watch anything
50-
}.AddDefaultEnvironmentVariables(hostEnv.IsDevelopment()));
43+
var nodeServices = Configuration.CreateNodeServices(
44+
appBuilder.ApplicationServices,
45+
new NodeServicesOptions
46+
{
47+
WatchFileExtensions = new string[] { } // Don't watch anything
48+
});
5149

5250
// Get a filename matching the middleware Node script
5351
var script = EmbeddedResourceReader.Read(typeof(WebpackDevMiddleware),
5452
"/Content/Node/webpack-dev-middleware.js");
5553
var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit
5654

5755
// Tell Node to start the server hosting webpack-dev-middleware
56+
var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment));
57+
var projectPath = options.ProjectPath ?? hostEnv.ContentRootPath;
5858
var devServerOptions = new
5959
{
6060
webpackConfigPath = Path.Combine(projectPath, options.ConfigFile ?? DefaultConfigFile),

0 commit comments

Comments
 (0)