使用自定义 VirtualPathProvider 加载嵌入式资源部分视图
Posted
技术标签:
【中文标题】使用自定义 VirtualPathProvider 加载嵌入式资源部分视图【英文标题】:Using custom VirtualPathProvider to load embedded resource Partial Views 【发布时间】:2011-10-12 20:37:15 【问题描述】:我编写了自定义的 VirtualFile 和 VirtualPathProvider 实现,它们成功地获得了作为部分视图的嵌入式资源。
但是,当我尝试渲染它们时,会产生以下错误:
The view at '~/Succeed.Web/Succeed.Web.Controls.SImporter._SImporter.cshtml' must derive from WebViewPage, or WebViewPage<TModel>.
在渲染局部视图时,在常规视图中,它如下所示:
Html.RenderPartial("~/Succeed.Web/Succeed.Web.Controls.SImporter._SImporter.cshtml");
是什么让它相信这不是片面的观点?
编辑:我发布了我的虚拟文件和虚拟文件提供程序实现的代码,供任何偶然发现此问题的人寻找使该组件正常工作的解决方案。这个问题也适用于那些基于问题标题的人。
这里是 VirtualFile 的实现供参考:
public class SVirtualFile : VirtualFile
private string m_path;
public SVirtualFile(string virtualPath)
: base(virtualPath)
m_path = VirtualPathUtility.ToAppRelative(virtualPath);
public override System.IO.Stream Open()
var parts = m_path.Split('/');
var assemblyName = parts[1];
var resourceName = parts[2];
assemblyName = Path.Combine(HttpRuntime.BinDirectory, assemblyName);
var assembly = System.Reflection.Assembly.LoadFile(assemblyName + ".dll");
if (assembly != null)
return assembly.GetManifestResourceStream(resourceName);
return null;
虚拟路径提供者:
public class SVirtualPathProvider : VirtualPathProvider
public SVirtualPathProvider()
private bool IsEmbeddedResourcePath(string virtualPath)
var checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.StartsWith("~/Succeed.Web/", StringComparison.InvariantCultureIgnoreCase);
public override bool FileExists(string virtualPath)
return IsEmbeddedResourcePath(virtualPath) || base.FileExists(virtualPath);
public override VirtualFile GetFile(string virtualPath)
if (IsEmbeddedResourcePath(virtualPath))
return new SVirtualFile(virtualPath);
else
return base.GetFile(virtualPath);
public override CacheDependency GetCacheDependency( string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
if (IsEmbeddedResourcePath(virtualPath))
return null;
else
return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
当然,不要忘记在 Application_Start() 事件的项目的 Global.asax 文件中注册这个新的提供程序
System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new SVirtualPathProvider());
【问题讨论】:
【参考方案1】:因为现在您正在从某个未知位置提供您的视图,所以不再有 ~/Views/web.config
文件适用并指示您的剃刀视图 (<pages pageBaseType="System.Web.Mvc.WebViewPage">
) 的基类。所以你可以在每个嵌入视图的顶部添加一个@inherits 指令来指示基类。
@inherits System.Web.Mvc.WebViewPage
@model ...
【讨论】:
非常感谢。我唯一的遗憾是我只能投一票。 =P 给这个答案投票 @darin Dimitrov 我想你不知道我可以用来鼓励智能感知在我的 dll 中将其视为视图的技巧吗?现在,我对放置在我的 dll 中的视图几乎没有智能感知支持 @MatthewCox,我对视图中的 Intellisense 了解不多。很久以前我就不再依赖它了。如果文件具有.cshtml
扩展名,您不会获得 Intellisense 吗?
是的,但是非常有限,这是奇怪的部分。例如,它不支持@Html.[whatever]
@FeistyMango - @Html
助手的智能感知依赖于 Views 文件夹中 web.config
中的命名空间。正如答案所暗示的那样,您应该能够将System.Web.Mvc.Html
和System.Web.Mvc
直接导入视图以获得智能感知。【参考方案2】:
我使用 OPs 的答案作为基础,但对其进行了扩展,并将问题的答案合并到我的解决方案中。
这似乎是关于 SO 的一个常见问题,我还没有看到完整的答案,所以我认为分享我的工作解决方案可能会有所帮助。
我从数据库加载资源,并将它们缓存在默认缓存 (System.Web.Caching.Cache) 中。
我最终做的是在用于查找资源的 KEY 上创建自定义 CacheDependency。这样,每当我的其他代码使该缓存无效时(在编辑等时),对该键的缓存依赖就会被删除,而 VirtualPathProvider 反过来会使其缓存无效并且 VirtualFile 会被重新加载。
我还更改了代码,使其自动添加继承语句,这样它就不需要存储在我的数据库资源中,并且我还自动添加了一些默认的 using 语句,因为这个“视图”不是通过正常加载的频道,因此您的 web.config 或 viewstart 中包含的任何默认内容均不可用。
自定义虚拟文件:
public class CustomVirtualFile : VirtualFile
private readonly string virtualPath;
public CustomVirtualFile(string virtualPath)
: base(virtualPath)
this.virtualPath = VirtualPathUtility.ToAppRelative(virtualPath);
private static string LoadResource(string resourceKey)
// Load from your database respository or whatever here...
// Note that the caching is disabled for this content in the virtual path
// provider, so you must cache this yourself in your repository.
// My implementation using my custom service locator that sits on top of
// Ninject
var contentRepository = FrameworkHelper.Resolve<IContentRepository>();
var resource = contentRepository.GetContent(resourceKey);
if (String.IsNullOrWhiteSpace(resource))
resource = String.Empty;
return resource;
public override Stream Open()
// Always in format: "~/CMS/0.cshtml"
var key = virtualPath.Replace("~/CMS/", "").Replace(".cshtml", "");
var resource = LoadResource(key);
// this automatically appends the inherit and default using statements
// ... add any others here you like or append them to your resource.
resource = String.Format("01", "@inherits System.Web.Mvc.WebViewPage<dynamic>\r\n" +
"@using System.Web.Mvc\r\n" +
"@using System.Web.Mvc.Html\r\n", resource);
return resource.ToStream();
CustomVirtualPathProvider:
public class CustomVirtualPathProvider : VirtualPathProvider
private static bool IsCustomContentPath(string virtualPath)
var checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.StartsWith("~/CMS/", StringComparison.InvariantCultureIgnoreCase);
public override bool FileExists(string virtualPath)
return IsCustomContentPath(virtualPath) || base.FileExists(virtualPath);
public override VirtualFile GetFile(string virtualPath)
return IsCustomContentPath(virtualPath) ? new CustomVirtualFile(virtualPath) : base.GetFile(virtualPath);
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
if (IsCustomContentPath(virtualPath))
var key = VirtualPathUtility.ToAppRelative(virtualPath);
key = key.Replace("~/CMS/", "").Replace(".cshtml", "");
var cacheKey = String.Format(ContentRepository.ContentCacheKeyFormat, key);
var dependencyKey = new String[1];
dependencyKey[0] = string.Format(cacheKey);
return new CacheDependency(null, dependencyKey);
return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
if (IsCustomContentPath(virtualPath))
return virtualPath;
return base.GetFileHash(virtualPath, virtualPathDependencies);
希望这会有所帮助!
【讨论】:
【参考方案3】:我非常依赖 OP 中的信息以及 Darin Dimitrov 的回答来创建一个 simple prototype 用于跨项目共享 MVC 组件。虽然这些非常有帮助,但我仍然遇到了一些在原型中解决的额外障碍,例如使用 @model 的共享视图。
【讨论】:
以上是关于使用自定义 VirtualPathProvider 加载嵌入式资源部分视图的主要内容,如果未能解决你的问题,请参考以下文章