更新:看起来Technitium找到了另一种方法,这看起来更容易,至少在较新版本的ASP.NET MVC上.(复制他的评论如下)
我不确定这是否是ASP.NET MVC 3中的新功能,但是当我将Inherits属性从引用C#语法中的泛型转换为CLR语法时,标准
ViewPageParserFilter
已正确解析泛型 -CustomViewTypeParserFilter
不需要.使用Justin的例子,这意味着交换
<%@ Page Language="C#" MyNewProperty="From @Page directive!" Inherits="JG.ParserFilter.CustomViewPage
至
<%@ Page Language="C#" MyNewProperty="From @Page directive!"` Inherits="JG.ParserFilter.CustomViewPage`1[MvcApplication1.Models.FooModel]>
原答案如下:
好的,我解决了这个问题.这是一项非常有趣的练习,解决方案非常重要,但是一旦你第一次使用它就不会太难.
这是底层问题:ASP.NET页面解析器不支持泛型作为页面类型.
ASP.NET MVC解决这个问题的方法是欺骗底层页面解析器,使其认为页面不是通用的.他们通过构建自定义PageParserFilter和自定义FileLevelPageControlBuilder来实现此目的.解析器过滤器查找泛型类型,如果找到一个类型,则将其交换为非泛型ViewPage类型,以便ASP.NET解析器不会阻塞.然后,在页面编译生命周期的后期,他们的自定义页面构建器类将通用类型重新交换.
这是因为通用ViewPage类型派生自非泛型ViewPage,并且在(非泛型)基类上存在@Page指令中设置的所有有趣属性.因此,在@Page指令中设置属性时真正发生的是,这些属性名称是针对非泛型ViewPage基类进行验证的.
无论如何,这在大多数情况下都很好用,但不适用于你的,因为他们在其页面过滤器实现中将ViewPage硬编码为非泛型基类型,并且不提供更改它的简单方法.这就是为什么你在错误消息中看到ViewPage的原因,因为在ASP.NET交换ViewPage占位符和在编译之前交换通用ViewPage之间发生错误.
解决方法是创建您自己的以下版本:
页面解析器过滤器 - 这几乎是MVC源中ViewTypeParserFilter.cs的精确副本,唯一的区别是它引用了自定义ViewPage和页面构建器类型而不是MVC的
页面构建器 - 这与MVC源中的ViewPageControlBuilder.cs相同,但它将类放在您自己的命名空间中而不是它们的名称空间中.
直接从System.Web.Mvc.ViewPage(非泛型版本)派生自定义viewpage类.在这个新的非泛型类上粘贴任何自定义属性.
从#3派生一个泛型类,从ASP.NET MVC源代码的ViewPage实现中复制代码.
如果您还需要用户控件指令的自定义属性,请对用户控件(@Control)重复#2,#3和#4.
然后,您需要更改views目录(而不是主应用程序的web.config)中的web.config以使用这些新类型而不是MVC的默认类型.
我附上了一些代码示例,说明了它是如何工作的.非常感谢Phil Haack的文章,以帮助我理解这一点,虽然我不得不在MVC和ASP.NET源代码中进行大量探索,以便真正理解它.
首先,我将从web.config中所需的web.config更改开始:
现在,这是页面解析器过滤器(上面的#1):
namespace JG.ParserFilter { using System; using System.Collections; using System.Web.UI; using System.Web.Mvc; internal class CustomViewTypeParserFilter : PageParserFilter { private string _viewBaseType; private DirectiveType _directiveType = DirectiveType.Unknown; private bool _viewTypeControlAdded; public override void PreprocessDirective(string directiveName, IDictionary attributes) { base.PreprocessDirective(directiveName, attributes); string defaultBaseType = null; // If we recognize the directive, keep track of what it was. If we don't recognize // the directive then just stop. switch (directiveName) { case "page": _directiveType = DirectiveType.Page; defaultBaseType = typeof(JG.ParserFilter.CustomViewPage).FullName; // JG: inject custom types here break; case "control": _directiveType = DirectiveType.UserControl; defaultBaseType = typeof(JG.ParserFilter.CustomViewUserControl).FullName; // JG: inject custom types here break; case "master": _directiveType = DirectiveType.Master; defaultBaseType = typeof(System.Web.Mvc.ViewMasterPage).FullName; break; } if (_directiveType == DirectiveType.Unknown) { // If we're processing an unknown directive (e.g. a register directive), stop processing return; } // Look for an inherit attribute string inherits = (string)attributes["inherits"]; if (!String.IsNullOrEmpty(inherits)) { // If it doesn't look like a generic type, don't do anything special, // and let the parser do its normal processing if (IsGenericTypeString(inherits)) { // Remove the inherits attribute so the parser doesn't blow up attributes["inherits"] = defaultBaseType; // Remember the full type string so we can later give it to the ControlBuilder _viewBaseType = inherits; } } } private static bool IsGenericTypeString(string typeName) { // Detect C# and VB generic syntax // REVIEW: what about other languages? return typeName.IndexOfAny(new char[] { '<', '(' }) >= 0; } public override void ParseComplete(ControlBuilder rootBuilder) { base.ParseComplete(rootBuilder); // If it's our page ControlBuilder, give it the base type string CustomViewPageControlBuilder pageBuilder = rootBuilder as JG.ParserFilter.CustomViewPageControlBuilder; // JG: inject custom types here if (pageBuilder != null) { pageBuilder.PageBaseType = _viewBaseType; } CustomViewUserControlControlBuilder userControlBuilder = rootBuilder as JG.ParserFilter.CustomViewUserControlControlBuilder; // JG: inject custom types here if (userControlBuilder != null) { userControlBuilder.UserControlBaseType = _viewBaseType; } } public override bool ProcessCodeConstruct(CodeConstructType codeType, string code) { if (codeType == CodeConstructType.ExpressionSnippet && !_viewTypeControlAdded && _viewBaseType != null && _directiveType == DirectiveType.Master) { // If we're dealing with a master page that needs to have its base type set, do it here. // It's done by adding the ViewType control, which has a builder that sets the base type. // The code currently assumes that the file in question contains a code snippet, since // that's the item we key off of in order to know when to add the ViewType control. Hashtable attribs = new Hashtable(); attribs["typename"] = _viewBaseType; AddControl(typeof(System.Web.Mvc.ViewType), attribs); _viewTypeControlAdded = true; } return base.ProcessCodeConstruct(codeType, code); } // Everything else in this class is unrelated to our 'inherits' handling. // Since PageParserFilter blocks everything by default, we need to unblock it public override bool AllowCode { get { return true; } } public override bool AllowBaseType(Type baseType) { return true; } public override bool AllowControl(Type controlType, ControlBuilder builder) { return true; } public override bool AllowVirtualReference(string referenceVirtualPath, VirtualReferenceType referenceType) { return true; } public override bool AllowServerSideInclude(string includeVirtualPath) { return true; } public override int NumberOfControlsAllowed { get { return -1; } } public override int NumberOfDirectDependenciesAllowed { get { return -1; } } public override int TotalNumberOfDependenciesAllowed { get { return -1; } } private enum DirectiveType { Unknown, Page, UserControl, Master, } } }
这是页面构建器类(上面的#2):
namespace JG.ParserFilter { using System.CodeDom; using System.Web.UI; internal sealed class CustomViewPageControlBuilder : FileLevelPageControlBuilder { public string PageBaseType { get; set; } public override void ProcessGeneratedCode( CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod) { // If we find got a base class string, use it if (PageBaseType != null) { derivedType.BaseTypes[0] = new CodeTypeReference(PageBaseType); } } } }
这里是自定义视图页面类:非泛型基础(上面的#3)和泛型派生类(上面的#4):
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Diagnostics.CodeAnalysis; using System.Web.Mvc; namespace JG.ParserFilter { [FileLevelControlBuilder(typeof(JG.ParserFilter.CustomViewPageControlBuilder))] public class CustomViewPage : System.Web.Mvc.ViewPage //, IAttributeAccessor { public string MyNewProperty { get; set; } } [FileLevelControlBuilder(typeof(JG.ParserFilter.CustomViewPageControlBuilder))] public class CustomViewPage: CustomViewPage where TModel : class { // code copied from source of ViewPage private ViewDataDictionary _viewData; public new AjaxHelper Ajax { get; set; } public new HtmlHelper Html { get; set; } public new TModel Model { get { return ViewData.Model; } } [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public new ViewDataDictionary ViewData { get { if (_viewData == null) { SetViewData(new ViewDataDictionary ()); } return _viewData; } set { SetViewData(value); } } public override void InitHelpers() { base.InitHelpers(); Ajax = new AjaxHelper (ViewContext, this); Html = new HtmlHelper (ViewContext, this); } protected override void SetViewData(ViewDataDictionary viewData) { _viewData = new ViewDataDictionary (viewData); base.SetViewData(_viewData); } } }
以下是用户控件的相应类(上面的#5):
namespace JG.ParserFilter { using System.Diagnostics.CodeAnalysis; using System.Web.Mvc; using System.Web.UI; [FileLevelControlBuilder(typeof(JG.ParserFilter.CustomViewUserControlControlBuilder))] public class CustomViewUserControl : System.Web.Mvc.ViewUserControl { public string MyNewProperty { get; set; } } public class CustomViewUserControl: CustomViewUserControl where TModel : class { private AjaxHelper _ajaxHelper; private HtmlHelper _htmlHelper; private ViewDataDictionary _viewData; public new AjaxHelper Ajax { get { if (_ajaxHelper == null) { _ajaxHelper = new AjaxHelper (ViewContext, this); } return _ajaxHelper; } } public new HtmlHelper Html { get { if (_htmlHelper == null) { _htmlHelper = new HtmlHelper (ViewContext, this); } return _htmlHelper; } } public new TModel Model { get { return ViewData.Model; } } [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public new ViewDataDictionary ViewData { get { EnsureViewData(); return _viewData; } set { SetViewData(value); } } protected override void SetViewData(ViewDataDictionary viewData) { _viewData = new ViewDataDictionary (viewData); base.SetViewData(_viewData); } } } namespace JG.ParserFilter { using System.CodeDom; using System.Web.UI; internal sealed class CustomViewUserControlControlBuilder : FileLevelUserControlBuilder { internal string UserControlBaseType { get; set; } public override void ProcessGeneratedCode( CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod) { // If we find got a base class string, use it if (UserControlBaseType != null) { derivedType.BaseTypes[0] = new CodeTypeReference(UserControlBaseType); } } } }
最后,这是一个示例视图,显示了这一点:
<%@ Page Language="C#" MyNewProperty="From @Page directive!" Inherits="JG.ParserFilter.CustomViewPage" %> <%=Model.SomeString %>
this.MyNewPrroperty = <%=MyNewProperty%>