ASP.NET MVC 6:在单独的程序集中查看组件
ASP.NET MVC 6: view components in a separate assembly
我想在 MVC 6 Web 启动项目的单独程序集中定义视图组件(ASP.NET MVC 6 中的新组件),以便我可以在多个 Web 项目中重用它们。示例解决方案可能如下所示:
- BookStore.Components (包含公共视图组件)
- BookStore.Web1 (引用 BookStore.Components)
- BookStore.Web2 (引用 BookStore.Components)
我创建了一个新的Class库(包),并在里面创建了一个视图组件。我还按照嵌套文件夹约定创建了视图。我的 BookStore.Components
项目如下所示:
当我尝试从我的 Web 项目调用此视图组件时:
@Component.Invoke("BookOfTheMonth")
...我收到内容正文为空的 500 错误。似乎已发现 ViewComponent class,但未发现该组件的 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 项目不同的程序集中的视图组件?
我对 Github 做了一些研究,发现 PhysicalFileProvider
(link) IFileInfo GetFileInfo(string subpath)
method is used by Razor engine (link) 用于获取要编译的真实文件。
此方法的当前实现
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
。
更新2017-03-09
在 Visual Studio 2017 年使用 MS Build 时情况发生了一些变化。幸运的是,它要简单得多。以下是如何让它发挥作用:
在外部程序集中,将其添加到 csproj 文件中:
<ItemGroup>
<EmbeddedResource Include="Views/**/*.cshtml" />
</ItemGroup>
在主 web 项目中,添加这个 NuGet 包:
Microsoft.Extensions.FileProviders.Embedded
然后在启动中,将外部程序集添加到文件提供程序列表中:
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
);
});
CompositeFileProvider
和 EmbeddedFileProvider
都是新的,因此您需要从 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-01-15
目前外部视图组件存在一个痛苦的问题。您对视图 cshtml 文件所做的任何更改都不会自动重新编译。即使是强制 Visual Studio 清理和重建也无法做到这一点。您需要更改组件程序集中的 .cs 文件才能触发视图重新编译,但看起来这将在未来得到纠正。这个问题的原因解释在这里:https://github.com/aspnet/Mvc/issues/3750#issuecomment-171765303
截至 .NetCore v3.x:
- [可选] 删除
Microsoft.Extensions.FileProviders.Embedded
nuget 包
- 安装
Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
nuget 包
- 调用
.AddRazorRuntimeCompilation()
,例如:services.AddMvc().AddRazorRuntimeCompilation()
- 而不是
services.Configure<RazorViewEngineOptions>(options =>
{
options.FileProviders.Add(new EmbeddedFileProvider(
typeof(SampleClassInAssembly).Assembly
));
});
添加这个:
services.Configure<MvcRazorRuntimeCompilationOptions>(options =>
{
options.FileProviders.Add(new EmbeddedFileProvider(
typeof(SampleClassInAssembly).Assembly
));
});
你很高兴。
我想在 MVC 6 Web 启动项目的单独程序集中定义视图组件(ASP.NET MVC 6 中的新组件),以便我可以在多个 Web 项目中重用它们。示例解决方案可能如下所示:
- BookStore.Components (包含公共视图组件)
- BookStore.Web1 (引用 BookStore.Components)
- BookStore.Web2 (引用 BookStore.Components)
我创建了一个新的Class库(包),并在里面创建了一个视图组件。我还按照嵌套文件夹约定创建了视图。我的 BookStore.Components
项目如下所示:
当我尝试从我的 Web 项目调用此视图组件时:
@Component.Invoke("BookOfTheMonth")
...我收到内容正文为空的 500 错误。似乎已发现 ViewComponent class,但未发现该组件的 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 项目不同的程序集中的视图组件?
我对 Github 做了一些研究,发现 PhysicalFileProvider
(link) IFileInfo GetFileInfo(string subpath)
method is used by Razor engine (link) 用于获取要编译的真实文件。
此方法的当前实现
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
。
更新2017-03-09
在 Visual Studio 2017 年使用 MS Build 时情况发生了一些变化。幸运的是,它要简单得多。以下是如何让它发挥作用:
在外部程序集中,将其添加到 csproj 文件中:
<ItemGroup>
<EmbeddedResource Include="Views/**/*.cshtml" />
</ItemGroup>
在主 web 项目中,添加这个 NuGet 包:
Microsoft.Extensions.FileProviders.Embedded
然后在启动中,将外部程序集添加到文件提供程序列表中:
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 ); });
CompositeFileProvider
和 EmbeddedFileProvider
都是新的,因此您需要从 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-01-15 目前外部视图组件存在一个痛苦的问题。您对视图 cshtml 文件所做的任何更改都不会自动重新编译。即使是强制 Visual Studio 清理和重建也无法做到这一点。您需要更改组件程序集中的 .cs 文件才能触发视图重新编译,但看起来这将在未来得到纠正。这个问题的原因解释在这里:https://github.com/aspnet/Mvc/issues/3750#issuecomment-171765303
截至 .NetCore v3.x:
- [可选] 删除
Microsoft.Extensions.FileProviders.Embedded
nuget 包 - 安装
Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
nuget 包 - 调用
.AddRazorRuntimeCompilation()
,例如:services.AddMvc().AddRazorRuntimeCompilation()
- 而不是
services.Configure<RazorViewEngineOptions>(options =>
{
options.FileProviders.Add(new EmbeddedFileProvider(
typeof(SampleClassInAssembly).Assembly
));
});
添加这个:
services.Configure<MvcRazorRuntimeCompilationOptions>(options =>
{
options.FileProviders.Add(new EmbeddedFileProvider(
typeof(SampleClassInAssembly).Assembly
));
});
你很高兴。