I have an application using ASP.NET Core MVC and an Angular UI framework.
I can run the application in IIS Express Development Environment without issue. When I switch to the IIS Express Production environment or deploy to an IIS host, my index referenced files cannot be read showing a browser error:
Uncaught SyntaxError: Unexpected token '<'
These pages look like they are loading the index page as opposed to the .js or .css files.
Here is a snippet of the underlying runtime.js as it should be loaded into browser, it is not loaded with index.html.
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ function webpackJsonpCallback(data) {
/******/ var chunkIds = data[0];
/******/ var moreModules = data[1];
/******/ var executeModules = data[2];
/******/
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
From my understanding this should be a naming or pathing error, but I am able to access the index file in the browser, the files are all in the same folder, and there is no filename hashing. I notice some of the folder structures seem to be excluded from the .csproj but that does not seem to impact the files themselves.
I assume this is related to startup.cs, .csproj instuctions, or IIS configuration but without more specific errors I cannot see what is going wrong.
Program.cs:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SNAP
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SNAP.Data;
namespace SNAP
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
AppData.configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddControllersWithViews();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "SNAP-UI/dist/SNAP-UI";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
//Cannot recognize environment?
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "SNAP-UI";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
}
}
.csproj:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>SNAP-UI\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<!-- Set this to true if you enable server-side prerendering -->
<BuildServerSideRenderer>false</BuildServerSideRenderer>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.2" />
<PackageReference Include="nb.Core" Version="6.2.3" />
<PackageReference Include="nb.Data" Version="4.3.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.19" />
</ItemGroup>
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Compile Remove="SNAP-UI\**" />
<Compile Remove="SNAPUI\**" />
<Content Remove="$(SpaRoot)**" />
<Content Remove="SNAP-UI\**" />
<Content Remove="SNAPUI\**" />
<EmbeddedResource Remove="SNAP-UI\**" />
<EmbeddedResource Remove="SNAPUI\**" />
<None Remove="$(SpaRoot)**" />
<None Remove="SNAP-UI\**" />
<None Remove="SNAPUI\**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<ItemGroup>
<None Remove="SNAP-UI\browserslist" />
<None Remove="SNAP-UI\e2e\protractor.conf.js" />
<None Remove="SNAP-UI\e2e\src\app.e2e-spec.ts" />
<None Remove="SNAP-UI\e2e\src\app.po.ts" />
<None Remove="SNAP-UI\e2e\tsconfig.e2e.json" />
<None Remove="SNAP-UI\src\app\angular-material.module.ts" />
<None Remove="SNAP-UI\src\app\app.server.module.ts" />
<None Remove="SNAP-UI\src\app\core\base.service.ts" />
<None Remove="SNAP-UI\src\app\core\deal.service.ts" />
<None Remove="SNAP-UI\src\app\counter\counter.component.html" />
<None Remove="SNAP-UI\src\app\counter\counter.component.spec.ts" />
<None Remove="SNAP-UI\src\app\counter\counter.component.ts" />
<None Remove="SNAP-UI\src\app\deals\Deal.ts" />
<None Remove="SNAP-UI\src\app\deals\dealcalendar.component.ts" />
<None Remove="SNAP-UI\src\app\fetch-data\fetch-data.component.html" />
<None Remove="SNAP-UI\src\app\fetch-data\fetch-data.component.ts" />
<None Remove="SNAP-UI\src\app\home\home.component.html" />
<None Remove="SNAP-UI\src\app\home\home.component.ts" />
<None Remove="SNAP-UI\src\app\kendo_ui.module.ts" />
<None Remove="SNAP-UI\src\app\nav-menu\nav-menu.component.ts" />
<None Remove="SNAP-UI\src\karma.conf.js" />
<None Remove="SNAP-UI\src\tsconfig.app.json" />
<None Remove="SNAP-UI\src\tsconfig.server.json" />
<None Remove="SNAP-UI\src\tsconfig.spec.json" />
<None Remove="SNAP-UI\src\tslint.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SNAP.Data\SNAP.Data.csproj" />
<ProjectReference Include="..\SNAP.Domain\SNAP.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<TypeScriptCompile Include="SNAP-UI\src\app\angular-material.module.ts" />
<TypeScriptCompile Include="SNAP-UI\src\app\core\base.service.ts" />
<TypeScriptCompile Include="SNAP-UI\src\app\core\deal.service.ts" />
<TypeScriptCompile Include="SNAP-UI\src\app\deals\deal.ts" />
<TypeScriptCompile Include="SNAP-UI\src\app\deals\dealcalendar.component.ts" />
<TypeScriptCompile Include="SNAP-UI\src\app\kendo_ui.module.ts" />
<TypeScriptCompile Include="SNAP-UI\src\app\nav-menu\nav-menu.component.ts" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build --prod --ec=false --oh=media" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr --prod --ec=false --oh=media" Condition=" '$(BuildServerSideRenderer)' == 'true' " />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
<DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>
Update 1:
After further IIS investigation, it seems the page is making some 304 redirects for the single page application. This occurs in both IIS Express (Production mode) and on the IIS host. The server is also serving text/HTML for the non-index components. I do not have a web config in the main project but it is generated when published. I have updated the published web.config to point only at the index page. This has not resolved the issue.
web.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\SNAP.dll" stdoutLogEnabled="true" stdoutLogFile="C:\source\TempLog\" hostingModel="inprocess">
<handlerSettings>
<handlerSetting name="debugFile" value="C:\source\TempLog\tempDebug.log" />
<handlerSetting name="debugLevel" value="FILE,TRACE" />
</handlerSettings>
</aspNetCore>
</system.webServer>
</location>
<system.webServer>
<rewrite>
<rules>
<rule name="AddTrailingSlashRule1" stopProcessing="true">
<match url="(.*[^/])$" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
</conditions>
<action type="Redirect" url="{R:1}/" />
</rule>
<rule name="SPA Routes" stopProcessing="true">
<!-- match everything by default -->
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<!-- unless its a file -->
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<!-- or a directory -->
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<!-- or is under the /api directory -->
<add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
<!-- list other routes or route prefixes here if you need to handle them server side -->
</conditions>
<!-- rewrite it to /index.html -->
<action type="Rewrite" url="/index.html" />
</rule>
</rules>
</rewrite>
<tracing>
<traceFailedRequests>
<add path="*">
<traceAreas>
<add provider="ASP" verbosity="Verbose" />
<add provider="ASPNET" areas="Infrastructure,Module,Page,AppServices" verbosity="Verbose" />
<add provider="ISAPI Extension" verbosity="Verbose" />
<add provider="WWW Server" areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module,FastCGI,WebSocket,ANCM,Rewrite" verbosity="Verbose" />
</traceAreas>
<failureDefinitions statusCodes="200-600" />
</add>
</traceFailedRequests>
</tracing>
</system.webServer>
</configuration>
<!--ProjectGuid: d1eca6b3-ff92-4130-9f13-9b13719c7794-->
Update 2:
After further investigation I noticed 2 points of interest in Program.cs. When run in production mode on IIS Express, the app does not seem to hit the server side until the app is refreshed in the browser. It also seems like CreateHostBuilder
is hanging in IIS Express production mode. I confirmed using breakpoints that the app seems to fully complete startup.cs (the only call from program.cs). If I attempt to move the breakpoint past CreateHostBuilder
I get this error.
Unable to set the next statement. This thread has called into a function that cannot be displayed.
Program.cs
namespace SNAP
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
Console.WriteLine("Build Complete");
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
So, in my case when I create project with SPA. I Use next configuration. s
https://github.com/tematre/Site/blob/master/TemaTre.Site.Resume/Startup.cs
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultFiles()
.UseStaticFiles()
.UseDirectoryBrowser();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
So, you has no UseDirectoryBrowser()
let's try, may be it helps you.
One more comment: .UseStaticFiles() serves files from wwwroot but it can be changed. (From screenshot I see that you files stores in 'distrib' folder)
Other option is use UseSpaStaticFiles()
or UseSpa()
UseSpaStaticFiles
- Serve static file like image, css, js in asset
folder of angular app
UseSpa
- let asp.net core know which directory you want to run your
angular app, what dist folder when running in production mode and
which command to run angular app in dev mode
There are examples of using of this methods:
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});