99using Microsoft . AspNetCore . Hosting ;
1010using System . Threading ;
1111using Microsoft . AspNetCore . SpaServices . Proxy ;
12+ using Microsoft . AspNetCore . Http ;
1213
1314namespace Microsoft . AspNetCore . SpaServices . AngularCli
1415{
1516 internal class AngularCliMiddleware
1617 {
1718 private const string _middlewareResourceName = "/Content/Node/angular-cli-middleware.js" ;
1819
20+ internal readonly static string AngularCliMiddlewareKey = Guid . NewGuid ( ) . ToString ( ) ;
21+
1922 private readonly INodeServices _nodeServices ;
2023 private readonly string _middlewareScriptPath ;
2124
22- public AngularCliMiddleware ( ISpaBuilder spaBuilder , string sourcePath )
25+ public AngularCliMiddleware ( IApplicationBuilder appBuilder , string sourcePath , string urlPrefix , string defaultPage )
2326 {
2427 if ( string . IsNullOrEmpty ( sourcePath ) )
2528 {
2629 throw new ArgumentException ( "Cannot be null or empty" , nameof ( sourcePath ) ) ;
2730 }
2831
2932 // Prepare to make calls into Node
30- var appBuilder = spaBuilder . AppBuilder ;
3133 _nodeServices = CreateNodeServicesInstance ( appBuilder , sourcePath ) ;
3234 _middlewareScriptPath = GetAngularCliMiddlewareScriptPath ( appBuilder ) ;
3335
3436 // Start Angular CLI and attach to middleware pipeline
3537 var angularCliServerInfoTask = StartAngularCliServerAsync ( ) ;
36- spaBuilder . AddStartupTask ( angularCliServerInfoTask ) ;
3738
3839 // Proxy the corresponding requests through ASP.NET and into the Node listener
3940 // Anything under /<publicpath> (e.g., /dist) is proxied as a normal HTTP request
4041 // with a typical timeout (100s is the default from HttpClient).
41- UseProxyToLocalAngularCliMiddleware ( appBuilder , spaBuilder . PublicPath ,
42+ UseProxyToLocalAngularCliMiddleware ( appBuilder , urlPrefix , defaultPage ,
4243 angularCliServerInfoTask , TimeSpan . FromSeconds ( 100 ) ) ;
4344
4445 // Advertise the availability of this feature to other SPA middleware
45- spaBuilder . Properties . Add ( this , null ) ;
46+ appBuilder . Properties . Add ( AngularCliMiddlewareKey , this ) ;
4647 }
4748
4849 public Task StartAngularCliBuilderAsync ( string cliAppName )
@@ -65,6 +66,11 @@ private static INodeServices CreateNodeServicesInstance(
6566 ProjectPath = Path . Combine ( Directory . GetCurrentDirectory ( ) , sourcePath ) ,
6667 } ;
6768
69+ if ( ! Directory . Exists ( nodeServicesOptions . ProjectPath ) )
70+ {
71+ throw new DirectoryNotFoundException ( $ "Directory not found: { nodeServicesOptions . ProjectPath } ") ;
72+ }
73+
6874 return NodeServicesFactory . CreateNodeServices ( nodeServicesOptions ) ;
6975 }
7076
@@ -99,7 +105,7 @@ private async Task<AngularCliServerInfo> StartAngularCliServerAsync()
99105 }
100106
101107 private static void UseProxyToLocalAngularCliMiddleware (
102- IApplicationBuilder appBuilder , string publicPath ,
108+ IApplicationBuilder appBuilder , string urlPrefix , string defaultPage ,
103109 Task < AngularCliServerInfo > serverInfoTask , TimeSpan requestTimeout )
104110 {
105111 // This is hardcoded to use http://localhost because:
@@ -110,7 +116,37 @@ private static void UseProxyToLocalAngularCliMiddleware(
110116 var proxyOptionsTask = serverInfoTask . ContinueWith (
111117 task => new ConditionalProxyMiddlewareTarget (
112118 "http" , "localhost" , task . Result . Port . ToString ( ) ) ) ;
113- appBuilder . UseMiddleware < ConditionalProxyMiddleware > ( publicPath , requestTimeout , proxyOptionsTask ) ;
119+
120+ // Requests outside /<urlPrefix> are proxied to the default page
121+ var hasRewrittenUrlMarker = new object ( ) ;
122+ var defaultPageUrl = SpaDefaultPageExtensions . GetDefaultPageUrl (
123+ urlPrefix , defaultPage ) ;
124+ var urlPrefixIsRoot = string . IsNullOrEmpty ( urlPrefix ) || urlPrefix == "/" ;
125+ appBuilder . Use ( ( context , next ) =>
126+ {
127+ if ( ! urlPrefixIsRoot && ! context . Request . Path . StartsWithSegments ( urlPrefix ) )
128+ {
129+ context . Items [ hasRewrittenUrlMarker ] = context . Request . Path ;
130+ context . Request . Path = defaultPageUrl ;
131+ }
132+
133+ return next ( ) ;
134+ } ) ;
135+
136+ appBuilder . UseMiddleware < ConditionalProxyMiddleware > ( urlPrefix , requestTimeout , proxyOptionsTask ) ;
137+
138+ // If we rewrote the path, rewrite it back. Don't want to interfere with
139+ // any other middleware.
140+ appBuilder . Use ( ( context , next ) =>
141+ {
142+ if ( context . Items . ContainsKey ( hasRewrittenUrlMarker ) )
143+ {
144+ context . Request . Path = ( PathString ) context . Items [ hasRewrittenUrlMarker ] ;
145+ context . Items . Remove ( hasRewrittenUrlMarker ) ;
146+ }
147+
148+ return next ( ) ;
149+ } ) ;
114150 }
115151
116152#pragma warning disable CS0649
0 commit comments