You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.NET 6中实现非API请求重定向至Angular路由

Azure Linux App Service页面刷新404:替代HashLocationStrategy的.NET 6重定向方案

问题背景

在Azure Linux App Service生产环境中,前端单页应用刷新页面时会返回404错误。之前通过在app.routing.module.ts中启用HashLocationStrategy解决了问题,但该方案会导致URL带#号,不利于SEO。现在希望撤销该路由策略修改,转而通过.NET 6 API后端处理路由重定向,但尝试app.MapWhen()或修改app.MapControllers()为app.UseEndpoints(options => options.MapControllers())均未成功。

当前代码

Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpsRedirection(options => options.HttpsPort = 443);

// Add services to the container.
builder.Services.AddCors(options => 
    options.AddPolicy("Default", policyBuilder =>
    {
        policyBuilder
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader();
    })
);

builder.Services.AddControllers();
builder.Services.AddAuthentication(x =>
{
    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
    o.SaveToken = true;
    o.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(
                builder.Configuration
                    .GetSection(ApplicationSettings.Section)
                    .GetValue<string>("JWT:Secret")))
    };
});

builder.Services.AddSwaggerGen(config =>
{
    config.SwaggerDoc("Backend", new OpenApiInfo { Title = "Backend", Version = "0.1"});

    config.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Description = "Please enter a valid token",
        Name = "Authorization",
        Type = SecuritySchemeType.Http,
        BearerFormat = "JWT",
        Scheme = "Bearer"
    });

    var securityRequirement = new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    };

    config.AddSecurityRequirement(securityRequirement);
});

builder.Services.Configure<ApplicationSettings>(
    builder.Configuration.GetSection(ApplicationSettings.Section));

builder.Services.AddDbContext<InstituteDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("Database")));

// Repositories
builder.Services.AddScoped<ICareCategoryRepository, CareCategoryRepository>();
builder.Services.AddScoped<ICareRepository, CareRepository>();

builder.Services.AddScoped<IUserRepository, UserRepository>();

builder.Services.AddScoped<IReservationInfoRepository, ReservationInfoRepository>();

// Services
builder.Services.AddScoped<ICareService, CareService>();
builder.Services.AddScoped<ICareCategoryService, CareCategoryService>();

builder.Services.AddScoped<IUserService, UserService>();

builder.Services.AddScoped<IReservationService, ReservationService>();

builder.Services.AddScoped<IReservationInfoService, ReservationInfoService>();

var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<InstituteDbContext>();
    context.Database.Migrate();
}

app.UseMiddleware<ExceptionHandlerMiddleware>();
app.UseMiddleware<JwtMiddleware>();
app.UseMiddleware<AntiXssMiddleware>();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/Backend/swagger.json", "Backend");
    });
}
else
{
    app.UseHttpsRedirection();
}

app.UseRouting();

app.UseCors("Default");

app.UseAuthentication();
app.UseAuthorization();

app.UseDefaultFiles();
app.UseStaticFiles();

app.MapControllers();

app.Run();

.htaccess

RewriteEngine On

# Redirection to HTTPS:
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

# Redirection of requests to index.html
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^.*$ - [NC,L]
# Redirect all non-file routes to index.html
RewriteRule ^(?!.*\.).*$ index.html [NC,L]

解决方案

要实现无哈希路由且解决刷新404的问题,需要同时调整.NET 6中间件配置和.htaccess规则,确保API请求正常处理,前端路由请求回退到index.html。

1. 修改Program.cs添加回退路由

在app.MapControllers();之后添加条件回退路由,仅对非API路径的请求重定向到index.html,避免干扰API接口:

app.MapControllers();

// 添加回退路由:非API请求全部指向index.html
app.MapWhen(context => !context.Request.Path.StartsWithSegments("/api"), builder =>
{
    builder.MapFallbackToFile("index.html");
});

app.Run();

同时确保中间件顺序正确:UseDefaultFiles必须在UseStaticFiles之前(你的代码中已经符合这个顺序),这样服务器会先尝试加载默认文档,再处理静态文件请求。

2. 调整.htaccess规则

原.htaccess的重写规则会将所有非文件/目录请求重定向到index.html,这会拦截API请求。修改规则,排除API路径:

RewriteEngine On

# 强制HTTPS
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

# 仅对非API路径处理回退
RewriteCond %{REQUEST_URI} !^/api
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d
RewriteRule ^ index.html [NC,L]

关键说明

  • .htaccess的规则会在.NET中间件之前执行,所以必须先排除API路径,让这些请求直接到达.NET后端处理。
  • .NET的MapWhen进一步确保非API请求都回退到index.html,让前端路由接管页面渲染,避免刷新404。
  • 这种方案保留了干净的URL结构,完全支持SEO,同时解决了单页应用刷新404的问题。

内容的提问来源于stack exchange,提问作者Scryper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 16:54:58