@@ -416,44 +416,6 @@ void RegisterDbContext<TContext>(string connectionStringKey) where TContext : Db
416416
417417 }
418418
419- // In development, set up Vite proxy BEFORE rewrite rules so it can handle .ts/.js files
420- if ( app . Environment . IsDevelopment ( ) )
421- {
422- // Development: Proxy Vue.js assets to Vite dev server for hot module replacement (HMR)
423- // This middleware intercepts requests for Vue assets and forwards them to the Vite dev server
424- app . Use ( async ( context , next ) =>
425- {
426- if ( ViteProxyHelpers . ShouldProxyToVite ( context , VueAppNames ) )
427- {
428- try
429- {
430- // Use the registered HttpClient from dependency injection
431- var httpClientFactory = context . RequestServices . GetRequiredService < IHttpClientFactory > ( ) ;
432- var httpClient = httpClientFactory . CreateClient ( "ViteProxy" ) ;
433-
434- // Build the Vite server URL and try to proxy directly
435- var viteUrl = ViteProxyHelpers . BuildViteUrl ( context . Request . Path , context . Request . QueryString , VueAppNames ) ;
436- var requestMessage = ViteProxyHelpers . CreateProxyRequest ( context , viteUrl ) ;
437- using var response = await httpClient . SendAsync ( requestMessage , HttpCompletionOption . ResponseHeadersRead , context . RequestAborted ) ;
438-
439- // Copy the response back to the client
440- await ViteProxyHelpers . CopyProxyResponse ( context , response ) ;
441- return ; // Successfully proxied, don't continue to static files
442- }
443- catch ( Exception ex )
444- {
445- var logger = context . RequestServices . GetRequiredService < ILogger < Program > > ( ) ;
446- logger . LogDebug ( ex , "Vite server not available, falling back to static files for {Path}" ,
447- Uri . EscapeDataString ( context . Request . Path . Value ?? "unknown" ) ) ;
448- // Fall through to static file serving
449- }
450- }
451-
452- // Continue to static file serving (either Vite not needed or not available)
453- await next ( ) ;
454- } ) ;
455- }
456-
457419 var rewriteOptions = new RewriteOptions ( ) ;
458420
459421 // Add redirects and rewrites for each SPA using centralized app names
@@ -470,9 +432,7 @@ void RegisterDbContext<TContext>(string connectionStringKey) where TContext : Db
470432 rewriteOptions . AddRewrite ( $@ "(?i)^{ escapedAppName } ", $ "/2/vue/src/{ lowerAppName } /index.html", true ) ;
471433 }
472434
473- app . UseRewriter ( rewriteOptions ) ;
474-
475- //for the vue src files, use directories in the url but serve index.html
435+ // Default-file convention for /vue (legacy path).
476436 app . UseDefaultFiles ( new DefaultFilesOptions
477437 {
478438 DefaultFileNames = new List < string > { "index.html" } ,
@@ -482,29 +442,68 @@ void RegisterDbContext<TContext>(string connectionStringKey) where TContext : Db
482442 RedirectToAppendTrailingSlash = true
483443 } ) ;
484444
485- // Static file serving configuration
486- // Serve built Vue files - in development proxy middleware runs first,
487- // in production these files are served directly
488- app . UseStaticFiles ( new StaticFileOptions
489- {
490- FileProvider = new PhysicalFileProvider (
491- Path . Combine ( builder . Environment . ContentRootPath , "wwwroot/vue" ) ) ,
492- RequestPath = "/2/vue"
493- } ) ;
494-
495- // Serve other static files
445+ // General static files (favicon, /css, /js, /images, etc.).
496446 app . UseStaticFiles ( ) ;
497447
498- // Add sitemap middleware after static file handling
499448 app . UseSitemapMiddleware ( ) ;
500449
501- // apply settings define earlier
450+ // Routing first so subsequent middleware can defer to a matched MVC endpoint.
502451 app . UseRouting ( ) ;
503452 app . UseAuthentication ( ) ;
504453 app . UseAuthorization ( ) ;
505454 app . UseCookiePolicy ( ) ;
506455 app . UseSession ( ) ;
507456
457+ // SPA shell serving — Vue app prefixes like /CMS, /Effort, etc.
458+ // Only runs when no MVC controller endpoint claimed the path, so attribute-routed
459+ // legacy endpoints (e.g. /CMS/Files → CMSController.Files) reach the controller
460+ // instead of being rewritten to the SPA shell.
461+ app . UseWhen (
462+ ctx => ctx . GetEndpoint ( ) is null ,
463+ branch =>
464+ {
465+ if ( app . Environment . IsDevelopment ( ) )
466+ {
467+ // Dev: proxy Vue assets and SPA routes to the Vite dev server (HMR).
468+ branch . Use ( async ( context , next ) =>
469+ {
470+ if ( ViteProxyHelpers . ShouldProxyToVite ( context , VueAppNames ) )
471+ {
472+ try
473+ {
474+ var httpClientFactory = context . RequestServices . GetRequiredService < IHttpClientFactory > ( ) ;
475+ var httpClient = httpClientFactory . CreateClient ( "ViteProxy" ) ;
476+
477+ var viteUrl = ViteProxyHelpers . BuildViteUrl ( context . Request . Path , context . Request . QueryString , VueAppNames ) ;
478+ var requestMessage = ViteProxyHelpers . CreateProxyRequest ( context , viteUrl ) ;
479+ using var response = await httpClient . SendAsync ( requestMessage , HttpCompletionOption . ResponseHeadersRead , context . RequestAborted ) ;
480+
481+ await ViteProxyHelpers . CopyProxyResponse ( context , response ) ;
482+ return ;
483+ }
484+ catch ( Exception ex )
485+ {
486+ var logger = context . RequestServices . GetRequiredService < ILogger < Program > > ( ) ;
487+ logger . LogDebug ( ex , "Vite server not available, falling back to static files for {Path}" ,
488+ Uri . EscapeDataString ( context . Request . Path . Value ?? "unknown" ) ) ;
489+ }
490+ }
491+
492+ await next ( ) ;
493+ } ) ;
494+ }
495+
496+ // Prod (and dev fallback): rewrite SPA routes to the built SPA shell,
497+ // then serve the static file from wwwroot/vue.
498+ branch . UseRewriter ( rewriteOptions ) ;
499+ branch . UseStaticFiles ( new StaticFileOptions
500+ {
501+ FileProvider = new PhysicalFileProvider (
502+ Path . Combine ( builder . Environment . ContentRootPath , "wwwroot/vue" ) ) ,
503+ RequestPath = "/2/vue"
504+ } ) ;
505+ } ) ;
506+
508507 // All health-check pipeline wiring lives in HealthCheckExtensions.
509508 app . UseViperHealthChecks ( ) ;
510509
0 commit comments