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

如何在ASP.NET Core MVC中实现单域名多路径站点部署?

单域名多路径部署ASP.NET Core MVC多站点解决方案

1. 路由与站点识别配置

核心是从请求路径中提取站点标识(如site1/site2),并将其传递到后续业务逻辑,有两种常用实现方式:

方式1:用MapWhen分支请求

在Program.cs中按站点前缀拆分请求管道,同时重写路径移除前缀,让MVC按常规路由处理:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();

var app = builder.Build();

// 匹配所有站点前缀请求
app.MapWhen(context => 
    context.Request.Path.Value.Split('/').Length > 1 && 
    context.Request.Path.Value.Split('/')[1].StartsWith("site"), 
    siteApp =>
    {
        var siteKey = siteApp.Request.Path.Value.Split('/')[1];
        
        // 将站点标识存入请求上下文,并重写路径
        siteApp.Use(async (context, next) =>
        {
            context.Items["SiteKey"] = siteKey;
            context.Request.Path = context.Request.Path.Value.Replace($"/{siteKey}", string.Empty);
            await next();
        });

        siteApp.UseStaticFiles();
        siteApp.UseRouting();
        siteApp.UseAuthorization();
        siteApp.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });

// 根路径默认路由(可选)
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

方式2:全局路由携带站点参数

直接在路由模板中加入siteKey参数,控制器通过路由绑定获取:

// Program.cs中配置全局路由
app.MapControllerRoute(
    name: "site",
    pattern: "{siteKey}/{controller=Home}/{action=Index}/{id?}");

控制器中接收参数:

public IActionResult Index([FromRoute] string siteKey)
{
    // 基于siteKey执行站点专属逻辑
    return View();
}

2. 站点上下文全局注入

创建SiteContext封装站点配置,通过中间件注入到请求上下文,避免重复提取:

public class SiteContext
{
    public string SiteKey { get; set; }
    public string Language { get; set; }
    public string Theme { get; set; }
}

// 站点配置模型
public class SiteConfig
{
    public string Language { get; set; }
    public string Theme { get; set; }
}

编写中间件注入上下文:

public class SiteContextMiddleware
{
    private readonly RequestDelegate _next;

    public SiteContextMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, IOptions<Dictionary<string, SiteConfig>> siteConfigs)
    {
        var pathSegments = context.Request.Path.Value.Split('/');
        if (pathSegments.Length > 1 && siteConfigs.Value.ContainsKey(pathSegments[1]))
        {
            var siteKey = pathSegments[1];
            context.Items["SiteContext"] = new SiteContext
            {
                SiteKey = siteKey,
                Language = siteConfigs.Value[siteKey].Language,
                Theme = siteConfigs.Value[siteKey].Theme
            };
        }
        await _next(context);
    }
}

// Program.cs中注册中间件
app.UseMiddleware<SiteContextMiddleware>();

在appsettings.json中配置站点参数:

{
  "SiteConfigs": {
    "site1": {
      "Language": "zh-CN",
      "Theme": "light"
    },
    "site2": {
      "Language": "en-US",
      "Theme": "dark"
    }
  }
}

绑定配置到服务:

builder.Services.Configure<Dictionary<string, SiteConfig>>(builder.Configuration.GetSection("SiteConfigs"));

3. 静态资源与业务内容隔离

静态资源

将每个站点的静态资源放在wwwroot/sites/{siteKey}目录(如wwwroot/sites/site1/images),中间件中动态指定静态文件路径:

// 在MapWhen分支中配置
siteApp.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, $"wwwroot/sites/{siteKey}")),
    RequestPath = "/site-resources"
});

视图中引用资源:

<img src="/site-resources/images/logo.png" />

业务内容

数据库表中添加SiteKey字段,查询时过滤当前站点数据:

public async Task<List<Article>> GetSiteArticles(string siteKey)
{
    return await _dbContext.Articles.Where(a => a.SiteKey == siteKey).ToListAsync();
}

4. 站点专属本地化

为每个站点创建独立资源文件,结构示例:

Resources/
├─ site1/
│  └─ Views/
│     └─ Home/
│        └─ Index.resx
├─ site2/
│  └─ Views/
│     └─ Home/
│        └─ Index.resx
└─ Shared/
   └─ Views/
      └─ Home/
         └─ Index.resx

自定义资源提供器,根据站点标识加载对应资源:

public class SiteResourceProvider : IStringLocalizerProvider
{
    public IStringLocalizer Create(Type resourceSource)
    {
        var httpContext = new HttpContextAccessor().HttpContext;
        var siteKey = httpContext?.Items["SiteKey"] as string;
        var baseName = string.IsNullOrEmpty(siteKey) 
            ? $"Resources.Shared.{resourceSource.FullName}" 
            : $"Resources.{siteKey}.{resourceSource.FullName}";
        return new ResourceManagerStringLocalizer(baseName, resourceSource.Assembly);
    }
}

注册本地化服务:

builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
builder.Services.AddSingleton<IStringLocalizerProvider, SiteResourceProvider>();

5. 视图差异化处理

自定义视图引擎,优先查找站点专属视图,再回退到共享视图:

public class SiteViewEngine : RazorViewEngine
{
    public SiteViewEngine(IRazorPageFactoryProvider pageFactoryProvider, 
        IRazorPageActivator pageActivator, 
        HtmlEncoder htmlEncoder, 
        IOptions<RazorViewEngineOptions> optionsAccessor) 
        : base(pageFactoryProvider, pageActivator, htmlEncoder, optionsAccessor)
    {
    }

    protected override IEnumerable<string> GetViewLocations(ViewLocationExpanderContext context, string viewName)
    {
        var siteKey = context.ActionContext.HttpContext.Items["SiteKey"] as string;
        var siteSpecificLocations = new List<string>();
        if (!string.IsNullOrEmpty(siteKey))
        {
            siteSpecificLocations.AddRange(new[]
            {
                $"/Views/{siteKey}/{context.ControllerName}/{viewName}.cshtml",
                $"/Views/{siteKey}/Shared/{viewName}.cshtml"
            });
        }
        // 先返回站点专属路径,再返回默认共享路径
        return siteSpecificLocations.Concat(base.GetViewLocations(context, viewName));
    }
}

替换默认视图引擎:

builder.Services.AddSingleton<IViewEngine, SiteViewEngine>();

6. 部署注意事项

  • IIS部署:无需为每个siteX创建独立应用程序,将主站点设为应用程序即可,确保请求路径完整传递到ASP.NET Core。
  • 反向代理:使用Nginx等代理时,配置路径转发规则,保留原始请求路径。
  • 环境配置:通过环境变量覆盖appsettings.json中的站点配置,适配不同部署环境。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 23:20:26