mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-23 10:08:57 +00:00
Compare commits
339 Commits
angular4-p
...
2.2.0-prev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3265b92086 | ||
|
|
ef999a57fd | ||
|
|
e231b29e58 | ||
|
|
d8662392a0 | ||
|
|
587ead92e4 | ||
|
|
622be9084a | ||
|
|
817c0e4f24 | ||
|
|
c2f15f4199 | ||
|
|
24ebd9ff3e | ||
|
|
15884cbadb | ||
|
|
4bc67d6a60 | ||
|
|
9281cbaf72 | ||
|
|
cd2b52bbd0 | ||
|
|
78dd174aed | ||
|
|
4d7485bba2 | ||
|
|
a4bb72e58c | ||
|
|
9c741600d2 | ||
|
|
c8342594a8 | ||
|
|
ea3a7bc897 | ||
|
|
578124e1ce | ||
|
|
0da7460fa2 | ||
|
|
5fa87a0313 | ||
|
|
067298bead | ||
|
|
cf659b3fda | ||
|
|
1e85713d75 | ||
|
|
a04813edc3 | ||
|
|
e49b0e439e | ||
|
|
d65a4f3b6d | ||
|
|
47828ee727 | ||
|
|
0d1c99c1d1 | ||
|
|
240dd82473 | ||
|
|
b616db4918 | ||
|
|
436cdb0e96 | ||
|
|
2698d77487 | ||
|
|
447359af84 | ||
|
|
823630c508 | ||
|
|
c35a3a17c0 | ||
|
|
98c47a1c70 | ||
|
|
0a1c83ab81 | ||
|
|
2167b28ffd | ||
|
|
966bfcb172 | ||
|
|
b8f85dc2d9 | ||
|
|
8c84e35392 | ||
|
|
944e8d396d | ||
|
|
6ba3014c57 | ||
|
|
961619c5c9 | ||
|
|
da66cc1fee | ||
|
|
b7ba837d6e | ||
|
|
7d30f2bbc3 | ||
|
|
424e5ed91a | ||
|
|
67267eabf7 | ||
|
|
1e5b6f864e | ||
|
|
8ddacb8b73 | ||
|
|
2c81117b4b | ||
|
|
4d151a599e | ||
|
|
78f7dccfab | ||
|
|
7f550fb469 | ||
|
|
087a459c9c | ||
|
|
f22297a4db | ||
|
|
61b4951961 | ||
|
|
02beb11a5c | ||
|
|
c42db123bd | ||
|
|
04b8c17bdd | ||
|
|
8583f205f9 | ||
|
|
e6af1b892e | ||
|
|
24766621e1 | ||
|
|
ae4c4d6e8f | ||
|
|
8553647ce8 | ||
|
|
67560266ab | ||
|
|
ce49379afb | ||
|
|
4dc05d96ac | ||
|
|
53742ad649 | ||
|
|
91a1d83acd | ||
|
|
1d504e4565 | ||
|
|
873cfa9adf | ||
|
|
41b8642c2d | ||
|
|
d6b67ca75d | ||
|
|
4af70a0907 | ||
|
|
bf5f40b1ed | ||
|
|
a515f6bb0a | ||
|
|
12a2314a5e | ||
|
|
f35c814fc7 | ||
|
|
dbaa453d18 | ||
|
|
b2373e157e | ||
|
|
08c2f231ea | ||
|
|
6274733565 | ||
|
|
5f6f288056 | ||
|
|
78e583d0fb | ||
|
|
7c07beb494 | ||
|
|
e7ffb8bb71 | ||
|
|
3e6f7f3e45 | ||
|
|
0d83504863 | ||
|
|
370b5f7341 | ||
|
|
0b53b92bc6 | ||
|
|
116f33c66c | ||
|
|
c3964a0437 | ||
|
|
f291f87dfc | ||
|
|
a1c2c18326 | ||
|
|
d6588c31bf | ||
|
|
15d2f5a898 | ||
|
|
814441c933 | ||
|
|
a98c1459b5 | ||
|
|
975d537a0a | ||
|
|
4af2e8670e | ||
|
|
160c91f1b9 | ||
|
|
8ded472fe9 | ||
|
|
f9c62ebb5a | ||
|
|
d5a664e481 | ||
|
|
74512dc3b2 | ||
|
|
e6285f30ae | ||
|
|
bd5793e284 | ||
|
|
02bbcb68f1 | ||
|
|
18140929e7 | ||
|
|
50ba6114ee | ||
|
|
cd3e3c667c | ||
|
|
0de9f0e3ce | ||
|
|
9b1509a52b | ||
|
|
a8809f9a96 | ||
|
|
68c4620a55 | ||
|
|
296435e40c | ||
|
|
aeabbdcada | ||
|
|
96d7f85327 | ||
|
|
30333e250a | ||
|
|
08002e961b | ||
|
|
0c77224f46 | ||
|
|
a83ec3a053 | ||
|
|
a16343681b | ||
|
|
c8b337ebaa | ||
|
|
7bf5516bb2 | ||
|
|
2d98a1808c | ||
|
|
e583a17ef8 | ||
|
|
ba0d82d801 | ||
|
|
64389a9bbe | ||
|
|
86e94d7812 | ||
|
|
9f05a3d34b | ||
|
|
63e0af2ee8 | ||
|
|
e67a30132f | ||
|
|
d51bef194c | ||
|
|
dc5e980efa | ||
|
|
e0ab3ddcca | ||
|
|
0c058894c2 | ||
|
|
98385cbcb0 | ||
|
|
77cac3b6be | ||
|
|
051150475f | ||
|
|
78436adb08 | ||
|
|
09317b83a8 | ||
|
|
a0269fb0ad | ||
|
|
64ed1c7945 | ||
|
|
04fe1204a9 | ||
|
|
e2030fb1fa | ||
|
|
e5f1299239 | ||
|
|
c922eee1d6 | ||
|
|
8b243e8cc7 | ||
|
|
28920c7691 | ||
|
|
412ec1b427 | ||
|
|
c62a3b491c | ||
|
|
559832bb6d | ||
|
|
45d645931b | ||
|
|
8d6119f31d | ||
|
|
0291686b20 | ||
|
|
7c52be5e42 | ||
|
|
900e9ca835 | ||
|
|
ad758b1060 | ||
|
|
cd9ad38a99 | ||
|
|
e057cb35ec | ||
|
|
eea2066a6d | ||
|
|
d6ae8829b6 | ||
|
|
a94ac6f37e | ||
|
|
a40adab38d | ||
|
|
c2a284d5b8 | ||
|
|
fc398d602a | ||
|
|
90c59ff4e7 | ||
|
|
a7e715c88f | ||
|
|
6dddc9d01d | ||
|
|
128683be0e | ||
|
|
5ed1a35ce0 | ||
|
|
680ba7497a | ||
|
|
287c10fd2e | ||
|
|
63f7ac9330 | ||
|
|
d2858beaa1 | ||
|
|
5f1450c9ba | ||
|
|
c83605baff | ||
|
|
fc12d722b8 | ||
|
|
372e597f34 | ||
|
|
3715ec7c3f | ||
|
|
d2eaa36372 | ||
|
|
169ef12cd8 | ||
|
|
f5d58f3f2e | ||
|
|
c2e4d4f261 | ||
|
|
50481fe23f | ||
|
|
895a61160e | ||
|
|
b8b769aa74 | ||
|
|
c4aad6bcab | ||
|
|
27f1d07d21 | ||
|
|
0cb14a3c68 | ||
|
|
2457b4ee5d | ||
|
|
789ea5a320 | ||
|
|
a902874754 | ||
|
|
f43ea777eb | ||
|
|
c79db4e8e2 | ||
|
|
d1198aeab2 | ||
|
|
9528dd7432 | ||
|
|
b8c006a3e9 | ||
|
|
a9ddf1413f | ||
|
|
8b37dc8561 | ||
|
|
7b07fb66eb | ||
|
|
cdb04c74f9 | ||
|
|
a74941e3c8 | ||
|
|
a0a710a0df | ||
|
|
576e3debf1 | ||
|
|
d5f5ad7fdc | ||
|
|
dab0faea66 | ||
|
|
2df0febfba | ||
|
|
e65ecebac6 | ||
|
|
bb0727c34c | ||
|
|
4903e12373 | ||
|
|
56c806b34e | ||
|
|
8acba88160 | ||
|
|
b434eefd83 | ||
|
|
44f86eb8a1 | ||
|
|
44360b6955 | ||
|
|
22ff88ab79 | ||
|
|
70d89b9ad3 | ||
|
|
72b1e627b0 | ||
|
|
c94563c700 | ||
|
|
781c5dc37c | ||
|
|
814df07034 | ||
|
|
c2f63f21fd | ||
|
|
4492d3eab7 | ||
|
|
efdfbf5dbb | ||
|
|
6f2e408083 | ||
|
|
70a4a68e7c | ||
|
|
a1942edbfe | ||
|
|
fd90301b1c | ||
|
|
58145d33e3 | ||
|
|
2135c30d66 | ||
|
|
5191531813 | ||
|
|
03018f32b7 | ||
|
|
e632d2bb69 | ||
|
|
7a56f643f7 | ||
|
|
056b955f52 | ||
|
|
0993f46c9e | ||
|
|
4ba0358987 | ||
|
|
721e3c45fd | ||
|
|
27f59c6971 | ||
|
|
943fb2f482 | ||
|
|
2c4bc302de | ||
|
|
5b634ee33c | ||
|
|
6e1fa2b2c3 | ||
|
|
f72ebbd66e | ||
|
|
391ceec559 | ||
|
|
076b5304e0 | ||
|
|
94fc84a9b4 | ||
|
|
9cce26ebd8 | ||
|
|
513d288a5d | ||
|
|
429a432dd3 | ||
|
|
76928bcbaa | ||
|
|
95c81729cd | ||
|
|
8352ec7626 | ||
|
|
bebb82eb69 | ||
|
|
3da87dd3d3 | ||
|
|
117c1a6cbd | ||
|
|
53f5a77490 | ||
|
|
5fd9b93324 | ||
|
|
55bd0c05cb | ||
|
|
f9068819be | ||
|
|
85bf621386 | ||
|
|
328eb0451b | ||
|
|
bc7742148e | ||
|
|
4ceed817a3 | ||
|
|
6d85e752e1 | ||
|
|
460f202832 | ||
|
|
73054559cc | ||
|
|
d007a3fbba | ||
|
|
fcd3cf9dd7 | ||
|
|
48bca777fe | ||
|
|
aadcfa1fb7 | ||
|
|
f683b39806 | ||
|
|
c128fa6e06 | ||
|
|
ed7e07a057 | ||
|
|
749d112b08 | ||
|
|
3ef23ece23 | ||
|
|
90922fb3c6 | ||
|
|
68e84bac3f | ||
|
|
bcb359e355 | ||
|
|
a7a3a359fa | ||
|
|
a38d3bdc4b | ||
|
|
627da65692 | ||
|
|
8e5f255346 | ||
|
|
67ba6271a0 | ||
|
|
a3bcc0d863 | ||
|
|
5e7bb0f5c5 | ||
|
|
8396be24c8 | ||
|
|
728b18431d | ||
|
|
32d9bdee6c | ||
|
|
9a59bccad9 | ||
|
|
e50d4825c3 | ||
|
|
bf6d2227db | ||
|
|
06c94d54eb | ||
|
|
bcaa3af92e | ||
|
|
3685592b01 | ||
|
|
f3b7103c83 | ||
|
|
2acdad38cd | ||
|
|
0dd24068ca | ||
|
|
0526852e05 | ||
|
|
e331355c1d | ||
|
|
60c8a3471d | ||
|
|
c7b7f40d2c | ||
|
|
d8c0bd8898 | ||
|
|
5127a74d3f | ||
|
|
e38399fbf4 | ||
|
|
045c05fc88 | ||
|
|
b444831c8d | ||
|
|
2aaceaa9f8 | ||
|
|
1c5bd98d8a | ||
|
|
0626bb1594 | ||
|
|
d9e75c89ae | ||
|
|
ebe9d8ae92 | ||
|
|
dbdc81a06a | ||
|
|
d42cd10975 | ||
|
|
8a1d64cc73 | ||
|
|
087183ea18 | ||
|
|
9b131ea7d3 | ||
|
|
75fd215b1a | ||
|
|
38997c3f3f | ||
|
|
bee4c6ff11 | ||
|
|
cc859306a3 | ||
|
|
44512226c6 | ||
|
|
a2bde750be | ||
|
|
c592282646 | ||
|
|
b86e9f9254 | ||
|
|
970dc6c457 | ||
|
|
6baa2faf09 | ||
|
|
918e7edbff | ||
|
|
e1c90b3601 | ||
|
|
b9e62cd4c9 | ||
|
|
ad9cfbb176 | ||
|
|
941ae9a34d | ||
|
|
3645ae6cf2 |
19
.appveyor.yml
Executable file
19
.appveyor.yml
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
init:
|
||||||
|
- git config --global core.autocrlf true
|
||||||
|
install:
|
||||||
|
- ps: Install-Product node 6.9.2 x64
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- dev
|
||||||
|
- /^release\/.*$/
|
||||||
|
- /^(.*\/)?ci-.*$/
|
||||||
|
build_script:
|
||||||
|
- ps: .\run.ps1 default-build
|
||||||
|
clone_depth: 1
|
||||||
|
environment:
|
||||||
|
global:
|
||||||
|
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||||
|
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||||
|
test: 'off'
|
||||||
|
deploy: 'off'
|
||||||
|
os: Visual Studio 2017
|
||||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -24,19 +24,8 @@ nuget.exe
|
|||||||
*.ncrunchsolution
|
*.ncrunchsolution
|
||||||
*.*sdf
|
*.*sdf
|
||||||
*.ipch
|
*.ipch
|
||||||
|
|
||||||
.vs/
|
.vs/
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
/.build/
|
/.build/
|
||||||
|
|
||||||
# The templates can't contain their own .gitignore files, because Yeoman has strange default handling for
|
|
||||||
# files with that name (https://github.com/npm/npm/issues/1862). So, each template instead has a template_gitignore
|
|
||||||
# file which gets renamed after the files are copied. And so any files that need to be excluded in the source
|
|
||||||
# repo have to be excluded here.
|
|
||||||
/templates/*/node_modules/
|
|
||||||
/templates/*/wwwroot/dist/
|
|
||||||
/templates/*/ClientApp/dist/
|
|
||||||
/templates/*/yarn.lock
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
global.json
|
||||||
/templates/*/Properties/launchSettings.json
|
|
||||||
|
|||||||
11
.travis.yml
11
.travis.yml
@@ -12,8 +12,13 @@ addons:
|
|||||||
- zlib1g
|
- zlib1g
|
||||||
mono: none
|
mono: none
|
||||||
os:
|
os:
|
||||||
- linux
|
- linux
|
||||||
- osx
|
- osx
|
||||||
osx_image: xcode7.1
|
osx_image: xcode7.1
|
||||||
script:
|
script:
|
||||||
- ./build.sh
|
- ./build.sh
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- dev
|
||||||
|
- /^release\/.*$/
|
||||||
|
- /^(.*\/)?ci-.*$/
|
||||||
|
|||||||
13
.vsts-pipelines/builds/ci-internal.yml
Normal file
13
.vsts-pipelines/builds/ci-internal.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
trigger:
|
||||||
|
- master
|
||||||
|
- release/*
|
||||||
|
|
||||||
|
resources:
|
||||||
|
repositories:
|
||||||
|
- repository: buildtools
|
||||||
|
type: git
|
||||||
|
name: aspnet-BuildTools
|
||||||
|
ref: refs/heads/release/2.2
|
||||||
|
|
||||||
|
phases:
|
||||||
|
- template: .vsts-pipelines/templates/project-ci.yml@buildtools
|
||||||
15
.vsts-pipelines/builds/ci-public.yml
Normal file
15
.vsts-pipelines/builds/ci-public.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
trigger:
|
||||||
|
- master
|
||||||
|
- release/*
|
||||||
|
|
||||||
|
# See https://github.com/aspnet/BuildTools
|
||||||
|
resources:
|
||||||
|
repositories:
|
||||||
|
- repository: buildtools
|
||||||
|
type: github
|
||||||
|
endpoint: DotNet-Bot GitHub Connection
|
||||||
|
name: aspnet/BuildTools
|
||||||
|
ref: refs/heads/release/2.2
|
||||||
|
|
||||||
|
phases:
|
||||||
|
- template: .vsts-pipelines/templates/project-ci.yml@buildtools
|
||||||
15
Directory.Build.props
Normal file
15
Directory.Build.props
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<Project>
|
||||||
|
<Import Project="version.props" />
|
||||||
|
<Import Project="build\dependencies.props" />
|
||||||
|
<Import Project="build\sources.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Product>Microsoft ASP.NET Core</Product>
|
||||||
|
<RepositoryUrl>https://github.com/aspnet/javascriptservices</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||||
|
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)build\Key.snk</AssemblyOriginatorKeyFile>
|
||||||
|
<SignAssembly>true</SignAssembly>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
10
Directory.Build.targets
Normal file
10
Directory.Build.targets
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
|
||||||
|
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">$(MicrosoftNETCoreApp21PackageVersion)</RuntimeFrameworkVersion>
|
||||||
|
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.2' ">$(MicrosoftNETCoreApp22PackageVersion)</RuntimeFrameworkVersion>
|
||||||
|
<NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
|
||||||
|
<!-- aspnet/BuildTools#662 Don't police what version of NetCoreApp we use -->
|
||||||
|
<NETCoreAppMaximumVersion>99.9</NETCoreAppMaximumVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.26430.4
|
VisualStudioVersion = 15.0.26730.16
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 15.0.26730.03
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{27304DDE-AFB2-4F8B-B765-E3E2F11E886C}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{27304DDE-AFB2-4F8B-B765-E3E2F11E886C}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
src\Directory.Build.props = src\Directory.Build.props
|
||||||
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.NodeServices", "src\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj", "{66B77203-1469-41DF-92F2-2BE6900BD36F}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.NodeServices", "src\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj", "{66B77203-1469-41DF-92F2-2BE6900BD36F}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -28,14 +31,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Webpack", "samples\misc\Web
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NodeServicesExamples", "samples\misc\NodeServicesExamples\NodeServicesExamples.csproj", "{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NodeServicesExamples", "samples\misc\NodeServicesExamples\NodeServicesExamples.csproj", "{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{1598B415-73F1-4B37-B3B4-0A10677ABB2D}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{645F7363-1240-4FB6-9422-B32A327C979F}"
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E415FE14-13B0-469F-836D-95059E6BAA6E}"
|
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
src\build\common.props = src\build\common.props
|
Directory.Build.props = Directory.Build.props
|
||||||
src\build\Key.snk = src\build\Key.snk
|
Directory.Build.targets = Directory.Build.targets
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SpaServices.Extensions", "src\Microsoft.AspNetCore.SpaServices.Extensions\Microsoft.AspNetCore.SpaServices.Extensions.csproj", "{D40BD1C4-6A6F-4213-8535-1057F3EB3400}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -66,6 +69,10 @@ Global
|
|||||||
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}.Release|Any CPU.Build.0 = Release|Any CPU
|
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D40BD1C4-6A6F-4213-8535-1057F3EB3400}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D40BD1C4-6A6F-4213-8535-1057F3EB3400}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D40BD1C4-6A6F-4213-8535-1057F3EB3400}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D40BD1C4-6A6F-4213-8535-1057F3EB3400}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -78,5 +85,9 @@ Global
|
|||||||
{1931B19A-EC42-4D56-B2D0-FB06D17244DA} = {E6A161EA-646C-4033-9090-95BE809AB8D9}
|
{1931B19A-EC42-4D56-B2D0-FB06D17244DA} = {E6A161EA-646C-4033-9090-95BE809AB8D9}
|
||||||
{DE479DC3-1461-4EAD-A188-4AF7AA4AE344} = {E6A161EA-646C-4033-9090-95BE809AB8D9}
|
{DE479DC3-1461-4EAD-A188-4AF7AA4AE344} = {E6A161EA-646C-4033-9090-95BE809AB8D9}
|
||||||
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE} = {E6A161EA-646C-4033-9090-95BE809AB8D9}
|
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE} = {E6A161EA-646C-4033-9090-95BE809AB8D9}
|
||||||
|
{D40BD1C4-6A6F-4213-8535-1057F3EB3400} = {27304DDE-AFB2-4F8B-B765-E3E2F11E886C}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {DDF59B0D-2DEC-45D6-8667-DCB767487101}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
Copyright (c) .NET Foundation. All rights reserved.
|
Copyright (c) .NET Foundation and Contributors
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
these files except in compliance with the License. You may obtain a copy of the
|
this file except in compliance with the License. You may obtain a copy of the
|
||||||
License at
|
License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software distributed
|
Unless required by applicable law or agreed to in writing, software distributed
|
||||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<packageSources>
|
<packageSources>
|
||||||
<add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json" />
|
<clear />
|
||||||
<add key="AspNetCoreTools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" />
|
<!-- Restore sources should be defined in build/sources.props. -->
|
||||||
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
|
|
||||||
</packageSources>
|
</packageSources>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
7
NuGetPackageVerifier.json
Normal file
7
NuGetPackageVerifier.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"Default": {
|
||||||
|
"rules": [
|
||||||
|
"DefaultCompositeRule"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
48
README.md
48
README.md
@@ -24,29 +24,37 @@ This repo contains:
|
|||||||
* Server-side and client-side routing integration ([docs](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#routing-helper-mapspafallbackroute))
|
* Server-side and client-side routing integration ([docs](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#routing-helper-mapspafallbackroute))
|
||||||
* Server-side and client-side validation integration
|
* Server-side and client-side validation integration
|
||||||
* "Lazy loading" for Knockout apps
|
* "Lazy loading" for Knockout apps
|
||||||
* A Yeoman generator that creates preconfigured app starting points ([guide](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/))
|
|
||||||
* Samples and docs
|
* Samples and docs
|
||||||
|
|
||||||
It's cross-platform (Windows, Linux, or macOS) and works with .NET Core 1.0.1 or later.
|
It's cross-platform (Windows, Linux, or macOS) and works with .NET Core 2.0 or later.
|
||||||
|
|
||||||
## Creating new applications
|
## Creating new applications
|
||||||
|
|
||||||
If you want to build a brand-new ASP.NET Core app that uses Angular / React / Knockout on the client, consider starting with the `aspnetcore-spa` generator. This lets you choose your client-side framework. It generates a starting point that includes applicable features such as Webpack dev middleware, server-side prerendering, and efficient production builds. It's much easier than configuring everything to work together manually!
|
Prerequisites:
|
||||||
|
|
||||||
To do this, install Yeoman and these generator templates:
|
* [.NET Core 2.0](https://www.microsoft.com/net/core) (or later) SDK
|
||||||
|
* [Node.js](https://nodejs.org/) version 6 (or later)
|
||||||
|
|
||||||
npm install -g yo generator-aspnetcore-spa
|
With these prerequisites, you can immediately create new ASP.NET Core applications that use Angular, React, or React+Redux without having to install anything extra.
|
||||||
|
|
||||||
Generate your new application starting point:
|
### Option 1: Creating Angular/React/Redux applications from the command line (cross-platform)
|
||||||
|
|
||||||
cd some-empty-directory
|
In an empty directory, run (for example) `dotnet new angular`. Other supported SPA frameworks include React and React+Redux. You can see the list of available SPA templates by running `dotnet new spa`.
|
||||||
yo aspnetcore-spa
|
|
||||||
|
|
||||||
Once the generator has run and restored all the dependencies, you can start up your new ASP.NET Core SPA:
|
Once the generator has run and restored all the dependencies, you can start up your new ASP.NET Core SPA:
|
||||||
|
|
||||||
|
npm install
|
||||||
dotnet run
|
dotnet run
|
||||||
|
|
||||||
For a more detailed walkthrough, see [getting started with the `aspnetcore-spa` generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/).
|
### Option 2: Creating Angular/React/Redux applications using Visual Studio 2017 Update 3 or later (Windows only)
|
||||||
|
|
||||||
|
Using the `File`->`New Project` dialog, select *ASP.NET Core Web Application*. You will then be offered the option to create an application with Angular, React, or React+Redux. When the application is created, you can build and run it in the normal way.
|
||||||
|
|
||||||
|
### More info and other SPA frameworks
|
||||||
|
|
||||||
|
For a more detailed (albeit somewhat outdated) walkthrough, see [getting started with the `aspnetcore-spa` generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/).
|
||||||
|
|
||||||
|
If you want to build an ASP.NET Core application with Aurelia, Knockout, or Vue, you can use the `Microsoft.AspNetCore.SpaTemplates` package. On the command line, run `dotnet new --install Microsoft.AspNetCore.SpaTemplates`. Then you will be able to run `dotnet new aurelia` (or `dotnet new vue`, etc.) to create your new application.
|
||||||
|
|
||||||
## Adding to existing applications
|
## Adding to existing applications
|
||||||
|
|
||||||
@@ -58,18 +66,13 @@ If you have an existing ASP.NET Core application, or if you just want to use the
|
|||||||
* Find [documentation and usage examples here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.NodeServices#microsoftaspnetcorenodeservices).
|
* Find [documentation and usage examples here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.NodeServices#microsoftaspnetcorenodeservices).
|
||||||
* `Microsoft.AspNetCore.SpaServices`
|
* `Microsoft.AspNetCore.SpaServices`
|
||||||
* This provides infrastructure that's generally useful when building Single Page Applications (SPAs) with technologies such as Angular or React (for example, server-side prerendering and webpack middleware). Internally, it uses the `NodeServices` package to implement its features.
|
* This provides infrastructure that's generally useful when building Single Page Applications (SPAs) with technologies such as Angular or React (for example, server-side prerendering and webpack middleware). Internally, it uses the `NodeServices` package to implement its features.
|
||||||
* Find [documentation and usage examples here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#microsoftaspnetcorespaservices).
|
* Find [documentation and usage examples here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#microsoftaspnetcorespaservices)
|
||||||
* `Microsoft.AspNetCore.AngularServices`
|
|
||||||
* This builds on the `SpaServices` package and includes features specific to Angular. Currently, this includes validation helpers.
|
|
||||||
* The code is [here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.AngularServices). You'll find a usage example for [the validation helper here](https://github.com/aspnet/JavaScriptServices/blob/dev/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.ts).
|
|
||||||
|
|
||||||
There was previously a `Microsoft.AspNetCore.ReactServices` but this is not currently needed - all applicable functionality is in `Microsoft.AspNetCore.SpaServices`, because it's sufficiently general. We might add a new `Microsoft.AspNetCore.ReactServices` package in the future if new React-specific requirements emerge.
|
There were previously other packages called `Microsoft.AspNetCore.AngularServices` and `Microsoft.AspNetCore.ReactServices` but these are not currently needed - all applicable functionality is in `Microsoft.AspNetCore.SpaServices`, because it's sufficiently general.
|
||||||
|
|
||||||
If you want to build a helper library for some other SPA framework, you can do so by taking a dependency on `Microsoft.AspNetCore.SpaServices` and wrapping its functionality in whatever way is most useful for your SPA framework.
|
If you want to build a helper library for some other SPA framework, you can do so by taking a dependency on `Microsoft.AspNetCore.SpaServices` and wrapping its functionality in whatever way is most useful for your SPA framework.
|
||||||
|
|
||||||
## Samples and templates
|
## Samples
|
||||||
|
|
||||||
Inside this repo, [the `templates` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/templates) contains the application starting points that the `aspnetcore-spa` generator emits. You can clone this repo and run those applications directly. But it's easier to [use the Yeoman tool to run the generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/).
|
|
||||||
|
|
||||||
The [`samples` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/samples) contains examples of:
|
The [`samples` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/samples) contains examples of:
|
||||||
|
|
||||||
@@ -88,13 +91,6 @@ The [`samples` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
If you're interested in contributing to the various packages, samples, and project templates in this repo, that's great! You can run the code in this repo as follows:
|
If you're interested in contributing to the various packages, samples, and project templates in this repo, that's great!
|
||||||
|
|
||||||
* Clone the repo
|
Before working on a pull request, especially if it's more than a trivial fix (for example, for a typo), it's usually a good idea first to file an issue describing what you're proposing to do and how it will work. Then you can find out if it's likely that such a pull request will be accepted, and how it fits into wider ongoing plans.
|
||||||
* Run `dotnet restore` at the repo root dir
|
|
||||||
* Go to whatever sample or template you want to run (for example, `cd templates/AngularSpa`)
|
|
||||||
* Restore NPM dependencies (run `npm install`)
|
|
||||||
* If the sample/template you're trying to run has a file called `webpack.config.vendor.js` at its root, run `webpack --config webpack.config.vendor.js`. It it has a file called `webpack.config.js`, run `webpack` (no args). You might need to install webpack first, by running `npm install -g webpack`.
|
|
||||||
* Launch it (`dotnet run`)
|
|
||||||
|
|
||||||
If you're planning to submit a pull request, and if it's more than a trivial fix (for example, for a typo), it's usually a good idea first to file an issue describing what you're proposing to do and how it will work. Then you can find out if it's likely that such a pull request will be accepted, and how it fits into wider ongoing plans.
|
|
||||||
|
|||||||
40
appveyor.yml
40
appveyor.yml
@@ -1,40 +0,0 @@
|
|||||||
init:
|
|
||||||
- git config --global core.autocrlf true
|
|
||||||
install:
|
|
||||||
- ps: Install-Product node 6.9.2 x64
|
|
||||||
# .NET Core SDK binaries
|
|
||||||
# Download .NET Core 2.0 Preview 1 SDK and add to PATH
|
|
||||||
- ps: $urlCurrent = "https://download.microsoft.com/download/0/6/5/0656B047-5F2F-4281-A851-F30776F8616D/dotnet-dev-win-x64.2.0.0-preview1-005977.zip"
|
|
||||||
- ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetsdk"
|
|
||||||
- ps: mkdir $env:DOTNET_INSTALL_DIR -Force | Out-Null
|
|
||||||
- ps: $tempFileCurrent = [System.IO.Path]::GetTempFileName()
|
|
||||||
- ps: (New-Object System.Net.WebClient).DownloadFile($urlCurrent, $tempFileCurrent)
|
|
||||||
- ps: Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFileCurrent, $env:DOTNET_INSTALL_DIR)
|
|
||||||
- ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path"
|
|
||||||
build_script:
|
|
||||||
- ps: Push-Location
|
|
||||||
- cd templates/package-builder
|
|
||||||
- npm install
|
|
||||||
- npm run build
|
|
||||||
- ps: Pop-Location
|
|
||||||
artifacts:
|
|
||||||
- path: templates\package-builder\dist\artifacts\generator-aspnetcore-spa.tar.gz
|
|
||||||
name: generator-aspnetcore-spa
|
|
||||||
- path: templates\package-builder\dist\artifacts\*.nupkg
|
|
||||||
name: Microsoft.AspNetCore.SpaTemplates
|
|
||||||
type: NuGetPackage
|
|
||||||
# - ps: .\build.ps1
|
|
||||||
clone_depth: 1
|
|
||||||
test_script:
|
|
||||||
- dotnet restore
|
|
||||||
- ps: Push-Location
|
|
||||||
- cd test
|
|
||||||
- npm install selenium-standalone
|
|
||||||
- ps: Start-Process node './start-selenium.js'
|
|
||||||
- npm install
|
|
||||||
- npm test
|
|
||||||
on_finish :
|
|
||||||
- ps: Pop-Location
|
|
||||||
# After running tests, upload results to Appveyor
|
|
||||||
- ps: (new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\test\tmp\junit\*.xml))
|
|
||||||
deploy: off
|
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
@ECHO OFF
|
@ECHO OFF
|
||||||
PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE"
|
PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE"
|
||||||
|
|||||||
67
build.ps1
67
build.ps1
@@ -1,67 +0,0 @@
|
|||||||
$ErrorActionPreference = "Stop"
|
|
||||||
|
|
||||||
function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $retries)
|
|
||||||
{
|
|
||||||
while($true)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Invoke-WebRequest $url -OutFile $downloadLocation
|
|
||||||
break
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
$exceptionMessage = $_.Exception.Message
|
|
||||||
Write-Host "Failed to download '$url': $exceptionMessage"
|
|
||||||
if ($retries -gt 0) {
|
|
||||||
$retries--
|
|
||||||
Write-Host "Waiting 10 seconds before retrying. Retries left: $retries"
|
|
||||||
Start-Sleep -Seconds 10
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$exception = $_.Exception
|
|
||||||
throw $exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cd $PSScriptRoot
|
|
||||||
|
|
||||||
$repoFolder = $PSScriptRoot
|
|
||||||
$env:REPO_FOLDER = $repoFolder
|
|
||||||
|
|
||||||
$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip"
|
|
||||||
if ($env:KOREBUILD_ZIP)
|
|
||||||
{
|
|
||||||
$koreBuildZip=$env:KOREBUILD_ZIP
|
|
||||||
}
|
|
||||||
|
|
||||||
$buildFolder = ".build"
|
|
||||||
$buildFile="$buildFolder\KoreBuild.ps1"
|
|
||||||
|
|
||||||
if (!(Test-Path $buildFolder)) {
|
|
||||||
Write-Host "Downloading KoreBuild from $koreBuildZip"
|
|
||||||
|
|
||||||
$tempFolder=$env:TEMP + "\KoreBuild-" + [guid]::NewGuid()
|
|
||||||
New-Item -Path "$tempFolder" -Type directory | Out-Null
|
|
||||||
|
|
||||||
$localZipFile="$tempFolder\korebuild.zip"
|
|
||||||
|
|
||||||
DownloadWithRetry -url $koreBuildZip -downloadLocation $localZipFile -retries 6
|
|
||||||
|
|
||||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
||||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($localZipFile, $tempFolder)
|
|
||||||
|
|
||||||
New-Item -Path "$buildFolder" -Type directory | Out-Null
|
|
||||||
copy-item "$tempFolder\**\build\*" $buildFolder -Recurse
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
if (Test-Path $tempFolder) {
|
|
||||||
Remove-Item -Recurse -Force $tempFolder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&"$buildFile" @args
|
|
||||||
48
build.sh
48
build.sh
@@ -1,46 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
repoFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
cd $repoFolder
|
|
||||||
|
|
||||||
koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip"
|
set -euo pipefail
|
||||||
if [ ! -z $KOREBUILD_ZIP ]; then
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
koreBuildZip=$KOREBUILD_ZIP
|
|
||||||
fi
|
|
||||||
|
|
||||||
buildFolder=".build"
|
# Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs)
|
||||||
buildFile="$buildFolder/KoreBuild.sh"
|
chmod +x "$DIR/run.sh"; sync
|
||||||
|
"$DIR/run.sh" default-build "$@"
|
||||||
if test ! -d $buildFolder; then
|
|
||||||
echo "Downloading KoreBuild from $koreBuildZip"
|
|
||||||
|
|
||||||
tempFolder="/tmp/KoreBuild-$(uuidgen)"
|
|
||||||
mkdir $tempFolder
|
|
||||||
|
|
||||||
localZipFile="$tempFolder/korebuild.zip"
|
|
||||||
|
|
||||||
retries=6
|
|
||||||
until (wget -O $localZipFile $koreBuildZip 2>/dev/null || curl -o $localZipFile --location $koreBuildZip 2>/dev/null)
|
|
||||||
do
|
|
||||||
echo "Failed to download '$koreBuildZip'"
|
|
||||||
if [ "$retries" -le 0 ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
retries=$((retries - 1))
|
|
||||||
echo "Waiting 10 seconds before retrying. Retries left: $retries"
|
|
||||||
sleep 10s
|
|
||||||
done
|
|
||||||
|
|
||||||
unzip -q -d $tempFolder $localZipFile
|
|
||||||
|
|
||||||
mkdir $buildFolder
|
|
||||||
cp -r $tempFolder/**/build/** $buildFolder
|
|
||||||
|
|
||||||
chmod +x $buildFile
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
if test -d $tempFolder; then
|
|
||||||
rm -rf $tempFolder
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
$buildFile -r $repoFolder "$@"
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<Project>
|
|
||||||
<Import Project="dependencies.props" />
|
|
||||||
<Import Project="..\version.props" />
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<Product>Microsoft ASP.NET Core</Product>
|
|
||||||
<RepositoryUrl>https://github.com/aspnet/javascriptservices</RepositoryUrl>
|
|
||||||
<RepositoryType>git</RepositoryType>
|
|
||||||
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)Key.snk</AssemblyOriginatorKeyFile>
|
|
||||||
<SignAssembly>true</SignAssembly>
|
|
||||||
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
|
|
||||||
<VersionSuffix Condition="'$(VersionSuffix)'!='' AND '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Internal.AspNetCore.Sdk" Version="$(InternalAspNetCoreSdkVersion)" PrivateAssets="All" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFrameworkIdentifier)'=='.NETFramework' AND '$(OutputType)'=='library'">
|
|
||||||
<PackageReference Include="NETStandard.Library" Version="$(BundledNETStandardPackageVersion)" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,11 +1,31 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AspNetCoreVersion>2.0.0-*</AspNetCoreVersion>
|
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||||
<AutoMapperVersion>5.0.2</AutoMapperVersion>
|
|
||||||
<CoreFxVersion>4.3.0</CoreFxVersion>
|
|
||||||
<InternalAspNetCoreSdkVersion>2.1.0-*</InternalAspNetCoreSdkVersion>
|
|
||||||
<JsonNetVersion>10.0.1</JsonNetVersion>
|
|
||||||
<NETStandardImplicitPackageVersion>$(BundledNETStandardPackageVersion)</NETStandardImplicitPackageVersion>
|
|
||||||
<ThreadingDataflowVersion>4.7.0</ThreadingDataflowVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Label="Package Versions">
|
||||||
|
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview2-20181004.6</InternalAspNetCoreSdkPackageVersion>
|
||||||
|
<MicrosoftAspNetCoreDiagnosticsPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreDiagnosticsPackageVersion>
|
||||||
|
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
|
||||||
|
<MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreHostingPackageVersion>
|
||||||
|
<MicrosoftAspNetCoreMvcPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreMvcPackageVersion>
|
||||||
|
<MicrosoftAspNetCoreMvcTagHelpersPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreMvcTagHelpersPackageVersion>
|
||||||
|
<MicrosoftAspNetCoreMvcViewFeaturesPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreMvcViewFeaturesPackageVersion>
|
||||||
|
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||||
|
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||||
|
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
||||||
|
<MicrosoftAspNetCoreWebSocketsPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreWebSocketsPackageVersion>
|
||||||
|
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
||||||
|
<MicrosoftExtensionsFileProvidersPhysicalPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsFileProvidersPhysicalPackageVersion>
|
||||||
|
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsLoggingConsolePackageVersion>
|
||||||
|
<MicrosoftExtensionsLoggingDebugPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsLoggingDebugPackageVersion>
|
||||||
|
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
|
||||||
|
<MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
|
||||||
|
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview3-27001-02</MicrosoftNETCoreApp22PackageVersion>
|
||||||
|
<MicrosoftNETSdkRazorPackageVersion>2.2.0-preview3-35425</MicrosoftNETSdkRazorPackageVersion>
|
||||||
|
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||||
|
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
|
||||||
|
<SystemThreadingTasksDataflowPackageVersion>4.9.0</SystemThreadingTasksDataflowPackageVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
|
||||||
|
<PropertyGroup Label="Package Versions: Pinned" />
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
16
build/repo.props
Normal file
16
build/repo.props
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<Project>
|
||||||
|
<Import Project="dependencies.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- These properties are use by the automation that updates dependencies.props -->
|
||||||
|
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
|
||||||
|
<LineupPackageVersion>2.2.0-*</LineupPackageVersion>
|
||||||
|
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp20PackageVersion)" />
|
||||||
|
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
|
||||||
|
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp22PackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -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" WorkingDirectory="%(NpmModules.RootDir)%(Directory)" />
|
|
||||||
</Target>
|
|
||||||
</Project>
|
|
||||||
17
build/sources.props
Normal file
17
build/sources.props
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<Project>
|
||||||
|
<Import Project="$(DotNetRestoreSourcePropsPath)" Condition="'$(DotNetRestoreSourcePropsPath)' != ''"/>
|
||||||
|
|
||||||
|
<PropertyGroup Label="RestoreSources">
|
||||||
|
<RestoreSources>$(DotNetRestoreSources)</RestoreSources>
|
||||||
|
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true' AND '$(AspNetUniverseBuildOffline)' != 'true' ">
|
||||||
|
$(RestoreSources);
|
||||||
|
https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
|
||||||
|
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
|
||||||
|
https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
|
||||||
|
</RestoreSources>
|
||||||
|
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true'">
|
||||||
|
$(RestoreSources);
|
||||||
|
https://api.nuget.org/v3/index.json;
|
||||||
|
</RestoreSources>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
2
korebuild-lock.txt
Normal file
2
korebuild-lock.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
version:2.2.0-preview2-20181004.6
|
||||||
|
commithash:c04c4b2f5018632647f96210ab01876661302dac
|
||||||
10
korebuild.json
Normal file
10
korebuild.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.2/tools/korebuild.schema.json",
|
||||||
|
"channel": "release/2.2",
|
||||||
|
"toolsets": {
|
||||||
|
"nodejs": {
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "6.9"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
run.cmd
Normal file
2
run.cmd
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE"
|
||||||
209
run.ps1
Normal file
209
run.ps1
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
#!/usr/bin/env powershell
|
||||||
|
#requires -version 4
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Executes KoreBuild commands.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`.
|
||||||
|
|
||||||
|
.PARAMETER Command
|
||||||
|
The KoreBuild command to run.
|
||||||
|
|
||||||
|
.PARAMETER Path
|
||||||
|
The folder to build. Defaults to the folder containing this script.
|
||||||
|
|
||||||
|
.PARAMETER Channel
|
||||||
|
The channel of KoreBuild to download. Overrides the value from the config file.
|
||||||
|
|
||||||
|
.PARAMETER DotNetHome
|
||||||
|
The directory where .NET Core tools will be stored.
|
||||||
|
|
||||||
|
.PARAMETER ToolsSource
|
||||||
|
The base url where build tools can be downloaded. Overrides the value from the config file.
|
||||||
|
|
||||||
|
.PARAMETER Update
|
||||||
|
Updates KoreBuild to the latest version even if a lock file is present.
|
||||||
|
|
||||||
|
.PARAMETER Reinstall
|
||||||
|
Re-installs KoreBuild
|
||||||
|
|
||||||
|
.PARAMETER ConfigFile
|
||||||
|
The path to the configuration file that stores values. Defaults to korebuild.json.
|
||||||
|
|
||||||
|
.PARAMETER ToolsSourceSuffix
|
||||||
|
The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores.
|
||||||
|
|
||||||
|
.PARAMETER CI
|
||||||
|
Sets up CI specific settings and variables.
|
||||||
|
|
||||||
|
.PARAMETER Arguments
|
||||||
|
Arguments to be passed to the command
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be.
|
||||||
|
When the lockfile is not present, KoreBuild will create one using latest available version from $Channel.
|
||||||
|
|
||||||
|
The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set
|
||||||
|
in the file are overridden by command line parameters.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Example config file:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json",
|
||||||
|
"channel": "master",
|
||||||
|
"toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#>
|
||||||
|
[CmdletBinding(PositionalBinding = $false)]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true, Position = 0)]
|
||||||
|
[string]$Command,
|
||||||
|
[string]$Path = $PSScriptRoot,
|
||||||
|
[Alias('c')]
|
||||||
|
[string]$Channel,
|
||||||
|
[Alias('d')]
|
||||||
|
[string]$DotNetHome,
|
||||||
|
[Alias('s')]
|
||||||
|
[string]$ToolsSource,
|
||||||
|
[Alias('u')]
|
||||||
|
[switch]$Update,
|
||||||
|
[switch]$Reinstall,
|
||||||
|
[string]$ToolsSourceSuffix,
|
||||||
|
[string]$ConfigFile = $null,
|
||||||
|
[switch]$CI,
|
||||||
|
[Parameter(ValueFromRemainingArguments = $true)]
|
||||||
|
[string[]]$Arguments
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version 2
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
#
|
||||||
|
# Functions
|
||||||
|
#
|
||||||
|
|
||||||
|
function Get-KoreBuild {
|
||||||
|
|
||||||
|
$lockFile = Join-Path $Path 'korebuild-lock.txt'
|
||||||
|
|
||||||
|
if (!(Test-Path $lockFile) -or $Update) {
|
||||||
|
Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1
|
||||||
|
if (!$version) {
|
||||||
|
Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'"
|
||||||
|
}
|
||||||
|
$version = $version.TrimStart('version:').Trim()
|
||||||
|
$korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version)
|
||||||
|
|
||||||
|
if ($Reinstall -and (Test-Path $korebuildPath)) {
|
||||||
|
Remove-Item -Force -Recurse $korebuildPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Test-Path $korebuildPath)) {
|
||||||
|
Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version"
|
||||||
|
New-Item -ItemType Directory -Path $korebuildPath | Out-Null
|
||||||
|
$remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip"
|
||||||
|
|
||||||
|
try {
|
||||||
|
$tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip"
|
||||||
|
Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix
|
||||||
|
if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) {
|
||||||
|
# Use built-in commands where possible as they are cross-plat compatible
|
||||||
|
Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# Fallback to old approach for old installations of PowerShell
|
||||||
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||||
|
[System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Remove-Item $tmpfile -ErrorAction Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $korebuildPath
|
||||||
|
}
|
||||||
|
|
||||||
|
function Join-Paths([string]$path, [string[]]$childPaths) {
|
||||||
|
$childPaths | ForEach-Object { $path = Join-Path $path $_ }
|
||||||
|
return $path
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) {
|
||||||
|
if ($RemotePath -notlike 'http*') {
|
||||||
|
Copy-Item $RemotePath $LocalPath
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$retries = 10
|
||||||
|
while ($retries -gt 0) {
|
||||||
|
$retries -= 1
|
||||||
|
try {
|
||||||
|
Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Verbose "Request failed. $retries retries remaining"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Error "Download failed: '$RemotePath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main
|
||||||
|
#
|
||||||
|
|
||||||
|
# Load configuration or set defaults
|
||||||
|
|
||||||
|
$Path = Resolve-Path $Path
|
||||||
|
if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' }
|
||||||
|
|
||||||
|
if (Test-Path $ConfigFile) {
|
||||||
|
try {
|
||||||
|
$config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json
|
||||||
|
if ($config) {
|
||||||
|
if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel }
|
||||||
|
if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host -ForegroundColor Red $Error[0]
|
||||||
|
Write-Error "$ConfigFile contains invalid JSON."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$DotNetHome) {
|
||||||
|
$DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } `
|
||||||
|
elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} `
|
||||||
|
elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}`
|
||||||
|
else { Join-Path $PSScriptRoot '.dotnet'}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$Channel) { $Channel = 'master' }
|
||||||
|
if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' }
|
||||||
|
|
||||||
|
# Execute
|
||||||
|
|
||||||
|
$korebuildPath = Get-KoreBuild
|
||||||
|
Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1')
|
||||||
|
|
||||||
|
try {
|
||||||
|
Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI
|
||||||
|
Invoke-KoreBuildCommand $Command @Arguments
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Remove-Module 'KoreBuild' -ErrorAction Ignore
|
||||||
|
}
|
||||||
256
run.sh
Executable file
256
run.sh
Executable file
@@ -0,0 +1,256 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
#
|
||||||
|
# variables
|
||||||
|
#
|
||||||
|
|
||||||
|
RESET="\033[0m"
|
||||||
|
RED="\033[0;31m"
|
||||||
|
YELLOW="\033[0;33m"
|
||||||
|
MAGENTA="\033[0;95m"
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
[ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet"
|
||||||
|
verbose=false
|
||||||
|
update=false
|
||||||
|
reinstall=false
|
||||||
|
repo_path="$DIR"
|
||||||
|
channel=''
|
||||||
|
tools_source=''
|
||||||
|
tools_source_suffix=''
|
||||||
|
ci=false
|
||||||
|
|
||||||
|
#
|
||||||
|
# Functions
|
||||||
|
#
|
||||||
|
__usage() {
|
||||||
|
echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] <Arguments>...]"
|
||||||
|
echo ""
|
||||||
|
echo "Arguments:"
|
||||||
|
echo " command The command to be run."
|
||||||
|
echo " <Arguments>... Arguments passed to the command. Variable number of arguments allowed."
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --verbose Show verbose output."
|
||||||
|
echo " -c|--channel <CHANNEL> The channel of KoreBuild to download. Overrides the value from the config file.."
|
||||||
|
echo " --config-file <FILE> The path to the configuration file that stores values. Defaults to korebuild.json."
|
||||||
|
echo " -d|--dotnet-home <DIR> The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet."
|
||||||
|
echo " --path <PATH> The directory to build. Defaults to the directory containing the script."
|
||||||
|
echo " -s|--tools-source|-ToolsSource <URL> The base url where build tools can be downloaded. Overrides the value from the config file."
|
||||||
|
echo " --tools-source-suffix|-ToolsSourceSuffix <SUFFIX> The suffix to append to tools-source. Useful for query strings."
|
||||||
|
echo " -u|--update Update to the latest KoreBuild even if the lock file is present."
|
||||||
|
echo " --reinstall Reinstall KoreBuild."
|
||||||
|
echo " --ci Apply CI specific settings and environment variables."
|
||||||
|
echo ""
|
||||||
|
echo "Description:"
|
||||||
|
echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be."
|
||||||
|
echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel."
|
||||||
|
|
||||||
|
if [[ "${1:-}" != '--no-exit' ]]; then
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_korebuild() {
|
||||||
|
local version
|
||||||
|
local lock_file="$repo_path/korebuild-lock.txt"
|
||||||
|
if [ ! -f "$lock_file" ] || [ "$update" = true ]; then
|
||||||
|
__get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix"
|
||||||
|
fi
|
||||||
|
version="$(grep 'version:*' -m 1 "$lock_file")"
|
||||||
|
if [[ "$version" == '' ]]; then
|
||||||
|
__error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
||||||
|
local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version"
|
||||||
|
|
||||||
|
if [ "$reinstall" = true ] && [ -d "$korebuild_path" ]; then
|
||||||
|
rm -rf "$korebuild_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
if [ ! -d "$korebuild_path" ]; then
|
||||||
|
mkdir -p "$korebuild_path"
|
||||||
|
local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip"
|
||||||
|
tmpfile="$(mktemp)"
|
||||||
|
echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}"
|
||||||
|
if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then
|
||||||
|
unzip -q -d "$korebuild_path" "$tmpfile"
|
||||||
|
fi
|
||||||
|
rm "$tmpfile" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
source "$korebuild_path/KoreBuild.sh"
|
||||||
|
} || {
|
||||||
|
if [ -d "$korebuild_path" ]; then
|
||||||
|
echo "Cleaning up after failed installation"
|
||||||
|
rm -rf "$korebuild_path" || true
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__error() {
|
||||||
|
echo -e "${RED}error: $*${RESET}" 1>&2
|
||||||
|
}
|
||||||
|
|
||||||
|
__warn() {
|
||||||
|
echo -e "${YELLOW}warning: $*${RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
__machine_has() {
|
||||||
|
hash "$1" > /dev/null 2>&1
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
__get_remote_file() {
|
||||||
|
local remote_path=$1
|
||||||
|
local local_path=$2
|
||||||
|
local remote_path_suffix=$3
|
||||||
|
|
||||||
|
if [[ "$remote_path" != 'http'* ]]; then
|
||||||
|
cp "$remote_path" "$local_path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local failed=false
|
||||||
|
if __machine_has wget; then
|
||||||
|
wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true
|
||||||
|
else
|
||||||
|
failed=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$failed" = true ] && __machine_has curl; then
|
||||||
|
failed=false
|
||||||
|
curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$failed" = true ]; then
|
||||||
|
__error "Download failed: $remote_path" 1>&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# main
|
||||||
|
#
|
||||||
|
|
||||||
|
command="${1:-}"
|
||||||
|
shift
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-\?|-h|--help)
|
||||||
|
__usage --no-exit
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-c|--channel|-Channel)
|
||||||
|
shift
|
||||||
|
channel="${1:-}"
|
||||||
|
[ -z "$channel" ] && __usage
|
||||||
|
;;
|
||||||
|
--config-file|-ConfigFile)
|
||||||
|
shift
|
||||||
|
config_file="${1:-}"
|
||||||
|
[ -z "$config_file" ] && __usage
|
||||||
|
if [ ! -f "$config_file" ]; then
|
||||||
|
__error "Invalid value for --config-file. $config_file does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
-d|--dotnet-home|-DotNetHome)
|
||||||
|
shift
|
||||||
|
DOTNET_HOME="${1:-}"
|
||||||
|
[ -z "$DOTNET_HOME" ] && __usage
|
||||||
|
;;
|
||||||
|
--path|-Path)
|
||||||
|
shift
|
||||||
|
repo_path="${1:-}"
|
||||||
|
[ -z "$repo_path" ] && __usage
|
||||||
|
;;
|
||||||
|
-s|--tools-source|-ToolsSource)
|
||||||
|
shift
|
||||||
|
tools_source="${1:-}"
|
||||||
|
[ -z "$tools_source" ] && __usage
|
||||||
|
;;
|
||||||
|
--tools-source-suffix|-ToolsSourceSuffix)
|
||||||
|
shift
|
||||||
|
tools_source_suffix="${1:-}"
|
||||||
|
[ -z "$tools_source_suffix" ] && __usage
|
||||||
|
;;
|
||||||
|
-u|--update|-Update)
|
||||||
|
update=true
|
||||||
|
;;
|
||||||
|
--reinstall|-[Rr]einstall)
|
||||||
|
reinstall=true
|
||||||
|
;;
|
||||||
|
--ci|-[Cc][Ii])
|
||||||
|
ci=true
|
||||||
|
;;
|
||||||
|
--verbose|-Verbose)
|
||||||
|
verbose=true
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! __machine_has unzip; then
|
||||||
|
__error 'Missing required command: unzip'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! __machine_has curl && ! __machine_has wget; then
|
||||||
|
__error 'Missing required command. Either wget or curl is required.'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json"
|
||||||
|
if [ -f "$config_file" ]; then
|
||||||
|
if __machine_has jq ; then
|
||||||
|
if jq '.' "$config_file" >/dev/null ; then
|
||||||
|
config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")"
|
||||||
|
config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")"
|
||||||
|
else
|
||||||
|
__error "$config_file contains invalid JSON."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif __machine_has python ; then
|
||||||
|
if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then
|
||||||
|
config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")"
|
||||||
|
config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")"
|
||||||
|
else
|
||||||
|
__error "$config_file contains invalid JSON."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif __machine_has python3 ; then
|
||||||
|
if python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then
|
||||||
|
config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")"
|
||||||
|
config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")"
|
||||||
|
else
|
||||||
|
__error "$config_file contains invalid JSON."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
__error 'Missing required command: jq or python. Could not parse the JSON file.'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ ! -z "${config_channel:-}" ] && channel="$config_channel"
|
||||||
|
[ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -z "$channel" ] && channel='master'
|
||||||
|
[ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools'
|
||||||
|
|
||||||
|
get_korebuild
|
||||||
|
set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci"
|
||||||
|
invoke_korebuild_command "$command" "$@"
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<Import Project="..\..\..\build\dependencies.props" />
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<OutputType>exe</OutputType>
|
<OutputType>exe</OutputType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -14,7 +12,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ namespace ConsoleApplication
|
|||||||
// Since .NET Core 1.1, the HTTP hosting model has become basically as fast as the Socket hosting model
|
// Since .NET Core 1.1, the HTTP hosting model has become basically as fast as the Socket hosting model
|
||||||
//options.UseSocketHosting();
|
//options.UseSocketHosting();
|
||||||
|
|
||||||
options.ProjectPath = Directory.GetCurrentDirectory();
|
|
||||||
options.WatchFileExtensions = new string[] {}; // Don't watch anything
|
options.WatchFileExtensions = new string[] {}; // Don't watch anything
|
||||||
});
|
});
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.NodeServices;
|
using Microsoft.AspNetCore.NodeServices;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.Prerendering;
|
||||||
|
|
||||||
namespace NodeServicesExamples.Controllers
|
namespace NodeServicesExamples.Controllers
|
||||||
{
|
{
|
||||||
@@ -34,6 +35,20 @@ namespace NodeServicesExamples.Controllers
|
|||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Prerendering([FromServices] ISpaPrerenderer prerenderer)
|
||||||
|
{
|
||||||
|
var result = await prerenderer.RenderToString("./Node/prerenderPage");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(result.RedirectUrl))
|
||||||
|
{
|
||||||
|
return Redirect(result.RedirectUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewData["PrerenderedHtml"] = result.Html;
|
||||||
|
ViewData["PrerenderedGlobals"] = result.CreateGlobalsAssignmentScript();
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
public IActionResult Error()
|
public IActionResult Error()
|
||||||
{
|
{
|
||||||
return View("~/Views/Shared/Error.cshtml");
|
return View("~/Views/Shared/Error.cshtml");
|
||||||
|
|||||||
14
samples/misc/NodeServicesExamples/Node/prerenderPage.js
Normal file
14
samples/misc/NodeServicesExamples/Node/prerenderPage.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
var createServerRenderer = require('aspnet-prerendering').createServerRenderer;
|
||||||
|
|
||||||
|
module.exports = createServerRenderer(function(params) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
var message = 'The HTML was returned by the prerendering boot function. '
|
||||||
|
+ 'The boot function received the following params:'
|
||||||
|
+ '<pre>' + JSON.stringify(params, null, 4) + '</pre>';
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
html: '<h3>Hello, world!</h3>' + message,
|
||||||
|
globals: { sampleData: { nodeVersion: process.version } }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,25 +1,24 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<Import Project="..\..\..\build\dependencies.props" />
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
|
||||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
|
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.SpaServices\Microsoft.AspNetCore.SpaServices.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(MicrosoftAspNetCoreMvcPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Sdk.Razor" Version="$(MicrosoftNETSdkRazorPackageVersion)" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace NodeServicesExamples
|
|||||||
|
|
||||||
// Enable Node Services
|
// Enable Node Services
|
||||||
services.AddNodeServices();
|
services.AddNodeServices();
|
||||||
|
services.AddSpaPrerenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
|||||||
@@ -9,4 +9,5 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a asp-action="ES2015Transpilation">ES2015 transpilation</a></li>
|
<li><a asp-action="ES2015Transpilation">ES2015 transpilation</a></li>
|
||||||
<li><a asp-action="Chart">Server-side chart rendering</a></li>
|
<li><a asp-action="Chart">Server-side chart rendering</a></li>
|
||||||
|
<li><a asp-action="Prerendering">Server-side SPA prerendering</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<h1>Server-side prerendering</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This sample demonstrates how you can invoke a JavaScript module that contains
|
||||||
|
prerendering logic for a Single-Page Application framework.
|
||||||
|
</p>
|
||||||
|
</p>
|
||||||
|
Your prerendering boot function will receive parameters that describe the page
|
||||||
|
being rendered and any data supplied by the .NET code. The return value should be
|
||||||
|
a promise that resolves with data to be injected into the page, such as the
|
||||||
|
rendered HTML and any global data that should be made available to client-side code.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@Html.Raw(ViewData["PrerenderedHtml"])
|
||||||
|
|
||||||
|
<script>@Html.Raw(ViewData["PrerenderedGlobals"])</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Demonstrates how client-side code can receive data from the prerendering process
|
||||||
|
console.log('Received Node version from prerendering logic: ' + sampleData.nodeVersion);
|
||||||
|
</script>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
"name": "nodeservicesexamples",
|
"name": "nodeservicesexamples",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"aspnet-prerendering": "^2.0.6",
|
||||||
"babel-core": "^6.7.4",
|
"babel-core": "^6.7.4",
|
||||||
"babel-preset-es2015": "^6.6.0",
|
"babel-preset-es2015": "^6.6.0",
|
||||||
"node-chartist": "^1.0.2"
|
"node-chartist": "^1.0.2"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<Import Project="..\..\..\build\dependencies.props" />
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
|
||||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -13,13 +11,14 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(MicrosoftAspNetCoreMvcPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Sdk.Razor" Version="$(MicrosoftNETSdkRazorPackageVersion)" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
||||||
|
|||||||
28
scripts/Regenerate-JSFiles.ps1
Normal file
28
scripts/Regenerate-JSFiles.ps1
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[cmdletbinding(SupportsShouldProcess = $true)]
|
||||||
|
param(
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
Set-StrictMode -Version 2
|
||||||
|
|
||||||
|
Push-Location "src"
|
||||||
|
try {
|
||||||
|
$dirs = Get-ChildItem -Directory
|
||||||
|
foreach($dir in $dirs)
|
||||||
|
{
|
||||||
|
Push-Location $dir
|
||||||
|
try{
|
||||||
|
if(Test-Path -Path "package.json")
|
||||||
|
{
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally{
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
13
src/Directory.Build.props
Normal file
13
src/Directory.Build.props
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project>
|
||||||
|
<Import Project="..\Directory.Build.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,12 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<Import Project="..\..\build\common.props" />
|
|
||||||
|
|
||||||
<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>
|
|
||||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Remove="node_modules\**\*" />
|
||||||
<EmbeddedResource Include="Content\**\*" />
|
<EmbeddedResource Include="Content\**\*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@@ -17,13 +14,8 @@
|
|||||||
<ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
|
<ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Tpl.Dataflow" Version="$(MicrosoftDataflowVersion)" />
|
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="$(SystemThreadingTasksDataflowPackageVersion)" />
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">
|
|
||||||
<PackageReference Include="System.IO.Pipes" Version="$(CoreFxVersion)" />
|
|
||||||
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="$(ThreadingDataflowVersion)" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">
|
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A specialisation of the OutOfProcessNodeInstance base class that uses a lightweight binary streaming protocol
|
/// A specialisation of the OutOfProcessNodeInstance base class that uses a lightweight binary streaming protocol
|
||||||
/// to perform RPC invocations. The physical transport is Named Pipes on Windows, or Domain Sockets on Linux/Mac.
|
/// to perform RPC invocations. The physical transport is Named Pipes on Windows, or Domain Sockets on Linux/Mac.
|
||||||
/// For details on the binary streaming protocol, see
|
/// For details on the binary streaming protocol, see <see cref="Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections.VirtualConnectionClient" />
|
||||||
/// Microsoft.AspNetCore.NodeServices.HostingModels.VirtualConnections.VirtualConnectionClient.
|
|
||||||
/// The advantage versus using HTTP for RPC is that this is faster (not surprisingly - there's much less overhead
|
/// The advantage versus using HTTP for RPC is that this is faster (not surprisingly - there's much less overhead
|
||||||
/// because we don't need most of the functionality of HTTP.
|
/// because we don't need most of the functionality of HTTP.
|
||||||
///
|
///
|
||||||
@@ -49,6 +48,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,
|
||||||
@@ -237,4 +237,4 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets
|
|||||||
}
|
}
|
||||||
#pragma warning restore 649
|
#pragma warning restore 649
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class VirtualConnection extends Duplex {
|
|||||||
|
|
||||||
public _write(chunk: Buffer | string, encodingIfString: string, callback: EndWriteCallback) {
|
public _write(chunk: Buffer | string, encodingIfString: string, callback: EndWriteCallback) {
|
||||||
if (typeof chunk === 'string') {
|
if (typeof chunk === 'string') {
|
||||||
chunk = new Buffer(chunk as string, encodingIfString);
|
chunk = Buffer.from(chunk as string, encodingIfString);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._beginWriteCallback(chunk as Buffer, callback);
|
this._beginWriteCallback(chunk as Buffer, callback);
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class VirtualConnectionsCollection {
|
|||||||
newVirtualConnection.on('finish', () => {
|
newVirtualConnection.on('finish', () => {
|
||||||
// The virtual connection was closed locally. Clean up locally, and notify the remote that we're done.
|
// The virtual connection was closed locally. Clean up locally, and notify the remote that we're done.
|
||||||
this._onVirtualConnectionWasClosed(header.connectionIdString);
|
this._onVirtualConnectionWasClosed(header.connectionIdString);
|
||||||
this._sendFrame(header.connectionIdBinary, new Buffer(0));
|
this._sendFrame(header.connectionIdBinary, Buffer.alloc(0));
|
||||||
});
|
});
|
||||||
|
|
||||||
this._virtualConnections[header.connectionIdString] = newVirtualConnection;
|
this._virtualConnections[header.connectionIdString] = newVirtualConnection;
|
||||||
@@ -180,7 +180,7 @@ class VirtualConnectionsCollection {
|
|||||||
* Sends a number serialized in the correct format for .NET to receive as a System.Int32
|
* Sends a number serialized in the correct format for .NET to receive as a System.Int32
|
||||||
*/
|
*/
|
||||||
private _sendInt32LE(value: number, callback?: EndWriteCallback) {
|
private _sendInt32LE(value: number, callback?: EndWriteCallback) {
|
||||||
const buf = new Buffer(4);
|
const buf = Buffer.alloc(4);
|
||||||
buf.writeInt32LE(value, 0);
|
buf.writeInt32LE(value, 0);
|
||||||
this._socket.write(buf, callback);
|
this._socket.write(buf, callback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
"AssemblyIdentity": "Microsoft.AspNetCore.NodeServices.Sockets, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
||||||
|
"Types": [
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.Sockets.NodeServicesOptionsExtensions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"Static": true,
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "UseSocketHosting",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "options",
|
||||||
|
"Type": "Microsoft.AspNetCore.NodeServices.NodeServicesOptions"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections.VirtualConnectionReadErrorHandler",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Sealed": true,
|
||||||
|
"BaseType": "System.MulticastDelegate",
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Invoke",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "ex",
|
||||||
|
"Type": "System.Exception"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "BeginInvoke",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "ex",
|
||||||
|
"Type": "System.Exception"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "callback",
|
||||||
|
"Type": "System.AsyncCallback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "object",
|
||||||
|
"Type": "System.Object"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.IAsyncResult",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "EndInvoke",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "result",
|
||||||
|
"Type": "System.IAsyncResult"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "object",
|
||||||
|
"Type": "System.Object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "method",
|
||||||
|
"Type": "System.IntPtr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
4244
src/Microsoft.AspNetCore.NodeServices.Sockets/package-lock.json
generated
Normal file
4244
src/Microsoft.AspNetCore.NodeServices.Sockets/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,14 +5,15 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"build": "./node_modules/.bin/webpack"
|
"build": "webpack --mode production"
|
||||||
},
|
},
|
||||||
"author": "Microsoft",
|
"author": "Microsoft",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^6.0.42",
|
"@types/node": "^10.9.2",
|
||||||
"ts-loader": "^0.8.2",
|
"ts-loader": "^4.5.0",
|
||||||
"typescript": "^2.0.0",
|
"typescript": "^3.0.1",
|
||||||
"webpack": "^1.13.1"
|
"webpack": "^4.17.1",
|
||||||
|
"webpack-cli": "^3.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
target: 'node',
|
target: 'node',
|
||||||
externals: ['fs', 'net', 'events', 'readline', 'stream'],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [ '.ts' ]
|
extensions: [ '.ts' ]
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
rules: [
|
||||||
{ test: /\.ts$/, loader: 'ts-loader' },
|
{ test: /\.ts$/, use: 'ts-loader' },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
entry: {
|
entry: {
|
||||||
@@ -14,7 +15,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
libraryTarget: 'commonjs',
|
libraryTarget: 'commonjs',
|
||||||
path: './Content/Node',
|
path: path.join(__dirname, 'Content', 'Node'),
|
||||||
filename: '[name].js'
|
filename: '[name].js'
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
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;
|
||||||
@@ -33,14 +35,24 @@ namespace Microsoft.AspNetCore.NodeServices
|
|||||||
InvocationTimeoutMilliseconds = DefaultInvocationTimeoutMilliseconds;
|
InvocationTimeoutMilliseconds = DefaultInvocationTimeoutMilliseconds;
|
||||||
WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone();
|
WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone();
|
||||||
|
|
||||||
// In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few
|
|
||||||
// things that you'd otherwise have to specify manually
|
|
||||||
var hostEnv = serviceProvider.GetService<IHostingEnvironment>();
|
var hostEnv = serviceProvider.GetService<IHostingEnvironment>();
|
||||||
if (hostEnv != null)
|
if (hostEnv != null)
|
||||||
{
|
{
|
||||||
|
// In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few
|
||||||
|
// things that you'd otherwise have to specify manually
|
||||||
ProjectPath = hostEnv.ContentRootPath;
|
ProjectPath = hostEnv.ContentRootPath;
|
||||||
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
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ProjectPath = Directory.GetCurrentDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
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>();
|
||||||
@@ -93,5 +105,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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,305 +1,416 @@
|
|||||||
(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
|
(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
|
||||||
/******/ // The module cache
|
/******/ // The module cache
|
||||||
/******/ var installedModules = {};
|
/******/ var installedModules = {};
|
||||||
|
/******/
|
||||||
/******/ // The require function
|
/******/ // The require function
|
||||||
/******/ function __webpack_require__(moduleId) {
|
/******/ function __webpack_require__(moduleId) {
|
||||||
|
/******/
|
||||||
/******/ // Check if module is in cache
|
/******/ // Check if module is in cache
|
||||||
/******/ if(installedModules[moduleId])
|
/******/ if(installedModules[moduleId]) {
|
||||||
/******/ return installedModules[moduleId].exports;
|
/******/ return installedModules[moduleId].exports;
|
||||||
|
/******/ }
|
||||||
/******/ // Create a new module (and put it into the cache)
|
/******/ // Create a new module (and put it into the cache)
|
||||||
/******/ var module = installedModules[moduleId] = {
|
/******/ var module = installedModules[moduleId] = {
|
||||||
/******/ exports: {},
|
/******/ i: moduleId,
|
||||||
/******/ id: moduleId,
|
/******/ l: false,
|
||||||
/******/ loaded: false
|
/******/ exports: {}
|
||||||
/******/ };
|
/******/ };
|
||||||
|
/******/
|
||||||
/******/ // Execute the module function
|
/******/ // Execute the module function
|
||||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||||
|
/******/
|
||||||
/******/ // Flag the module as loaded
|
/******/ // Flag the module as loaded
|
||||||
/******/ module.loaded = true;
|
/******/ module.l = true;
|
||||||
|
/******/
|
||||||
/******/ // Return the exports of the module
|
/******/ // Return the exports of the module
|
||||||
/******/ return module.exports;
|
/******/ return module.exports;
|
||||||
/******/ }
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
/******/ // expose the modules object (__webpack_modules__)
|
/******/ // expose the modules object (__webpack_modules__)
|
||||||
/******/ __webpack_require__.m = modules;
|
/******/ __webpack_require__.m = modules;
|
||||||
|
/******/
|
||||||
/******/ // expose the module cache
|
/******/ // expose the module cache
|
||||||
/******/ __webpack_require__.c = installedModules;
|
/******/ __webpack_require__.c = installedModules;
|
||||||
|
/******/
|
||||||
|
/******/ // define getter function for harmony exports
|
||||||
|
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||||
|
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||||
|
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||||
|
/******/ }
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // define __esModule on exports
|
||||||
|
/******/ __webpack_require__.r = function(exports) {
|
||||||
|
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||||
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
/******/ }
|
||||||
|
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // create a fake namespace object
|
||||||
|
/******/ // mode & 1: value is a module id, require it
|
||||||
|
/******/ // mode & 2: merge all properties of value into the ns
|
||||||
|
/******/ // mode & 4: return value when already ns object
|
||||||
|
/******/ // mode & 8|1: behave like require
|
||||||
|
/******/ __webpack_require__.t = function(value, mode) {
|
||||||
|
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||||
|
/******/ if(mode & 8) return value;
|
||||||
|
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||||
|
/******/ var ns = Object.create(null);
|
||||||
|
/******/ __webpack_require__.r(ns);
|
||||||
|
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||||
|
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||||
|
/******/ return ns;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||||
|
/******/ __webpack_require__.n = function(module) {
|
||||||
|
/******/ var getter = module && module.__esModule ?
|
||||||
|
/******/ function getDefault() { return module['default']; } :
|
||||||
|
/******/ function getModuleExports() { return module; };
|
||||||
|
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||||
|
/******/ return getter;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Object.prototype.hasOwnProperty.call
|
||||||
|
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||||
|
/******/
|
||||||
/******/ // __webpack_public_path__
|
/******/ // __webpack_public_path__
|
||||||
/******/ __webpack_require__.p = "";
|
/******/ __webpack_require__.p = "";
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
/******/ // Load entry module and return exports
|
/******/ // Load entry module and return exports
|
||||||
/******/ return __webpack_require__(0);
|
/******/ return __webpack_require__(__webpack_require__.s = 1);
|
||||||
/******/ })
|
/******/ })
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
/******/ ([
|
/******/ ([
|
||||||
/* 0 */
|
/* 0 */
|
||||||
/***/ function(module, exports, __webpack_require__) {
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
module.exports = __webpack_require__(1);
|
module.exports = require("path");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
/***/ },
|
|
||||||
/* 1 */
|
/* 1 */
|
||||||
/***/ function(module, exports, __webpack_require__) {
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
"use strict";
|
module.exports = __webpack_require__(2);
|
||||||
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
|
|
||||||
// but simplifies things for the consumer of this module.
|
|
||||||
__webpack_require__(2);
|
|
||||||
var http = __webpack_require__(3);
|
|
||||||
var path = __webpack_require__(4);
|
|
||||||
var ArgsUtil_1 = __webpack_require__(5);
|
|
||||||
var ExitWhenParentExits_1 = __webpack_require__(6);
|
|
||||||
// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
|
|
||||||
// reference to Node's runtime 'require' function.
|
|
||||||
var dynamicRequire = eval('require');
|
|
||||||
var server = http.createServer(function (req, res) {
|
|
||||||
readRequestBodyAsJson(req, function (bodyJson) {
|
|
||||||
var hasSentResult = false;
|
|
||||||
var callback = function (errorValue, successValue) {
|
|
||||||
if (!hasSentResult) {
|
|
||||||
hasSentResult = true;
|
|
||||||
if (errorValue) {
|
|
||||||
respondWithError(res, errorValue);
|
|
||||||
}
|
|
||||||
else if (typeof successValue !== 'string') {
|
|
||||||
// Arbitrary object/number/etc - JSON-serialize it
|
|
||||||
var successValueJson = void 0;
|
|
||||||
try {
|
|
||||||
successValueJson = JSON.stringify(successValue);
|
|
||||||
}
|
|
||||||
catch (ex) {
|
|
||||||
// JSON serialization error - pass it back to .NET
|
|
||||||
respondWithError(res, ex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.setHeader('Content-Type', 'application/json');
|
|
||||||
res.end(successValueJson);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// String - can bypass JSON-serialization altogether
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.end(successValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Support streamed responses
|
|
||||||
Object.defineProperty(callback, 'stream', {
|
|
||||||
enumerable: true,
|
|
||||||
get: function () {
|
|
||||||
if (!hasSentResult) {
|
|
||||||
hasSentResult = true;
|
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
var resolvedPath = path.resolve(process.cwd(), bodyJson.moduleName);
|
|
||||||
var invokedModule = dynamicRequire(resolvedPath);
|
|
||||||
var func = bodyJson.exportedFunctionName ? invokedModule[bodyJson.exportedFunctionName] : invokedModule;
|
|
||||||
if (!func) {
|
|
||||||
throw new Error('The module "' + resolvedPath + '" has no export named "' + bodyJson.exportedFunctionName + '"');
|
|
||||||
}
|
|
||||||
func.apply(null, [callback].concat(bodyJson.args));
|
|
||||||
}
|
|
||||||
catch (synchronousException) {
|
|
||||||
callback(synchronousException, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
var parsedArgs = ArgsUtil_1.parseArgs(process.argv);
|
|
||||||
var requestedPortOrZero = parsedArgs.port || 0; // 0 means 'let the OS decide'
|
|
||||||
server.listen(requestedPortOrZero, 'localhost', function () {
|
|
||||||
// Signal to HttpNodeHost which port it should make its HTTP connections on
|
|
||||||
console.log('[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on port ' + server.address().port + '\]');
|
|
||||||
// Signal to the NodeServices base class that we're ready to accept invocations
|
|
||||||
console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
|
|
||||||
});
|
|
||||||
ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid), /* ignoreSigint */ true);
|
|
||||||
function readRequestBodyAsJson(request, callback) {
|
|
||||||
var requestBodyAsString = '';
|
|
||||||
request.on('data', function (chunk) { requestBodyAsString += chunk; });
|
|
||||||
request.on('end', function () { callback(JSON.parse(requestBodyAsString)); });
|
|
||||||
}
|
|
||||||
function respondWithError(res, errorValue) {
|
|
||||||
res.statusCode = 500;
|
|
||||||
res.end(errorValue.stack || errorValue.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/***/ },
|
/***/ }),
|
||||||
/* 2 */
|
/* 2 */
|
||||||
/***/ function(module, exports) {
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
// When Node writes to stdout/strerr, we capture that and convert the lines into calls on the
|
"use strict";
|
||||||
// active .NET ILogger. But by default, stdout/stderr don't have any way of distinguishing
|
|
||||||
// linebreaks inside log messages from the linebreaks that delimit separate log messages,
|
exports.__esModule = true;
|
||||||
// so multiline strings will end up being written to the ILogger as multiple independent
|
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
|
||||||
// log messages. This makes them very hard to make sense of, especially when they represent
|
// but simplifies things for the consumer of this module.
|
||||||
// something like stack traces.
|
__webpack_require__(3);
|
||||||
//
|
__webpack_require__(4);
|
||||||
// To fix this, we intercept stdout/stderr writes, and replace internal linebreaks with a
|
var http = __webpack_require__(5);
|
||||||
// marker token. When .NET receives the lines, it converts the marker tokens back to regular
|
var path = __webpack_require__(0);
|
||||||
// linebreaks within the logged messages.
|
var ArgsUtil_1 = __webpack_require__(6);
|
||||||
//
|
var ExitWhenParentExits_1 = __webpack_require__(7);
|
||||||
// Note that it's better to do the interception at the stdout/stderr level, rather than at
|
// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
|
||||||
// the console.log/console.error (etc.) level, because this takes place after any native
|
// reference to Node's runtime 'require' function.
|
||||||
// message formatting has taken place (e.g., inserting values for % placeholders).
|
var dynamicRequire = eval('require');
|
||||||
var findInternalNewlinesRegex = /\n(?!$)/g;
|
var server = http.createServer(function (req, res) {
|
||||||
var encodedNewline = '__ns_newline__';
|
readRequestBodyAsJson(req, function (bodyJson) {
|
||||||
encodeNewlinesWrittenToStream(process.stdout);
|
var hasSentResult = false;
|
||||||
encodeNewlinesWrittenToStream(process.stderr);
|
var callback = function (errorValue, successValue) {
|
||||||
function encodeNewlinesWrittenToStream(outputStream) {
|
if (!hasSentResult) {
|
||||||
var origWriteFunction = outputStream.write;
|
hasSentResult = true;
|
||||||
outputStream.write = function (value) {
|
if (errorValue) {
|
||||||
// Only interfere with the write if it's definitely a string
|
respondWithError(res, errorValue);
|
||||||
if (typeof value === 'string') {
|
}
|
||||||
var argsClone = Array.prototype.slice.call(arguments, 0);
|
else if (typeof successValue !== 'string') {
|
||||||
argsClone[0] = encodeNewlinesInString(value);
|
// Arbitrary object/number/etc - JSON-serialize it
|
||||||
origWriteFunction.apply(this, argsClone);
|
var successValueJson = void 0;
|
||||||
}
|
try {
|
||||||
else {
|
successValueJson = JSON.stringify(successValue);
|
||||||
origWriteFunction.apply(this, arguments);
|
}
|
||||||
}
|
catch (ex) {
|
||||||
};
|
// JSON serialization error - pass it back to .NET
|
||||||
}
|
respondWithError(res, ex);
|
||||||
function encodeNewlinesInString(str) {
|
return;
|
||||||
return str.replace(findInternalNewlinesRegex, encodedNewline);
|
}
|
||||||
}
|
res.setHeader('Content-Type', 'application/json');
|
||||||
|
res.end(successValueJson);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// String - can bypass JSON-serialization altogether
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.end(successValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Support streamed responses
|
||||||
|
Object.defineProperty(callback, 'stream', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
if (!hasSentResult) {
|
||||||
|
hasSentResult = true;
|
||||||
|
res.setHeader('Content-Type', 'application/octet-stream');
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
var resolvedPath = path.resolve(process.cwd(), bodyJson.moduleName);
|
||||||
|
var invokedModule = dynamicRequire(resolvedPath);
|
||||||
|
var func = bodyJson.exportedFunctionName ? invokedModule[bodyJson.exportedFunctionName] : invokedModule;
|
||||||
|
if (!func) {
|
||||||
|
throw new Error('The module "' + resolvedPath + '" has no export named "' + bodyJson.exportedFunctionName + '"');
|
||||||
|
}
|
||||||
|
func.apply(null, [callback].concat(bodyJson.args));
|
||||||
|
}
|
||||||
|
catch (synchronousException) {
|
||||||
|
callback(synchronousException, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var parsedArgs = ArgsUtil_1.parseArgs(process.argv);
|
||||||
|
var requestedPortOrZero = parsedArgs.port || 0; // 0 means 'let the OS decide'
|
||||||
|
server.listen(requestedPortOrZero, 'localhost', function () {
|
||||||
|
var addressInfo = server.address();
|
||||||
|
// Signal to HttpNodeHost which loopback IP address (IPv4 or IPv6) and port it should make its HTTP connections on
|
||||||
|
console.log('[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on {' + addressInfo.address + '} port ' + addressInfo.port + '\]');
|
||||||
|
// Signal to the NodeServices base class that we're ready to accept invocations
|
||||||
|
console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
|
||||||
|
});
|
||||||
|
ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid), /* ignoreSigint */ true);
|
||||||
|
function readRequestBodyAsJson(request, callback) {
|
||||||
|
var requestBodyAsString = '';
|
||||||
|
request.on('data', function (chunk) { requestBodyAsString += chunk; });
|
||||||
|
request.on('end', function () { callback(JSON.parse(requestBodyAsString)); });
|
||||||
|
}
|
||||||
|
function respondWithError(res, errorValue) {
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
errorMessage: errorValue.message || errorValue,
|
||||||
|
errorDetails: errorValue.stack || null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***/ },
|
/***/ }),
|
||||||
/* 3 */
|
/* 3 */
|
||||||
/***/ function(module, exports) {
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
module.exports = require("http");
|
"use strict";
|
||||||
|
|
||||||
/***/ },
|
exports.__esModule = true;
|
||||||
|
var path = __webpack_require__(0);
|
||||||
|
var startsWith = function (str, prefix) { return str.substring(0, prefix.length) === prefix; };
|
||||||
|
var appRootDir = process.cwd();
|
||||||
|
function patchedLStat(pathToStatLong, fsReqWrap) {
|
||||||
|
try {
|
||||||
|
// If the lstat completes without errors, we don't modify its behavior at all
|
||||||
|
return origLStat.apply(this, arguments);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
var shouldOverrideError = startsWith(ex.message, 'EPERM') // It's a permissions error
|
||||||
|
&& typeof appRootDirLong === 'string'
|
||||||
|
&& startsWith(appRootDirLong, pathToStatLong) // ... for an ancestor directory
|
||||||
|
&& ex.stack.indexOf('Object.realpathSync ') >= 0; // ... during symlink resolution
|
||||||
|
if (shouldOverrideError) {
|
||||||
|
// Fake the result to give the same result as an 'lstat' on the app root dir.
|
||||||
|
// This stops Node failing to load modules just because it doesn't know whether
|
||||||
|
// ancestor directories are symlinks or not. If there's a genuine file
|
||||||
|
// permissions issue, it will still surface later when Node actually
|
||||||
|
// tries to read the file.
|
||||||
|
return origLStat.call(this, appRootDir, fsReqWrap);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// In any other case, preserve the original error
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
// It's only necessary to apply this workaround on Windows
|
||||||
|
var appRootDirLong = null;
|
||||||
|
var origLStat = null;
|
||||||
|
if (/^win/.test(process.platform)) {
|
||||||
|
try {
|
||||||
|
// Get the app's root dir in Node's internal "long" format (e.g., \\?\C:\dir\subdir)
|
||||||
|
appRootDirLong = path._makeLong(appRootDir);
|
||||||
|
// Actually apply the patch, being as defensive as possible
|
||||||
|
var bindingFs = process.binding('fs');
|
||||||
|
origLStat = bindingFs.lstat;
|
||||||
|
if (typeof origLStat === 'function') {
|
||||||
|
bindingFs.lstat = patchedLStat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
// If some future version of Node throws (e.g., to prevent use of process.binding()),
|
||||||
|
// don't apply the patch, but still let the application run.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
/* 4 */
|
/* 4 */
|
||||||
/***/ function(module, exports) {
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
module.exports = require("path");
|
// When Node writes to stdout/strerr, we capture that and convert the lines into calls on the
|
||||||
|
// active .NET ILogger. But by default, stdout/stderr don't have any way of distinguishing
|
||||||
|
// linebreaks inside log messages from the linebreaks that delimit separate log messages,
|
||||||
|
// so multiline strings will end up being written to the ILogger as multiple independent
|
||||||
|
// log messages. This makes them very hard to make sense of, especially when they represent
|
||||||
|
// something like stack traces.
|
||||||
|
//
|
||||||
|
// To fix this, we intercept stdout/stderr writes, and replace internal linebreaks with a
|
||||||
|
// marker token. When .NET receives the lines, it converts the marker tokens back to regular
|
||||||
|
// linebreaks within the logged messages.
|
||||||
|
//
|
||||||
|
// Note that it's better to do the interception at the stdout/stderr level, rather than at
|
||||||
|
// the console.log/console.error (etc.) level, because this takes place after any native
|
||||||
|
// message formatting has taken place (e.g., inserting values for % placeholders).
|
||||||
|
var findInternalNewlinesRegex = /\n(?!$)/g;
|
||||||
|
var encodedNewline = '__ns_newline__';
|
||||||
|
encodeNewlinesWrittenToStream(process.stdout);
|
||||||
|
encodeNewlinesWrittenToStream(process.stderr);
|
||||||
|
function encodeNewlinesWrittenToStream(outputStream) {
|
||||||
|
var origWriteFunction = outputStream.write;
|
||||||
|
outputStream.write = function (value) {
|
||||||
|
// Only interfere with the write if it's definitely a string
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
var argsClone = Array.prototype.slice.call(arguments, 0);
|
||||||
|
argsClone[0] = encodeNewlinesInString(value);
|
||||||
|
origWriteFunction.apply(this, argsClone);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
origWriteFunction.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function encodeNewlinesInString(str) {
|
||||||
|
return str.replace(findInternalNewlinesRegex, encodedNewline);
|
||||||
|
}
|
||||||
|
|
||||||
/***/ },
|
|
||||||
|
/***/ }),
|
||||||
/* 5 */
|
/* 5 */
|
||||||
/***/ function(module, exports) {
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
"use strict";
|
module.exports = require("http");
|
||||||
function parseArgs(args) {
|
|
||||||
// Very simplistic parsing which is sufficient for the cases needed. We don't want to bring in any external
|
|
||||||
// dependencies (such as an args-parsing library) to this file.
|
|
||||||
var result = {};
|
|
||||||
var currentKey = null;
|
|
||||||
args.forEach(function (arg) {
|
|
||||||
if (arg.indexOf('--') === 0) {
|
|
||||||
var argName = arg.substring(2);
|
|
||||||
result[argName] = undefined;
|
|
||||||
currentKey = argName;
|
|
||||||
}
|
|
||||||
else if (currentKey) {
|
|
||||||
result[currentKey] = arg;
|
|
||||||
currentKey = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
exports.parseArgs = parseArgs;
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
/***/ },
|
|
||||||
/* 6 */
|
/* 6 */
|
||||||
/***/ function(module, exports) {
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
/*
|
"use strict";
|
||||||
In general, we want the Node child processes to be terminated as soon as the parent .NET processes exit,
|
|
||||||
because we have no further use for them. If the .NET process shuts down gracefully, it will run its
|
|
||||||
finalizers, one of which (in OutOfProcessNodeInstance.cs) will kill its associated Node process immediately.
|
|
||||||
|
|
||||||
But if the .NET process is terminated forcefully (e.g., on Linux/OSX with 'kill -9'), then it won't have
|
exports.__esModule = true;
|
||||||
any opportunity to shut down its child processes, and by default they will keep running. In this case, it's
|
function parseArgs(args) {
|
||||||
up to the child process to detect this has happened and terminate itself.
|
// Very simplistic parsing which is sufficient for the cases needed. We don't want to bring in any external
|
||||||
|
// dependencies (such as an args-parsing library) to this file.
|
||||||
There are many possible approaches to detecting when a parent process has exited, most of which behave
|
var result = {};
|
||||||
differently between Windows and Linux/OS X:
|
var currentKey = null;
|
||||||
|
args.forEach(function (arg) {
|
||||||
- On Windows, the parent process can mark its child as being a 'job' that should auto-terminate when
|
if (arg.indexOf('--') === 0) {
|
||||||
the parent does (http://stackoverflow.com/a/4657392). Not cross-platform.
|
var argName = arg.substring(2);
|
||||||
- The child Node process can get a callback when the parent disconnects (process.on('disconnect', ...)).
|
result[argName] = undefined;
|
||||||
But despite http://stackoverflow.com/a/16487966, no callback fires in any case I've tested (Windows / OS X).
|
currentKey = argName;
|
||||||
- The child Node process can get a callback when its stdin/stdout are disconnected, as described at
|
}
|
||||||
http://stackoverflow.com/a/15693934. This works well on OS X, but calling stdout.resume() on Windows
|
else if (currentKey) {
|
||||||
causes the process to terminate prematurely.
|
result[currentKey] = arg;
|
||||||
- I don't know why, but on Windows, it's enough to invoke process.stdin.resume(). For some reason this causes
|
currentKey = null;
|
||||||
the child Node process to exit as soon as the parent one does, but I don't see this documented anywhere.
|
}
|
||||||
- You can poll to see if the parent process, or your stdin/stdout connection to it, is gone
|
});
|
||||||
- You can directly pass a parent process PID to the child, and then have the child poll to see if it's
|
return result;
|
||||||
still running (e.g., using process.kill(pid, 0), which doesn't kill it but just tests whether it exists,
|
}
|
||||||
as per https://nodejs.org/api/process.html#process_process_kill_pid_signal)
|
exports.parseArgs = parseArgs;
|
||||||
- Or, on each poll, you can try writing to process.stdout. If the parent has died, then this will throw.
|
|
||||||
However I don't see this documented anywhere. It would be nice if you could just poll for whether or not
|
|
||||||
process.stdout is still connected (without actually writing to it) but I haven't found any property whose
|
|
||||||
value changes until you actually try to write to it.
|
|
||||||
|
|
||||||
Of these, the only cross-platform approach that is actually documented as a valid strategy is simply polling
|
|
||||||
to check whether the parent PID is still running. So that's what we do here.
|
|
||||||
*/
|
|
||||||
"use strict";
|
|
||||||
var pollIntervalMs = 1000;
|
|
||||||
function exitWhenParentExits(parentPid, ignoreSigint) {
|
|
||||||
setInterval(function () {
|
|
||||||
if (!processExists(parentPid)) {
|
|
||||||
// Can't log anything at this point, because out stdout was connected to the parent,
|
|
||||||
// but the parent is gone.
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
}, pollIntervalMs);
|
|
||||||
if (ignoreSigint) {
|
|
||||||
// Pressing ctrl+c in the terminal sends a SIGINT to all processes in the foreground process tree.
|
|
||||||
// By default, the Node process would then exit before the .NET process, because ASP.NET implements
|
|
||||||
// a delayed shutdown to allow ongoing requests to complete.
|
|
||||||
//
|
|
||||||
// This is problematic, because if Node exits first, the CopyToAsync code in ConditionalProxyMiddleware
|
|
||||||
// will experience a read fault, and logs a huge load of errors. Fortunately, since the Node process is
|
|
||||||
// already set up to shut itself down if it detects the .NET process is terminated, all we have to do is
|
|
||||||
// ignore the SIGINT. The Node process will then terminate automatically after the .NET process does.
|
|
||||||
//
|
|
||||||
// A better solution would be to have WebpackDevMiddleware listen for SIGINT and gracefully close any
|
|
||||||
// ongoing EventSource connections before letting the Node process exit, independently of the .NET
|
|
||||||
// process exiting. However, doing this well in general is very nontrivial (see all the discussion at
|
|
||||||
// https://github.com/nodejs/node/issues/2642).
|
|
||||||
process.on('SIGINT', function () {
|
|
||||||
console.log('Received SIGINT. Waiting for .NET process to exit...');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.exitWhenParentExits = exitWhenParentExits;
|
|
||||||
function processExists(pid) {
|
|
||||||
try {
|
|
||||||
// Sending signal 0 - on all platforms - tests whether the process exists. As long as it doesn't
|
|
||||||
// throw, that means it does exist.
|
|
||||||
process.kill(pid, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (ex) {
|
|
||||||
// If the reason for the error is that we don't have permission to ask about this process,
|
|
||||||
// report that as a separate problem.
|
|
||||||
if (ex.code === 'EPERM') {
|
|
||||||
throw new Error("Attempted to check whether process " + pid + " was running, but got a permissions error.");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/***/ }
|
/***/ }),
|
||||||
|
/* 7 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/*
|
||||||
|
In general, we want the Node child processes to be terminated as soon as the parent .NET processes exit,
|
||||||
|
because we have no further use for them. If the .NET process shuts down gracefully, it will run its
|
||||||
|
finalizers, one of which (in OutOfProcessNodeInstance.cs) will kill its associated Node process immediately.
|
||||||
|
|
||||||
|
But if the .NET process is terminated forcefully (e.g., on Linux/OSX with 'kill -9'), then it won't have
|
||||||
|
any opportunity to shut down its child processes, and by default they will keep running. In this case, it's
|
||||||
|
up to the child process to detect this has happened and terminate itself.
|
||||||
|
|
||||||
|
There are many possible approaches to detecting when a parent process has exited, most of which behave
|
||||||
|
differently between Windows and Linux/OS X:
|
||||||
|
|
||||||
|
- On Windows, the parent process can mark its child as being a 'job' that should auto-terminate when
|
||||||
|
the parent does (http://stackoverflow.com/a/4657392). Not cross-platform.
|
||||||
|
- The child Node process can get a callback when the parent disconnects (process.on('disconnect', ...)).
|
||||||
|
But despite http://stackoverflow.com/a/16487966, no callback fires in any case I've tested (Windows / OS X).
|
||||||
|
- The child Node process can get a callback when its stdin/stdout are disconnected, as described at
|
||||||
|
http://stackoverflow.com/a/15693934. This works well on OS X, but calling stdout.resume() on Windows
|
||||||
|
causes the process to terminate prematurely.
|
||||||
|
- I don't know why, but on Windows, it's enough to invoke process.stdin.resume(). For some reason this causes
|
||||||
|
the child Node process to exit as soon as the parent one does, but I don't see this documented anywhere.
|
||||||
|
- You can poll to see if the parent process, or your stdin/stdout connection to it, is gone
|
||||||
|
- You can directly pass a parent process PID to the child, and then have the child poll to see if it's
|
||||||
|
still running (e.g., using process.kill(pid, 0), which doesn't kill it but just tests whether it exists,
|
||||||
|
as per https://nodejs.org/api/process.html#process_process_kill_pid_signal)
|
||||||
|
- Or, on each poll, you can try writing to process.stdout. If the parent has died, then this will throw.
|
||||||
|
However I don't see this documented anywhere. It would be nice if you could just poll for whether or not
|
||||||
|
process.stdout is still connected (without actually writing to it) but I haven't found any property whose
|
||||||
|
value changes until you actually try to write to it.
|
||||||
|
|
||||||
|
Of these, the only cross-platform approach that is actually documented as a valid strategy is simply polling
|
||||||
|
to check whether the parent PID is still running. So that's what we do here.
|
||||||
|
*/
|
||||||
|
exports.__esModule = true;
|
||||||
|
var pollIntervalMs = 1000;
|
||||||
|
function exitWhenParentExits(parentPid, ignoreSigint) {
|
||||||
|
setInterval(function () {
|
||||||
|
if (!processExists(parentPid)) {
|
||||||
|
// Can't log anything at this point, because out stdout was connected to the parent,
|
||||||
|
// but the parent is gone.
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
}, pollIntervalMs);
|
||||||
|
if (ignoreSigint) {
|
||||||
|
// Pressing ctrl+c in the terminal sends a SIGINT to all processes in the foreground process tree.
|
||||||
|
// By default, the Node process would then exit before the .NET process, because ASP.NET implements
|
||||||
|
// a delayed shutdown to allow ongoing requests to complete.
|
||||||
|
//
|
||||||
|
// This is problematic, because if Node exits first, the CopyToAsync code in ConditionalProxyMiddleware
|
||||||
|
// will experience a read fault, and logs a huge load of errors. Fortunately, since the Node process is
|
||||||
|
// already set up to shut itself down if it detects the .NET process is terminated, all we have to do is
|
||||||
|
// ignore the SIGINT. The Node process will then terminate automatically after the .NET process does.
|
||||||
|
//
|
||||||
|
// A better solution would be to have WebpackDevMiddleware listen for SIGINT and gracefully close any
|
||||||
|
// ongoing EventSource connections before letting the Node process exit, independently of the .NET
|
||||||
|
// process exiting. However, doing this well in general is very nontrivial (see all the discussion at
|
||||||
|
// https://github.com/nodejs/node/issues/2642).
|
||||||
|
process.on('SIGINT', function () {
|
||||||
|
console.log('Received SIGINT. Waiting for .NET process to exit...');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.exitWhenParentExits = exitWhenParentExits;
|
||||||
|
function processExists(pid) {
|
||||||
|
try {
|
||||||
|
// Sending signal 0 - on all platforms - tests whether the process exists. As long as it doesn't
|
||||||
|
// throw, that means it does exist.
|
||||||
|
process.kill(pid, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
// If the reason for the error is that we don't have permission to ask about this process,
|
||||||
|
// report that as a separate problem.
|
||||||
|
if (ex.code === 'EPERM') {
|
||||||
|
throw new Error("Attempted to check whether process " + pid + " was running, but got a permissions error.");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***/ })
|
||||||
/******/ ])));
|
/******/ ])));
|
||||||
@@ -21,8 +21,8 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
|
|||||||
/// <seealso cref="Microsoft.AspNetCore.NodeServices.HostingModels.OutOfProcessNodeInstance" />
|
/// <seealso cref="Microsoft.AspNetCore.NodeServices.HostingModels.OutOfProcessNodeInstance" />
|
||||||
internal class HttpNodeInstance : OutOfProcessNodeInstance
|
internal class HttpNodeInstance : OutOfProcessNodeInstance
|
||||||
{
|
{
|
||||||
private static readonly Regex PortMessageRegex =
|
private static readonly Regex EndpointMessageRegex =
|
||||||
new Regex(@"^\[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on port (\d+)\]$");
|
new Regex(@"^\[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on {(.*?)} port (\d+)\]$");
|
||||||
|
|
||||||
private static readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
|
private static readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
@@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
|
|||||||
|
|
||||||
private readonly HttpClient _client;
|
private readonly HttpClient _client;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private int _portNumber;
|
private string _endpoint;
|
||||||
|
|
||||||
public HttpNodeInstance(NodeServicesOptions options, int port = 0)
|
public HttpNodeInstance(NodeServicesOptions options, int port = 0)
|
||||||
: base(
|
: base(
|
||||||
@@ -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,
|
||||||
@@ -49,6 +50,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
|
|||||||
options.DebuggingPort)
|
options.DebuggingPort)
|
||||||
{
|
{
|
||||||
_client = new HttpClient();
|
_client = new HttpClient();
|
||||||
|
_client.Timeout = TimeSpan.FromMilliseconds(options.InvocationTimeoutMilliseconds + 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string MakeCommandLineOptions(int port)
|
private static string MakeCommandLineOptions(int port)
|
||||||
@@ -61,13 +63,15 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
|
|||||||
{
|
{
|
||||||
var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings);
|
var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings);
|
||||||
var payload = new StringContent(payloadJson, Encoding.UTF8, "application/json");
|
var payload = new StringContent(payloadJson, Encoding.UTF8, "application/json");
|
||||||
var response = await _client.PostAsync("http://localhost:" + _portNumber, payload, cancellationToken);
|
var response = await _client.PostAsync(_endpoint, payload, cancellationToken);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
// Unfortunately there's no true way to cancel ReadAsStringAsync calls, hence AbandonIfCancelled
|
// Unfortunately there's no true way to cancel ReadAsStringAsync calls, hence AbandonIfCancelled
|
||||||
var responseErrorString = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken);
|
var responseJson = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken);
|
||||||
throw new Exception("Call to Node module failed with error: " + responseErrorString);
|
var responseError = JsonConvert.DeserializeObject<RpcJsonResponse>(responseJson, jsonSerializerSettings);
|
||||||
|
|
||||||
|
throw new NodeInvocationException(responseError.ErrorMessage, responseError.ErrorDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseContentType = response.Content.Headers.ContentType;
|
var responseContentType = response.Content.Headers.ContentType;
|
||||||
@@ -107,13 +111,19 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
|
|||||||
|
|
||||||
protected override void OnOutputDataReceived(string outputData)
|
protected override void OnOutputDataReceived(string outputData)
|
||||||
{
|
{
|
||||||
// Watch for "port selected" messages, and when observed, store the port number
|
// Watch for "port selected" messages, and when observed,
|
||||||
|
// store the IP (IPv4/IPv6) and port number
|
||||||
// so we can use it when making HTTP requests. The child process will always send
|
// so we can use it when making HTTP requests. The child process will always send
|
||||||
// one of these messages before it sends a "ready for connections" message.
|
// one of these messages before it sends a "ready for connections" message.
|
||||||
var match = _portNumber != 0 ? null : PortMessageRegex.Match(outputData);
|
var match = string.IsNullOrEmpty(_endpoint) ? EndpointMessageRegex.Match(outputData) : null;
|
||||||
if (match != null && match.Success)
|
if (match != null && match.Success)
|
||||||
{
|
{
|
||||||
_portNumber = int.Parse(match.Groups[1].Captures[0].Value);
|
var port = int.Parse(match.Groups[2].Captures[0].Value);
|
||||||
|
var resolvedIpAddress = match.Groups[1].Captures[0].Value;
|
||||||
|
|
||||||
|
//IPv6 must be wrapped with [] brackets
|
||||||
|
resolvedIpAddress = resolvedIpAddress == "::1" ? $"[{resolvedIpAddress}]" : resolvedIpAddress;
|
||||||
|
_endpoint = $"http://{resolvedIpAddress}:{port}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -135,5 +145,13 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
|
|||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning disable 649 // These properties are populated via JSON deserialization
|
||||||
|
private class RpcJsonResponse
|
||||||
|
{
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
public string ErrorDetails { get; set; }
|
||||||
|
}
|
||||||
|
#pragma warning restore 649
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -295,7 +297,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
|
|||||||
|
|
||||||
// Make sure the Node process is finished
|
// Make sure the Node process is finished
|
||||||
// TODO: Is there a more graceful way to end it? Or does this still let it perform any cleanup?
|
// TODO: Is there a more graceful way to end it? Or does this still let it perform any cleanup?
|
||||||
if (!_nodeProcess.HasExited)
|
if (_nodeProcess != null && !_nodeProcess.HasExited)
|
||||||
{
|
{
|
||||||
_nodeProcess.Kill();
|
_nodeProcess.Kill();
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
@@ -383,12 +381,6 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
|
|||||||
{
|
{
|
||||||
OutputLogger.LogWarning(evt.Data);
|
OutputLogger.LogWarning(evt.Data);
|
||||||
}
|
}
|
||||||
else if (!initializationIsCompleted)
|
|
||||||
{
|
|
||||||
_connectionIsReadySource.SetException(
|
|
||||||
new InvalidOperationException("The Node.js process failed to initialize: " + evt.Data));
|
|
||||||
initializationIsCompleted = true;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
OnErrorDataReceived(UnencodeNewlines(evt.Data));
|
OnErrorDataReceived(UnencodeNewlines(evt.Data));
|
||||||
@@ -402,9 +394,11 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
|
|||||||
|
|
||||||
private static bool IsDebuggerMessage(string message)
|
private static bool IsDebuggerMessage(string message)
|
||||||
{
|
{
|
||||||
return message.StartsWith("Debugger attached", StringComparison.OrdinalIgnoreCase) ||
|
return message.StartsWith("Debugger attached", StringComparison.Ordinal) ||
|
||||||
message.StartsWith("Debugger listening ", StringComparison.OrdinalIgnoreCase) ||
|
message.StartsWith("Debugger listening ", StringComparison.Ordinal) ||
|
||||||
message.StartsWith("To start debugging", StringComparison.OrdinalIgnoreCase) ||
|
message.StartsWith("To start debugging", StringComparison.Ordinal) ||
|
||||||
|
message.Equals("Warning: This is an experimental feature and could change at any time.", StringComparison.Ordinal) ||
|
||||||
|
message.Equals("For help see https://nodejs.org/en/docs/inspector", StringComparison.Ordinal) ||
|
||||||
message.Contains("chrome-devtools:");
|
message.Contains("chrome-devtools:");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,19 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<Import Project="..\..\build\common.props" />
|
|
||||||
|
|
||||||
<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>
|
|
||||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Remove="node_modules\**\*" />
|
||||||
<EmbeddedResource Include="Content\**\*" />
|
<EmbeddedResource Include="Content\**\*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" />
|
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
|
||||||
</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>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">
|
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ module.exports = {
|
|||||||
|
|
||||||
## Hosting models
|
## Hosting models
|
||||||
|
|
||||||
NodeServices has a pluggable hosting/transport mechanism, because it is an abstraction over various possible ways to invoke Node.js from .NET. This allows more high-level facilities (e.g., for Angular prerendering) to be agnostic to the details of launching Node and communicating it - those high-level facilities can just trust that *somehow* we can invoke code in Node for them.
|
NodeServices has a pluggable hosting/transport mechanism, because it is an abstraction over various possible ways to invoke Node.js from .NET. This allows more high-level facilities (e.g., for Angular prerendering) to be agnostic to the details of launching Node and communicating with it - those high-level facilities can just trust that *somehow* we can invoke code in Node for them.
|
||||||
|
|
||||||
Using this abstraction, we could run Node inside the .NET process, in a separate process on the same machine, or even on a different machine altogether. At the time of writing, all the built-in hosting mechanisms work by launching Node as a separate process on the same machine as your .NET code.
|
Using this abstraction, we could run Node inside the .NET process, in a separate process on the same machine, or even on a different machine altogether. At the time of writing, all the built-in hosting mechanisms work by launching Node as a separate process on the same machine as your .NET code.
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
|
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
|
||||||
// but simplifies things for the consumer of this module.
|
// but simplifies things for the consumer of this module.
|
||||||
|
import './Util/PatchModuleResolutionLStat';
|
||||||
import './Util/OverrideStdOutputs';
|
import './Util/OverrideStdOutputs';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { parseArgs } from './Util/ArgsUtil';
|
import { parseArgs } from './Util/ArgsUtil';
|
||||||
import { exitWhenParentExits } from './Util/ExitWhenParentExits';
|
import { exitWhenParentExits } from './Util/ExitWhenParentExits';
|
||||||
|
import { AddressInfo } from 'net';
|
||||||
|
|
||||||
// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
|
// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
|
||||||
// reference to Node's runtime 'require' function.
|
// reference to Node's runtime 'require' function.
|
||||||
@@ -69,8 +71,10 @@ const server = http.createServer((req, res) => {
|
|||||||
const parsedArgs = parseArgs(process.argv);
|
const parsedArgs = parseArgs(process.argv);
|
||||||
const requestedPortOrZero = parsedArgs.port || 0; // 0 means 'let the OS decide'
|
const requestedPortOrZero = parsedArgs.port || 0; // 0 means 'let the OS decide'
|
||||||
server.listen(requestedPortOrZero, 'localhost', function () {
|
server.listen(requestedPortOrZero, 'localhost', function () {
|
||||||
// Signal to HttpNodeHost which port it should make its HTTP connections on
|
const addressInfo = server.address() as AddressInfo;
|
||||||
console.log('[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on port ' + server.address().port + '\]');
|
|
||||||
|
// Signal to HttpNodeHost which loopback IP address (IPv4 or IPv6) and port it should make its HTTP connections on
|
||||||
|
console.log('[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on {' + addressInfo.address + '} port ' + addressInfo.port + '\]');
|
||||||
|
|
||||||
// Signal to the NodeServices base class that we're ready to accept invocations
|
// Signal to the NodeServices base class that we're ready to accept invocations
|
||||||
console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
|
console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
|
||||||
@@ -86,5 +90,8 @@ function readRequestBodyAsJson(request, callback) {
|
|||||||
|
|
||||||
function respondWithError(res: http.ServerResponse, errorValue: any) {
|
function respondWithError(res: http.ServerResponse, errorValue: any) {
|
||||||
res.statusCode = 500;
|
res.statusCode = 500;
|
||||||
res.end(errorValue.stack || errorValue.toString());
|
res.end(JSON.stringify({
|
||||||
|
errorMessage: errorValue.message || errorValue,
|
||||||
|
errorDetails: errorValue.stack || null
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
const startsWith = (str: string, prefix: string) => str.substring(0, prefix.length) === prefix;
|
||||||
|
const appRootDir = process.cwd();
|
||||||
|
|
||||||
|
function patchedLStat(pathToStatLong: string, fsReqWrap?: any) {
|
||||||
|
try {
|
||||||
|
// If the lstat completes without errors, we don't modify its behavior at all
|
||||||
|
return origLStat.apply(this, arguments);
|
||||||
|
} catch(ex) {
|
||||||
|
const shouldOverrideError =
|
||||||
|
startsWith(ex.message, 'EPERM') // It's a permissions error
|
||||||
|
&& typeof appRootDirLong === 'string'
|
||||||
|
&& startsWith(appRootDirLong, pathToStatLong) // ... for an ancestor directory
|
||||||
|
&& ex.stack.indexOf('Object.realpathSync ') >= 0; // ... during symlink resolution
|
||||||
|
|
||||||
|
if (shouldOverrideError) {
|
||||||
|
// Fake the result to give the same result as an 'lstat' on the app root dir.
|
||||||
|
// This stops Node failing to load modules just because it doesn't know whether
|
||||||
|
// ancestor directories are symlinks or not. If there's a genuine file
|
||||||
|
// permissions issue, it will still surface later when Node actually
|
||||||
|
// tries to read the file.
|
||||||
|
return origLStat.call(this, appRootDir, fsReqWrap);
|
||||||
|
} else {
|
||||||
|
// In any other case, preserve the original error
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// It's only necessary to apply this workaround on Windows
|
||||||
|
let appRootDirLong: string = null;
|
||||||
|
let origLStat: Function = null;
|
||||||
|
if (/^win/.test(process.platform)) {
|
||||||
|
try {
|
||||||
|
// Get the app's root dir in Node's internal "long" format (e.g., \\?\C:\dir\subdir)
|
||||||
|
appRootDirLong = (path as any)._makeLong(appRootDir);
|
||||||
|
|
||||||
|
// Actually apply the patch, being as defensive as possible
|
||||||
|
const bindingFs = (process as any).binding('fs');
|
||||||
|
origLStat = bindingFs.lstat;
|
||||||
|
if (typeof origLStat === 'function') {
|
||||||
|
bindingFs.lstat = patchedLStat;
|
||||||
|
}
|
||||||
|
} catch(ex) {
|
||||||
|
// If some future version of Node throws (e.g., to prevent use of process.binding()),
|
||||||
|
// don't apply the patch, but still let the application run.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||||
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>
|
||||||
|
|||||||
935
src/Microsoft.AspNetCore.NodeServices/baseline.netcore.json
Normal file
935
src/Microsoft.AspNetCore.NodeServices/baseline.netcore.json
Normal file
@@ -0,0 +1,935 @@
|
|||||||
|
{
|
||||||
|
"AssemblyIdentity": "Microsoft.AspNetCore.NodeServices, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
||||||
|
"Types": [
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.Extensions.DependencyInjection.NodeServicesServiceCollectionExtensions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"Static": true,
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "AddNodeServices",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "serviceCollection",
|
||||||
|
"Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "AddNodeServices",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "serviceCollection",
|
||||||
|
"Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "setupAction",
|
||||||
|
"Type": "System.Action<Microsoft.AspNetCore.NodeServices.NodeServicesOptions>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.NodeServicesFactory",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"Static": true,
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "CreateNodeServices",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "options",
|
||||||
|
"Type": "Microsoft.AspNetCore.NodeServices.NodeServicesOptions"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "Microsoft.AspNetCore.NodeServices.INodeServices",
|
||||||
|
"Static": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.NodeServicesOptions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_NodeInstanceFactory",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Func<Microsoft.AspNetCore.NodeServices.HostingModels.INodeInstance>",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_NodeInstanceFactory",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.Func<Microsoft.AspNetCore.NodeServices.HostingModels.INodeInstance>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_ProjectPath",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.String",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_ProjectPath",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_WatchFileExtensions",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.String[]",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_WatchFileExtensions",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.String[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_NodeInstanceOutputLogger",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "Microsoft.Extensions.Logging.ILogger",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_NodeInstanceOutputLogger",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "Microsoft.Extensions.Logging.ILogger"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_LaunchWithDebugging",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Boolean",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_LaunchWithDebugging",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.Boolean"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_DebuggingPort",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Int32",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_DebuggingPort",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.Int32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_EnvironmentVariables",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Collections.Generic.IDictionary<System.String, System.String>",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_EnvironmentVariables",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.Collections.Generic.IDictionary<System.String, System.String>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_InvocationTimeoutMilliseconds",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Int32",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_InvocationTimeoutMilliseconds",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.Int32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_ApplicationStoppingToken",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Threading.CancellationToken",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_ApplicationStoppingToken",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.Threading.CancellationToken"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "serviceProvider",
|
||||||
|
"Type": "System.IServiceProvider"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.INodeServices",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Interface",
|
||||||
|
"Abstract": true,
|
||||||
|
"ImplementedInterfaces": [
|
||||||
|
"System.IDisposable"
|
||||||
|
],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "InvokeAsync<T0>",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "moduleName",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "args",
|
||||||
|
"Type": "System.Object[]",
|
||||||
|
"IsParams": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Threading.Tasks.Task<T0>",
|
||||||
|
"GenericParameter": [
|
||||||
|
{
|
||||||
|
"ParameterName": "T",
|
||||||
|
"ParameterPosition": 0,
|
||||||
|
"BaseTypeOrInterfaces": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "InvokeAsync<T0>",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "cancellationToken",
|
||||||
|
"Type": "System.Threading.CancellationToken"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "moduleName",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "args",
|
||||||
|
"Type": "System.Object[]",
|
||||||
|
"IsParams": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Threading.Tasks.Task<T0>",
|
||||||
|
"GenericParameter": [
|
||||||
|
{
|
||||||
|
"ParameterName": "T",
|
||||||
|
"ParameterPosition": 0,
|
||||||
|
"BaseTypeOrInterfaces": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "InvokeExportAsync<T0>",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "moduleName",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "exportedFunctionName",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "args",
|
||||||
|
"Type": "System.Object[]",
|
||||||
|
"IsParams": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Threading.Tasks.Task<T0>",
|
||||||
|
"GenericParameter": [
|
||||||
|
{
|
||||||
|
"ParameterName": "T",
|
||||||
|
"ParameterPosition": 0,
|
||||||
|
"BaseTypeOrInterfaces": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "InvokeExportAsync<T0>",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "cancellationToken",
|
||||||
|
"Type": "System.Threading.CancellationToken"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "moduleName",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "exportedFunctionName",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "args",
|
||||||
|
"Type": "System.Object[]",
|
||||||
|
"IsParams": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Threading.Tasks.Task<T0>",
|
||||||
|
"GenericParameter": [
|
||||||
|
{
|
||||||
|
"ParameterName": "T",
|
||||||
|
"ParameterPosition": 0,
|
||||||
|
"BaseTypeOrInterfaces": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.EmbeddedResourceReader",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"Static": true,
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Read",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "assemblyContainingType",
|
||||||
|
"Type": "System.Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "path",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.String",
|
||||||
|
"Static": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.StringAsTempFile",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [
|
||||||
|
"System.IDisposable"
|
||||||
|
],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_FileName",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.String",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Dispose",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Sealed": true,
|
||||||
|
"Virtual": true,
|
||||||
|
"ImplementedInterface": "System.IDisposable",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Finalize",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Override": true,
|
||||||
|
"Visibility": "Protected",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "content",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "applicationStoppingToken",
|
||||||
|
"Type": "System.Threading.CancellationToken"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.HostingModels.INodeInstance",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Interface",
|
||||||
|
"Abstract": true,
|
||||||
|
"ImplementedInterfaces": [
|
||||||
|
"System.IDisposable"
|
||||||
|
],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "InvokeExportAsync<T0>",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "cancellationToken",
|
||||||
|
"Type": "System.Threading.CancellationToken"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "moduleName",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "exportNameOrNull",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "args",
|
||||||
|
"Type": "System.Object[]",
|
||||||
|
"IsParams": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Threading.Tasks.Task<T0>",
|
||||||
|
"GenericParameter": [
|
||||||
|
{
|
||||||
|
"ParameterName": "T",
|
||||||
|
"ParameterPosition": 0,
|
||||||
|
"BaseTypeOrInterfaces": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.HostingModels.NodeInvocationException",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"BaseType": "System.Exception",
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_NodeInstanceUnavailable",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Boolean",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_AllowConnectionDraining",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Boolean",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "message",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "details",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "message",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "details",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "nodeInstanceUnavailable",
|
||||||
|
"Type": "System.Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "allowConnectionDraining",
|
||||||
|
"Type": "System.Boolean"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.HostingModels.NodeInvocationInfo",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_ModuleName",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.String",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_ModuleName",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_ExportedFunctionName",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.String",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_ExportedFunctionName",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_Args",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Object[]",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_Args",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.Object[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.HostingModels.NodeServicesOptionsExtensions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"Static": true,
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "UseHttpHosting",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "options",
|
||||||
|
"Type": "Microsoft.AspNetCore.NodeServices.NodeServicesOptions"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.HostingModels.OutOfProcessNodeInstance",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"ImplementedInterfaces": [
|
||||||
|
"Microsoft.AspNetCore.NodeServices.HostingModels.INodeInstance"
|
||||||
|
],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Dispose",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Sealed": true,
|
||||||
|
"Virtual": true,
|
||||||
|
"ImplementedInterface": "System.IDisposable",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "InvokeExportAsync<T0>",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "cancellationToken",
|
||||||
|
"Type": "System.Threading.CancellationToken"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "moduleName",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "exportNameOrNull",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "args",
|
||||||
|
"Type": "System.Object[]",
|
||||||
|
"IsParams": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Threading.Tasks.Task<T0>",
|
||||||
|
"Sealed": true,
|
||||||
|
"Virtual": true,
|
||||||
|
"ImplementedInterface": "Microsoft.AspNetCore.NodeServices.HostingModels.INodeInstance",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": [
|
||||||
|
{
|
||||||
|
"ParameterName": "T",
|
||||||
|
"ParameterPosition": 0,
|
||||||
|
"BaseTypeOrInterfaces": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "InvokeExportAsync<T0>",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "invocationInfo",
|
||||||
|
"Type": "Microsoft.AspNetCore.NodeServices.HostingModels.NodeInvocationInfo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "cancellationToken",
|
||||||
|
"Type": "System.Threading.CancellationToken"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Threading.Tasks.Task<T0>",
|
||||||
|
"Virtual": true,
|
||||||
|
"Abstract": true,
|
||||||
|
"Visibility": "Protected",
|
||||||
|
"GenericParameter": [
|
||||||
|
{
|
||||||
|
"ParameterName": "T",
|
||||||
|
"ParameterPosition": 0,
|
||||||
|
"BaseTypeOrInterfaces": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "PrepareNodeProcessStartInfo",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "entryPointFilename",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "projectPath",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "commandLineArguments",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "environmentVars",
|
||||||
|
"Type": "System.Collections.Generic.IDictionary<System.String, System.String>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "launchWithDebugging",
|
||||||
|
"Type": "System.Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "debuggingPort",
|
||||||
|
"Type": "System.Int32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Diagnostics.ProcessStartInfo",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Protected",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "OnOutputDataReceived",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "outputData",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Protected",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "OnErrorDataReceived",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "errorData",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Protected",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Dispose",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "disposing",
|
||||||
|
"Type": "System.Boolean"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Protected",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Finalize",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Override": true,
|
||||||
|
"Visibility": "Protected",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "entryPointScript",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "projectPath",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "watchFileExtensions",
|
||||||
|
"Type": "System.String[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "commandLineArguments",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "applicationStoppingToken",
|
||||||
|
"Type": "System.Threading.CancellationToken"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "nodeOutputLogger",
|
||||||
|
"Type": "Microsoft.Extensions.Logging.ILogger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "environmentVars",
|
||||||
|
"Type": "System.Collections.Generic.IDictionary<System.String, System.String>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "invocationTimeoutMilliseconds",
|
||||||
|
"Type": "System.Int32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "launchWithDebugging",
|
||||||
|
"Type": "System.Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "debuggingPort",
|
||||||
|
"Type": "System.Int32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Field",
|
||||||
|
"Name": "OutputLogger",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "Microsoft.Extensions.Logging.ILogger",
|
||||||
|
"ReadOnly": true,
|
||||||
|
"Visibility": "Protected",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
4244
src/Microsoft.AspNetCore.NodeServices/package-lock.json
generated
Normal file
4244
src/Microsoft.AspNetCore.NodeServices/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,14 +5,15 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"build": "./node_modules/.bin/webpack"
|
"build": "webpack --mode production"
|
||||||
},
|
},
|
||||||
"author": "Microsoft",
|
"author": "Microsoft",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^6.0.42",
|
"@types/node": "^10.9.2",
|
||||||
"ts-loader": "^0.8.2",
|
"ts-loader": "^4.5.0",
|
||||||
"typescript": "^2.0.0",
|
"typescript": "^3.0.1",
|
||||||
"webpack": "^1.13.1"
|
"webpack": "^4.17.1",
|
||||||
|
"webpack-cli": "^3.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
target: 'node',
|
target: 'node',
|
||||||
externals: ['fs', 'net', 'events', 'readline', 'stream'],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [ '.ts' ]
|
extensions: [ '.ts' ]
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
rules: [
|
||||||
{ test: /\.ts$/, loader: 'ts-loader' },
|
{ test: /\.ts$/, use: 'ts-loader' },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
entry: {
|
entry: {
|
||||||
@@ -14,7 +15,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
libraryTarget: 'commonjs',
|
libraryTarget: 'commonjs',
|
||||||
path: './Content/Node',
|
path: path.join(__dirname, 'Content', 'Node'),
|
||||||
filename: '[name].js'
|
filename: '[name].js'
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.NodeServices.Npm;
|
||||||
|
using Microsoft.AspNetCore.NodeServices.Util;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.Prerendering;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.Util;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.AngularCli
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an implementation of <see cref="ISpaPrerendererBuilder"/> that can build
|
||||||
|
/// an Angular application by invoking the Angular CLI.
|
||||||
|
/// </summary>
|
||||||
|
public class AngularCliBuilder : ISpaPrerendererBuilder
|
||||||
|
{
|
||||||
|
private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine
|
||||||
|
|
||||||
|
private readonly string _npmScriptName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an instance of <see cref="AngularCliBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="npmScript">The name of the script in your package.json file that builds the server-side bundle for your Angular application.</param>
|
||||||
|
public AngularCliBuilder(string npmScript)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(npmScript))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Cannot be null or empty.", nameof(npmScript));
|
||||||
|
}
|
||||||
|
|
||||||
|
_npmScriptName = npmScript;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task Build(ISpaBuilder spaBuilder)
|
||||||
|
{
|
||||||
|
var sourcePath = spaBuilder.Options.SourcePath;
|
||||||
|
if (string.IsNullOrEmpty(sourcePath))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"To use {nameof(AngularCliBuilder)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var logger = LoggerFinder.GetOrCreateLogger(
|
||||||
|
spaBuilder.ApplicationBuilder,
|
||||||
|
nameof(AngularCliBuilder));
|
||||||
|
var npmScriptRunner = new NpmScriptRunner(
|
||||||
|
sourcePath,
|
||||||
|
_npmScriptName,
|
||||||
|
"--watch",
|
||||||
|
null);
|
||||||
|
npmScriptRunner.AttachToLogger(logger);
|
||||||
|
|
||||||
|
using (var stdOutReader = new EventedStreamStringReader(npmScriptRunner.StdOut))
|
||||||
|
using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await npmScriptRunner.StdOut.WaitForMatch(
|
||||||
|
new Regex("Date", RegexOptions.None, RegexMatchTimeout));
|
||||||
|
}
|
||||||
|
catch (EndOfStreamException ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"The NPM script '{_npmScriptName}' exited without indicating success.\n" +
|
||||||
|
$"Output was: {stdOutReader.ReadAsString()}\n" +
|
||||||
|
$"Error output was: {stdErrReader.ReadAsString()}", ex);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"The NPM script '{_npmScriptName}' timed out without indicating success. " +
|
||||||
|
$"Output was: {stdOutReader.ReadAsString()}\n" +
|
||||||
|
$"Error output was: {stdErrReader.ReadAsString()}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.AspNetCore.NodeServices.Npm;
|
||||||
|
using Microsoft.AspNetCore.NodeServices.Util;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.Util;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.Extensions.Util;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.AngularCli
|
||||||
|
{
|
||||||
|
internal static class AngularCliMiddleware
|
||||||
|
{
|
||||||
|
private const string LogCategoryName = "Microsoft.AspNetCore.SpaServices";
|
||||||
|
private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine
|
||||||
|
|
||||||
|
public static void Attach(
|
||||||
|
ISpaBuilder spaBuilder,
|
||||||
|
string npmScriptName)
|
||||||
|
{
|
||||||
|
var sourcePath = spaBuilder.Options.SourcePath;
|
||||||
|
if (string.IsNullOrEmpty(sourcePath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(sourcePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(npmScriptName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(npmScriptName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start Angular CLI and attach to middleware pipeline
|
||||||
|
var appBuilder = spaBuilder.ApplicationBuilder;
|
||||||
|
var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName);
|
||||||
|
var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, npmScriptName, logger);
|
||||||
|
|
||||||
|
// Everything we proxy is hardcoded to target http://localhost because:
|
||||||
|
// - the requests are always from the local machine (we're not accepting remote
|
||||||
|
// requests that go directly to the Angular CLI middleware server)
|
||||||
|
// - given that, there's no reason to use https, and we couldn't even if we
|
||||||
|
// wanted to, because in general the Angular CLI server has no certificate
|
||||||
|
var targetUriTask = angularCliServerInfoTask.ContinueWith(
|
||||||
|
task => new UriBuilder("http", "localhost", task.Result.Port).Uri);
|
||||||
|
|
||||||
|
SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, () =>
|
||||||
|
{
|
||||||
|
// On each request, we create a separate startup task with its own timeout. That way, even if
|
||||||
|
// the first request times out, subsequent requests could still work.
|
||||||
|
var timeout = spaBuilder.Options.StartupTimeout;
|
||||||
|
return targetUriTask.WithTimeout(timeout,
|
||||||
|
$"The Angular CLI process did not start listening for requests " +
|
||||||
|
$"within the timeout period of {timeout.Seconds} seconds. " +
|
||||||
|
$"Check the log output for error information.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<AngularCliServerInfo> StartAngularCliServerAsync(
|
||||||
|
string sourcePath, string npmScriptName, ILogger logger)
|
||||||
|
{
|
||||||
|
var portNumber = TcpPortFinder.FindAvailablePort();
|
||||||
|
logger.LogInformation($"Starting @angular/cli on port {portNumber}...");
|
||||||
|
|
||||||
|
var npmScriptRunner = new NpmScriptRunner(
|
||||||
|
sourcePath, npmScriptName, $"--port {portNumber}", null);
|
||||||
|
npmScriptRunner.AttachToLogger(logger);
|
||||||
|
|
||||||
|
Match openBrowserLine;
|
||||||
|
using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch(
|
||||||
|
new Regex("open your browser on (http\\S+)", RegexOptions.None, RegexMatchTimeout));
|
||||||
|
}
|
||||||
|
catch (EndOfStreamException ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"The NPM script '{npmScriptName}' exited without indicating that the " +
|
||||||
|
$"Angular CLI was listening for requests. The error output was: " +
|
||||||
|
$"{stdErrReader.ReadAsString()}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = new Uri(openBrowserLine.Groups[1].Value);
|
||||||
|
var serverInfo = new AngularCliServerInfo { Port = uri.Port };
|
||||||
|
|
||||||
|
// Even after the Angular CLI claims to be listening for requests, there's a short
|
||||||
|
// period where it will give an error if you make a request too quickly
|
||||||
|
await WaitForAngularCliServerToAcceptRequests(uri);
|
||||||
|
|
||||||
|
return serverInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task WaitForAngularCliServerToAcceptRequests(Uri cliServerUri)
|
||||||
|
{
|
||||||
|
// To determine when it's actually ready, try making HEAD requests to '/'. If it
|
||||||
|
// produces any HTTP response (even if it's 404) then it's ready. If it rejects the
|
||||||
|
// connection then it's not ready. We keep trying forever because this is dev-mode
|
||||||
|
// only, and only a single startup attempt will be made, and there's a further level
|
||||||
|
// of timeouts enforced on a per-request basis.
|
||||||
|
var timeoutMilliseconds = 1000;
|
||||||
|
using (var client = new HttpClient())
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// If we get any HTTP response, the CLI server is ready
|
||||||
|
await client.SendAsync(
|
||||||
|
new HttpRequestMessage(HttpMethod.Head, cliServerUri),
|
||||||
|
new CancellationTokenSource(timeoutMilliseconds).Token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
// Depending on the host's networking configuration, the requests can take a while
|
||||||
|
// to go through, most likely due to the time spent resolving 'localhost'.
|
||||||
|
// Each time we have a failure, allow a bit longer next time (up to a maximum).
|
||||||
|
// This only influences the time until we regard the dev server as 'ready', so it
|
||||||
|
// doesn't affect the runtime perf (even in dev mode) once the first connection is made.
|
||||||
|
// Resolves https://github.com/aspnet/JavaScriptServices/issues/1611
|
||||||
|
if (timeoutMilliseconds < 10000)
|
||||||
|
{
|
||||||
|
timeoutMilliseconds += 3000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AngularCliServerInfo
|
||||||
|
{
|
||||||
|
public int Port { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.AngularCli
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for enabling Angular CLI middleware support.
|
||||||
|
/// </summary>
|
||||||
|
public static class AngularCliMiddlewareExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles requests by passing them through to an instance of the Angular CLI server.
|
||||||
|
/// This means you can always serve up-to-date CLI-built resources without having
|
||||||
|
/// to run the Angular CLI server manually.
|
||||||
|
///
|
||||||
|
/// This feature should only be used in development. For production deployments, be
|
||||||
|
/// sure not to enable the Angular CLI server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
|
||||||
|
/// <param name="npmScript">The name of the script in your package.json file that launches the Angular CLI process.</param>
|
||||||
|
public static void UseAngularCliServer(
|
||||||
|
this ISpaBuilder spaBuilder,
|
||||||
|
string npmScript)
|
||||||
|
{
|
||||||
|
if (spaBuilder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(spaBuilder));
|
||||||
|
}
|
||||||
|
|
||||||
|
var spaOptions = spaBuilder.Options;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(spaOptions.SourcePath))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"To use {nameof(UseAngularCliServer)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
AngularCliMiddleware.Attach(spaBuilder, npmScript);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices
|
||||||
|
{
|
||||||
|
internal class DefaultSpaBuilder : ISpaBuilder
|
||||||
|
{
|
||||||
|
public IApplicationBuilder ApplicationBuilder { get; }
|
||||||
|
|
||||||
|
public SpaOptions Options { get; }
|
||||||
|
|
||||||
|
public DefaultSpaBuilder(IApplicationBuilder applicationBuilder, SpaOptions options)
|
||||||
|
{
|
||||||
|
ApplicationBuilder = applicationBuilder
|
||||||
|
?? throw new ArgumentNullException(nameof(applicationBuilder));
|
||||||
|
|
||||||
|
Options = options
|
||||||
|
?? throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a class that provides mechanisms for configuring the hosting
|
||||||
|
/// of a Single Page Application (SPA) and attaching middleware.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISpaBuilder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="IApplicationBuilder"/> representing the middleware pipeline
|
||||||
|
/// in which the SPA is being hosted.
|
||||||
|
/// </summary>
|
||||||
|
IApplicationBuilder ApplicationBuilder { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes configuration options for hosting a SPA.
|
||||||
|
/// </summary>
|
||||||
|
SpaOptions Options { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>Helpers for building single-page applications on ASP.NET MVC Core.</Description>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Microsoft.AspNetCore.SpaServices\Microsoft.AspNetCore.SpaServices.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="$(MicrosoftAspNetCoreWebSocketsPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="$(MicrosoftExtensionsFileProvidersPhysicalPackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.AspNetCore.NodeServices.Util;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
// This is under the NodeServices namespace because post 2.1 it will be moved to that package
|
||||||
|
namespace Microsoft.AspNetCore.NodeServices.Npm
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the <c>script</c> entries defined in a <c>package.json</c> file,
|
||||||
|
/// capturing any output written to stdio.
|
||||||
|
/// </summary>
|
||||||
|
internal class NpmScriptRunner
|
||||||
|
{
|
||||||
|
public EventedStreamReader StdOut { get; }
|
||||||
|
public EventedStreamReader StdErr { get; }
|
||||||
|
|
||||||
|
private static Regex AnsiColorRegex = new Regex("\x001b\\[[0-9;]*m", RegexOptions.None, TimeSpan.FromSeconds(1));
|
||||||
|
|
||||||
|
public NpmScriptRunner(string workingDirectory, string scriptName, string arguments, IDictionary<string, string> envVars)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(workingDirectory))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Cannot be null or empty.", nameof(workingDirectory));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(scriptName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Cannot be null or empty.", nameof(scriptName));
|
||||||
|
}
|
||||||
|
|
||||||
|
var npmExe = "npm";
|
||||||
|
var completeArguments = $"run {scriptName} -- {arguments ?? string.Empty}";
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
// On Windows, the NPM executable is a .cmd file, so it can't be executed
|
||||||
|
// directly (except with UseShellExecute=true, but that's no good, because
|
||||||
|
// it prevents capturing stdio). So we need to invoke it via "cmd /c".
|
||||||
|
npmExe = "cmd";
|
||||||
|
completeArguments = $"/c npm {completeArguments}";
|
||||||
|
}
|
||||||
|
|
||||||
|
var processStartInfo = new ProcessStartInfo(npmExe)
|
||||||
|
{
|
||||||
|
Arguments = completeArguments,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardInput = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
WorkingDirectory = workingDirectory
|
||||||
|
};
|
||||||
|
|
||||||
|
if (envVars != null)
|
||||||
|
{
|
||||||
|
foreach (var keyValuePair in envVars)
|
||||||
|
{
|
||||||
|
processStartInfo.Environment[keyValuePair.Key] = keyValuePair.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var process = LaunchNodeProcess(processStartInfo);
|
||||||
|
StdOut = new EventedStreamReader(process.StandardOutput);
|
||||||
|
StdErr = new EventedStreamReader(process.StandardError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttachToLogger(ILogger logger)
|
||||||
|
{
|
||||||
|
// When the NPM task emits complete lines, pass them through to the real logger
|
||||||
|
StdOut.OnReceivedLine += line =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(line))
|
||||||
|
{
|
||||||
|
// NPM tasks commonly emit ANSI colors, but it wouldn't make sense to forward
|
||||||
|
// those to loggers (because a logger isn't necessarily any kind of terminal)
|
||||||
|
logger.LogInformation(StripAnsiColors(line));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
StdErr.OnReceivedLine += line =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(line))
|
||||||
|
{
|
||||||
|
logger.LogError(StripAnsiColors(line));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// But when it emits incomplete lines, assume this is progress information and
|
||||||
|
// hence just pass it through to StdOut regardless of logger config.
|
||||||
|
StdErr.OnReceivedChunk += chunk =>
|
||||||
|
{
|
||||||
|
var containsNewline = Array.IndexOf(
|
||||||
|
chunk.Array, '\n', chunk.Offset, chunk.Count) >= 0;
|
||||||
|
if (!containsNewline)
|
||||||
|
{
|
||||||
|
Console.Write(chunk.Array, chunk.Offset, chunk.Count);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string StripAnsiColors(string line)
|
||||||
|
=> AnsiColorRegex.Replace(line, string.Empty);
|
||||||
|
|
||||||
|
private static Process LaunchNodeProcess(ProcessStartInfo startInfo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var process = Process.Start(startInfo);
|
||||||
|
|
||||||
|
// See equivalent comment in OutOfProcessNodeInstance.cs for why
|
||||||
|
process.EnableRaisingEvents = true;
|
||||||
|
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var message = $"Failed to start 'npm'. To resolve this:.\n\n"
|
||||||
|
+ "[1] Ensure that 'npm' is installed and can be found in one of the PATH directories.\n"
|
||||||
|
+ $" Current PATH enviroment variable is: { Environment.GetEnvironmentVariable("PATH") }\n"
|
||||||
|
+ " Make sure the executable is in one of those directories, or update your PATH.\n\n"
|
||||||
|
+ "[2] See the InnerException for further details of the cause.";
|
||||||
|
throw new InvalidOperationException(message, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the ability to build a Single Page Application (SPA) on demand
|
||||||
|
/// so that it can be prerendered. This is only intended to be used at development
|
||||||
|
/// time. In production, a SPA should already have been built during publishing.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISpaPrerendererBuilder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Builds the Single Page Application so that a JavaScript entrypoint file
|
||||||
|
/// exists on disk. Prerendering middleware can then execute that file in
|
||||||
|
/// a Node environment.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> representing completion of the build process.</returns>
|
||||||
|
Task Build(ISpaBuilder spaBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.NodeServices;
|
||||||
|
using Microsoft.AspNetCore.SpaServices;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.Extensions.Util;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.Prerendering;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for configuring prerendering of a Single Page Application.
|
||||||
|
/// </summary>
|
||||||
|
public static class SpaPrerenderingExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enables server-side prerendering middleware for a Single Page Application.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
|
||||||
|
/// <param name="configuration">Supplies configuration for the prerendering middleware.</param>
|
||||||
|
public static void UseSpaPrerendering(
|
||||||
|
this ISpaBuilder spaBuilder,
|
||||||
|
Action<SpaPrerenderingOptions> configuration)
|
||||||
|
{
|
||||||
|
if (spaBuilder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(spaBuilder));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configuration));
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = new SpaPrerenderingOptions();
|
||||||
|
configuration.Invoke(options);
|
||||||
|
|
||||||
|
var capturedBootModulePath = options.BootModulePath;
|
||||||
|
if (string.IsNullOrEmpty(capturedBootModulePath))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"To use {nameof(UseSpaPrerendering)}, you " +
|
||||||
|
$"must set a nonempty value on the ${nameof(SpaPrerenderingOptions.BootModulePath)} " +
|
||||||
|
$"property on the ${nameof(SpaPrerenderingOptions)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're building on demand, start that process in the background now
|
||||||
|
var buildOnDemandTask = options.BootModuleBuilder?.Build(spaBuilder);
|
||||||
|
|
||||||
|
// Get all the necessary context info that will be used for each prerendering call
|
||||||
|
var applicationBuilder = spaBuilder.ApplicationBuilder;
|
||||||
|
var serviceProvider = applicationBuilder.ApplicationServices;
|
||||||
|
var nodeServices = GetNodeServices(serviceProvider);
|
||||||
|
var applicationStoppingToken = serviceProvider.GetRequiredService<IApplicationLifetime>()
|
||||||
|
.ApplicationStopping;
|
||||||
|
var applicationBasePath = serviceProvider.GetRequiredService<IHostingEnvironment>()
|
||||||
|
.ContentRootPath;
|
||||||
|
var moduleExport = new JavaScriptModuleExport(capturedBootModulePath);
|
||||||
|
var excludePathStrings = (options.ExcludeUrls ?? Array.Empty<string>())
|
||||||
|
.Select(url => new PathString(url))
|
||||||
|
.ToArray();
|
||||||
|
var buildTimeout = spaBuilder.Options.StartupTimeout;
|
||||||
|
|
||||||
|
applicationBuilder.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
// If this URL is excluded, skip prerendering.
|
||||||
|
// This is typically used to ensure that static client-side resources
|
||||||
|
// (e.g., /dist/*.css) are served normally or through SPA development
|
||||||
|
// middleware, and don't return the prerendered index.html page.
|
||||||
|
foreach (var excludePathString in excludePathStrings)
|
||||||
|
{
|
||||||
|
if (context.Request.Path.StartsWithSegments(excludePathString))
|
||||||
|
{
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're building on demand, wait for that to finish, or raise any build errors
|
||||||
|
if (buildOnDemandTask != null && !buildOnDemandTask.IsCompleted)
|
||||||
|
{
|
||||||
|
// For better debuggability, create a per-request timeout that makes it clear if the
|
||||||
|
// prerendering builder took too long for this request, but without aborting the
|
||||||
|
// underlying build task so that subsequent requests could still work.
|
||||||
|
await buildOnDemandTask.WithTimeout(buildTimeout,
|
||||||
|
$"The prerendering build process did not complete within the " +
|
||||||
|
$"timeout period of {buildTimeout.Seconds} seconds. " +
|
||||||
|
$"Check the log output for error information.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's no good if we try to return a 304. We need to capture the actual
|
||||||
|
// HTML content so it can be passed as a template to the prerenderer.
|
||||||
|
RemoveConditionalRequestHeaders(context.Request);
|
||||||
|
|
||||||
|
// Make sure we're not capturing compressed content, because then we'd have
|
||||||
|
// to decompress it. Since this sub-request isn't leaving the machine, there's
|
||||||
|
// little to no benefit in having compression on it.
|
||||||
|
var originalAcceptEncodingValue = GetAndRemoveAcceptEncodingHeader(context.Request);
|
||||||
|
|
||||||
|
// Capture the non-prerendered responses, which in production will typically only
|
||||||
|
// be returning the default SPA index.html page (because other resources will be
|
||||||
|
// served statically from disk). We will use this as a template in which to inject
|
||||||
|
// the prerendered output.
|
||||||
|
using (var outputBuffer = new MemoryStream())
|
||||||
|
{
|
||||||
|
var originalResponseStream = context.Response.Body;
|
||||||
|
context.Response.Body = outputBuffer;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await next();
|
||||||
|
outputBuffer.Seek(0, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
context.Response.Body = originalResponseStream;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(originalAcceptEncodingValue))
|
||||||
|
{
|
||||||
|
context.Request.Headers[HeaderNames.AcceptEncoding] = originalAcceptEncodingValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it isn't an HTML page that we can use as the template for prerendering,
|
||||||
|
// - ... because it's not text/html
|
||||||
|
// - ... or because it's an error
|
||||||
|
// then prerendering doesn't apply to this request, so just pass through the
|
||||||
|
// response as-is. Note that the non-text/html case is not an error: this is
|
||||||
|
// typically how the SPA dev server responses for static content are returned
|
||||||
|
// in development mode.
|
||||||
|
var canPrerender = IsSuccessStatusCode(context.Response.StatusCode)
|
||||||
|
&& IsHtmlContentType(context.Response.ContentType);
|
||||||
|
if (!canPrerender)
|
||||||
|
{
|
||||||
|
await outputBuffer.CopyToAsync(context.Response.Body);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most prerendering logic will want to know about the original, unprerendered
|
||||||
|
// HTML that the client would be getting otherwise. Typically this is used as
|
||||||
|
// a template from which the fully prerendered page can be generated.
|
||||||
|
var customData = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "originalHtml", Encoding.UTF8.GetString(outputBuffer.GetBuffer()) }
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the developer wants to use custom logic to pass arbitrary data to the
|
||||||
|
// prerendering JS code (e.g., to pass through cookie data), now's their chance
|
||||||
|
options.SupplyData?.Invoke(context, customData);
|
||||||
|
|
||||||
|
var (unencodedAbsoluteUrl, unencodedPathAndQuery)
|
||||||
|
= GetUnencodedUrlAndPathQuery(context);
|
||||||
|
var renderResult = await Prerenderer.RenderToString(
|
||||||
|
applicationBasePath,
|
||||||
|
nodeServices,
|
||||||
|
applicationStoppingToken,
|
||||||
|
moduleExport,
|
||||||
|
unencodedAbsoluteUrl,
|
||||||
|
unencodedPathAndQuery,
|
||||||
|
customDataParameter: customData,
|
||||||
|
timeoutMilliseconds: 0,
|
||||||
|
requestPathBase: context.Request.PathBase.ToString());
|
||||||
|
|
||||||
|
await ServePrerenderResult(context, renderResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsHtmlContentType(string contentType)
|
||||||
|
{
|
||||||
|
if (string.Equals(contentType, "text/html", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentType != null
|
||||||
|
&& contentType.StartsWith("text/html;", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSuccessStatusCode(int statusCode)
|
||||||
|
=> statusCode >= 200 && statusCode < 300;
|
||||||
|
|
||||||
|
private static void RemoveConditionalRequestHeaders(HttpRequest request)
|
||||||
|
{
|
||||||
|
request.Headers.Remove(HeaderNames.IfMatch);
|
||||||
|
request.Headers.Remove(HeaderNames.IfModifiedSince);
|
||||||
|
request.Headers.Remove(HeaderNames.IfNoneMatch);
|
||||||
|
request.Headers.Remove(HeaderNames.IfUnmodifiedSince);
|
||||||
|
request.Headers.Remove(HeaderNames.IfRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetAndRemoveAcceptEncodingHeader(HttpRequest request)
|
||||||
|
{
|
||||||
|
var headers = request.Headers;
|
||||||
|
var value = (string)null;
|
||||||
|
|
||||||
|
if (headers.ContainsKey(HeaderNames.AcceptEncoding))
|
||||||
|
{
|
||||||
|
value = headers[HeaderNames.AcceptEncoding];
|
||||||
|
headers.Remove(HeaderNames.AcceptEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (string, string) GetUnencodedUrlAndPathQuery(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
// This is a duplicate of code from Prerenderer.cs in the SpaServices package.
|
||||||
|
// Once the SpaServices.Extension package implementation gets merged back into
|
||||||
|
// SpaServices, this duplicate can be removed. To remove this, change the code
|
||||||
|
// above that calls Prerenderer.RenderToString to use the internal overload
|
||||||
|
// that takes an HttpContext instead of a url/path+query pair.
|
||||||
|
var requestFeature = httpContext.Features.Get<IHttpRequestFeature>();
|
||||||
|
var unencodedPathAndQuery = requestFeature.RawTarget;
|
||||||
|
var request = httpContext.Request;
|
||||||
|
var unencodedAbsoluteUrl = $"{request.Scheme}://{request.Host}{unencodedPathAndQuery}";
|
||||||
|
return (unencodedAbsoluteUrl, unencodedPathAndQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task ServePrerenderResult(HttpContext context, RenderToStringResult renderResult)
|
||||||
|
{
|
||||||
|
context.Response.Clear();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(renderResult.RedirectUrl))
|
||||||
|
{
|
||||||
|
var permanentRedirect = renderResult.StatusCode.GetValueOrDefault() == 301;
|
||||||
|
context.Response.Redirect(renderResult.RedirectUrl, permanentRedirect);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The Globals property exists for back-compatibility but is meaningless
|
||||||
|
// for prerendering that returns complete HTML pages
|
||||||
|
if (renderResult.Globals != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{nameof(renderResult.Globals)} is not " +
|
||||||
|
$"supported when prerendering via {nameof(UseSpaPrerendering)}(). Instead, " +
|
||||||
|
$"your prerendering logic should return a complete HTML page, in which you " +
|
||||||
|
$"embed any information you wish to return to the client.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renderResult.StatusCode.HasValue)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = renderResult.StatusCode.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.ContentType = "text/html";
|
||||||
|
await context.Response.WriteAsync(renderResult.Html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static INodeServices GetNodeServices(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
// Use the registered instance, or create a new private instance if none is registered
|
||||||
|
var instance = (INodeServices)serviceProvider.GetService(typeof(INodeServices));
|
||||||
|
return instance ?? NodeServicesFactory.CreateNodeServices(
|
||||||
|
new NodeServicesOptions(serviceProvider));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.Prerendering;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents options for the SPA prerendering middleware.
|
||||||
|
/// </summary>
|
||||||
|
public class SpaPrerenderingOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an <see cref="ISpaPrerendererBuilder"/> that the prerenderer will invoke before
|
||||||
|
/// looking for the boot module file.
|
||||||
|
///
|
||||||
|
/// This is only intended to be used during development as a way of generating the JavaScript boot
|
||||||
|
/// file automatically when the application runs. This property should be left as <c>null</c> in
|
||||||
|
/// production applications.
|
||||||
|
/// </summary>
|
||||||
|
public ISpaPrerendererBuilder BootModuleBuilder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path, relative to your application root, of the JavaScript file
|
||||||
|
/// containing prerendering logic.
|
||||||
|
/// </summary>
|
||||||
|
public string BootModulePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an array of URL prefixes for which prerendering should not run.
|
||||||
|
/// </summary>
|
||||||
|
public string[] ExcludeUrls { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a callback that will be invoked during prerendering, allowing you to pass additional
|
||||||
|
/// data to the prerendering entrypoint code.
|
||||||
|
/// </summary>
|
||||||
|
public Action<HttpContext, IDictionary<string, object>> SupplyData { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.Extensions.Proxy
|
||||||
|
{
|
||||||
|
// This duplicates and updates the proxying logic in SpaServices so that we can update
|
||||||
|
// the project templates without waiting for 2.1 to ship. When 2.1 is ready to ship,
|
||||||
|
// merge the additional proxying features (e.g., proxying websocket connections) back
|
||||||
|
// into the SpaServices proxying code. It's all internal.
|
||||||
|
internal class ConditionalProxyMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly Task<Uri> _baseUriTask;
|
||||||
|
private readonly string _pathPrefix;
|
||||||
|
private readonly bool _pathPrefixIsRoot;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly CancellationToken _applicationStoppingToken;
|
||||||
|
|
||||||
|
public ConditionalProxyMiddleware(
|
||||||
|
RequestDelegate next,
|
||||||
|
string pathPrefix,
|
||||||
|
TimeSpan requestTimeout,
|
||||||
|
Task<Uri> baseUriTask,
|
||||||
|
IApplicationLifetime applicationLifetime)
|
||||||
|
{
|
||||||
|
if (!pathPrefix.StartsWith("/"))
|
||||||
|
{
|
||||||
|
pathPrefix = "/" + pathPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
_next = next;
|
||||||
|
_pathPrefix = pathPrefix;
|
||||||
|
_pathPrefixIsRoot = string.Equals(_pathPrefix, "/", StringComparison.Ordinal);
|
||||||
|
_baseUriTask = baseUriTask;
|
||||||
|
_httpClient = SpaProxy.CreateHttpClientForProxy(requestTimeout);
|
||||||
|
_applicationStoppingToken = applicationLifetime.ApplicationStopping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
if (context.Request.Path.StartsWithSegments(_pathPrefix) || _pathPrefixIsRoot)
|
||||||
|
{
|
||||||
|
var didProxyRequest = await SpaProxy.PerformProxyRequest(
|
||||||
|
context, _httpClient, _baseUriTask, _applicationStoppingToken, proxy404s: false);
|
||||||
|
if (didProxyRequest)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a request we can proxy
|
||||||
|
await _next.Invoke(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.Extensions.Proxy
|
||||||
|
{
|
||||||
|
// This duplicates and updates the proxying logic in SpaServices so that we can update
|
||||||
|
// the project templates without waiting for 2.1 to ship. When 2.1 is ready to ship,
|
||||||
|
// remove the old ConditionalProxy.cs from SpaServices and replace its usages with this.
|
||||||
|
// Doesn't affect public API surface - it's all internal.
|
||||||
|
internal static class SpaProxy
|
||||||
|
{
|
||||||
|
private const int DefaultWebSocketBufferSize = 4096;
|
||||||
|
private const int StreamCopyBufferSize = 81920;
|
||||||
|
|
||||||
|
// Don't forward User-Agent/Accept because of https://github.com/aspnet/JavaScriptServices/issues/1469
|
||||||
|
// Others just aren't applicable in proxy scenarios
|
||||||
|
private static readonly string[] NotForwardedWebSocketHeaders = new[] { "Accept", "Connection", "Host", "User-Agent", "Upgrade", "Sec-WebSocket-Key", "Sec-WebSocket-Version" };
|
||||||
|
|
||||||
|
public static HttpClient CreateHttpClientForProxy(TimeSpan requestTimeout)
|
||||||
|
{
|
||||||
|
var handler = new HttpClientHandler
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = false,
|
||||||
|
UseCookies = false,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return new HttpClient(handler)
|
||||||
|
{
|
||||||
|
Timeout = requestTimeout
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<bool> PerformProxyRequest(
|
||||||
|
HttpContext context,
|
||||||
|
HttpClient httpClient,
|
||||||
|
Task<Uri> baseUriTask,
|
||||||
|
CancellationToken applicationStoppingToken,
|
||||||
|
bool proxy404s)
|
||||||
|
{
|
||||||
|
// Stop proxying if either the server or client wants to disconnect
|
||||||
|
var proxyCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(
|
||||||
|
context.RequestAborted,
|
||||||
|
applicationStoppingToken).Token;
|
||||||
|
|
||||||
|
// We allow for the case where the target isn't known ahead of time, and want to
|
||||||
|
// delay proxied requests until the target becomes known. This is useful, for example,
|
||||||
|
// when proxying to Angular CLI middleware: we won't know what port it's listening
|
||||||
|
// on until it finishes starting up.
|
||||||
|
var baseUri = await baseUriTask;
|
||||||
|
var targetUri = new Uri(
|
||||||
|
baseUri,
|
||||||
|
context.Request.Path + context.Request.QueryString);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (context.WebSockets.IsWebSocketRequest)
|
||||||
|
{
|
||||||
|
await AcceptProxyWebSocketRequest(context, ToWebSocketScheme(targetUri), proxyCancellationToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var requestMessage = CreateProxyHttpRequest(context, targetUri))
|
||||||
|
using (var responseMessage = await httpClient.SendAsync(
|
||||||
|
requestMessage,
|
||||||
|
HttpCompletionOption.ResponseHeadersRead,
|
||||||
|
proxyCancellationToken))
|
||||||
|
{
|
||||||
|
if (!proxy404s)
|
||||||
|
{
|
||||||
|
if (responseMessage.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
// We're not proxying 404s, i.e., we want to resume the middleware pipeline
|
||||||
|
// and let some other middleware handle this.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await CopyProxyHttpResponse(context, responseMessage, proxyCancellationToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// If we're aborting because either the client disconnected, or the server
|
||||||
|
// is shutting down, don't treat this as an error.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
// This kind of exception can also occur if a proxy read/write gets interrupted
|
||||||
|
// due to the process shutting down.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
throw new HttpRequestException(
|
||||||
|
$"Failed to proxy the request to {targetUri.ToString()}, because the request to " +
|
||||||
|
$"the proxy target failed. Check that the proxy target server is running and " +
|
||||||
|
$"accepting requests to {baseUri.ToString()}.\n\n" +
|
||||||
|
$"The underlying exception message was '{ex.Message}'." +
|
||||||
|
$"Check the InnerException for more details.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpRequestMessage CreateProxyHttpRequest(HttpContext context, Uri uri)
|
||||||
|
{
|
||||||
|
var request = context.Request;
|
||||||
|
|
||||||
|
var requestMessage = new HttpRequestMessage();
|
||||||
|
var requestMethod = request.Method;
|
||||||
|
if (!HttpMethods.IsGet(requestMethod) &&
|
||||||
|
!HttpMethods.IsHead(requestMethod) &&
|
||||||
|
!HttpMethods.IsDelete(requestMethod) &&
|
||||||
|
!HttpMethods.IsTrace(requestMethod))
|
||||||
|
{
|
||||||
|
var streamContent = new StreamContent(request.Body);
|
||||||
|
requestMessage.Content = streamContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the request headers
|
||||||
|
foreach (var header in request.Headers)
|
||||||
|
{
|
||||||
|
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
|
||||||
|
{
|
||||||
|
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestMessage.Headers.Host = uri.Authority;
|
||||||
|
requestMessage.RequestUri = uri;
|
||||||
|
requestMessage.Method = new HttpMethod(request.Method);
|
||||||
|
|
||||||
|
return requestMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CopyProxyHttpResponse(HttpContext context, HttpResponseMessage responseMessage, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
context.Response.Headers[header.Key] = header.Value.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
|
||||||
|
context.Response.Headers.Remove("transfer-encoding");
|
||||||
|
|
||||||
|
using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
|
||||||
|
{
|
||||||
|
await responseStream.CopyToAsync(context.Response.Body, StreamCopyBufferSize, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Uri ToWebSocketScheme(Uri uri)
|
||||||
|
{
|
||||||
|
if (uri == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
var uriBuilder = new UriBuilder(uri);
|
||||||
|
if (string.Equals(uriBuilder.Scheme, "https", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
uriBuilder.Scheme = "wss";
|
||||||
|
}
|
||||||
|
else if (string.Equals(uriBuilder.Scheme, "http", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
uriBuilder.Scheme = "ws";
|
||||||
|
}
|
||||||
|
|
||||||
|
return uriBuilder.Uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> AcceptProxyWebSocketRequest(HttpContext context, Uri destinationUri, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destinationUri == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(destinationUri));
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var client = new ClientWebSocket())
|
||||||
|
{
|
||||||
|
foreach (var headerEntry in context.Request.Headers)
|
||||||
|
{
|
||||||
|
if (!NotForwardedWebSocketHeaders.Contains(headerEntry.Key, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.Options.SetRequestHeader(headerEntry.Key, headerEntry.Value);
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
// On net461, certain header names are reserved and can't be set.
|
||||||
|
// We filter out the known ones via the test above, but there could
|
||||||
|
// be others arbitrarily set by the client. It's not helpful to
|
||||||
|
// consider it an error, so just skip non-forwardable headers.
|
||||||
|
// The perf implications of handling this via a catch aren't an
|
||||||
|
// issue since this is a dev-time only feature.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Note that this is not really good enough to make Websockets work with
|
||||||
|
// Angular CLI middleware. For some reason, ConnectAsync takes over 1 second,
|
||||||
|
// on Windows, by which time the logic in SockJS has already timed out and made
|
||||||
|
// it fall back on some other transport (xhr_streaming, usually). It's fine
|
||||||
|
// on Linux though, completing almost instantly.
|
||||||
|
//
|
||||||
|
// The slowness on Windows does not cause a problem though, because the transport
|
||||||
|
// fallback logic works correctly and doesn't surface any errors, but it would be
|
||||||
|
// better if ConnectAsync was fast enough and the initial Websocket transport
|
||||||
|
// could actually be used.
|
||||||
|
await client.ConnectAsync(destinationUri, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (WebSocketException)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 400;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var server = await context.WebSockets.AcceptWebSocketAsync(client.SubProtocol))
|
||||||
|
{
|
||||||
|
var bufferSize = DefaultWebSocketBufferSize;
|
||||||
|
await Task.WhenAll(
|
||||||
|
PumpWebSocket(client, server, bufferSize, cancellationToken),
|
||||||
|
PumpWebSocket(server, client, bufferSize, cancellationToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task PumpWebSocket(WebSocket source, WebSocket destination, int bufferSize, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (bufferSize <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = new byte[bufferSize];
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Because WebSocket.ReceiveAsync doesn't work well with CancellationToken (it doesn't
|
||||||
|
// actually exit when the token notifies, at least not in the 'server' case), use
|
||||||
|
// polling. The perf might not be ideal, but this is a dev-time feature only.
|
||||||
|
var resultTask = source.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultTask.IsCompleted)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = resultTask.Result; // We know it's completed already
|
||||||
|
if (result.MessageType == WebSocketMessageType.Close)
|
||||||
|
{
|
||||||
|
if (destination.State == WebSocketState.Open || destination.State == WebSocketState.CloseReceived)
|
||||||
|
{
|
||||||
|
await destination.CloseOutputAsync(source.CloseStatus.Value, source.CloseStatusDescription, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await destination.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.SpaServices;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.Extensions.Proxy;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for proxying requests to a local SPA development server during
|
||||||
|
/// development. Not for use in production applications.
|
||||||
|
/// </summary>
|
||||||
|
public static class SpaProxyingExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the application to forward incoming requests to a local Single Page
|
||||||
|
/// Application (SPA) development server. This is only intended to be used during
|
||||||
|
/// development. Do not enable this middleware in production applications.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
|
||||||
|
/// <param name="baseUri">The target base URI to which requests should be proxied.</param>
|
||||||
|
public static void UseProxyToSpaDevelopmentServer(
|
||||||
|
this ISpaBuilder spaBuilder,
|
||||||
|
string baseUri)
|
||||||
|
{
|
||||||
|
UseProxyToSpaDevelopmentServer(
|
||||||
|
spaBuilder,
|
||||||
|
new Uri(baseUri));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the application to forward incoming requests to a local Single Page
|
||||||
|
/// Application (SPA) development server. This is only intended to be used during
|
||||||
|
/// development. Do not enable this middleware in production applications.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
|
||||||
|
/// <param name="baseUri">The target base URI to which requests should be proxied.</param>
|
||||||
|
public static void UseProxyToSpaDevelopmentServer(
|
||||||
|
this ISpaBuilder spaBuilder,
|
||||||
|
Uri baseUri)
|
||||||
|
{
|
||||||
|
UseProxyToSpaDevelopmentServer(
|
||||||
|
spaBuilder,
|
||||||
|
() => Task.FromResult(baseUri));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the application to forward incoming requests to a local Single Page
|
||||||
|
/// Application (SPA) development server. This is only intended to be used during
|
||||||
|
/// development. Do not enable this middleware in production applications.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
|
||||||
|
/// <param name="baseUriTaskFactory">A callback that will be invoked on each request to supply a <see cref="Task"/> that resolves with the target base URI to which requests should be proxied.</param>
|
||||||
|
public static void UseProxyToSpaDevelopmentServer(
|
||||||
|
this ISpaBuilder spaBuilder,
|
||||||
|
Func<Task<Uri>> baseUriTaskFactory)
|
||||||
|
{
|
||||||
|
var applicationBuilder = spaBuilder.ApplicationBuilder;
|
||||||
|
var applicationStoppingToken = GetStoppingToken(applicationBuilder);
|
||||||
|
|
||||||
|
// Since we might want to proxy WebSockets requests (e.g., by default, AngularCliMiddleware
|
||||||
|
// requires it), enable it for the app
|
||||||
|
applicationBuilder.UseWebSockets();
|
||||||
|
|
||||||
|
// It's important not to time out the requests, as some of them might be to
|
||||||
|
// server-sent event endpoints or similar, where it's expected that the response
|
||||||
|
// takes an unlimited time and never actually completes
|
||||||
|
var neverTimeOutHttpClient =
|
||||||
|
SpaProxy.CreateHttpClientForProxy(Timeout.InfiniteTimeSpan);
|
||||||
|
|
||||||
|
// Proxy all requests to the SPA development server
|
||||||
|
applicationBuilder.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
var didProxyRequest = await SpaProxy.PerformProxyRequest(
|
||||||
|
context, neverTimeOutHttpClient, baseUriTaskFactory(), applicationStoppingToken,
|
||||||
|
proxy404s: true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CancellationToken GetStoppingToken(IApplicationBuilder appBuilder)
|
||||||
|
{
|
||||||
|
var applicationLifetime = appBuilder
|
||||||
|
.ApplicationServices
|
||||||
|
.GetService(typeof(IApplicationLifetime));
|
||||||
|
return ((IApplicationLifetime)applicationLifetime).ApplicationStopping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.AspNetCore.NodeServices.Npm;
|
||||||
|
using Microsoft.AspNetCore.NodeServices.Util;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.Util;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.Extensions.Util;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer
|
||||||
|
{
|
||||||
|
internal static class ReactDevelopmentServerMiddleware
|
||||||
|
{
|
||||||
|
private const string LogCategoryName = "Microsoft.AspNetCore.SpaServices";
|
||||||
|
private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine
|
||||||
|
|
||||||
|
public static void Attach(
|
||||||
|
ISpaBuilder spaBuilder,
|
||||||
|
string npmScriptName)
|
||||||
|
{
|
||||||
|
var sourcePath = spaBuilder.Options.SourcePath;
|
||||||
|
if (string.IsNullOrEmpty(sourcePath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(sourcePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(npmScriptName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(npmScriptName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start create-react-app and attach to middleware pipeline
|
||||||
|
var appBuilder = spaBuilder.ApplicationBuilder;
|
||||||
|
var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName);
|
||||||
|
var portTask = StartCreateReactAppServerAsync(sourcePath, npmScriptName, logger);
|
||||||
|
|
||||||
|
// Everything we proxy is hardcoded to target http://localhost because:
|
||||||
|
// - the requests are always from the local machine (we're not accepting remote
|
||||||
|
// requests that go directly to the create-react-app server)
|
||||||
|
// - given that, there's no reason to use https, and we couldn't even if we
|
||||||
|
// wanted to, because in general the create-react-app server has no certificate
|
||||||
|
var targetUriTask = portTask.ContinueWith(
|
||||||
|
task => new UriBuilder("http", "localhost", task.Result).Uri);
|
||||||
|
|
||||||
|
SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, () =>
|
||||||
|
{
|
||||||
|
// On each request, we create a separate startup task with its own timeout. That way, even if
|
||||||
|
// the first request times out, subsequent requests could still work.
|
||||||
|
var timeout = spaBuilder.Options.StartupTimeout;
|
||||||
|
return targetUriTask.WithTimeout(timeout,
|
||||||
|
$"The create-react-app server did not start listening for requests " +
|
||||||
|
$"within the timeout period of {timeout.Seconds} seconds. " +
|
||||||
|
$"Check the log output for error information.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<int> StartCreateReactAppServerAsync(
|
||||||
|
string sourcePath, string npmScriptName, ILogger logger)
|
||||||
|
{
|
||||||
|
var portNumber = TcpPortFinder.FindAvailablePort();
|
||||||
|
logger.LogInformation($"Starting create-react-app server on port {portNumber}...");
|
||||||
|
|
||||||
|
var envVars = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "PORT", portNumber.ToString() },
|
||||||
|
{ "BROWSER", "none" }, // We don't want create-react-app to open its own extra browser window pointing to the internal dev server port
|
||||||
|
};
|
||||||
|
var npmScriptRunner = new NpmScriptRunner(
|
||||||
|
sourcePath, npmScriptName, null, envVars);
|
||||||
|
npmScriptRunner.AttachToLogger(logger);
|
||||||
|
|
||||||
|
using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Although the React dev server may eventually tell us the URL it's listening on,
|
||||||
|
// it doesn't do so until it's finished compiling, and even then only if there were
|
||||||
|
// no compiler warnings. So instead of waiting for that, consider it ready as soon
|
||||||
|
// as it starts listening for requests.
|
||||||
|
await npmScriptRunner.StdOut.WaitForMatch(
|
||||||
|
new Regex("Starting the development server", RegexOptions.None, RegexMatchTimeout));
|
||||||
|
}
|
||||||
|
catch (EndOfStreamException ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"The NPM script '{npmScriptName}' exited without indicating that the " +
|
||||||
|
$"create-react-app server was listening for requests. The error output was: " +
|
||||||
|
$"{stdErrReader.ReadAsString()}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return portNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for enabling React development server middleware support.
|
||||||
|
/// </summary>
|
||||||
|
public static class ReactDevelopmentServerMiddlewareExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles requests by passing them through to an instance of the create-react-app server.
|
||||||
|
/// This means you can always serve up-to-date CLI-built resources without having
|
||||||
|
/// to run the create-react-app server manually.
|
||||||
|
///
|
||||||
|
/// This feature should only be used in development. For production deployments, be
|
||||||
|
/// sure not to enable the create-react-app server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
|
||||||
|
/// <param name="npmScript">The name of the script in your package.json file that launches the create-react-app server.</param>
|
||||||
|
public static void UseReactDevelopmentServer(
|
||||||
|
this ISpaBuilder spaBuilder,
|
||||||
|
string npmScript)
|
||||||
|
{
|
||||||
|
if (spaBuilder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(spaBuilder));
|
||||||
|
}
|
||||||
|
|
||||||
|
var spaOptions = spaBuilder.Options;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(spaOptions.SourcePath))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"To use {nameof(UseReactDevelopmentServer)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDevelopmentServerMiddleware.Attach(spaBuilder, npmScript);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.SpaServices;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods used for configuring an application to
|
||||||
|
/// host a client-side Single Page Application (SPA).
|
||||||
|
/// </summary>
|
||||||
|
public static class SpaApplicationBuilderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles all requests from this point in the middleware chain by returning
|
||||||
|
/// the default page for the Single Page Application (SPA).
|
||||||
|
///
|
||||||
|
/// This middleware should be placed late in the chain, so that other middleware
|
||||||
|
/// for serving static files, MVC actions, etc., takes precedence.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
|
||||||
|
/// <param name="configuration">
|
||||||
|
/// This callback will be invoked so that additional middleware can be registered within
|
||||||
|
/// the context of this SPA.
|
||||||
|
/// </param>
|
||||||
|
public static void UseSpa(this IApplicationBuilder app, Action<ISpaBuilder> configuration)
|
||||||
|
{
|
||||||
|
if (configuration == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configuration));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the options configured in DI (or blank if none was configured). We have to clone it
|
||||||
|
// otherwise if you have multiple UseSpa calls, their configurations would interfere with one another.
|
||||||
|
var optionsProvider = app.ApplicationServices.GetService<IOptions<SpaOptions>>();
|
||||||
|
var options = new SpaOptions(optionsProvider.Value);
|
||||||
|
|
||||||
|
var spaBuilder = new DefaultSpaBuilder(app, options);
|
||||||
|
configuration.Invoke(spaBuilder);
|
||||||
|
SpaDefaultPageMiddleware.Attach(spaBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices
|
||||||
|
{
|
||||||
|
internal class SpaDefaultPageMiddleware
|
||||||
|
{
|
||||||
|
public static void Attach(ISpaBuilder spaBuilder)
|
||||||
|
{
|
||||||
|
if (spaBuilder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(spaBuilder));
|
||||||
|
}
|
||||||
|
|
||||||
|
var app = spaBuilder.ApplicationBuilder;
|
||||||
|
var options = spaBuilder.Options;
|
||||||
|
|
||||||
|
// Rewrite all requests to the default page
|
||||||
|
app.Use((context, next) =>
|
||||||
|
{
|
||||||
|
context.Request.Path = options.DefaultPage;
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Serve it as a static file
|
||||||
|
// Developers who need to host more than one SPA with distinct default pages can
|
||||||
|
// override the file provider
|
||||||
|
app.UseSpaStaticFilesInternal(
|
||||||
|
options.DefaultPageStaticFileOptions ?? new StaticFileOptions(),
|
||||||
|
allowFallbackOnServingWebRootFiles: true);
|
||||||
|
|
||||||
|
// If the default file didn't get served as a static file (usually because it was not
|
||||||
|
// present on disk), the SPA is definitely not going to work.
|
||||||
|
app.Use((context, next) =>
|
||||||
|
{
|
||||||
|
var message = "The SPA default page middleware could not return the default page " +
|
||||||
|
$"'{options.DefaultPage}' because it was not found, and no other middleware " +
|
||||||
|
"handled the request.\n";
|
||||||
|
|
||||||
|
// Try to clarify the common scenario where someone runs an application in
|
||||||
|
// Production environment without first publishing the whole application
|
||||||
|
// or at least building the SPA.
|
||||||
|
var hostEnvironment = (IHostingEnvironment)context.RequestServices.GetService(typeof(IHostingEnvironment));
|
||||||
|
if (hostEnvironment != null && hostEnvironment.IsProduction())
|
||||||
|
{
|
||||||
|
message += "Your application is running in Production mode, so make sure it has " +
|
||||||
|
"been published, or that you have built your SPA manually. Alternatively you " +
|
||||||
|
"may wish to switch to the Development environment.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Describes options for hosting a Single Page Application (SPA).
|
||||||
|
/// </summary>
|
||||||
|
public class SpaOptions
|
||||||
|
{
|
||||||
|
private PathString _defaultPage = "/index.html";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new instance of <see cref="SpaOptions"/>.
|
||||||
|
/// </summary>
|
||||||
|
public SpaOptions()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new instance of <see cref="SpaOptions"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="copyFromOptions">An instance of <see cref="SpaOptions"/> from which values should be copied.</param>
|
||||||
|
internal SpaOptions(SpaOptions copyFromOptions)
|
||||||
|
{
|
||||||
|
_defaultPage = copyFromOptions.DefaultPage;
|
||||||
|
DefaultPageStaticFileOptions = copyFromOptions.DefaultPageStaticFileOptions;
|
||||||
|
SourcePath = copyFromOptions.SourcePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the URL of the default page that hosts your SPA user interface.
|
||||||
|
/// The default value is <c>"/index.html"</c>.
|
||||||
|
/// </summary>
|
||||||
|
public PathString DefaultPage
|
||||||
|
{
|
||||||
|
get => _defaultPage;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value.Value))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"The value for {nameof(DefaultPage)} cannot be null or empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_defaultPage = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="StaticFileOptions"/> that supplies content
|
||||||
|
/// for serving the SPA's default page.
|
||||||
|
///
|
||||||
|
/// If not set, a default file provider will read files from the
|
||||||
|
/// <see cref="IHostingEnvironment.WebRootPath"/>, which by default is
|
||||||
|
/// the <c>wwwroot</c> directory.
|
||||||
|
/// </summary>
|
||||||
|
public StaticFileOptions DefaultPageStaticFileOptions { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path, relative to the application working directory,
|
||||||
|
/// of the directory that contains the SPA source files during
|
||||||
|
/// development. The directory may not exist in published applications.
|
||||||
|
/// </summary>
|
||||||
|
public string SourcePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum duration that a request will wait for the SPA
|
||||||
|
/// to become ready to serve to the client.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan StartupTimeout { get; set; } = TimeSpan.FromSeconds(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an implementation of <see cref="ISpaStaticFileProvider"/> that supplies
|
||||||
|
/// physical files at a location configured using <see cref="SpaStaticFilesOptions"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal class DefaultSpaStaticFileProvider : ISpaStaticFileProvider
|
||||||
|
{
|
||||||
|
private IFileProvider _fileProvider;
|
||||||
|
|
||||||
|
public DefaultSpaStaticFileProvider(
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
SpaStaticFilesOptions options)
|
||||||
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(options.RootPath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"The {nameof(options.RootPath)} property " +
|
||||||
|
$"of {nameof(options)} cannot be null or empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var env = serviceProvider.GetRequiredService<IHostingEnvironment>();
|
||||||
|
var absoluteRootPath = Path.Combine(
|
||||||
|
env.ContentRootPath,
|
||||||
|
options.RootPath);
|
||||||
|
|
||||||
|
// PhysicalFileProvider will throw if you pass a non-existent path,
|
||||||
|
// but we don't want that scenario to be an error because for SPA
|
||||||
|
// scenarios, it's better if non-existing directory just means we
|
||||||
|
// don't serve any static files.
|
||||||
|
if (Directory.Exists(absoluteRootPath))
|
||||||
|
{
|
||||||
|
_fileProvider = new PhysicalFileProvider(absoluteRootPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFileProvider FileProvider => _fileProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a service that can provide static files to be served for a Single Page
|
||||||
|
/// Application (SPA).
|
||||||
|
/// </summary>
|
||||||
|
public interface ISpaStaticFileProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file provider, if available, that supplies the static files for the SPA.
|
||||||
|
/// The value is <c>null</c> if no file provider is available.
|
||||||
|
/// </summary>
|
||||||
|
IFileProvider FileProvider { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.StaticFiles;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for configuring an application to serve static files for a
|
||||||
|
/// Single Page Application (SPA).
|
||||||
|
/// </summary>
|
||||||
|
public static class SpaStaticFilesExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers an <see cref="ISpaStaticFileProvider"/> service that can provide static
|
||||||
|
/// files to be served for a Single Page Application (SPA).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
|
||||||
|
/// <param name="configuration">If specified, this callback will be invoked to set additional configuration options.</param>
|
||||||
|
public static void AddSpaStaticFiles(
|
||||||
|
this IServiceCollection services,
|
||||||
|
Action<SpaStaticFilesOptions> configuration = null)
|
||||||
|
{
|
||||||
|
services.AddSingleton<ISpaStaticFileProvider>(serviceProvider =>
|
||||||
|
{
|
||||||
|
// Use the options configured in DI (or blank if none was configured)
|
||||||
|
var optionsProvider = serviceProvider.GetService<IOptions<SpaStaticFilesOptions>>();
|
||||||
|
var options = optionsProvider.Value;
|
||||||
|
|
||||||
|
// Allow the developer to perform further configuration
|
||||||
|
configuration?.Invoke(options);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(options.RootPath))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"No {nameof(SpaStaticFilesOptions.RootPath)} " +
|
||||||
|
$"was set on the {nameof(SpaStaticFilesOptions)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DefaultSpaStaticFileProvider(serviceProvider, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the application to serve static files for a Single Page Application (SPA).
|
||||||
|
/// The files will be located using the registered <see cref="ISpaStaticFileProvider"/> service.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
|
||||||
|
public static void UseSpaStaticFiles(this IApplicationBuilder applicationBuilder)
|
||||||
|
{
|
||||||
|
UseSpaStaticFiles(applicationBuilder, new StaticFileOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the application to serve static files for a Single Page Application (SPA).
|
||||||
|
/// The files will be located using the registered <see cref="ISpaStaticFileProvider"/> service.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
|
||||||
|
/// <param name="options">Specifies options for serving the static files.</param>
|
||||||
|
public static void UseSpaStaticFiles(this IApplicationBuilder applicationBuilder, StaticFileOptions options)
|
||||||
|
{
|
||||||
|
if (applicationBuilder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(applicationBuilder));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
UseSpaStaticFilesInternal(applicationBuilder,
|
||||||
|
staticFileOptions: options,
|
||||||
|
allowFallbackOnServingWebRootFiles: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void UseSpaStaticFilesInternal(
|
||||||
|
this IApplicationBuilder app,
|
||||||
|
StaticFileOptions staticFileOptions,
|
||||||
|
bool allowFallbackOnServingWebRootFiles)
|
||||||
|
{
|
||||||
|
if (staticFileOptions == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(staticFileOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the file provider was explicitly supplied, that takes precedence over any other
|
||||||
|
// configured file provider. This is most useful if the application hosts multiple SPAs
|
||||||
|
// (via multiple calls to UseSpa()), so each needs to serve its own separate static files
|
||||||
|
// instead of using AddSpaStaticFiles/UseSpaStaticFiles.
|
||||||
|
// But if no file provider was specified, try to get one from the DI config.
|
||||||
|
if (staticFileOptions.FileProvider == null)
|
||||||
|
{
|
||||||
|
var shouldServeStaticFiles = ShouldServeStaticFiles(
|
||||||
|
app,
|
||||||
|
allowFallbackOnServingWebRootFiles,
|
||||||
|
out var fileProviderOrDefault);
|
||||||
|
if (shouldServeStaticFiles)
|
||||||
|
{
|
||||||
|
staticFileOptions.FileProvider = fileProviderOrDefault;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The registered ISpaStaticFileProvider says we shouldn't
|
||||||
|
// serve static files
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
app.UseStaticFiles(staticFileOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldServeStaticFiles(
|
||||||
|
IApplicationBuilder app,
|
||||||
|
bool allowFallbackOnServingWebRootFiles,
|
||||||
|
out IFileProvider fileProviderOrDefault)
|
||||||
|
{
|
||||||
|
var spaStaticFilesService = app.ApplicationServices.GetService<ISpaStaticFileProvider>();
|
||||||
|
if (spaStaticFilesService != null)
|
||||||
|
{
|
||||||
|
// If an ISpaStaticFileProvider was configured but it says no IFileProvider is available
|
||||||
|
// (i.e., it supplies 'null'), this implies we should not serve any static files. This
|
||||||
|
// is typically the case in development when SPA static files are being served from a
|
||||||
|
// SPA development server (e.g., Angular CLI or create-react-app), in which case no
|
||||||
|
// directory of prebuilt files will exist on disk.
|
||||||
|
fileProviderOrDefault = spaStaticFilesService.FileProvider;
|
||||||
|
return fileProviderOrDefault != null;
|
||||||
|
}
|
||||||
|
else if (!allowFallbackOnServingWebRootFiles)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"To use {nameof(UseSpaStaticFiles)}, you must " +
|
||||||
|
$"first register an {nameof(ISpaStaticFileProvider)} in the service provider, typically " +
|
||||||
|
$"by calling services.{nameof(AddSpaStaticFiles)}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fall back on serving wwwroot
|
||||||
|
fileProviderOrDefault = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents options for serving static files for a Single Page Application (SPA).
|
||||||
|
/// </summary>
|
||||||
|
public class SpaStaticFilesOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path, relative to the application root, of the directory in which
|
||||||
|
/// the physical files are located.
|
||||||
|
///
|
||||||
|
/// If the specified directory does not exist, then the
|
||||||
|
/// <see cref="SpaStaticFilesExtensions.UseSpaStaticFiles(Builder.IApplicationBuilder)"/>
|
||||||
|
/// middleware will not serve any static files.
|
||||||
|
/// </summary>
|
||||||
|
public string RootPath { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.NodeServices.Util
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps a <see cref="StreamReader"/> to expose an evented API, issuing notifications
|
||||||
|
/// when the stream emits partial lines, completed lines, or finally closes.
|
||||||
|
/// </summary>
|
||||||
|
internal class EventedStreamReader
|
||||||
|
{
|
||||||
|
public delegate void OnReceivedChunkHandler(ArraySegment<char> chunk);
|
||||||
|
public delegate void OnReceivedLineHandler(string line);
|
||||||
|
public delegate void OnStreamClosedHandler();
|
||||||
|
|
||||||
|
public event OnReceivedChunkHandler OnReceivedChunk;
|
||||||
|
public event OnReceivedLineHandler OnReceivedLine;
|
||||||
|
public event OnStreamClosedHandler OnStreamClosed;
|
||||||
|
|
||||||
|
private readonly StreamReader _streamReader;
|
||||||
|
private readonly StringBuilder _linesBuffer;
|
||||||
|
|
||||||
|
public EventedStreamReader(StreamReader streamReader)
|
||||||
|
{
|
||||||
|
_streamReader = streamReader ?? throw new ArgumentNullException(nameof(streamReader));
|
||||||
|
_linesBuffer = new StringBuilder();
|
||||||
|
Task.Factory.StartNew(Run);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Match> WaitForMatch(Regex regex)
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<Match>();
|
||||||
|
var completionLock = new object();
|
||||||
|
|
||||||
|
OnReceivedLineHandler onReceivedLineHandler = null;
|
||||||
|
OnStreamClosedHandler onStreamClosedHandler = null;
|
||||||
|
|
||||||
|
void ResolveIfStillPending(Action applyResolution)
|
||||||
|
{
|
||||||
|
lock (completionLock)
|
||||||
|
{
|
||||||
|
if (!tcs.Task.IsCompleted)
|
||||||
|
{
|
||||||
|
OnReceivedLine -= onReceivedLineHandler;
|
||||||
|
OnStreamClosed -= onStreamClosedHandler;
|
||||||
|
applyResolution();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onReceivedLineHandler = line =>
|
||||||
|
{
|
||||||
|
var match = regex.Match(line);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
ResolveIfStillPending(() => tcs.SetResult(match));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onStreamClosedHandler = () =>
|
||||||
|
{
|
||||||
|
ResolveIfStillPending(() => tcs.SetException(new EndOfStreamException()));
|
||||||
|
};
|
||||||
|
|
||||||
|
OnReceivedLine += onReceivedLineHandler;
|
||||||
|
OnStreamClosed += onStreamClosedHandler;
|
||||||
|
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Run()
|
||||||
|
{
|
||||||
|
var buf = new char[8 * 1024];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var chunkLength = await _streamReader.ReadAsync(buf, 0, buf.Length);
|
||||||
|
if (chunkLength == 0)
|
||||||
|
{
|
||||||
|
OnClosed();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnChunk(new ArraySegment<char>(buf, 0, chunkLength));
|
||||||
|
|
||||||
|
var lineBreakPos = Array.IndexOf(buf, '\n', 0, chunkLength);
|
||||||
|
if (lineBreakPos < 0)
|
||||||
|
{
|
||||||
|
_linesBuffer.Append(buf, 0, chunkLength);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_linesBuffer.Append(buf, 0, lineBreakPos + 1);
|
||||||
|
OnCompleteLine(_linesBuffer.ToString());
|
||||||
|
_linesBuffer.Clear();
|
||||||
|
_linesBuffer.Append(buf, lineBreakPos + 1, chunkLength - (lineBreakPos + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnChunk(ArraySegment<char> chunk)
|
||||||
|
{
|
||||||
|
var dlg = OnReceivedChunk;
|
||||||
|
dlg?.Invoke(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCompleteLine(string line)
|
||||||
|
{
|
||||||
|
var dlg = OnReceivedLine;
|
||||||
|
dlg?.Invoke(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClosed()
|
||||||
|
{
|
||||||
|
var dlg = OnStreamClosed;
|
||||||
|
dlg?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.NodeServices.Util
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Captures the completed-line notifications from a <see cref="EventedStreamReader"/>,
|
||||||
|
/// combining the data into a single <see cref="string"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal class EventedStreamStringReader : IDisposable
|
||||||
|
{
|
||||||
|
private EventedStreamReader _eventedStreamReader;
|
||||||
|
private bool _isDisposed;
|
||||||
|
private StringBuilder _stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
public EventedStreamStringReader(EventedStreamReader eventedStreamReader)
|
||||||
|
{
|
||||||
|
_eventedStreamReader = eventedStreamReader
|
||||||
|
?? throw new ArgumentNullException(nameof(eventedStreamReader));
|
||||||
|
_eventedStreamReader.OnReceivedLine += OnReceivedLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadAsString() => _stringBuilder.ToString();
|
||||||
|
|
||||||
|
private void OnReceivedLine(string line) => _stringBuilder.AppendLine(line);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
_eventedStreamReader.OnReceivedLine -= OnReceivedLine;
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Console;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.Util
|
||||||
|
{
|
||||||
|
internal static class LoggerFinder
|
||||||
|
{
|
||||||
|
public static ILogger GetOrCreateLogger(
|
||||||
|
IApplicationBuilder appBuilder,
|
||||||
|
string logCategoryName)
|
||||||
|
{
|
||||||
|
// If the DI system gives us a logger, use it. Otherwise, set up a default one.
|
||||||
|
var loggerFactory = appBuilder.ApplicationServices.GetService<ILoggerFactory>();
|
||||||
|
var logger = loggerFactory != null
|
||||||
|
? loggerFactory.CreateLogger(logCategoryName)
|
||||||
|
: new ConsoleLogger(logCategoryName, null, false);
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.Extensions.Util
|
||||||
|
{
|
||||||
|
internal static class TaskTimeoutExtensions
|
||||||
|
{
|
||||||
|
public static async Task WithTimeout(this Task task, TimeSpan timeoutDelay, string message)
|
||||||
|
{
|
||||||
|
if (task == await Task.WhenAny(task, Task.Delay(timeoutDelay)))
|
||||||
|
{
|
||||||
|
task.Wait(); // Allow any errors to propagate
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new TimeoutException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeoutDelay, string message)
|
||||||
|
{
|
||||||
|
if (task == await Task.WhenAny(task, Task.Delay(timeoutDelay)))
|
||||||
|
{
|
||||||
|
return task.Result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new TimeoutException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.Util
|
||||||
|
{
|
||||||
|
internal static class TcpPortFinder
|
||||||
|
{
|
||||||
|
public static int FindAvailablePort()
|
||||||
|
{
|
||||||
|
var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||||
|
listener.Start();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
listener.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,825 @@
|
|||||||
|
{
|
||||||
|
"AssemblyIdentity": "Microsoft.AspNetCore.SpaServices.Extensions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
||||||
|
"Types": [
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.Extensions.DependencyInjection.SpaStaticFilesExtensions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"Static": true,
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "AddSpaStaticFiles",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "services",
|
||||||
|
"Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "configuration",
|
||||||
|
"Type": "System.Action<Microsoft.AspNetCore.SpaServices.StaticFiles.SpaStaticFilesOptions>",
|
||||||
|
"DefaultValue": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "UseSpaStaticFiles",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "applicationBuilder",
|
||||||
|
"Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "UseSpaStaticFiles",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "applicationBuilder",
|
||||||
|
"Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "options",
|
||||||
|
"Type": "Microsoft.AspNetCore.Builder.StaticFileOptions"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.Builder.SpaPrerenderingExtensions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"Static": true,
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "UseSpaPrerendering",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "spaBuilder",
|
||||||
|
"Type": "Microsoft.AspNetCore.SpaServices.ISpaBuilder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "configuration",
|
||||||
|
"Type": "System.Action<Microsoft.AspNetCore.Builder.SpaPrerenderingOptions>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.Builder.SpaPrerenderingOptions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_BootModuleBuilder",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "Microsoft.AspNetCore.SpaServices.Prerendering.ISpaPrerendererBuilder",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_BootModuleBuilder",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "Microsoft.AspNetCore.SpaServices.Prerendering.ISpaPrerendererBuilder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_BootModulePath",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.String",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_BootModulePath",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_ExcludeUrls",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.String[]",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_ExcludeUrls",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.String[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_SupplyData",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Action<Microsoft.AspNetCore.Http.HttpContext, System.Collections.Generic.IDictionary<System.String, System.Object>>",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_SupplyData",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.Action<Microsoft.AspNetCore.Http.HttpContext, System.Collections.Generic.IDictionary<System.String, System.Object>>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.Builder.SpaProxyingExtensions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"Static": true,
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "UseProxyToSpaDevelopmentServer",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "spaBuilder",
|
||||||
|
"Type": "Microsoft.AspNetCore.SpaServices.ISpaBuilder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "baseUri",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "UseProxyToSpaDevelopmentServer",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "spaBuilder",
|
||||||
|
"Type": "Microsoft.AspNetCore.SpaServices.ISpaBuilder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "baseUri",
|
||||||
|
"Type": "System.Uri"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "UseProxyToSpaDevelopmentServer",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "spaBuilder",
|
||||||
|
"Type": "Microsoft.AspNetCore.SpaServices.ISpaBuilder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "baseUriTaskFactory",
|
||||||
|
"Type": "System.Func<System.Threading.Tasks.Task<System.Uri>>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.Builder.SpaApplicationBuilderExtensions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"Static": true,
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "UseSpa",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "app",
|
||||||
|
"Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "configuration",
|
||||||
|
"Type": "System.Action<Microsoft.AspNetCore.SpaServices.ISpaBuilder>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.SpaServices.ISpaBuilder",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Interface",
|
||||||
|
"Abstract": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_ApplicationBuilder",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_Options",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "Microsoft.AspNetCore.SpaServices.SpaOptions",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.SpaServices.SpaOptions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_DefaultPage",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "Microsoft.AspNetCore.Http.PathString",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_DefaultPage",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "Microsoft.AspNetCore.Http.PathString"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_DefaultPageStaticFileOptions",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "Microsoft.AspNetCore.Builder.StaticFileOptions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_DefaultPageStaticFileOptions",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "Microsoft.AspNetCore.Builder.StaticFileOptions"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_SourcePath",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.String",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_SourcePath",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_StartupTimeout",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.TimeSpan",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_StartupTimeout",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.TimeSpan"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.SpaServices.StaticFiles.ISpaStaticFileProvider",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Interface",
|
||||||
|
"Abstract": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_FileProvider",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.SpaServices.StaticFiles.SpaStaticFilesOptions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "get_RootPath",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.String",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "set_RootPath",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "value",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer.ReactDevelopmentServerMiddlewareExtensions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"Static": true,
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "UseReactDevelopmentServer",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "spaBuilder",
|
||||||
|
"Type": "Microsoft.AspNetCore.SpaServices.ISpaBuilder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "npmScript",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.SpaServices.Prerendering.ISpaPrerendererBuilder",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Interface",
|
||||||
|
"Abstract": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Build",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "spaBuilder",
|
||||||
|
"Type": "Microsoft.AspNetCore.SpaServices.ISpaBuilder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Threading.Tasks.Task",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.SpaServices.AngularCli.AngularCliBuilder",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"ImplementedInterfaces": [
|
||||||
|
"Microsoft.AspNetCore.SpaServices.Prerendering.ISpaPrerendererBuilder"
|
||||||
|
],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Build",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "spaBuilder",
|
||||||
|
"Type": "Microsoft.AspNetCore.SpaServices.ISpaBuilder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Threading.Tasks.Task",
|
||||||
|
"Sealed": true,
|
||||||
|
"Virtual": true,
|
||||||
|
"ImplementedInterface": "Microsoft.AspNetCore.SpaServices.Prerendering.ISpaPrerendererBuilder",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "npmScript",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.SpaServices.AngularCli.AngularCliMiddlewareExtensions",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Abstract": true,
|
||||||
|
"Static": true,
|
||||||
|
"Sealed": true,
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "UseAngularCliServer",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "spaBuilder",
|
||||||
|
"Type": "Microsoft.AspNetCore.SpaServices.ISpaBuilder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "npmScript",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Static": true,
|
||||||
|
"Extension": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.Util.EventedStreamReader+OnReceivedChunkHandler",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Sealed": true,
|
||||||
|
"BaseType": "System.MulticastDelegate",
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Invoke",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "chunk",
|
||||||
|
"Type": "System.ArraySegment<System.Char>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "BeginInvoke",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "chunk",
|
||||||
|
"Type": "System.ArraySegment<System.Char>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "callback",
|
||||||
|
"Type": "System.AsyncCallback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "object",
|
||||||
|
"Type": "System.Object"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.IAsyncResult",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "EndInvoke",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "result",
|
||||||
|
"Type": "System.IAsyncResult"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "object",
|
||||||
|
"Type": "System.Object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "method",
|
||||||
|
"Type": "System.IntPtr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.Util.EventedStreamReader+OnReceivedLineHandler",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Sealed": true,
|
||||||
|
"BaseType": "System.MulticastDelegate",
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Invoke",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "line",
|
||||||
|
"Type": "System.String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "BeginInvoke",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "line",
|
||||||
|
"Type": "System.String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "callback",
|
||||||
|
"Type": "System.AsyncCallback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "object",
|
||||||
|
"Type": "System.Object"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.IAsyncResult",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "EndInvoke",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "result",
|
||||||
|
"Type": "System.IAsyncResult"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "object",
|
||||||
|
"Type": "System.Object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "method",
|
||||||
|
"Type": "System.IntPtr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Microsoft.AspNetCore.NodeServices.Util.EventedStreamReader+OnStreamClosedHandler",
|
||||||
|
"Visibility": "Public",
|
||||||
|
"Kind": "Class",
|
||||||
|
"Sealed": true,
|
||||||
|
"BaseType": "System.MulticastDelegate",
|
||||||
|
"ImplementedInterfaces": [],
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "Invoke",
|
||||||
|
"Parameters": [],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "BeginInvoke",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "callback",
|
||||||
|
"Type": "System.AsyncCallback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "object",
|
||||||
|
"Type": "System.Object"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.IAsyncResult",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Method",
|
||||||
|
"Name": "EndInvoke",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "result",
|
||||||
|
"Type": "System.IAsyncResult"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnType": "System.Void",
|
||||||
|
"Virtual": true,
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Kind": "Constructor",
|
||||||
|
"Name": ".ctor",
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "object",
|
||||||
|
"Type": "System.Object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "method",
|
||||||
|
"Type": "System.IntPtr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Visibility": "Public",
|
||||||
|
"GenericParameter": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenericParameters": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,178 +1,224 @@
|
|||||||
(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
|
(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
|
||||||
/******/ // The module cache
|
/******/ // The module cache
|
||||||
/******/ var installedModules = {};
|
/******/ var installedModules = {};
|
||||||
|
/******/
|
||||||
/******/ // The require function
|
/******/ // The require function
|
||||||
/******/ function __webpack_require__(moduleId) {
|
/******/ function __webpack_require__(moduleId) {
|
||||||
|
/******/
|
||||||
/******/ // Check if module is in cache
|
/******/ // Check if module is in cache
|
||||||
/******/ if(installedModules[moduleId])
|
/******/ if(installedModules[moduleId]) {
|
||||||
/******/ return installedModules[moduleId].exports;
|
/******/ return installedModules[moduleId].exports;
|
||||||
|
/******/ }
|
||||||
/******/ // Create a new module (and put it into the cache)
|
/******/ // Create a new module (and put it into the cache)
|
||||||
/******/ var module = installedModules[moduleId] = {
|
/******/ var module = installedModules[moduleId] = {
|
||||||
/******/ exports: {},
|
/******/ i: moduleId,
|
||||||
/******/ id: moduleId,
|
/******/ l: false,
|
||||||
/******/ loaded: false
|
/******/ exports: {}
|
||||||
/******/ };
|
/******/ };
|
||||||
|
/******/
|
||||||
/******/ // Execute the module function
|
/******/ // Execute the module function
|
||||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||||
|
/******/
|
||||||
/******/ // Flag the module as loaded
|
/******/ // Flag the module as loaded
|
||||||
/******/ module.loaded = true;
|
/******/ module.l = true;
|
||||||
|
/******/
|
||||||
/******/ // Return the exports of the module
|
/******/ // Return the exports of the module
|
||||||
/******/ return module.exports;
|
/******/ return module.exports;
|
||||||
/******/ }
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
/******/ // expose the modules object (__webpack_modules__)
|
/******/ // expose the modules object (__webpack_modules__)
|
||||||
/******/ __webpack_require__.m = modules;
|
/******/ __webpack_require__.m = modules;
|
||||||
|
/******/
|
||||||
/******/ // expose the module cache
|
/******/ // expose the module cache
|
||||||
/******/ __webpack_require__.c = installedModules;
|
/******/ __webpack_require__.c = installedModules;
|
||||||
|
/******/
|
||||||
|
/******/ // define getter function for harmony exports
|
||||||
|
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||||
|
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||||
|
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||||
|
/******/ }
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // define __esModule on exports
|
||||||
|
/******/ __webpack_require__.r = function(exports) {
|
||||||
|
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||||
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
/******/ }
|
||||||
|
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // create a fake namespace object
|
||||||
|
/******/ // mode & 1: value is a module id, require it
|
||||||
|
/******/ // mode & 2: merge all properties of value into the ns
|
||||||
|
/******/ // mode & 4: return value when already ns object
|
||||||
|
/******/ // mode & 8|1: behave like require
|
||||||
|
/******/ __webpack_require__.t = function(value, mode) {
|
||||||
|
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||||
|
/******/ if(mode & 8) return value;
|
||||||
|
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||||
|
/******/ var ns = Object.create(null);
|
||||||
|
/******/ __webpack_require__.r(ns);
|
||||||
|
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||||
|
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||||
|
/******/ return ns;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||||
|
/******/ __webpack_require__.n = function(module) {
|
||||||
|
/******/ var getter = module && module.__esModule ?
|
||||||
|
/******/ function getDefault() { return module['default']; } :
|
||||||
|
/******/ function getModuleExports() { return module; };
|
||||||
|
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||||
|
/******/ return getter;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Object.prototype.hasOwnProperty.call
|
||||||
|
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||||
|
/******/
|
||||||
/******/ // __webpack_public_path__
|
/******/ // __webpack_public_path__
|
||||||
/******/ __webpack_require__.p = "";
|
/******/ __webpack_require__.p = "";
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
/******/ // Load entry module and return exports
|
/******/ // Load entry module and return exports
|
||||||
/******/ return __webpack_require__(0);
|
/******/ return __webpack_require__(__webpack_require__.s = 0);
|
||||||
/******/ })
|
/******/ })
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
/******/ ([
|
/******/ ([
|
||||||
/* 0 */
|
/* 0 */
|
||||||
/***/ function(module, exports, __webpack_require__) {
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
module.exports = __webpack_require__(1);
|
module.exports = __webpack_require__(1);
|
||||||
|
|
||||||
|
|
||||||
/***/ },
|
/***/ }),
|
||||||
/* 1 */
|
/* 1 */
|
||||||
/***/ function(module, exports, __webpack_require__) {
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
var path = __webpack_require__(2);
|
|
||||||
// Separate declaration and export just to add type checking on function signature
|
exports.__esModule = true;
|
||||||
exports.renderToString = renderToStringImpl;
|
var path = __webpack_require__(2);
|
||||||
// This function is invoked by .NET code (via NodeServices). Its job is to hand off execution to the application's
|
// Separate declaration and export just to add type checking on function signature
|
||||||
// prerendering boot function. It can operate in two modes:
|
exports.renderToString = renderToStringImpl;
|
||||||
// [1] Legacy mode
|
// This function is invoked by .NET code (via NodeServices). Its job is to hand off execution to the application's
|
||||||
// This is for backward compatibility with projects created with templates older than the generator version 0.6.0.
|
// prerendering boot function. It can operate in two modes:
|
||||||
// In this mode, we don't really do anything here - we just load the 'aspnet-prerendering' NPM module (which must
|
// [1] Legacy mode
|
||||||
// exist in node_modules, and must be v1.x (not v2+)), and pass through all the parameters to it. Code in
|
// This is for backward compatibility with projects created with templates older than the generator version 0.6.0.
|
||||||
// 'aspnet-prerendering' v1.x will locate the boot function and invoke it.
|
// In this mode, we don't really do anything here - we just load the 'aspnet-prerendering' NPM module (which must
|
||||||
// The drawback to this mode is that, for it to work, you have to deploy node_modules to production.
|
// exist in node_modules, and must be v1.x (not v2+)), and pass through all the parameters to it. Code in
|
||||||
// [2] Current mode
|
// 'aspnet-prerendering' v1.x will locate the boot function and invoke it.
|
||||||
// This is for projects created with the Yeoman generator 0.6.0+ (or projects manually updated). In this mode,
|
// The drawback to this mode is that, for it to work, you have to deploy node_modules to production.
|
||||||
// we don't invoke 'require' at runtime at all. All our dependencies are bundled into the NuGet package, so you
|
// [2] Current mode
|
||||||
// don't have to deploy node_modules to production.
|
// This is for projects created with the Yeoman generator 0.6.0+ (or projects manually updated). In this mode,
|
||||||
// To determine whether we're in mode [1] or [2], the code locates your prerendering boot function, and checks whether
|
// we don't invoke 'require' at runtime at all. All our dependencies are bundled into the NuGet package, so you
|
||||||
// a certain flag is attached to the function instance.
|
// don't have to deploy node_modules to production.
|
||||||
function renderToStringImpl(callback, applicationBasePath, bootModule, absoluteRequestUrl, requestPathAndQuery, customDataParameter, overrideTimeoutMilliseconds) {
|
// To determine whether we're in mode [1] or [2], the code locates your prerendering boot function, and checks whether
|
||||||
try {
|
// a certain flag is attached to the function instance.
|
||||||
var forceLegacy = isLegacyAspNetPrerendering();
|
function renderToStringImpl(callback, applicationBasePath, bootModule, absoluteRequestUrl, requestPathAndQuery, customDataParameter, overrideTimeoutMilliseconds) {
|
||||||
var renderToStringFunc = !forceLegacy && findRenderToStringFunc(applicationBasePath, bootModule);
|
try {
|
||||||
var isNotLegacyMode = renderToStringFunc && renderToStringFunc['isServerRenderer'];
|
var forceLegacy = isLegacyAspNetPrerendering();
|
||||||
if (isNotLegacyMode) {
|
var renderToStringFunc = !forceLegacy && findRenderToStringFunc(applicationBasePath, bootModule);
|
||||||
// Current (non-legacy) mode - we invoke the exported function directly (instead of going through aspnet-prerendering)
|
var isNotLegacyMode = renderToStringFunc && renderToStringFunc['isServerRenderer'];
|
||||||
// It's type-safe to just apply the incoming args to this function, because we already type-checked that it's a RenderToStringFunc,
|
if (isNotLegacyMode) {
|
||||||
// just like renderToStringImpl itself is.
|
// Current (non-legacy) mode - we invoke the exported function directly (instead of going through aspnet-prerendering)
|
||||||
renderToStringFunc.apply(null, arguments);
|
// It's type-safe to just apply the incoming args to this function, because we already type-checked that it's a RenderToStringFunc,
|
||||||
}
|
// just like renderToStringImpl itself is.
|
||||||
else {
|
renderToStringFunc.apply(null, arguments);
|
||||||
// Legacy mode - just hand off execution to 'aspnet-prerendering' v1.x, which must exist in node_modules at runtime
|
}
|
||||||
var aspNetPrerenderingV1RenderToString = __webpack_require__(3).renderToString;
|
else {
|
||||||
if (aspNetPrerenderingV1RenderToString) {
|
// Legacy mode - just hand off execution to 'aspnet-prerendering' v1.x, which must exist in node_modules at runtime
|
||||||
aspNetPrerenderingV1RenderToString(callback, applicationBasePath, bootModule, absoluteRequestUrl, requestPathAndQuery, customDataParameter, overrideTimeoutMilliseconds);
|
var aspNetPrerenderingV1RenderToString = __webpack_require__(3).renderToString;
|
||||||
}
|
if (aspNetPrerenderingV1RenderToString) {
|
||||||
else {
|
aspNetPrerenderingV1RenderToString(callback, applicationBasePath, bootModule, absoluteRequestUrl, requestPathAndQuery, customDataParameter, overrideTimeoutMilliseconds);
|
||||||
callback('If you use aspnet-prerendering >= 2.0.0, you must update your server-side boot module to call createServerRenderer. '
|
}
|
||||||
+ 'Either update your boot module code, or revert to aspnet-prerendering version 1.x');
|
else {
|
||||||
}
|
callback('If you use aspnet-prerendering >= 2.0.0, you must update your server-side boot module to call createServerRenderer. '
|
||||||
}
|
+ 'Either update your boot module code, or revert to aspnet-prerendering version 1.x');
|
||||||
}
|
}
|
||||||
catch (ex) {
|
}
|
||||||
// Make sure loading errors are reported back to the .NET part of the app
|
}
|
||||||
callback('Prerendering failed because of error: '
|
catch (ex) {
|
||||||
+ ex.stack
|
// Make sure loading errors are reported back to the .NET part of the app
|
||||||
+ '\nCurrent directory is: '
|
callback('Prerendering failed because of error: '
|
||||||
+ process.cwd());
|
+ ex.stack
|
||||||
}
|
+ '\nCurrent directory is: '
|
||||||
}
|
+ process.cwd());
|
||||||
;
|
}
|
||||||
function findBootModule(applicationBasePath, bootModule) {
|
}
|
||||||
var bootModuleNameFullPath = path.resolve(applicationBasePath, bootModule.moduleName);
|
;
|
||||||
if (bootModule.webpackConfig) {
|
function findBootModule(applicationBasePath, bootModule) {
|
||||||
// If you're using asp-prerender-webpack-config, you're definitely in legacy mode
|
var bootModuleNameFullPath = path.resolve(applicationBasePath, bootModule.moduleName);
|
||||||
return null;
|
if (bootModule.webpackConfig) {
|
||||||
}
|
// If you're using asp-prerender-webpack-config, you're definitely in legacy mode
|
||||||
else {
|
return null;
|
||||||
return require(bootModuleNameFullPath);
|
}
|
||||||
}
|
else {
|
||||||
}
|
return require(bootModuleNameFullPath);
|
||||||
function findRenderToStringFunc(applicationBasePath, bootModule) {
|
}
|
||||||
// First try to load the module
|
}
|
||||||
var foundBootModule = findBootModule(applicationBasePath, bootModule);
|
function findRenderToStringFunc(applicationBasePath, bootModule) {
|
||||||
if (foundBootModule === null) {
|
// First try to load the module
|
||||||
return null; // Must be legacy mode
|
var foundBootModule = findBootModule(applicationBasePath, bootModule);
|
||||||
}
|
if (foundBootModule === null) {
|
||||||
// Now try to pick out the function they want us to invoke
|
return null; // Must be legacy mode
|
||||||
var renderToStringFunc;
|
}
|
||||||
if (bootModule.exportName) {
|
// Now try to pick out the function they want us to invoke
|
||||||
// Explicitly-named export
|
var renderToStringFunc;
|
||||||
renderToStringFunc = foundBootModule[bootModule.exportName];
|
if (bootModule.exportName) {
|
||||||
}
|
// Explicitly-named export
|
||||||
else if (typeof foundBootModule !== 'function') {
|
renderToStringFunc = foundBootModule[bootModule.exportName];
|
||||||
// TypeScript-style default export
|
}
|
||||||
renderToStringFunc = foundBootModule.default;
|
else if (typeof foundBootModule !== 'function') {
|
||||||
}
|
// TypeScript-style default export
|
||||||
else {
|
renderToStringFunc = foundBootModule["default"];
|
||||||
// Native default export
|
}
|
||||||
renderToStringFunc = foundBootModule;
|
else {
|
||||||
}
|
// Native default export
|
||||||
// Validate the result
|
renderToStringFunc = foundBootModule;
|
||||||
if (typeof renderToStringFunc !== 'function') {
|
}
|
||||||
if (bootModule.exportName) {
|
// Validate the result
|
||||||
throw new Error("The module at " + bootModule.moduleName + " has no function export named " + bootModule.exportName + ".");
|
if (typeof renderToStringFunc !== 'function') {
|
||||||
}
|
if (bootModule.exportName) {
|
||||||
else {
|
throw new Error("The module at " + bootModule.moduleName + " has no function export named " + bootModule.exportName + ".");
|
||||||
throw new Error("The module at " + bootModule.moduleName + " does not export a default function, and you have not specified which export to invoke.");
|
}
|
||||||
}
|
else {
|
||||||
}
|
throw new Error("The module at " + bootModule.moduleName + " does not export a default function, and you have not specified which export to invoke.");
|
||||||
return renderToStringFunc;
|
}
|
||||||
}
|
}
|
||||||
function isLegacyAspNetPrerendering() {
|
return renderToStringFunc;
|
||||||
var version = getAspNetPrerenderingPackageVersion();
|
}
|
||||||
return version && /^1\./.test(version);
|
function isLegacyAspNetPrerendering() {
|
||||||
}
|
var version = getAspNetPrerenderingPackageVersion();
|
||||||
function getAspNetPrerenderingPackageVersion() {
|
return version && /^1\./.test(version);
|
||||||
try {
|
}
|
||||||
var packageEntryPoint = require.resolve('aspnet-prerendering');
|
function getAspNetPrerenderingPackageVersion() {
|
||||||
var packageDir = path.dirname(packageEntryPoint);
|
try {
|
||||||
var packageJsonPath = path.join(packageDir, 'package.json');
|
var packageEntryPoint = require.resolve('aspnet-prerendering');
|
||||||
var packageJson = require(packageJsonPath);
|
var packageDir = path.dirname(packageEntryPoint);
|
||||||
return packageJson.version.toString();
|
var packageJsonPath = path.join(packageDir, 'package.json');
|
||||||
}
|
var packageJson = require(packageJsonPath);
|
||||||
catch (ex) {
|
return packageJson.version.toString();
|
||||||
// Implies aspnet-prerendering isn't in node_modules at all (or node_modules itself doesn't exist,
|
}
|
||||||
// which will be the case in production based on latest templates).
|
catch (ex) {
|
||||||
return null;
|
// Implies aspnet-prerendering isn't in node_modules at all (or node_modules itself doesn't exist,
|
||||||
}
|
// which will be the case in production based on latest templates).
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***/ },
|
/***/ }),
|
||||||
/* 2 */
|
/* 2 */
|
||||||
/***/ function(module, exports) {
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
module.exports = require("path");
|
module.exports = require("path");
|
||||||
|
|
||||||
/***/ },
|
/***/ }),
|
||||||
/* 3 */
|
/* 3 */
|
||||||
/***/ function(module, exports) {
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
module.exports = require("aspnet-prerendering");
|
module.exports = require("aspnet-prerendering");
|
||||||
|
|
||||||
/***/ }
|
/***/ })
|
||||||
/******/ ])));
|
/******/ ])));
|
||||||
@@ -1,86 +1,133 @@
|
|||||||
(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
|
(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
|
||||||
/******/ // The module cache
|
/******/ // The module cache
|
||||||
/******/ var installedModules = {};
|
/******/ var installedModules = {};
|
||||||
|
/******/
|
||||||
/******/ // The require function
|
/******/ // The require function
|
||||||
/******/ function __webpack_require__(moduleId) {
|
/******/ function __webpack_require__(moduleId) {
|
||||||
|
/******/
|
||||||
/******/ // Check if module is in cache
|
/******/ // Check if module is in cache
|
||||||
/******/ if(installedModules[moduleId])
|
/******/ if(installedModules[moduleId]) {
|
||||||
/******/ return installedModules[moduleId].exports;
|
/******/ return installedModules[moduleId].exports;
|
||||||
|
/******/ }
|
||||||
/******/ // Create a new module (and put it into the cache)
|
/******/ // Create a new module (and put it into the cache)
|
||||||
/******/ var module = installedModules[moduleId] = {
|
/******/ var module = installedModules[moduleId] = {
|
||||||
/******/ exports: {},
|
/******/ i: moduleId,
|
||||||
/******/ id: moduleId,
|
/******/ l: false,
|
||||||
/******/ loaded: false
|
/******/ exports: {}
|
||||||
/******/ };
|
/******/ };
|
||||||
|
/******/
|
||||||
/******/ // Execute the module function
|
/******/ // Execute the module function
|
||||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||||
|
/******/
|
||||||
/******/ // Flag the module as loaded
|
/******/ // Flag the module as loaded
|
||||||
/******/ module.loaded = true;
|
/******/ module.l = true;
|
||||||
|
/******/
|
||||||
/******/ // Return the exports of the module
|
/******/ // Return the exports of the module
|
||||||
/******/ return module.exports;
|
/******/ return module.exports;
|
||||||
/******/ }
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
/******/ // expose the modules object (__webpack_modules__)
|
/******/ // expose the modules object (__webpack_modules__)
|
||||||
/******/ __webpack_require__.m = modules;
|
/******/ __webpack_require__.m = modules;
|
||||||
|
/******/
|
||||||
/******/ // expose the module cache
|
/******/ // expose the module cache
|
||||||
/******/ __webpack_require__.c = installedModules;
|
/******/ __webpack_require__.c = installedModules;
|
||||||
|
/******/
|
||||||
|
/******/ // define getter function for harmony exports
|
||||||
|
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||||
|
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||||
|
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||||
|
/******/ }
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // define __esModule on exports
|
||||||
|
/******/ __webpack_require__.r = function(exports) {
|
||||||
|
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||||
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
/******/ }
|
||||||
|
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // create a fake namespace object
|
||||||
|
/******/ // mode & 1: value is a module id, require it
|
||||||
|
/******/ // mode & 2: merge all properties of value into the ns
|
||||||
|
/******/ // mode & 4: return value when already ns object
|
||||||
|
/******/ // mode & 8|1: behave like require
|
||||||
|
/******/ __webpack_require__.t = function(value, mode) {
|
||||||
|
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||||
|
/******/ if(mode & 8) return value;
|
||||||
|
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||||
|
/******/ var ns = Object.create(null);
|
||||||
|
/******/ __webpack_require__.r(ns);
|
||||||
|
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||||
|
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||||
|
/******/ return ns;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||||
|
/******/ __webpack_require__.n = function(module) {
|
||||||
|
/******/ var getter = module && module.__esModule ?
|
||||||
|
/******/ function getDefault() { return module['default']; } :
|
||||||
|
/******/ function getModuleExports() { return module; };
|
||||||
|
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||||
|
/******/ return getter;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Object.prototype.hasOwnProperty.call
|
||||||
|
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||||
|
/******/
|
||||||
/******/ // __webpack_public_path__
|
/******/ // __webpack_public_path__
|
||||||
/******/ __webpack_require__.p = "";
|
/******/ __webpack_require__.p = "";
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
/******/ // Load entry module and return exports
|
/******/ // Load entry module and return exports
|
||||||
/******/ return __webpack_require__(0);
|
/******/ return __webpack_require__(__webpack_require__.s = 4);
|
||||||
/******/ })
|
/******/ })
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
/******/ ([
|
/******/ ([
|
||||||
/* 0 */
|
/* 0 */,
|
||||||
/***/ function(module, exports, __webpack_require__) {
|
|
||||||
|
|
||||||
module.exports = __webpack_require__(4);
|
|
||||||
|
|
||||||
|
|
||||||
/***/ },
|
|
||||||
/* 1 */,
|
/* 1 */,
|
||||||
/* 2 */,
|
/* 2 */,
|
||||||
/* 3 */,
|
/* 3 */,
|
||||||
/* 4 */
|
/* 4 */
|
||||||
/***/ function(module, exports, __webpack_require__) {
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
"use strict";
|
module.exports = __webpack_require__(5);
|
||||||
// Pass through the invocation to the 'aspnet-webpack' package, verifying that it can be loaded
|
|
||||||
function createWebpackDevServer(callback) {
|
|
||||||
var aspNetWebpack;
|
|
||||||
try {
|
|
||||||
aspNetWebpack = __webpack_require__(5);
|
|
||||||
}
|
|
||||||
catch (ex) {
|
|
||||||
// Developers sometimes have trouble with badly-configured Node installations, where it's unable
|
|
||||||
// to find node_modules. Or they accidentally fail to deploy node_modules, or even to run 'npm install'.
|
|
||||||
// Make sure such errors are reported back to the .NET part of the app.
|
|
||||||
callback('Webpack dev middleware failed because of an error while loading \'aspnet-webpack\'. Error was: '
|
|
||||||
+ ex.stack
|
|
||||||
+ '\nCurrent directory is: '
|
|
||||||
+ process.cwd());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return aspNetWebpack.createWebpackDevServer.apply(this, arguments);
|
|
||||||
}
|
|
||||||
exports.createWebpackDevServer = createWebpackDevServer;
|
|
||||||
|
|
||||||
|
|
||||||
/***/ },
|
/***/ }),
|
||||||
/* 5 */
|
/* 5 */
|
||||||
/***/ function(module, exports) {
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
module.exports = require("aspnet-webpack");
|
"use strict";
|
||||||
|
|
||||||
/***/ }
|
exports.__esModule = true;
|
||||||
|
// Pass through the invocation to the 'aspnet-webpack' package, verifying that it can be loaded
|
||||||
|
function createWebpackDevServer(callback) {
|
||||||
|
var aspNetWebpack;
|
||||||
|
try {
|
||||||
|
aspNetWebpack = __webpack_require__(6);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
// Developers sometimes have trouble with badly-configured Node installations, where it's unable
|
||||||
|
// to find node_modules. Or they accidentally fail to deploy node_modules, or even to run 'npm install'.
|
||||||
|
// Make sure such errors are reported back to the .NET part of the app.
|
||||||
|
callback('Webpack dev middleware failed because of an error while loading \'aspnet-webpack\'. Error was: '
|
||||||
|
+ ex.stack
|
||||||
|
+ '\nCurrent directory is: '
|
||||||
|
+ process.cwd());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return aspNetWebpack.createWebpackDevServer.apply(this, arguments);
|
||||||
|
}
|
||||||
|
exports.createWebpackDevServer = createWebpackDevServer;
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 6 */
|
||||||
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
|
module.exports = require("aspnet-webpack");
|
||||||
|
|
||||||
|
/***/ })
|
||||||
/******/ ])));
|
/******/ ])));
|
||||||
@@ -1,18 +1,22 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<Import Project="..\..\build\common.props" />
|
|
||||||
|
|
||||||
<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>
|
|
||||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Remove="node_modules\**\*" />
|
||||||
<EmbeddedResource Include="Content\**\*" />
|
<EmbeddedResource Include="Content\**\*" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
<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)" />
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.TagHelpers" Version="$(MicrosoftAspNetCoreMvcTagHelpersPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.ViewFeatures" Version="$(MicrosoftAspNetCoreMvcViewFeaturesPackageVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">
|
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.NodeServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default implementation of a DI service that provides convenient access to
|
||||||
|
/// server-side prerendering APIs. This is an alternative to prerendering via
|
||||||
|
/// the asp-prerender-module tag helper.
|
||||||
|
/// </summary>
|
||||||
|
internal class DefaultSpaPrerenderer : ISpaPrerenderer
|
||||||
|
{
|
||||||
|
private readonly string _applicationBasePath;
|
||||||
|
private readonly CancellationToken _applicationStoppingToken;
|
||||||
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
private readonly INodeServices _nodeServices;
|
||||||
|
|
||||||
|
public DefaultSpaPrerenderer(
|
||||||
|
INodeServices nodeServices,
|
||||||
|
IApplicationLifetime applicationLifetime,
|
||||||
|
IHostingEnvironment hostingEnvironment,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
|
{
|
||||||
|
_applicationBasePath = hostingEnvironment.ContentRootPath;
|
||||||
|
_applicationStoppingToken = applicationLifetime.ApplicationStopping;
|
||||||
|
_httpContextAccessor = httpContextAccessor;
|
||||||
|
_nodeServices = nodeServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<RenderToStringResult> RenderToString(
|
||||||
|
string moduleName,
|
||||||
|
string exportName = null,
|
||||||
|
object customDataParameter = null,
|
||||||
|
int timeoutMilliseconds = default(int))
|
||||||
|
{
|
||||||
|
return Prerenderer.RenderToString(
|
||||||
|
_applicationBasePath,
|
||||||
|
_nodeServices,
|
||||||
|
_applicationStoppingToken,
|
||||||
|
new JavaScriptModuleExport(moduleName) { ExportName = exportName },
|
||||||
|
_httpContextAccessor.HttpContext,
|
||||||
|
customDataParameter,
|
||||||
|
timeoutMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a service that can perform server-side prerendering for
|
||||||
|
/// JavaScript-based Single Page Applications. This is an alternative
|
||||||
|
/// to using the 'asp-prerender-module' tag helper.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISpaPrerenderer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes JavaScript code to perform server-side prerendering for a
|
||||||
|
/// Single-Page Application. This is an alternative to using the
|
||||||
|
/// 'asp-prerender-module' tag helper.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="moduleName">The JavaScript module that exports a prerendering function.</param>
|
||||||
|
/// <param name="exportName">The name of the export from the JavaScript module, if it is not the default export.</param>
|
||||||
|
/// <param name="customDataParameter">An optional JSON-serializable object to pass to the JavaScript prerendering function.</param>
|
||||||
|
/// <param name="timeoutMilliseconds">If specified, the prerendering task will time out after this duration if not already completed.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<RenderToStringResult> RenderToString(
|
||||||
|
string moduleName,
|
||||||
|
string exportName = null,
|
||||||
|
object customDataParameter = null,
|
||||||
|
int timeoutMilliseconds = default(int));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
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.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.NodeServices;
|
using Microsoft.AspNetCore.NodeServices;
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
||||||
{
|
{
|
||||||
@@ -24,6 +22,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 +34,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.
|
||||||
@@ -85,31 +87,17 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
|||||||
/// <returns>A <see cref="Task"/> representing the operation.</returns>
|
/// <returns>A <see cref="Task"/> representing the operation.</returns>
|
||||||
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||||
{
|
{
|
||||||
// We want to pass the original, unencoded incoming URL data through to Node, so that
|
|
||||||
// server-side code has the same view of the URL as client-side code (on the client,
|
|
||||||
// location.pathname returns an unencoded string).
|
|
||||||
// The following logic handles special characters in URL paths in the same way that
|
|
||||||
// Node and client-side JS does. For example, the path "/a=b%20c" gets passed through
|
|
||||||
// unchanged (whereas other .NET APIs do change it - Path.Value will return it as
|
|
||||||
// "/a=b c" and Path.ToString() will return it as "/a%3db%20c")
|
|
||||||
var requestFeature = ViewContext.HttpContext.Features.Get<IHttpRequestFeature>();
|
|
||||||
var unencodedPathAndQuery = requestFeature.RawTarget;
|
|
||||||
|
|
||||||
var request = ViewContext.HttpContext.Request;
|
|
||||||
var unencodedAbsoluteUrl = $"{request.Scheme}://{request.Host}{unencodedPathAndQuery}";
|
|
||||||
|
|
||||||
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
|
||||||
},
|
},
|
||||||
unencodedAbsoluteUrl,
|
ViewContext.HttpContext,
|
||||||
unencodedPathAndQuery,
|
|
||||||
CustomDataParameter,
|
CustomDataParameter,
|
||||||
TimeoutMillisecondsParameter,
|
TimeoutMillisecondsParameter);
|
||||||
request.PathBase.ToString());
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(result.RedirectUrl))
|
if (!string.IsNullOrEmpty(result.RedirectUrl))
|
||||||
{
|
{
|
||||||
@@ -128,19 +116,10 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
|||||||
|
|
||||||
// Also attach any specified globals to the 'window' object. This is useful for transferring
|
// Also attach any specified globals to the 'window' object. This is useful for transferring
|
||||||
// general state between server and client.
|
// general state between server and client.
|
||||||
if (result.Globals != null)
|
var globalsScript = result.CreateGlobalsAssignmentScript();
|
||||||
|
if (!string.IsNullOrEmpty(globalsScript))
|
||||||
{
|
{
|
||||||
var stringBuilder = new StringBuilder();
|
output.PostElement.SetHtmlContent($"<script>{globalsScript}</script>");
|
||||||
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($"<script>{stringBuilder}</script>");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user