mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-23 01:58:29 +00:00
Compare commits
1 Commits
2.1.1
...
rel/2.0.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e19873874 |
@@ -1,19 +0,0 @@
|
|||||||
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,8 +24,19 @@ 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,13 +12,8 @@ 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-.*$/
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
<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>
|
|
||||||
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
|
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
|
|
||||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">$(MicrosoftNETCoreApp21PackageVersion)</RuntimeFrameworkVersion>
|
|
||||||
<NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
|
|
||||||
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.26730.16
|
VisualStudioVersion = 15.0.26430.4
|
||||||
MinimumVisualStudioVersion = 15.0.26730.03
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
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
|
||||||
@@ -31,13 +28,13 @@ 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}") = "Solution Items", "Solution Items", "{645F7363-1240-4FB6-9422-B32A327C979F}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{1598B415-73F1-4B37-B3B4-0A10677ABB2D}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
|
||||||
Directory.Build.props = Directory.Build.props
|
|
||||||
Directory.Build.targets = Directory.Build.targets
|
|
||||||
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}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E415FE14-13B0-469F-836D-95059E6BAA6E}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
src\build\common.props = src\build\common.props
|
||||||
|
src\build\Key.snk = src\build\Key.snk
|
||||||
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@@ -69,10 +66,6 @@ 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
|
||||||
@@ -85,9 +78,5 @@ 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,12 +1,10 @@
|
|||||||
Copyright (c) .NET Foundation and Contributors
|
Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
|
||||||
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
|
||||||
this file except in compliance with the License. You may obtain a copy of the
|
these files 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
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<clear />
|
||||||
<!-- Restore sources should be defined in build/sources.props. -->
|
<add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-master/api/v3/index.json" />
|
||||||
|
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
|
||||||
</packageSources>
|
</packageSources>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"Default": {
|
|
||||||
"rules": [
|
|
||||||
"DefaultCompositeRule"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
48
README.md
48
README.md
@@ -24,37 +24,29 @@ 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 2.0 or later.
|
It's cross-platform (Windows, Linux, or macOS) and works with .NET Core 1.0.1 or later.
|
||||||
|
|
||||||
## Creating new applications
|
## Creating new applications
|
||||||
|
|
||||||
Prerequisites:
|
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!
|
||||||
|
|
||||||
* [.NET Core 2.0](https://www.microsoft.com/net/core) (or later) SDK
|
To do this, install Yeoman and these generator templates:
|
||||||
* [Node.js](https://nodejs.org/) version 6 (or later)
|
|
||||||
|
|
||||||
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.
|
npm install -g yo generator-aspnetcore-spa
|
||||||
|
|
||||||
### Option 1: Creating Angular/React/Redux applications from the command line (cross-platform)
|
Generate your new application starting point:
|
||||||
|
|
||||||
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`.
|
cd some-empty-directory
|
||||||
|
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
|
||||||
|
|
||||||
### Option 2: Creating Angular/React/Redux applications using Visual Studio 2017 Update 3 or later (Windows only)
|
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/).
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -66,13 +58,18 @@ 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 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.
|
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.
|
||||||
|
|
||||||
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
|
## Samples and templates
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
@@ -91,6 +88,13 @@ 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!
|
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:
|
||||||
|
|
||||||
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.
|
* Clone the repo
|
||||||
|
* 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
Executable file
40
appveyor.yml
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
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 = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE"
|
PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE"
|
||||||
67
build.ps1
Normal file
67
build.ps1
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
$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/rel/2.0.0-preview2.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,8 +1,46 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
repoFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
cd $repoFolder
|
||||||
|
|
||||||
set -euo pipefail
|
koreBuildZip="https://github.com/aspnet/KoreBuild/archive/rel/2.0.0-preview2.zip"
|
||||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
if [ ! -z $KOREBUILD_ZIP ]; then
|
||||||
|
koreBuildZip=$KOREBUILD_ZIP
|
||||||
|
fi
|
||||||
|
|
||||||
# Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs)
|
buildFolder=".build"
|
||||||
chmod +x "$DIR/run.sh"; sync
|
buildFile="$buildFolder/KoreBuild.sh"
|
||||||
"$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 "$@"
|
||||||
|
|||||||
23
build/common.props
Normal file
23
build/common.props
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<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'">
|
||||||
|
<PackageReference Include="NETStandard.Library" Version="$(BundledNETStandardPackageVersion)" />
|
||||||
|
<PackageReference Include="NETStandard.Library.NETFramework" Version="$(NETStandardLibraryNETFrameworkVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -1,28 +1,12 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
<AspNetCoreVersion>2.0.0-preview2-*</AspNetCoreVersion>
|
||||||
|
<AutoMapperVersion>5.0.2</AutoMapperVersion>
|
||||||
|
<InternalAspNetCoreSdkVersion>2.1.0-*</InternalAspNetCoreSdkVersion>
|
||||||
|
<JsonNetVersion>10.0.1</JsonNetVersion>
|
||||||
|
<NETStandardImplicitPackageVersion>$(BundledNETStandardPackageVersion)</NETStandardImplicitPackageVersion>
|
||||||
|
<NETStandardLibraryNETFrameworkVersion>2.0.0-*</NETStandardLibraryNETFrameworkVersion>
|
||||||
|
<RuntimeFrameworkVersion Condition="'$(TargetFramework)'=='netcoreapp2.0'">2.0.0-*</RuntimeFrameworkVersion>
|
||||||
|
<ThreadingDataflowVersion>4.7.0</ThreadingDataflowVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Package Versions">
|
|
||||||
<InternalAspNetCoreSdkPackageVersion>2.1.1-rtm-15790</InternalAspNetCoreSdkPackageVersion>
|
|
||||||
<MicrosoftAspNetCoreDiagnosticsPackageVersion>2.1.0</MicrosoftAspNetCoreDiagnosticsPackageVersion>
|
|
||||||
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.1.0</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
|
|
||||||
<MicrosoftAspNetCoreHostingPackageVersion>2.1.0</MicrosoftAspNetCoreHostingPackageVersion>
|
|
||||||
<MicrosoftAspNetCoreMvcPackageVersion>2.1.0</MicrosoftAspNetCoreMvcPackageVersion>
|
|
||||||
<MicrosoftAspNetCoreMvcTagHelpersPackageVersion>2.1.0</MicrosoftAspNetCoreMvcTagHelpersPackageVersion>
|
|
||||||
<MicrosoftAspNetCoreMvcViewFeaturesPackageVersion>2.1.0</MicrosoftAspNetCoreMvcViewFeaturesPackageVersion>
|
|
||||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.1.0</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
|
||||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.1.0</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
|
||||||
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.1.0</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
|
||||||
<MicrosoftAspNetCoreWebSocketsPackageVersion>2.1.0</MicrosoftAspNetCoreWebSocketsPackageVersion>
|
|
||||||
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.0</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
|
||||||
<MicrosoftExtensionsFileProvidersPhysicalPackageVersion>2.1.0</MicrosoftExtensionsFileProvidersPhysicalPackageVersion>
|
|
||||||
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0</MicrosoftExtensionsLoggingConsolePackageVersion>
|
|
||||||
<MicrosoftExtensionsLoggingDebugPackageVersion>2.1.0</MicrosoftExtensionsLoggingDebugPackageVersion>
|
|
||||||
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
|
|
||||||
<MicrosoftNETCoreApp21PackageVersion>2.1.0</MicrosoftNETCoreApp21PackageVersion>
|
|
||||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
|
||||||
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
|
|
||||||
<SystemThreadingTasksDataflowPackageVersion>4.9.0</SystemThreadingTasksDataflowPackageVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
<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.1.0-rc1-*</LineupPackageVersion>
|
|
||||||
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp20PackageVersion)" />
|
|
||||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
version:2.1.1-rtm-15790
|
|
||||||
commithash:274c65868e735f29f4078c1884c61c4371ee1fc0
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.1/tools/korebuild.schema.json",
|
|
||||||
"channel": "release/2.1",
|
|
||||||
"toolsets": {
|
|
||||||
"nodejs": {
|
|
||||||
"required": true,
|
|
||||||
"minVersion": "6.9"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
run.cmd
2
run.cmd
@@ -1,2 +0,0 @@
|
|||||||
@ECHO OFF
|
|
||||||
PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE"
|
|
||||||
196
run.ps1
196
run.ps1
@@ -1,196 +0,0 @@
|
|||||||
#!/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 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 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/dev/tools/korebuild.schema.json",
|
|
||||||
"channel": "dev",
|
|
||||||
"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,
|
|
||||||
[string]$ConfigFile,
|
|
||||||
[string]$ToolsSourceSuffix,
|
|
||||||
[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 (!(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 'Expand-Archive' -ErrorAction Ignore) {
|
|
||||||
# Use built-in commands where possible as they are cross-plat compatible
|
|
||||||
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-Warning "$ConfigFile could not be read. Its settings will be ignored."
|
|
||||||
Write-Warning $Error[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = 'dev' }
|
|
||||||
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
|
|
||||||
Invoke-KoreBuildCommand $Command @Arguments
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
Remove-Module 'KoreBuild' -ErrorAction Ignore
|
|
||||||
}
|
|
||||||
231
run.sh
231
run.sh
@@ -1,231 +0,0 @@
|
|||||||
#!/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
|
|
||||||
repo_path="$DIR"
|
|
||||||
channel=''
|
|
||||||
tools_source=''
|
|
||||||
tools_source_suffix=''
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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 ""
|
|
||||||
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 [ ! -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
|
|
||||||
;;
|
|
||||||
--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
|
|
||||||
__warn "$config_file is invalid JSON. Its settings will be ignored."
|
|
||||||
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
|
|
||||||
__warn "$config_file is invalid JSON. Its settings will be ignored."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
__warn 'Missing required command: jq or pyton. Could not parse the JSON file. Its settings will be ignored.'
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ ! -z "${config_channel:-}" ] && channel="$config_channel"
|
|
||||||
[ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -z "$channel" ] && channel='dev'
|
|
||||||
[ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools'
|
|
||||||
|
|
||||||
get_korebuild
|
|
||||||
set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file"
|
|
||||||
invoke_korebuild_command "$command" "$@"
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<Import Project="..\..\..\build\common.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
|
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<OutputType>exe</OutputType>
|
<OutputType>exe</OutputType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -12,7 +14,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(AspNetCoreVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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,7 +1,6 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
@@ -35,20 +34,6 @@ 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");
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
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,23 +1,25 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<Import Project="..\..\..\build\common.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
|
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.SpaServices\Microsoft.AspNetCore.SpaServices.csproj" />
|
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(MicrosoftAspNetCoreMvcPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(AspNetCoreVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ 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,5 +9,4 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<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,7 +2,6 @@
|
|||||||
"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"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<Import Project="..\..\..\build\common.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
|
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -11,13 +13,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(MicrosoftAspNetCoreMvcPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(AspNetCoreVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
<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>netstandard2.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>
|
||||||
<ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
|
<ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
|
||||||
</ItemGroup>
|
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="$(ThreadingDataflowVersion)" />
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="$(SystemThreadingTasksDataflowPackageVersion)" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">
|
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ 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 <see cref="Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections.VirtualConnectionClient" />
|
/// For details on the binary streaming protocol, see
|
||||||
|
/// 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.
|
||||||
///
|
///
|
||||||
@@ -237,4 +238,4 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets
|
|||||||
}
|
}
|
||||||
#pragma warning restore 649
|
#pragma warning restore 649
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
{
|
|
||||||
"AssemblyIdentity": "Microsoft.AspNetCore.NodeServices.Sockets, Version=2.0.3.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": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.AspNetCore.NodeServices.HostingModels;
|
using Microsoft.AspNetCore.NodeServices.HostingModels;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -35,18 +34,14 @@ 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>();
|
var applicationLifetime = serviceProvider.GetService<IApplicationLifetime>();
|
||||||
if (applicationLifetime != null)
|
if (applicationLifetime != null)
|
||||||
|
|||||||
@@ -55,11 +55,10 @@
|
|||||||
// 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.
|
||||||
__webpack_require__(2);
|
__webpack_require__(2);
|
||||||
__webpack_require__(4);
|
var http = __webpack_require__(3);
|
||||||
var http = __webpack_require__(5);
|
var path = __webpack_require__(4);
|
||||||
var path = __webpack_require__(3);
|
var ArgsUtil_1 = __webpack_require__(5);
|
||||||
var ArgsUtil_1 = __webpack_require__(6);
|
var ExitWhenParentExits_1 = __webpack_require__(6);
|
||||||
var ExitWhenParentExits_1 = __webpack_require__(7);
|
|
||||||
// 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.
|
||||||
var dynamicRequire = eval('require');
|
var dynamicRequire = eval('require');
|
||||||
@@ -121,8 +120,8 @@
|
|||||||
var parsedArgs = ArgsUtil_1.parseArgs(process.argv);
|
var parsedArgs = ArgsUtil_1.parseArgs(process.argv);
|
||||||
var requestedPortOrZero = parsedArgs.port || 0; // 0 means 'let the OS decide'
|
var requestedPortOrZero = parsedArgs.port || 0; // 0 means 'let the OS decide'
|
||||||
server.listen(requestedPortOrZero, 'localhost', function () {
|
server.listen(requestedPortOrZero, 'localhost', function () {
|
||||||
// Signal to HttpNodeHost which loopback IP address (IPv4 or IPv6) and port it should make its HTTP connections on
|
// Signal to HttpNodeHost which port it should make its HTTP connections on
|
||||||
console.log('[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on {' + server.address().address + '} port ' + server.address().port + '\]');
|
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
|
// 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]');
|
||||||
});
|
});
|
||||||
@@ -134,73 +133,12 @@
|
|||||||
}
|
}
|
||||||
function respondWithError(res, errorValue) {
|
function respondWithError(res, errorValue) {
|
||||||
res.statusCode = 500;
|
res.statusCode = 500;
|
||||||
res.end(JSON.stringify({
|
res.end(errorValue.stack || errorValue.toString());
|
||||||
errorMessage: errorValue.message || errorValue,
|
|
||||||
errorDetails: errorValue.stack || null
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/***/ },
|
/***/ },
|
||||||
/* 2 */
|
/* 2 */
|
||||||
/***/ function(module, exports, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
var path = __webpack_require__(3);
|
|
||||||
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) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/***/ },
|
|
||||||
/* 3 */
|
|
||||||
/***/ function(module, exports) {
|
|
||||||
|
|
||||||
module.exports = require("path");
|
|
||||||
|
|
||||||
/***/ },
|
|
||||||
/* 4 */
|
|
||||||
/***/ function(module, exports) {
|
/***/ function(module, exports) {
|
||||||
|
|
||||||
// When Node writes to stdout/strerr, we capture that and convert the lines into calls on the
|
// When Node writes to stdout/strerr, we capture that and convert the lines into calls on the
|
||||||
@@ -241,13 +179,19 @@
|
|||||||
|
|
||||||
|
|
||||||
/***/ },
|
/***/ },
|
||||||
/* 5 */
|
/* 3 */
|
||||||
/***/ function(module, exports) {
|
/***/ function(module, exports) {
|
||||||
|
|
||||||
module.exports = require("http");
|
module.exports = require("http");
|
||||||
|
|
||||||
/***/ },
|
/***/ },
|
||||||
/* 6 */
|
/* 4 */
|
||||||
|
/***/ function(module, exports) {
|
||||||
|
|
||||||
|
module.exports = require("path");
|
||||||
|
|
||||||
|
/***/ },
|
||||||
|
/* 5 */
|
||||||
/***/ function(module, exports) {
|
/***/ function(module, exports) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
@@ -273,7 +217,7 @@
|
|||||||
|
|
||||||
|
|
||||||
/***/ },
|
/***/ },
|
||||||
/* 7 */
|
/* 6 */
|
||||||
/***/ function(module, exports) {
|
/***/ function(module, exports) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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 EndpointMessageRegex =
|
private static readonly Regex PortMessageRegex =
|
||||||
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 string _endpoint;
|
private int _portNumber;
|
||||||
|
|
||||||
public HttpNodeInstance(NodeServicesOptions options, int port = 0)
|
public HttpNodeInstance(NodeServicesOptions options, int port = 0)
|
||||||
: base(
|
: base(
|
||||||
@@ -50,7 +50,6 @@ 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)
|
||||||
@@ -63,15 +62,13 @@ 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(_endpoint, payload, cancellationToken);
|
var response = await _client.PostAsync("http://localhost:" + _portNumber, 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 responseJson = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken);
|
var responseErrorString = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken);
|
||||||
var responseError = JsonConvert.DeserializeObject<RpcJsonResponse>(responseJson, jsonSerializerSettings);
|
throw new Exception("Call to Node module failed with error: " + responseErrorString);
|
||||||
|
|
||||||
throw new NodeInvocationException(responseError.ErrorMessage, responseError.ErrorDetails);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseContentType = response.Content.Headers.ContentType;
|
var responseContentType = response.Content.Headers.ContentType;
|
||||||
@@ -111,19 +108,13 @@ 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,
|
// Watch for "port selected" messages, and when observed, store the port number
|
||||||
// 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 = string.IsNullOrEmpty(_endpoint) ? EndpointMessageRegex.Match(outputData) : null;
|
var match = _portNumber != 0 ? null : PortMessageRegex.Match(outputData);
|
||||||
if (match != null && match.Success)
|
if (match != null && match.Success)
|
||||||
{
|
{
|
||||||
var port = int.Parse(match.Groups[2].Captures[0].Value);
|
_portNumber = int.Parse(match.Groups[1].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
|
||||||
{
|
{
|
||||||
@@ -145,13 +136,5 @@ 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -297,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 != null && !_nodeProcess.HasExited)
|
if (!_nodeProcess.HasExited)
|
||||||
{
|
{
|
||||||
_nodeProcess.Kill();
|
_nodeProcess.Kill();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
<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>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
|
||||||
|
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</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="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
|
<PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" />
|
||||||
</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 with 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 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,6 +1,5 @@
|
|||||||
// 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';
|
||||||
@@ -70,8 +69,8 @@ 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 loopback IP address (IPv4 or IPv6) and port it should make its HTTP connections on
|
// Signal to HttpNodeHost which port it should make its HTTP connections on
|
||||||
console.log('[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on {' + server.address().address + '} port ' + server.address().port + '\]');
|
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
|
// 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]');
|
||||||
@@ -87,8 +86,5 @@ 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(JSON.stringify({
|
res.end(errorValue.stack || errorValue.toString());
|
||||||
errorMessage: errorValue.message || errorValue,
|
|
||||||
errorDetails: errorValue.stack || null
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.NodeServices
|
|||||||
/// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
|
/// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
|
||||||
public StringAsTempFile(string content, CancellationToken applicationStoppingToken)
|
public StringAsTempFile(string content, CancellationToken applicationStoppingToken)
|
||||||
{
|
{
|
||||||
FileName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
FileName = Path.GetTempFileName();
|
||||||
File.WriteAllText(FileName, content);
|
File.WriteAllText(FileName, content);
|
||||||
|
|
||||||
// Because .NET finalizers don't reliably run when the process is terminating, also
|
// Because .NET finalizers don't reliably run when the process is terminating, also
|
||||||
|
|||||||
@@ -1,935 +0,0 @@
|
|||||||
{
|
|
||||||
"AssemblyIdentity": "Microsoft.AspNetCore.NodeServices, Version=2.0.3.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": "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": "Dispose",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Sealed": true,
|
|
||||||
"Virtual": true,
|
|
||||||
"ImplementedInterface": "System.IDisposable",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
// 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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// 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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
// 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// 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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
// 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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// 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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// 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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,20 @@
|
|||||||
<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>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
|
||||||
|
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</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" />
|
||||||
</ItemGroup>
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.TagHelpers" Version="$(AspNetCoreVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.ViewFeatures" Version="$(AspNetCoreVersion)" />
|
||||||
<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' ">
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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,11 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
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
|
||||||
{
|
{
|
||||||
@@ -87,6 +90,19 @@ 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,
|
||||||
@@ -95,9 +111,11 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
|||||||
{
|
{
|
||||||
ExportName = ExportName
|
ExportName = ExportName
|
||||||
},
|
},
|
||||||
ViewContext.HttpContext,
|
unencodedAbsoluteUrl,
|
||||||
|
unencodedPathAndQuery,
|
||||||
CustomDataParameter,
|
CustomDataParameter,
|
||||||
TimeoutMillisecondsParameter);
|
TimeoutMillisecondsParameter,
|
||||||
|
request.PathBase.ToString());
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(result.RedirectUrl))
|
if (!string.IsNullOrEmpty(result.RedirectUrl))
|
||||||
{
|
{
|
||||||
@@ -116,10 +134,19 @@ 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.
|
||||||
var globalsScript = result.CreateGlobalsAssignmentScript();
|
if (result.Globals != null)
|
||||||
if (!string.IsNullOrEmpty(globalsScript))
|
|
||||||
{
|
{
|
||||||
output.PostElement.SetHtmlContent($"<script>{globalsScript}</script>");
|
var stringBuilder = new StringBuilder();
|
||||||
|
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>");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ using System;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.NodeServices;
|
using Microsoft.AspNetCore.NodeServices;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
||||||
{
|
{
|
||||||
@@ -16,40 +14,6 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
|||||||
|
|
||||||
private static StringAsTempFile NodeScript;
|
private static StringAsTempFile NodeScript;
|
||||||
|
|
||||||
internal static Task<RenderToStringResult> RenderToString(
|
|
||||||
string applicationBasePath,
|
|
||||||
INodeServices nodeServices,
|
|
||||||
CancellationToken applicationStoppingToken,
|
|
||||||
JavaScriptModuleExport bootModule,
|
|
||||||
HttpContext httpContext,
|
|
||||||
object customDataParameter,
|
|
||||||
int timeoutMilliseconds)
|
|
||||||
{
|
|
||||||
// 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 = httpContext.Features.Get<IHttpRequestFeature>();
|
|
||||||
var unencodedPathAndQuery = requestFeature.RawTarget;
|
|
||||||
|
|
||||||
var request = httpContext.Request;
|
|
||||||
var unencodedAbsoluteUrl = $"{request.Scheme}://{request.Host}{unencodedPathAndQuery}";
|
|
||||||
|
|
||||||
return RenderToString(
|
|
||||||
applicationBasePath,
|
|
||||||
nodeServices,
|
|
||||||
applicationStoppingToken,
|
|
||||||
bootModule,
|
|
||||||
unencodedAbsoluteUrl,
|
|
||||||
unencodedPathAndQuery,
|
|
||||||
customDataParameter,
|
|
||||||
timeoutMilliseconds,
|
|
||||||
request.PathBase.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs server-side prerendering by invoking code in Node.js.
|
/// Performs server-side prerendering by invoking code in Node.js.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.SpaServices.Prerendering;
|
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Extension methods for setting up prerendering features in an <see cref="IServiceCollection" />.
|
|
||||||
/// </summary>
|
|
||||||
public static class PrerenderingServiceCollectionExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Configures the dependency injection system to supply an implementation
|
|
||||||
/// of <see cref="ISpaPrerenderer"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param>
|
|
||||||
public static void AddSpaPrerenderer(this IServiceCollection serviceCollection)
|
|
||||||
{
|
|
||||||
serviceCollection.AddHttpContextAccessor();
|
|
||||||
serviceCollection.AddSingleton<ISpaPrerenderer, DefaultSpaPrerenderer>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
||||||
{
|
{
|
||||||
@@ -32,29 +30,5 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
|||||||
/// If set, specifies the HTTP status code that should be sent back with the server response.
|
/// If set, specifies the HTTP status code that should be sent back with the server response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? StatusCode { get; set; }
|
public int? StatusCode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs a block of JavaScript code that assigns data from the
|
|
||||||
/// <see cref="Globals"/> property to the global namespace.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A block of JavaScript code.</returns>
|
|
||||||
public string CreateGlobalsAssignmentScript()
|
|
||||||
{
|
|
||||||
if (Globals == null)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stringBuilder = new StringBuilder();
|
|
||||||
|
|
||||||
foreach (var property in Globals.Properties())
|
|
||||||
{
|
|
||||||
stringBuilder.AppendFormat("window.{0} = {1};",
|
|
||||||
property.Name,
|
|
||||||
property.Value.ToString(Formatting.None));
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringBuilder.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ Behind the scenes, it uses the [`Microsoft.AspNetCore.NodeServices`](https://git
|
|||||||
|
|
||||||
### Installation into existing projects
|
### Installation into existing projects
|
||||||
|
|
||||||
* Install the `Microsoft.AspNetCore.SpaServices` NuGet package
|
* Add `Microsoft.AspNetCore.SpaServices` to the dependencies list in your `project.json` file
|
||||||
* Run `dotnet restore` (or if you use Visual Studio, just wait a moment - it will restore dependencies automatically)
|
* Run `dotnet restore` (or if you use Visual Studio, just wait a moment - it will restore dependencies automatically)
|
||||||
* Install supporting NPM packages for the features you'll be using:
|
* Install supporting NPM packages for the features you'll be using:
|
||||||
* For **server-side prerendering**, install `aspnet-prerendering`
|
* For **server-side prerendering**, install `aspnet-prerendering`
|
||||||
@@ -561,7 +561,7 @@ Typically, when you change a source file, the effects appear in your local brows
|
|||||||
First ensure you already have a working Webpack dev middleware setup. Then, install the `webpack-hot-middleware` NPM module:
|
First ensure you already have a working Webpack dev middleware setup. Then, install the `webpack-hot-middleware` NPM module:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install --save-dev webpack-hot-middleware
|
npm install --save webpack-hot-middleware
|
||||||
```
|
```
|
||||||
|
|
||||||
At the top of your `Startup.cs` file, add the following namespace reference:
|
At the top of your `Startup.cs` file, add the following namespace reference:
|
||||||
@@ -620,7 +620,7 @@ app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
|
|||||||
Also, install the NPM module `aspnet-webpack-react`, e.g.:
|
Also, install the NPM module `aspnet-webpack-react`, e.g.:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install --save-dev aspnet-webpack-react
|
npm install --save aspnet-webpack-react
|
||||||
```
|
```
|
||||||
|
|
||||||
Now if you edit any React component (e.g., in `.jsx` or `.tsx` files), the updated component will be injected into the running application, and will even preserve its in-memory state.
|
Now if you edit any React component (e.g., in `.jsx` or `.tsx` files), the updated component will be injected into the running application, and will even preserve its in-memory state.
|
||||||
|
|||||||
@@ -22,21 +22,14 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack
|
|||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly ConditionalProxyMiddlewareOptions _options;
|
private readonly ConditionalProxyMiddlewareOptions _options;
|
||||||
private readonly string _pathPrefix;
|
private readonly string _pathPrefix;
|
||||||
private readonly bool _pathPrefixIsRoot;
|
|
||||||
|
|
||||||
public ConditionalProxyMiddleware(
|
public ConditionalProxyMiddleware(
|
||||||
RequestDelegate next,
|
RequestDelegate next,
|
||||||
string pathPrefix,
|
string pathPrefix,
|
||||||
ConditionalProxyMiddlewareOptions options)
|
ConditionalProxyMiddlewareOptions options)
|
||||||
{
|
{
|
||||||
if (!pathPrefix.StartsWith("/"))
|
|
||||||
{
|
|
||||||
pathPrefix = "/" + pathPrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
_next = next;
|
_next = next;
|
||||||
_pathPrefix = pathPrefix;
|
_pathPrefix = pathPrefix;
|
||||||
_pathPrefixIsRoot = string.Equals(_pathPrefix, "/", StringComparison.Ordinal);
|
|
||||||
_options = options;
|
_options = options;
|
||||||
_httpClient = new HttpClient(new HttpClientHandler());
|
_httpClient = new HttpClient(new HttpClientHandler());
|
||||||
_httpClient.Timeout = _options.RequestTimeout;
|
_httpClient.Timeout = _options.RequestTimeout;
|
||||||
@@ -44,7 +37,7 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack
|
|||||||
|
|
||||||
public async Task Invoke(HttpContext context)
|
public async Task Invoke(HttpContext context)
|
||||||
{
|
{
|
||||||
if (context.Request.Path.StartsWithSegments(_pathPrefix) || _pathPrefixIsRoot)
|
if (context.Request.Path.StartsWithSegments(_pathPrefix))
|
||||||
{
|
{
|
||||||
var didProxyRequest = await PerformProxyRequest(context);
|
var didProxyRequest = await PerformProxyRequest(context);
|
||||||
if (didProxyRequest)
|
if (didProxyRequest)
|
||||||
@@ -72,7 +65,7 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack
|
|||||||
|
|
||||||
requestMessage.Headers.Host = _options.Host + ":" + _options.Port;
|
requestMessage.Headers.Host = _options.Host + ":" + _options.Port;
|
||||||
var uriString =
|
var uriString =
|
||||||
$"{_options.Scheme}://{_options.Host}:{_options.Port}{context.Request.Path}{context.Request.QueryString}";
|
$"{_options.Scheme}://{_options.Host}:{_options.Port}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}";
|
||||||
requestMessage.RequestUri = new Uri(uriString);
|
requestMessage.RequestUri = new Uri(uriString);
|
||||||
requestMessage.Method = new HttpMethod(context.Request.Method);
|
requestMessage.Method = new HttpMethod(context.Request.Method);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using System.Threading;
|
|||||||
using Microsoft.AspNetCore.NodeServices;
|
using Microsoft.AspNetCore.NodeServices;
|
||||||
using Microsoft.AspNetCore.SpaServices.Webpack;
|
using Microsoft.AspNetCore.SpaServices.Webpack;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Builder
|
namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
@@ -15,15 +14,6 @@ namespace Microsoft.AspNetCore.Builder
|
|||||||
{
|
{
|
||||||
private const string DefaultConfigFile = "webpack.config.js";
|
private const string DefaultConfigFile = "webpack.config.js";
|
||||||
|
|
||||||
private static readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
|
|
||||||
{
|
|
||||||
// Note that the aspnet-webpack JS code specifically expects options to be serialized with
|
|
||||||
// PascalCase property names, so it's important to be explicit about this contract resolver
|
|
||||||
ContractResolver = new DefaultContractResolver(),
|
|
||||||
|
|
||||||
TypeNameHandling = TypeNameHandling.None
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables Webpack dev middleware support. This hosts an instance of the Webpack compiler in memory
|
/// Enables Webpack dev middleware support. This hosts an instance of the Webpack compiler in memory
|
||||||
/// in your application so that you can always serve up-to-date Webpack-built resources without having
|
/// in your application so that you can always serve up-to-date Webpack-built resources without having
|
||||||
@@ -97,7 +87,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||||||
};
|
};
|
||||||
var devServerInfo =
|
var devServerInfo =
|
||||||
nodeServices.InvokeExportAsync<WebpackDevServerInfo>(nodeScript.FileName, "createWebpackDevServer",
|
nodeServices.InvokeExportAsync<WebpackDevServerInfo>(nodeScript.FileName, "createWebpackDevServer",
|
||||||
JsonConvert.SerializeObject(devServerOptions, jsonSerializerSettings)).Result;
|
JsonConvert.SerializeObject(devServerOptions)).Result;
|
||||||
|
|
||||||
// If we're talking to an older version of aspnet-webpack, it will return only a single PublicPath,
|
// If we're talking to an older version of aspnet-webpack, it will return only a single PublicPath,
|
||||||
// not an array of PublicPaths. Handle that scenario.
|
// not an array of PublicPaths. Handle that scenario.
|
||||||
@@ -111,9 +101,9 @@ namespace Microsoft.AspNetCore.Builder
|
|||||||
// plus /__webpack_hmr is proxied with infinite timeout, because it's an EventSource (long-lived request).
|
// plus /__webpack_hmr is proxied with infinite timeout, because it's an EventSource (long-lived request).
|
||||||
foreach (var publicPath in devServerInfo.PublicPaths)
|
foreach (var publicPath in devServerInfo.PublicPaths)
|
||||||
{
|
{
|
||||||
appBuilder.UseProxyToLocalWebpackDevMiddleware(publicPath + hmrEndpoint, devServerInfo.Port, Timeout.InfiniteTimeSpan);
|
|
||||||
appBuilder.UseProxyToLocalWebpackDevMiddleware(publicPath, devServerInfo.Port, TimeSpan.FromSeconds(100));
|
appBuilder.UseProxyToLocalWebpackDevMiddleware(publicPath, devServerInfo.Port, TimeSpan.FromSeconds(100));
|
||||||
}
|
}
|
||||||
|
appBuilder.UseProxyToLocalWebpackDevMiddleware(hmrEndpoint, devServerInfo.Port, Timeout.InfiniteTimeSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UseProxyToLocalWebpackDevMiddleware(this IApplicationBuilder appBuilder, string publicPath, int proxyToPort, TimeSpan requestTimeout)
|
private static void UseProxyToLocalWebpackDevMiddleware(this IApplicationBuilder appBuilder, string publicPath, int proxyToPort, TimeSpan requestTimeout)
|
||||||
|
|||||||
@@ -50,12 +50,5 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack
|
|||||||
/// the webpack compiler.
|
/// the webpack compiler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<string, string> EnvironmentVariables { get; set; }
|
public IDictionary<string, string> EnvironmentVariables { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Specifies a value for the "env" parameter to be passed into the Webpack configuration
|
|
||||||
/// function. The value must be JSON-serializable, and will only be used if the Webpack
|
|
||||||
/// configuration is exported as a function.
|
|
||||||
/// </summary>
|
|
||||||
public object EnvParam { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,730 +0,0 @@
|
|||||||
{
|
|
||||||
"AssemblyIdentity": "Microsoft.AspNetCore.SpaServices, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
|
||||||
"Types": [
|
|
||||||
{
|
|
||||||
"Name": "Microsoft.Extensions.DependencyInjection.PrerenderingServiceCollectionExtensions",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"Kind": "Class",
|
|
||||||
"Abstract": true,
|
|
||||||
"Static": true,
|
|
||||||
"Sealed": true,
|
|
||||||
"ImplementedInterfaces": [],
|
|
||||||
"Members": [
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "AddSpaPrerenderer",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "serviceCollection",
|
|
||||||
"Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Static": true,
|
|
||||||
"Extension": true,
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"GenericParameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Microsoft.AspNetCore.Builder.SpaRouteExtensions",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"Kind": "Class",
|
|
||||||
"Abstract": true,
|
|
||||||
"Static": true,
|
|
||||||
"Sealed": true,
|
|
||||||
"ImplementedInterfaces": [],
|
|
||||||
"Members": [
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "MapSpaFallbackRoute",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "routeBuilder",
|
|
||||||
"Type": "Microsoft.AspNetCore.Routing.IRouteBuilder"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "name",
|
|
||||||
"Type": "System.String"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "defaults",
|
|
||||||
"Type": "System.Object"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "constraints",
|
|
||||||
"Type": "System.Object",
|
|
||||||
"DefaultValue": "null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "dataTokens",
|
|
||||||
"Type": "System.Object",
|
|
||||||
"DefaultValue": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Static": true,
|
|
||||||
"Extension": true,
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "MapSpaFallbackRoute",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "routeBuilder",
|
|
||||||
"Type": "Microsoft.AspNetCore.Routing.IRouteBuilder"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "name",
|
|
||||||
"Type": "System.String"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "templatePrefix",
|
|
||||||
"Type": "System.String"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "defaults",
|
|
||||||
"Type": "System.Object"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "constraints",
|
|
||||||
"Type": "System.Object",
|
|
||||||
"DefaultValue": "null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "dataTokens",
|
|
||||||
"Type": "System.Object",
|
|
||||||
"DefaultValue": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Static": true,
|
|
||||||
"Extension": true,
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"GenericParameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Microsoft.AspNetCore.Builder.WebpackDevMiddleware",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"Kind": "Class",
|
|
||||||
"Abstract": true,
|
|
||||||
"Static": true,
|
|
||||||
"Sealed": true,
|
|
||||||
"ImplementedInterfaces": [],
|
|
||||||
"Members": [
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "UseWebpackDevMiddleware",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "appBuilder",
|
|
||||||
"Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "options",
|
|
||||||
"Type": "Microsoft.AspNetCore.SpaServices.Webpack.WebpackDevMiddlewareOptions",
|
|
||||||
"DefaultValue": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Static": true,
|
|
||||||
"Extension": true,
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"GenericParameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Microsoft.AspNetCore.SpaServices.Webpack.WebpackDevMiddlewareOptions",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"Kind": "Class",
|
|
||||||
"ImplementedInterfaces": [],
|
|
||||||
"Members": [
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_HotModuleReplacement",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.Boolean",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_HotModuleReplacement",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.Boolean"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_HotModuleReplacementEndpoint",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.String",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_HotModuleReplacementEndpoint",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.String"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_HotModuleReplacementServerPort",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.Int32",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_HotModuleReplacementServerPort",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.Int32"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_ReactHotModuleReplacement",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.Boolean",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_ReactHotModuleReplacement",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.Boolean"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_HotModuleReplacementClientOptions",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.Collections.Generic.IDictionary<System.String, System.String>",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_HotModuleReplacementClientOptions",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.Collections.Generic.IDictionary<System.String, System.String>"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_ConfigFile",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.String",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_ConfigFile",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.String"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"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_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": "Constructor",
|
|
||||||
"Name": ".ctor",
|
|
||||||
"Parameters": [],
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"GenericParameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Microsoft.AspNetCore.SpaServices.Prerendering.ISpaPrerenderer",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"Kind": "Interface",
|
|
||||||
"Abstract": true,
|
|
||||||
"ImplementedInterfaces": [],
|
|
||||||
"Members": [
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "RenderToString",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "moduleName",
|
|
||||||
"Type": "System.String"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "exportName",
|
|
||||||
"Type": "System.String",
|
|
||||||
"DefaultValue": "null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "customDataParameter",
|
|
||||||
"Type": "System.Object",
|
|
||||||
"DefaultValue": "null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "timeoutMilliseconds",
|
|
||||||
"Type": "System.Int32",
|
|
||||||
"DefaultValue": "0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.SpaServices.Prerendering.RenderToStringResult>",
|
|
||||||
"GenericParameter": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"GenericParameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Microsoft.AspNetCore.SpaServices.Prerendering.JavaScriptModuleExport",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"Kind": "Class",
|
|
||||||
"ImplementedInterfaces": [],
|
|
||||||
"Members": [
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_ModuleName",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.String",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_ExportName",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.String",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_ExportName",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.String"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Constructor",
|
|
||||||
"Name": ".ctor",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "moduleName",
|
|
||||||
"Type": "System.String"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"GenericParameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Microsoft.AspNetCore.SpaServices.Prerendering.Prerenderer",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"Kind": "Class",
|
|
||||||
"Abstract": true,
|
|
||||||
"Static": true,
|
|
||||||
"Sealed": true,
|
|
||||||
"ImplementedInterfaces": [],
|
|
||||||
"Members": [
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "RenderToString",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "applicationBasePath",
|
|
||||||
"Type": "System.String"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "nodeServices",
|
|
||||||
"Type": "Microsoft.AspNetCore.NodeServices.INodeServices"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "applicationStoppingToken",
|
|
||||||
"Type": "System.Threading.CancellationToken"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "bootModule",
|
|
||||||
"Type": "Microsoft.AspNetCore.SpaServices.Prerendering.JavaScriptModuleExport"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "requestAbsoluteUrl",
|
|
||||||
"Type": "System.String"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "requestPathAndQuery",
|
|
||||||
"Type": "System.String"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "customDataParameter",
|
|
||||||
"Type": "System.Object"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "timeoutMilliseconds",
|
|
||||||
"Type": "System.Int32"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "requestPathBase",
|
|
||||||
"Type": "System.String"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.SpaServices.Prerendering.RenderToStringResult>",
|
|
||||||
"Static": true,
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"GenericParameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Microsoft.AspNetCore.SpaServices.Prerendering.PrerenderTagHelper",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"Kind": "Class",
|
|
||||||
"BaseType": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelper",
|
|
||||||
"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_ExportName",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.String",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_ExportName",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.String"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_CustomDataParameter",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.Object",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_CustomDataParameter",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.Object"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_TimeoutMillisecondsParameter",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.Int32",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_TimeoutMillisecondsParameter",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.Int32"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_ViewContext",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_ViewContext",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "ProcessAsync",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "context",
|
|
||||||
"Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "output",
|
|
||||||
"Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Threading.Tasks.Task",
|
|
||||||
"Virtual": true,
|
|
||||||
"Override": true,
|
|
||||||
"ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Constructor",
|
|
||||||
"Name": ".ctor",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "serviceProvider",
|
|
||||||
"Type": "System.IServiceProvider"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"GenericParameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Microsoft.AspNetCore.SpaServices.Prerendering.RenderToStringResult",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"Kind": "Class",
|
|
||||||
"ImplementedInterfaces": [],
|
|
||||||
"Members": [
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_Globals",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "Newtonsoft.Json.Linq.JObject",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_Globals",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "Newtonsoft.Json.Linq.JObject"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_Html",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.String",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_Html",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.String"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_RedirectUrl",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.String",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_RedirectUrl",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.String"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "get_StatusCode",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.Nullable<System.Int32>",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "set_StatusCode",
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "value",
|
|
||||||
"Type": "System.Nullable<System.Int32>"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnType": "System.Void",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Method",
|
|
||||||
"Name": "CreateGlobalsAssignmentScript",
|
|
||||||
"Parameters": [],
|
|
||||||
"ReturnType": "System.String",
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Kind": "Constructor",
|
|
||||||
"Name": ".ctor",
|
|
||||||
"Parameters": [],
|
|
||||||
"Visibility": "Public",
|
|
||||||
"GenericParameter": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"GenericParameters": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
/node_modules/
|
|
||||||
**/*.js
|
|
||||||
**/*.d.ts
|
|
||||||
**/*.metadata.json
|
|
||||||
/compiled
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
!/*.js
|
|
||||||
!/*.d.ts
|
|
||||||
/compiled
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
|
|
||||||
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
|
|
||||||
License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
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
|
|
||||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
specific language governing permissions and limitations under the License.
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "aspnet-angular",
|
|
||||||
"version": "0.1.1",
|
|
||||||
"description": "Helpers for using Angular in ASP.NET Core projects",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"prepublish": "rimraf *.d.ts && ngc && echo 'Finished building NPM package \"aspnet-angular\"'",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/aspnet/JavaScriptServices.git"
|
|
||||||
},
|
|
||||||
"author": "Microsoft",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/aspnet/JavaScriptServices/issues"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@angular/common": "^4.3.2",
|
|
||||||
"@angular/compiler": "^4.3.2",
|
|
||||||
"@angular/compiler-cli": "^4.3.2",
|
|
||||||
"@angular/core": "^4.3.2",
|
|
||||||
"@angular/http": "^4.3.2",
|
|
||||||
"@angular/platform-browser": "^4.3.2",
|
|
||||||
"rimraf": "^2.6.1",
|
|
||||||
"rxjs": "^5.4.2",
|
|
||||||
"zone.js": "^0.8.16"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@angular/core": "^4.2.5 || ^5.0.0-beta"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { Provider, NgModule, Inject } from '@angular/core';
|
|
||||||
import { Headers, Http, ResponseOptions, RequestOptionsArgs, Response } from '@angular/http';
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import 'rxjs/add/observable/of';
|
|
||||||
import 'rxjs/add/operator/map';
|
|
||||||
const globalSerializedStateKey = 'HTTP_STATE_TRANSFER';
|
|
||||||
const backingStoreDIToken = 'HTTP_STATE_BACKING_STORE';
|
|
||||||
|
|
||||||
export interface CacheOptions {
|
|
||||||
permanent: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CachedHttpResponse {
|
|
||||||
headers: { [name: string]: any } | null;
|
|
||||||
status: number;
|
|
||||||
statusText: string | null;
|
|
||||||
text: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BackingStore = { [key: string]: CachedHttpResponse };
|
|
||||||
|
|
||||||
export class HttpWithStateTransfer {
|
|
||||||
private backingStore: BackingStore;
|
|
||||||
private http: Http;
|
|
||||||
|
|
||||||
constructor(@Inject(Http) http: Http, @Inject(backingStoreDIToken) backingStore: BackingStore) {
|
|
||||||
this.http = http;
|
|
||||||
this.backingStore = backingStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public stateForTransfer(): any {
|
|
||||||
return { [globalSerializedStateKey]: this.backingStore };
|
|
||||||
}
|
|
||||||
|
|
||||||
public get(url: string, options?: CacheOptions, requestOptions?: RequestOptionsArgs): Observable<Response> {
|
|
||||||
return this.getCachedResponse(/* cacheKey */ url, () => this.http.get(url, requestOptions), options);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCachedResponse(cacheKey: string, provider: () => Observable<Response>, options?: CacheOptions): Observable<Response> {
|
|
||||||
// By default, the cache is only used for the *first* client-side read. So, we're only performing
|
|
||||||
// a one-time transfer of server-side response to the client. If you want to keep and reuse cached
|
|
||||||
// responses continually during server-side and client-side execution, set 'permanent' to 'true.
|
|
||||||
const isClient = typeof window !== 'undefined';
|
|
||||||
const isPermanent = options && options.permanent;
|
|
||||||
|
|
||||||
const allowReadFromCache = isClient || isPermanent;
|
|
||||||
if (allowReadFromCache && this.backingStore.hasOwnProperty(cacheKey)) {
|
|
||||||
const cachedValue = this.backingStore[cacheKey];
|
|
||||||
if (!isPermanent) {
|
|
||||||
delete this.backingStore[cacheKey];
|
|
||||||
}
|
|
||||||
return Observable.of(new Response(new ResponseOptions({
|
|
||||||
body: cachedValue.text,
|
|
||||||
headers: new Headers(cachedValue.headers),
|
|
||||||
status: cachedValue.status,
|
|
||||||
url: cachedValue.url
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider()
|
|
||||||
.map(response => {
|
|
||||||
const allowWriteToCache = !isClient || isPermanent;
|
|
||||||
if (allowWriteToCache) {
|
|
||||||
this.backingStore[cacheKey] = {
|
|
||||||
headers: response.headers ? response.headers.toJSON() : null,
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
text: response.text(),
|
|
||||||
url: response.url
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function defaultBackingStoreFactory() {
|
|
||||||
const transferredData = typeof window !== 'undefined' ? (window as any)[globalSerializedStateKey] : null;
|
|
||||||
return transferredData || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
providers: [
|
|
||||||
// The backing store is a separate DI service so you could override exactly how it gets
|
|
||||||
// transferred from server to client
|
|
||||||
{ provide: backingStoreDIToken, useFactory: defaultBackingStoreFactory },
|
|
||||||
|
|
||||||
{ provide: HttpWithStateTransfer, useClass: HttpWithStateTransfer },
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class HttpWithStateTransferModule {
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './HttpWithStateTransfer';
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es5",
|
|
||||||
"declaration": true,
|
|
||||||
"outDir": ".",
|
|
||||||
"lib": ["es2015", "dom"]
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"src/index.ts"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
],
|
|
||||||
"angularCompilerOptions": {
|
|
||||||
"genDir": "compiled"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aspnet-prerendering",
|
"name": "aspnet-prerendering",
|
||||||
"version": "3.0.1",
|
"version": "2.0.5",
|
||||||
"description": "Helpers for server-side rendering of JavaScript applications in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
|
"description": "Helpers for server-side rendering of JavaScript applications in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"url": "https://github.com/aspnet/JavaScriptServices.git"
|
"url": "https://github.com/aspnet/JavaScriptServices.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"domain-task": "^3.0.0"
|
"domain-task": "^2.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^6.0.42",
|
"@types/node": "^6.0.42",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
/// <reference path="./PrerenderingInterfaces.d.ts" />
|
||||||
import * as url from 'url';
|
import * as url from 'url';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as domain from 'domain';
|
import * as domain from 'domain';
|
||||||
import { run as domainTaskRun, baseUrl as domainTaskBaseUrl } from 'domain-task/main';
|
import { run as domainTaskRun, baseUrl as domainTaskBaseUrl } from 'domain-task/main';
|
||||||
import { BootFunc, BootFuncParams, BootModuleInfo, RenderToStringCallback, RenderToStringFunc } from './PrerenderingInterfaces';
|
|
||||||
|
|
||||||
const defaultTimeoutMilliseconds = 30 * 1000;
|
const defaultTimeoutMilliseconds = 30 * 1000;
|
||||||
|
|
||||||
@@ -26,7 +26,6 @@ export function createServerRenderer(bootFunc: BootFunc): RenderToStringFunc {
|
|||||||
domainTasks: domainTaskCompletionPromise,
|
domainTasks: domainTaskCompletionPromise,
|
||||||
data: customDataParameter
|
data: customDataParameter
|
||||||
};
|
};
|
||||||
const absoluteBaseUrl = params.origin + params.baseUrl; // Should be same value as page's <base href>
|
|
||||||
|
|
||||||
// Open a new domain that can track all the async tasks involved in the app's execution
|
// Open a new domain that can track all the async tasks involved in the app's execution
|
||||||
domainTaskRun(/* code to run */ () => {
|
domainTaskRun(/* code to run */ () => {
|
||||||
@@ -36,7 +35,7 @@ export function createServerRenderer(bootFunc: BootFunc): RenderToStringFunc {
|
|||||||
bindPromiseContinuationsToDomain(domainTaskCompletionPromise, domain['active']);
|
bindPromiseContinuationsToDomain(domainTaskCompletionPromise, domain['active']);
|
||||||
|
|
||||||
// Make the base URL available to the 'domain-tasks/fetch' helper within this execution context
|
// Make the base URL available to the 'domain-tasks/fetch' helper within this execution context
|
||||||
domainTaskBaseUrl(absoluteBaseUrl);
|
domainTaskBaseUrl(absoluteRequestUrl);
|
||||||
|
|
||||||
// Begin rendering, and apply a timeout
|
// Begin rendering, and apply a timeout
|
||||||
const bootFuncPromise = bootFunc(params);
|
const bootFuncPromise = bootFunc(params);
|
||||||
|
|||||||
@@ -1,28 +1,26 @@
|
|||||||
export interface RenderToStringFunc {
|
interface RenderToStringFunc {
|
||||||
(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number, requestPathBase: string): void;
|
(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number, requestPathBase: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RenderToStringCallback {
|
interface RenderToStringCallback {
|
||||||
(error: any, result?: RenderResult): void;
|
(error: any, result?: RenderToStringResult): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RenderToStringResult {
|
interface RenderToStringResult {
|
||||||
html: string;
|
html: string;
|
||||||
statusCode?: number;
|
statusCode?: number;
|
||||||
globals?: { [key: string]: any };
|
globals?: { [key: string]: any };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RedirectResult {
|
interface RedirectResult {
|
||||||
redirectUrl: string;
|
redirectUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RenderResult = RenderToStringResult | RedirectResult;
|
interface BootFunc {
|
||||||
|
(params: BootFuncParams): Promise<RenderToStringResult>;
|
||||||
export interface BootFunc {
|
|
||||||
(params: BootFuncParams): Promise<RenderResult>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BootFuncParams {
|
interface BootFuncParams {
|
||||||
location: any; // e.g., Location object containing information '/some/path'
|
location: any; // e.g., Location object containing information '/some/path'
|
||||||
origin: string; // e.g., 'https://example.com:1234'
|
origin: string; // e.g., 'https://example.com:1234'
|
||||||
url: string; // e.g., '/some/path'
|
url: string; // e.g., '/some/path'
|
||||||
@@ -32,7 +30,7 @@ export interface BootFuncParams {
|
|||||||
data: any; // any custom object passed through from .NET
|
data: any; // any custom object passed through from .NET
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BootModuleInfo {
|
interface BootModuleInfo {
|
||||||
moduleName: string;
|
moduleName: string;
|
||||||
exportName?: string;
|
exportName?: string;
|
||||||
webpackConfig?: string;
|
webpackConfig?: string;
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
|
/// <reference path="./PrerenderingInterfaces.d.ts" />
|
||||||
|
|
||||||
export * from './Prerendering';
|
export * from './Prerendering';
|
||||||
export * from './PrerenderingInterfaces';
|
|
||||||
|
export type RenderResult = RenderToStringResult | RedirectResult;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aspnet-webpack-react",
|
"name": "aspnet-webpack-react",
|
||||||
"version": "3.0.0",
|
"version": "2.0.0",
|
||||||
"description": "Helpers for using Webpack with React in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
|
"description": "Helpers for using Webpack with React in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aspnet-webpack",
|
"name": "aspnet-webpack",
|
||||||
"version": "2.0.3",
|
"version": "1.0.29",
|
||||||
"description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
|
"description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -33,6 +33,6 @@
|
|||||||
"webpack": "^1.13.2"
|
"webpack": "^1.13.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"webpack": "^1.13.2 || ^2.1.0-beta || ^3.0.0"
|
"webpack": "^1.13.2 || ^2.1.0-beta"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user