通用继承的ViewPage<>和新的财产

时间:2009-09-26 03:37:20

标签: asp.net-mvc view

设定:

  • CustomViewEngine
  • CustomController Base
  • CustomViewPage Base(在此基础中,添加了一个新属性“MyCustomProperty”)

问题:

当强类型视图如:<@ Page Inherits="CustomViewPage<MyCustomObject" MyCustomProperty="Hello">时,我收到编译器“Parser”错误,指出MyCustomProperty不是System.Web.Mvc.ViewPage的公共属性

我做了很多试验和错误(见下文),看看是什么导致了这个错误,得出了以下结论:

  • 只有当我在视图的@Page指令中声明“MyCustomProperty”或任何其他属性时才会出现错误。
  • 错误将始终显示“System.Web.Mvc.ViewPage”而不是声明的inherits =“..”类。

1 个答案:

答案 0 :(得分:57)

更新:看起来像Technitium找到了另一种方法,这看起来更容易,至少在较新版本的ASP.NET MVC上。 (复制他的评论如下)

  

我不确定这是否是ASP.NET MVC 3中的新功能,但是当我交换它时   继承属性从引用C#语法中的泛型到CLR   语法,标准ViewPageParserFilter正确解析泛型 -   不需要CustomViewTypeParserFilter。使用贾斯汀的例子,这个   意味着交换

<%@ 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]>

以下原始答案:

好的,我解决了这个问题。这是一项非常有趣的练习,解决方案非常重要,但是一旦你第一次使用它就不会太难。

以下是基本问题:ASP.NET页面解析器不支持将泛型作为页面类型。

ASP.NET MVC解决这个问题的方法是欺骗底层页面解析器,使其认为页面不是通用的。他们通过构建自定义PageParserFilter和自定义FileLevelPageControlBuilder来实现此目的。解析器过滤器查找泛型类型,如果找到一个类型,则将其交换为非泛型ViewPage类型,以便ASP.NET解析器不会阻塞。然后,在页面编译生命周期的后期,他们的自定义页面构建器类将通用类型交换回来。

这是有效的,因为通用ViewPage类型派生自非泛型ViewPage,并且在(非泛型)基类上存在@Page指令中设置的所有有趣属性。因此,在@Page指令中设置属性时真正发生的是,这些属性名称是针对非泛型ViewPage基类进行验证的。

无论如何,这在大多数情况下效果很好,但不适用于你的,因为他们在其页面过滤器实现中将ViewPage硬编码为非通用基类型,并且不提供更改它的简单方法。这就是你在错误消息中看到ViewPage的原因,因为在ASP.NET交换ViewPage占位符和在编译之前交换通用ViewPage之间发生错误。

修复方法是创建您自己的以下版本:

  1. 页面解析器过滤器 - 这几乎是MVC源中ViewTypeParserFilter.cs的精确副本,唯一的区别是它引用了自定义ViewPage和页面构建器类型而不是MVC
  2. 页面构建器 - 这与MVC源中的ViewPageControlBuilder.cs相同,但它将类放在您自己的命名空间中,而不是他们的。
  3. 直接从System.Web.Mvc.ViewPage(非通用版本)派生自定义视图页面类。在这个新的非泛型类上粘贴任何自定义属性。
  4. 从#3派生一个泛型类,从ASP.NET MVC源代码的ViewPage实现中复制代码。
  5. 如果您还需要用户控制指令的自定义属性,则对用户控件(@Control)重复#2,#3和#4。
  6. 然后,您需要更改views目录(而不是主应用程序的web.config)中的web.config以使用这些新类型而不是MVC的默认类型。

    我附上了一些代码示例,说明了它是如何工作的。非常感谢Phil Haack的文章,以帮助我理解这一点,虽然我不得不在MVC和ASP.NET源代码上做很多事情来理解它。

    首先,我将从web.config中所需的web.config更改开始:

    <pages
        validateRequest="false"
        pageParserFilterType="JG.ParserFilter.CustomViewTypeParserFilter"
        pageBaseType="JG.ParserFilter.CustomViewPage"
        userControlBaseType="JG.ParserFilter.CustomViewUserControl">
    

    现在,这是页面解析器过滤器(上面的#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<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);
                }
            }
        }
    }
    

    最后,这是一个示例视图,显示了这一点:

    <%@ Page Language="C#" MyNewProperty="From @Page directive!"  Inherits="JG.ParserFilter.CustomViewPage<MvcApplication1.Models.FooModel>" %>
        <%=Model.SomeString %>
        <br /><br />this.MyNewPrroperty = <%=MyNewProperty%>
    </asp:Content>