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