ASP.NET Core MVC跨项目渲染分部视图遇未找到错误,求解决方案
问题:ASP.NET Core MVC跨项目渲染Razor类库的分部视图
我有一个包含大量控制器和视图的ASP.NET Core MVC大型Web项目,为实现组件复用,创建了两个独立项目:
Webapp:Web应用项目Panels:Razor类库项目
Panels中包含/Views/Shared/_Panel.cshtml,这是需要复用的分部视图。
我在Panels库中编写了扩展方法用于渲染这些视图:
using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc.Rendering; using Panels.db; using Panels.Services; namespace Panels.ProgramExtensions; public static class HtmlHelperExtensions { public static async Task RenderPanelAsync(this IHtmlHelper htmlHelper, PanelService service, string panelName) { Panel? panel = await service.GetPanelAsync(panelName); if (panel == null) { string missingName = $"_PanelMissing"; await htmlHelper.RenderPartialAsync(missingName, panelName); return; } string foundName = $"_Panel"; await htmlHelper.RenderPartialAsync(foundName, panel); } public static async Task<IHtmlContent> PanelAsync(this IHtmlHelper htmlHelper, PanelService service, string panelName) { Panel? panel = await service.GetPanelAsync(panelName); if (panel == null) { string missingName = $"_PanelMissing"; return await htmlHelper.PartialAsync(missingName, panelName); } string foundName = $"_Panel"; return await htmlHelper.PartialAsync(foundName, panel); } }
在Web应用项目中调用该方法:
//Some code <div class="justify-content-center"> @await Html.PanelAsync(panelService, "Navbar Core"); </div> //Some more code
执行时出现错误:
处理请求时发生未处理的异常。
InvalidOperationException: 未找到分部视图'_PanelMissing'。已搜索以下位置:
/Views/Home/_PanelMissing.cshtml
/Views/Shared/_PanelMissing.cshtml
/Pages/Shared/_PanelMissing.cshtml
尝试过以下方法但均未生效:
- 将视图添加到
WebHostEnvironment - 使用
ViewLocationExpander(因路径中的../存在问题) - 在
HtmlHelper扩展中使用绝对文件路径 - 映射两组端点
相关辅助代码:
ApplicationBuilderExtensions.cs
using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Panels.db; namespace Panels.ProgramExtensions; public static class ApplicationBuilderExtensions { public static IApplicationBuilder UsePanels(this IApplicationBuilder app) { // Setup views var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>(); var embeddedFileProvider = new EmbeddedFileProvider(typeof(Services.PanelService).GetTypeInfo().Assembly); env.ContentRootFileProvider = new CompositeFileProvider(env.ContentRootFileProvider, embeddedFileProvider); // Ensure database is created using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope()) { var context = serviceScope.ServiceProvider.GetRequiredService<PanelsContext>(); context.Database.EnsureCreated(); try { _ = context.Panels.FirstOrDefault(); } catch (Exception) { // Table doesn't exist yet. RelationalDatabaseCreator databaseCreator = (RelationalDatabaseCreator)context.Database.GetService<IDatabaseCreator>(); databaseCreator.CreateTables(); } } // Configure the routes for the Panels project app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "Panels", pattern: "Panels/{controller=Panel}/{action=Index}/{id?}", defaults: new { area = "Panels" }, constraints: new { area = "Panels" }); }); return app; } }
ServiceCollectionExtensions.cs
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Panels.Controllers; using Panels.db; using Panels.Services; namespace Panels.ProgramExtensions; public static class ServiceCollectionExtensions { public static IServiceCollection AddPanels(this IServiceCollection services, string connectionString) { services.AddDbContext<PanelsContext>(options => options.UseSqlServer(connectionString)); services.AddScoped<PanelService>(); services.AddControllersWithViews() .AddApplicationPart(typeof(PanelController).Assembly) .AddRazorRuntimeCompilation(); return services; } } public class PanelViewLocationExpander : IViewLocationExpander { public void PopulateValues(ViewLocationExpanderContext context) { } public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations) { viewLocations = new[] { "~/../Webapp-Panel/Views/Shared/{0}.cshtml" } .Union(viewLocations); return viewLocations; } }
解决方案
1. 确保Razor类库视图被正确嵌入
在Panels项目的.csproj文件中添加以下配置,保证视图被编译为嵌入式资源:
<PropertyGroup> <TargetFramework>net6.0</TargetFramework> <!-- 替换为你的.NET版本 --> <AddRazorSupportForMvc>true</AddRazorSupportForMvc> </PropertyGroup> <ItemGroup> <EmbeddedResource Include="Views/**/*.cshtml" /> </ItemGroup>
2. 修正Razor视图位置配置
修改ServiceCollectionExtensions.cs,注册正确的视图位置扩展器,禁用相对路径:
public static IServiceCollection AddPanels(this IServiceCollection services, string connectionString) { services.AddDbContext<PanelsContext>(options => options.UseSqlServer(connectionString)); services.AddScoped<PanelService>(); services.AddControllersWithViews() .AddApplicationPart(typeof(PanelController).Assembly) .AddRazorRuntimeCompilation() .AddRazorOptions(options => { options.ViewLocationExpanders.Add(new PanelViewLocationExpander()); // 添加Panels库视图的搜索路径 options.ViewLocations.Add("/Panels/Views/Shared/{0}.cshtml"); }); return services; } public class PanelViewLocationExpander : IViewLocationExpander { public void PopulateValues(ViewLocationExpanderContext context) { } public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations) { // 使用嵌入式资源的绝对路径格式 return new[] { "/Panels/Views/Shared/{0}.cshtml" } .Union(viewLocations); } }
3. 在扩展方法中指定完整视图路径
调用视图渲染方法时,直接传入Panels库视图的完整路径:
public static async Task<IHtmlContent> PanelAsync(this IHtmlHelper htmlHelper, PanelService service, string panelName) { Panel? panel = await service.GetPanelAsync(panelName); if (panel == null) { // 直接指定Panels库中缺失视图的路径 return await htmlHelper.PartialAsync("/Panels/Views/Shared/_PanelMissing.cshtml", panelName); } return await htmlHelper.PartialAsync("/Panels/Views/Shared/_Panel.cshtml", panel); }
4. 修正嵌入式文件提供者配置
确保UsePanels方法中正确设置嵌入式资源的命名空间:
public static IApplicationBuilder UsePanels(this IApplicationBuilder app) { var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>(); // 命名空间对应Panels项目的视图根目录,格式为[项目名].Views var embeddedFileProvider = new EmbeddedFileProvider( typeof(PanelService).Assembly, "Panels.Views"); env.WebRootFileProvider = new CompositeFileProvider(env.WebRootFileProvider, embeddedFileProvider); // 数据库初始化和路由代码保持不变 using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope()) { var context = serviceScope.ServiceProvider.GetRequiredService<PanelsContext>(); context.Database.EnsureCreated(); try { _ = context.Panels.FirstOrDefault(); } catch (Exception) { RelationalDatabaseCreator databaseCreator = (RelationalDatabaseCreator)context.Database.GetService<IDatabaseCreator>(); databaseCreator.CreateTables(); } } app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "Panels", pattern: "Panels/{controller=Panel}/{action=Index}/{id?}", defaults: new { area = "Panels" }, constraints: new { area = "Panels" }); }); return app; }
关键注意事项
- Razor类库的嵌入式视图命名空间默认是
[项目名称].Views,必须与嵌入式文件提供者的命名空间参数匹配。 - ASP.NET Core不支持跨项目的相对路径引用视图,必须使用嵌入式资源的绝对路径或通过
ViewLocationExpander配置搜索位置。 - 确认
_PanelMissing.cshtml存在于Panels项目的/Views/Shared/目录下。
内容的提问来源于stack exchange,提问作者JD9999
相关产品推荐
相关产品推荐

