2009-09-26 19 views
35

設置:通用繼承的ViewPage <>和新屬性

  • CustomViewEngine
  • CustomController基地
  • CustomViewPage基(在此基礎上,添加了新的屬性 「MyCustomProperty」)

問題:

當一個視圖是強類型的,如:<@ Page Inherits="CustomViewPage<MyCustomObject" MyCustomProperty="Hello">,我得到一個compi ler「解析器」錯誤,指出MyCustomProperty不是System.Web.Mvc.ViewPage的公共屬性

我已經做了大量的試驗和錯誤(請參閱下文),以查看導致此錯誤的原因並得出以下結論:

  • 只有在視圖的@Page指令中聲明「MyCustomProperty」或任何其他屬性時纔會出現該錯誤。
  • 該錯誤將始終顯示「System.Web.Mvc.ViewPage」,而不是聲明的inherits =「..」類。
+0

對不起,我不禁進一步,我很想知道這是爲什麼發生。請參閱http://stackoverflow.com/questions/1434734/asp-net-mvc-dropdownlist-selected-value-problem中的「輸入控件如何獲取其值」部分。這可能有所幫助,但可能不會,但MVC確實似乎以一種並不明顯的順序搜索值。 – 2009-09-26 03:59:02

+0

看不到我可以如何將鏈接應用到這種情況。我嘗試了所有類型的不同類的聲明與泛型類型和不同的遺傳模式,沒什麼=( – Omar 2009-09-26 04:27:56

+0

我發現這對谷歌:http://forums.asp.net/p/1432045/3408610.aspx#3408610 (最後發表於頁面) 但是,爲什麼它只適用於該用戶使用「原始.net格式類型規範」 – Omar 2009-09-26 04:34:26

回答

56

更新:看起來像Technitium發現了另一種方式來做到這一點,看起來要容易得多,至少在ASP.NET MVC的新版本。 (複製他下面的評論)

我不知道這是否是在ASP.NET MVC 3個新的,但是當我換了 繼承屬性從引用在C#語法的泛型CLR 語法中,標準ViewPageParserFilter正確解析的泛型 - 否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]> 

低於原來的答覆:

OK,我解決了這個。這是一個非常吸引人的練習,解決方案並不重要,但一旦你第一次使用它就不會太難。

以下是底層問題:ASP.NET頁面解析器不支持泛型作爲頁面類型。

ASP.NET MVC解決這個問題的方式是通過愚弄底層的頁面解析器來認爲頁面不是通用的。他們通過構建自定義PageParserFilter和自定義FileLevelPageControlBuilder來完成此操作。解析器篩選器查找泛型類型,如果找到,則將其替換爲非泛型ViewPage類型,以便ASP.NET解析器不會窒息。然後,很久以後在頁面編譯的生命週期,他們的自定義頁面生成器類交換泛型類型回。

這工作是因爲通用的ViewPage類型從非泛型的ViewPage派生,而所有在設定的有趣的屬性(非泛型)基類上存在@Page指令。因此,在@Page指令中設置屬性時發生的事情是,這些屬性名稱將針對非泛型ViewPage基類進行驗證。

無論如何,這在大多數情況下效果很好,但不適合你,因爲它們將ViewPage硬編碼爲其頁面過濾器實現中的非泛型基類型,並且不提供簡單的方法來更改它。這就是爲什麼你保存在您的錯誤信息看到的ViewPage,因爲錯誤時,交換回通用的ViewPage編譯之前就在的ViewPage佔位符ASP.NET掉期和時之間發生英寸

修復的方法是創建自己的以下版本:

  1. 頁面分析器篩選器 - 這是在MVC源ViewTypeParserFilter.cs的幾乎是一模一樣的副本,唯一的區別是,它是指您的自定義的ViewPage和頁面構建類型,而不是MVC的
  2. 頁面生成器 - 這等同於在MVC源ViewPageControlBuilder.cs,但是卻讓類在自己的名稱空間,而不是他們的。
  3. 派生自定義的ViewPage直接從System.Web.Mvc.ViewPage類(非通用版本)。堅持任何自定義屬性在這個新的非泛型類。
  4. 獲得來自#3一個泛型類,複製從ASP.NET MVC源的實現的ViewPage的代碼。
  5. 重複#2,#3,#4中的用戶控件(@Control),如果您還需要用戶的控制指令,自定義屬性了。

然後,您需要更改視圖目錄(而不是主應用程序的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 abov e)和通用派生類(上述第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> 
+6

你我的朋友是你我的英雄。如果我能通過屏幕伸出手並擁抱你,我會的。我從來沒有用我的經驗來解決這個問題..永遠!由於這個問題,我停止了一個星期的編碼,最後得到了答案。如果可以的話,我會給你我剩下的226個聲望,但我不能編輯賞金。 再次感謝你,非常完整的答案,有一個完美的解釋。希望這個答案能夠幫助那些試圖實現這個目標的人。 – Omar 2009-10-02 23:33:20

+0

感謝您的支持! MS代碼是這樣的,它代表甚至在他們自己的方式:( – Sly 2011-10-13 11:22:09

+1

我不知道這是否是在ASP.NET MVC 3個新的一個美女,但是當我換了'Inherits'屬性從引用泛型在C#語法CLR語法,正確的ViewPageParserFilter解析泛型 - 不需要'CustomViewTypeParserFilter'。使用Justin的例子,這意味着交換'<%@ Page Language =「C#」MyNewProperty =「從@Page指令!」Inherits =「JG.ParserFilter。 CustomViewPage '到'<%@ Page Language =「C#」MyNewProperty =「從@Page指令!」Inherits =「JG.ParserFilter.CustomViewPage \'1 [MvcApplication1.Models.FooModel]>'。 – Technetium 2012-05-02 16:29:34