2009-09-26 33 views
35

Setup:Generic thừa kế ViewPage <> và mới tài sản

  • CustomViewEngine
  • CustomController cơ sở
  • CustomViewPage Base (trong cơ sở này, một tài sản mới được thêm vào "MyCustomProperty")

Sự cố:

Khi chế độ xem được nhập mạnh mẽ chẳng hạn như: <@ Page Inherits="CustomViewPage<MyCustomObject" MyCustomProperty="Hello">, tôi nhận được một compi ler "Parser" lỗi nói rằng MyCustomProperty không phải là tài sản công cộng của System.Web.Mvc.ViewPage

Tôi đã thực hiện nhiều thử nghiệm và lỗi (xem bên dưới) để xem những gì gây ra lỗi này và đã đi đến kết luận sau:

  • Lỗi này chỉ xảy ra khi tôi khai báo "MyCustomProperty" hoặc bất kỳ thuộc tính nào khác trong chỉ thị @Page của chế độ xem.
  • Lỗi sẽ luôn hiển thị "System.Web.Mvc.ViewPage" thay vì lớp thừa kế được thừa kế = "..".
+0

Xin lỗi, tôi không thể trợ giúp thêm và tôi rất muốn biết tại sao điều này xảy ra. Xem phần "Cách kiểm soát đầu vào nhận giá trị của chúng" trong http://stackoverflow.com/questions/1434734/asp-net-mvc-dropdownlist-selected-value-problem. Điều này có thể giúp, nó có thể không, nhưng MVC dường như tìm kiếm các giá trị theo thứ tự không phải lúc nào cũng rõ ràng. –

+0

Không thể thấy cách tôi có thể áp dụng liên kết đến tình huống này. Tôi đã thử tất cả các loại khai báo lớp khác nhau với các loại chung chung và các mẫu thừa kế khác nhau, không có gì = ( – Omar

+0

Tôi tìm thấy điều này trên Google: http://forums.asp.net/p/1432045/3408610.aspx#3408610 (Bài viết cuối cùng về trang) Nhưng tại sao nó chỉ hoạt động nếu người dùng đó sử dụng "đặc tả loại định dạng net." – Omar

Trả lời

56

Cập nhật: Có vẻ như cách khác dễ dàng hơn, ít nhất là trên các phiên bản ASP.NET MVC mới hơn. (Sao chép bình luận của ông dưới đây)

Tôi không chắc chắn nếu điều này là mới trong ASP.NET MVC 3, nhưng khi tôi trao đổi các thuộc tính Inherits từ tham khảo chung trong cú pháp C# để CLR cú pháp, các tiêu chuẩn ViewPageParserFilter generics được phân tích cú pháp chính xác - no CustomViewTypeParserFilter bắt buộc. Sử dụng ví dụ của Justin, điều này có nghĩa trao đổi

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

câu trả lời gốc dưới đây:

OK, tôi giải quyết này. Là một bài tập hấp dẫn, và giải pháp là không tầm thường nhưng không quá khó khi bạn làm cho nó hoạt động lần đầu tiên.

Đây là vấn đề cơ bản: trình phân tích cú pháp trang ASP.NET không hỗ trợ Generics như một loại trang.

Cách ASP.NET MVC làm việc xung quanh việc này là bằng cách đánh lừa trình phân tích cú pháp trang cơ bản để nghĩ rằng trang đó không phải là chung chung. Họ đã làm điều này bằng cách xây dựng một tùy chỉnh PageParserFilter và tùy chỉnh FileLevelPageControlBuilder. Bộ lọc phân tích cú pháp tìm kiếm một kiểu chung, và nếu nó tìm thấy một loại, hãy hoán đổi nó cho loại ViewPage không chung chung sao cho trình phân tích cú pháp ASP.NET không bị nghẹt thở. Sau đó, sau này trong vòng đời biên dịch trang, lớp trình tạo trang tùy chỉnh của họ hoán đổi kiểu chung trở lại.

Điều này làm việc vì loại ViewPage chung xuất phát từ ViewPage không chung, và tất cả các thuộc tính thú vị được đặt trong một chỉ thị @Page tồn tại trên lớp cơ sở (không chung chung).Vì vậy, những gì thực sự xảy ra khi các thuộc tính được thiết lập trong chỉ thị @Page là các tên thuộc tính đó đang được xác nhận đối với lớp cơ sở ViewPage không chung chung.

Dù sao, điều này hoạt động tốt trong hầu hết các trường hợp, nhưng không phải trong trường hợp của bạn bởi vì chúng mã hóa ViewPage làm loại cơ sở không chung chung trong triển khai bộ lọc trang của chúng và không cung cấp cách dễ dàng để thay đổi. Đây là lý do tại sao bạn tiếp tục nhìn thấy ViewPage trong thông báo lỗi của bạn, vì lỗi xảy ra ở giữa khi ASP.NET hoán đổi trong trình giữ chỗ ViewPage và khi nó hoán đổi lại ViewPage chung trước khi biên dịch.

Việc sửa chữa là để tạo ra phiên bản của riêng bạn trong các cách sau:

  1. trang lọc phân tích cú pháp - điều này gần như là một bản sao chính xác của ViewTypeParserFilter.cs trong nguồn MVC, với sự khác biệt duy nhất là nó đề cập đến các loại trình tạo trang và lớp tùy chỉnh của bạn thay vì các công cụ xây dựng trang
  2. của MVC - điều này giống với ViewPageControlBuilder.cs trong nguồn MVC, nhưng nó đặt lớp trong không gian tên của riêng bạn thay vì của chúng.
  3. Lấy trực tiếp lớp viewpage tùy chỉnh của bạn từ System.Web.Mvc.ViewPage (phiên bản không phải là phiên bản chung). Gắn bất kỳ thuộc tính tùy chỉnh nào vào lớp không chung chung mới này.
  4. lấy được một lớp chung từ # 3, sao chép mã từ việc triển khai ViewPage của nguồn ASP.NET MVC.
  5. lặp lại # 2, # 3 và # 4 cho điều khiển người dùng (@Control) nếu bạn cũng cần các thuộc tính tùy chỉnh trên các chỉ thị điều khiển người dùng.

Sau đó, bạn cần thay đổi web.config trong thư mục lượt xem của bạn (không phải là web.config chính của ứng dụng) để sử dụng các loại mới này thay vì các loại mặc định của MVC.

Tôi đã đính kèm một số mẫu mã minh họa cách hoạt động của mã này. Rất cám ơn bài viết của Phil Haack để giúp tôi hiểu điều này, mặc dù tôi đã phải làm rất nhiều poking xung quanh mã nguồn MVC và ASP.NET quá để thực sự hiểu nó.

Trước tiên, tôi sẽ bắt đầu với những thay đổi cần thiết trong web.config web.config của bạn:

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

Bây giờ, đây là bộ lọc trang phân tích cú pháp (# 1 ở trên):

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, 
     } 
    } 
} 

Đây là lớp trình tạo trang (# 2 ở trên):

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); 
      } 
     } 
    } 
} 

Và đây là các lớp trang xem tùy chỉnh: cơ sở không chung chung (# 3 abov e) và các lớp được thừa kế chung (# 4 ở trên):

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); 
     } 

    } 
} 

Và đây là các lớp học tương ứng cho người dùng điều khiển (# 5 ở trên):

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); 
      } 
     } 
    } 
} 

Cuối cùng, đây là một mẫu Xem mà show hành động này:

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

Bạn của tôi là bạn của tôi anh hùng của tôi. Nếu tôi có thể với tới màn hình và ôm bạn, tôi sẽ làm thế. Tôi sẽ không bao giờ giải quyết điều này với kinh nghiệm của tôi .. bao giờ hết! Tôi đã dừng mã hóa trong một tuần vì vấn đề này và cuối cùng cũng có câu trả lời. Nếu tôi có thể, tôi sẽ cung cấp cho bạn phần còn lại của danh tiếng 226 của tôi, nhưng tôi không thể chỉnh sửa tiền thưởng. Cảm ơn bạn một lần nữa, câu trả lời rất kỹ lưỡng, với một lời giải thích hoàn hảo. Hy vọng câu trả lời này sẽ giúp những người khác cố gắng làm việc này. – Omar

+0

Cảm ơn vì điều này! Mã MS là một vẻ đẹp như vậy mà nó đứng ngay cả trên con đường riêng của họ: ( – Sly

+1

Tôi không chắc chắn nếu điều này là mới trong ASP.NET MVC 3, nhưng khi tôi trao đổi các thuộc tính 'Inherits' từ tham chiếu chung trong cú pháp C# để CLR cú pháp, tiêu chuẩn ViewPageParserFilter phân tích cú pháp generics chính xác - không có 'CustomViewTypeParserFilter' yêu cầu.Sử dụng các ví dụ của Justin, điều này có nghĩa là trao đổi' <% @ Page Ngôn ngữ = "C#" MyNewProperty = "Từ @Page chỉ thị!" Inherits = "JG.ParserFilter. CustomViewPage 'tới' <% @ Page Ngôn ngữ = "C#" MyNewProperty = "Từ chỉ thị @Page!" Inherits = "JG.ParserFilter.CustomViewPage \' 1 [MvcApplication1.Models.FooModel]> '. – Technetium

Các vấn đề liên quan