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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 11:09:50