如何在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
相关产品推荐
相关产品推荐

