Подтвердить что ты не робот

Generic Inherited ViewPage <> и новое свойство

Настройка:

  • CustomViewEngine
  • База CustomController
  • База CustomViewPage (в этой базе добавлено новое свойство "MyCustomProperty" )

Проблема:

При сильном вводе вида, например: <@ Page Inherits="CustomViewPage<MyCustomObject" MyCustomProperty="Hello">, я получаю сообщение об ошибке "Parser" компилятора, указывающее, что MyCustomProperty не является общедоступным свойством System.Web.Mvc.ViewPage

Я сделал множество проб и ошибок (см. ниже), чтобы узнать, что вызывает эту ошибку, и пришли к следующим выводам:

  • Ошибка возникает, когда я объявляю "MyCustomProperty" или любое другое свойство в директиве @Page представления.
  • Ошибка всегда будет отображать "System.Web.Mvc.ViewPage", а не объявленный класс inherits = "..".
4b9b3361

Ответ 1

Обновление: похоже, Technitium нашел другой способ сделать это, который выглядит намного проще, по крайней мере, на более новых версиях ASP.NET MVC. (скопировал свой комментарий ниже)

Я не уверен, что это новое в ASP.NET MVC 3, но когда я поменял Наследует атрибут от ссылки на общий текст в синтаксисе С# на CLR синтаксис, стандартный ViewPageParserFilter разобранный генератор правильно - нет CustomViewTypeParserFilter. Используя примеры Justin, это означает замену

<%@ Page Language="C#" MyNewProperty="From @Page directive!"
    Inherits="JG.ParserFilter.CustomViewPage<MvcApplication1.Models.FooModel>

to

<%@ 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 прямо перед компиляцией.

Исправление состоит в том, чтобы создать собственную версию следующего:

  • фильтр парсера страницы - это почти точная копия ViewTypeParserFilter.cs в источнике MVC, с той лишь разницей, что это относится к вашим настраиваемым типам ViewPage и page builder вместо MVC
  • page builder - это идентично ViewPageControlBuilder.cs в источнике MVC, но он помещает класс в ваше собственное пространство имен в отличие от их.
  • Вывести свой собственный класс представлений непосредственно из System.Web.Mvc.ViewPage(не универсальной версии). Привяжите любые пользовательские свойства к этому новому классу не общего.
  • вывести общий класс из # 3, скопировав код из исходной реализации ASP.NET MVC в ViewPage.
  • repeat # 2, # 3 и # 4 для пользовательских элементов управления (@Control), если вам также нужны настраиваемые свойства в директивах управления пользователями.

Затем вам нужно изменить web.config в своем каталоге представлений (а не в основном приложении web.config), чтобы использовать эти новые типы вместо стандартных по умолчанию MVC.

Я приложил некоторые примеры кода, иллюстрирующие, как это работает. Большое спасибо статье Фила Хаака, которая поможет мне понять это, хотя мне пришлось много процитировать исходный код 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 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 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 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);
            }
        }
    }
}

Наконец, вот пример View, который показывает это в действии:

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