【问题标题】:ASP.NET MVC 6: view components in a separate assemblyASP.NET MVC 6:在单独的程序集中查看组件
【发布时间】:2015-12-12 06:01:30
【问题描述】:

我想在独立于 MVC 6 Web 启动项目的程序集中定义视图组件(在 ASP.NET MVC 6 中是新的),以便我可以在多个 Web 项目中重用它们。示例解决方案可能如下所示:

  • BookStore.Components (包含通用视图组件)
  • BookStore.Web1 (引用 BookStore.Components)
  • BookStore.Web2 (引用 BookStore.Components)

我创建了一个新的类库(包)并在里面创建了一个视图组件。我还按照嵌套文件夹约定创建了视图。我的BookStore.Components 项目如下所示:

当我尝试从我的 Web 项目中调用此视图组件时:

@Component.Invoke("BookOfTheMonth")

...我收到一个 500 错误,内容为空。似乎发现了 ViewComponent 类,但没有发现该组件的 razor 视图。

我还尝试扩展 DefaultViewComponentDescriptorProvider 以便可以发现来自 BookStore.Components 程序集的视图组件:

定义了一个 AssemblyProvider

 public class AssemblyProvider : IAssemblyProvider
    {
        public IEnumerable<Assembly> CandidateAssemblies
        {
            get
            {
                yield return typeof(AssemblyProvider).Assembly;
                yield return typeof(BookStore.Components.BookOfTheMonthViewComponent).Assembly;
            }
        }
    }

使用 Autofac 注册的 AssemblyProvider

builder.RegisterType<AssemblyProvider>()
    .AsImplementedInterfaces();

builder.RegisterType<DefaultViewComponentDescriptorProvider>()
    .AsImplementedInterfaces();

我不确定是否需要注册上面的DefaultViewComponentDescriptorProvider,所以我尝试了有无它,但在调用视图组件的页面上仍然出现500错误。

如何调用与 MVC6 Web 项目分开的程序集中的视图组件?

【问题讨论】:

    标签: c# asp.net-core asp.net-core-mvc asp.net-core-viewcomponent


    【解决方案1】:

    2017-03-09 更新

    在使用 MS Build 的 Visual Studio 2017 中,情况发生了一些变化。幸运的是,它要简单得多。以下是如何让它发挥作用:

    在外部程序集中,将其添加到 csproj 文件中:

    <ItemGroup>
       <EmbeddedResource Include="Views/**/*.cshtml" />
    </ItemGroup>
    

    在主 Web 项目中,添加此 NuGet 包: Microsoft.Extensions.FileProviders.Embedded

    然后在 Startup 中,将外部程序集添加到 File Providers 列表中:

        services.Configure<RazorViewEngineOptions>(options =>
        {
            options.FileProviders.Add(new EmbeddedFileProvider(
                 typeof(SampleClassInAssembly).Assembly
                 # Prior to .Net Standard 2.0
                 # typeof(SampleClassInAssembly).GetTypeInfo().Assembly
            ));
        });
    

    我暂时将原始答案留在下面,以防人们仍在尝试使其与旧版本的 .Net Core 和 project.json 一起使用。

    ================================================ =================

    以下是完成这项工作的步骤。

    • 确保组件程序集中的视图结构与 Web 项目相同。请注意,我与问题一起发布的屏幕截图中有一个错误。
    • 在web项目的Startup.cs注册CompositeFileProvider

      services.Configure<RazorViewEngineOptions>(options =>
      {
          options.FileProvider = new CompositeFileProvider(
              new EmbeddedFileProvider(
                  typeof(BookOfTheMonthViewComponent).GetTypeInfo().Assembly,
                  "BookStore.Components"
              ),
              options.FileProvider
          );
      });
      

    CompositeFileProviderEmbeddedFileProvider 都是新的,因此您需要从 aspnetvnext NuGet 源获取它们。我通过添加这个来源做到了这一点:

    project.json中添加依赖:

    "Microsoft.AspNet.FileProviders.Composite": "1.0.0-*",
    "Microsoft.AspNet.FileProviders.Embedded": "1.0.0-*",
    

    最后,将其添加到Components 程序集的project.json

    "resource": "Views/**"
    

    这应该足以让它工作。

    这是一个工作演示: https://github.com/johnnyoshika/mvc6-view-components/tree/master

    这个答案是根据这里的讨论制定的:https://github.com/aspnet/Mvc/issues/3750

    2016 年 1 月 15 日更新 外部视图组件目前存在一个痛苦的问题。您对视图 cshtml 文件所做的任何更改都不会自动重新编译。即使是强制的 Visual Studio 清理和重建也不会这样做。您需要更改组件程序集中的 .cs 文件以触发视图重新编译,但看起来这将在未来得到纠正。这个问题的原因在这里解释一下:https://github.com/aspnet/Mvc/issues/3750#issuecomment-171765303

    【讨论】:

    • 谢谢,我等待这个解决方案一个世纪,现在我不必在项目中复制相同的视图!!! :)
    • @Johnny Oshika:有没有办法自动从程序集中加载视图组件?我的意思是创建类似的模块,这些模块被转储到一个名为Modules 的文件夹中,应用程序将自动加载所有这些模块,而无需在代码中明确定义它们?
    • @user2818430:我还没有尝试过这样做。 C# 类应该根据约定自动注册,但要注册视图,您可能必须在启动时注册它们。你试过了吗?
    • 我非常感谢你的这个回答,我试图找到一种方法来替换我的旧便携式区域,并且它缝合了这样做的方法。我对“命名空间”有点困惑,确保它与“assambleyInfo.cs”中定义的相同
    • 您对如何向该解决方案添加本地化资源有任何建议吗? stackoverflow.com/questions/34890555/…谢谢
    【解决方案2】:

    我在 Github 上做了一些研究,发现 Razor 引擎 (link) 使用 PhysicalFileProvider (link) IFileInfo GetFileInfo(string subpath) 方法来获取要编译的真实文件。

    此方法的当前实现

    public IFileInfo GetFileInfo(string subpath)
    {
         if (string.IsNullOrEmpty(subpath))
         {
             return new NotFoundFileInfo(subpath);
         }
    
         // Relative paths starting with a leading slash okay
         if (subpath.StartsWith("/", StringComparison.Ordinal))
         {
             subpath = subpath.Substring(1);
         }
    
         // Absolute paths not permitted.
         if (Path.IsPathRooted(subpath))
         {
             return new NotFoundFileInfo(subpath);
         }
    
         var fullPath = GetFullPath(subpath);
         if (fullPath == null)
         {
             return new NotFoundFileInfo(subpath);
         }
    
         var fileInfo = new FileInfo(fullPath);
         if (FileSystemInfoHelper.IsHiddenFile(fileInfo))
         {
             return new NotFoundFileInfo(subpath);
         }
    
         return new PhysicalFileInfo(_filesWatcher, fileInfo);
    }
    
    private string GetFullPath(string path)
    {
        var fullPath = Path.GetFullPath(Path.Combine(Root, path));
        if (!IsUnderneathRoot(fullPath))
        {
            return null;
        }
        return fullPath;
    }
    

    我们可以在这里看到绝对路径也不允许,GetFullPath 方法将路径与Root 结合在一起,这是您的主要 Web 应用程序根路径。

    所以我假设你不能从当前文件夹以外的其他文件夹打开ViewComponent

    【讨论】:

    • 有趣。我会继续挖掘,看看是否有解决方法。
    • @JohnnyOshika 我想唯一可能的解决方法是在类库中手动生成RazorFileInfoCollection 类。
    • 看起来我们可以利用一些钩子? IMvcBuilder 上有一个 AddPrecompiledRazorViews 扩展方法,它可以这样调用:services.AddMvc().AddPrecompiledRazorViews(assembly),但到目前为止我还没有让它工作。我尝试使用此处描述的技术strathweb.com/2014/12/… 预编译类库中的视图,但到目前为止,没有运气。
    • @JohnnyOshika 我尝试了相同的技术,但它仅适用于 Web 应用程序二进制文件,并且框架仅在运行时预编译。这个想法是我们需要在类库的某个地方创建 RazorFileInfoCollection 并注入到主程序集中,但目前很难实现
    • 是的,我也想不通。希望 MVC 团队的人能尽快参与进来:github.com/aspnet/Mvc/issues/3750
    【解决方案3】:

    .NetCore v3.x 开始:

    1. [可选] 删除 Microsoft.Extensions.FileProviders.Embedded nuget 包
    2. 安装Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilationnuget包
    3. 致电.AddRazorRuntimeCompilation(),例如:services.AddMvc().AddRazorRuntimeCompilation()
    4. 而不是
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.FileProviders.Add(new EmbeddedFileProvider(
             typeof(SampleClassInAssembly).Assembly
        ));
    });
    

    添加这个:

    services.Configure<MvcRazorRuntimeCompilationOptions>(options =>
    {
        options.FileProviders.Add(new EmbeddedFileProvider(
             typeof(SampleClassInAssembly).Assembly
        ));
    });
    

    你可以走了。

    Related github issue

    【讨论】:

    • 别忘了在属性中将default.cshtml 构建操作设置为“嵌入式资源”,这对我来说是最后一块拼图。
    猜你喜欢
    • 2010-09-06
    • 2023-04-09
    • 2016-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-19
    相关资源
    最近更新 更多