通用inheritance的ViewPage <>和新的属性
build立:
- CustomViewEngine
- CustomController Base
- CustomViewPage Base(在这个基础上,一个新的属性添加“MyCustomProperty”)
问题:
当一个视图是强types的,例如: <@ Page Inherits="CustomViewPage<MyCustomObject" MyCustomProperty="Hello">
,我得到一个编译器“Parser”错误,指出MyCustomProperty不是System.Web.Mvc.ViewPage的公共属性
我做了大量的试验和错误(见下文),看看是什么导致了这个错误,并得出了以下结论:
- 只有在视图的@Page指令中声明“MyCustomProperty”或任何其他属性时才会出现该错误。
- 该错误将始终显示“System.Web.Mvc.ViewPage”而不是声明的inheritance=“..”类。
更新:看起来像Technitiumfind了另一种方式来做到这一点,看起来更容易,至less在新版本的ASP.NET MVC。 (复制下面的评论)
我不确定这是否是ASP.NET MVC 3中的新增function,但是当我将C#语法中的generics转换为CLR语法的Inherits属性时,标准的
ViewPageParserFilter
正确parsing了generics – 不需要CustomViewTypeParserFilter
。 使用Justin的例子,这意味着交换
<%@ Page Language="C#" MyNewProperty="From @Page directive!" Inherits="JG.ParserFilter.CustomViewPage<MvcApplication1.Models.FooModel>
至
<%@ Page Language="C#" MyNewProperty="From @Page directive!"` Inherits="JG.ParserFilter.CustomViewPage`1[MvcApplication1.Models.FooModel]>
下面的原始答案:
好的,我解决了这个问题。 这是一个非常有趣的练习,解决scheme并不重要,但是一旦你第一次得到它就不会太难。
这是底层的问题:ASP.NET页面parsing器不支持generics作为页面types。
ASP.NET MVC解决这个问题的方式是通过愚弄底层的页面parsing器,认为页面不是通用的。 他们通过构build一个自定义的PageParserFilter和一个自定义的FileLevelPageControlBuilder来做到这一点 。 parsing器筛选器查找genericstypes,如果find,则将其replace为非genericsViewPagetypes,以便ASP.NETparsing器不会窒息。 然后,在页面编译生命周期的很久之后,它们的自定义页面构build器类将通用types交换回来。
这是有效的,因为通用ViewPagetypes派生自非genericsViewPage,并且在@Page指令中设置的所有有趣属性都存在于(非generics)基类中。 那么,在@Page指令中设置属性时会发生什么,这些属性名称正在针对非genericsViewPage基类进行validation。
无论如何,这在大多数情况下效果很好,但不适合你,因为它们将ViewPage硬编码为页面filter实现中的非generics基types,并且不提供简单的方法来更改它。 这就是为什么在错误信息中看到ViewPage的原因,因为当ASP.NET在ViewPage占位符交换和在编译之前交换通用ViewPage的权限之间发生错误时。
解决的办法是创build你自己的以下版本:
- 页面parsing器filter – 这几乎是MVC源代码中ViewTypeParserFilter.cs的精确副本,唯一的区别是它引用了您的自定义ViewPage和页面生成器types而不是MVC的
- 页面生成器 – 这与MVC源代码中的ViewPageControlBuilder.cs是一样的,但是它把类放在你自己的名字空间中,而不是它们的名字空间中。
- 直接从System.Web.Mvc.ViewPage(非通用版本)派生自定义的ViewPage类。 坚持任何自定义属性在这个新的非generics类。
- 从#3派生一个generics类,从ASP.NET MVC源代码实现ViewPage复制代码。
- 如果您还需要用户控制指令的自定义属性,请为用户控件(@Control)重复#2,#3和#4。
然后,您需要更改视图目录(而不是主应用程序的web.config)中的web.config,以使用这些新types而不是MVC的默认types。
我已经附上一些代码示例说明如何工作。 非常感谢Phil Haack的文章,帮助我理解这一点,尽pipe我不得不围绕MVC和ASP.NET源代码进行大量的尝试,才真正理解它。
首先,我将从web.config中的web.config更改开始:
<pages validateRequest="false" pageParserFilterType="JG.ParserFilter.CustomViewTypeParserFilter" pageBaseType="JG.ParserFilter.CustomViewPage" userControlBaseType="JG.ParserFilter.CustomViewUserControl">
现在,这是页面parsing器filter(上面的#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 (eg 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); } } } }
这里是自定义视图页面类:非generics基类(上面的#3)和generics派生类(上面的#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<TModel> : CustomViewPage where TModel : class { // code copied from source of ViewPage<T> private ViewDataDictionary<TModel> _viewData; public new AjaxHelper<TModel> Ajax { get; set; } public new HtmlHelper<TModel> Html { get; set; } public new TModel Model { get { return ViewData.Model; } } [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public new ViewDataDictionary<TModel> ViewData { get { if (_viewData == null) { SetViewData(new ViewDataDictionary<TModel>()); } return _viewData; } set { SetViewData(value); } } public override void InitHelpers() { base.InitHelpers(); Ajax = new AjaxHelper<TModel>(ViewContext, this); Html = new HtmlHelper<TModel>(ViewContext, this); } protected override void SetViewData(ViewDataDictionary viewData) { _viewData = new ViewDataDictionary<TModel>(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<TModel> : CustomViewUserControl where TModel : class { private AjaxHelper<TModel> _ajaxHelper; private HtmlHelper<TModel> _htmlHelper; private ViewDataDictionary<TModel> _viewData; public new AjaxHelper<TModel> Ajax { get { if (_ajaxHelper == null) { _ajaxHelper = new AjaxHelper<TModel>(ViewContext, this); } return _ajaxHelper; } } public new HtmlHelper<TModel> Html { get { if (_htmlHelper == null) { _htmlHelper = new HtmlHelper<TModel>(ViewContext, this); } return _htmlHelper; } } public new TModel Model { get { return ViewData.Model; } } [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public new ViewDataDictionary<TModel> ViewData { get { EnsureViewData(); return _viewData; } set { SetViewData(value); } } protected override void SetViewData(ViewDataDictionary viewData) { _viewData = new ViewDataDictionary<TModel>(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); } } } }
最后,下面是一个示例View,它显示了这个动作:
<%@ Page Language="C#" MyNewProperty="From @Page directive!" Inherits="JG.ParserFilter.CustomViewPage<MvcApplication1.Models.FooModel>" %> <%=Model.SomeString %> <br /><br />this.MyNewPrroperty = <%=MyNewProperty%> </asp:Content>