ASP.NET MVCのPage / MasterPage / UserControlタグの機能を拡張する

最近、古いプロプライエタリエンジンをPHPからASP.NETに変換し始め、SmartyテンプレートとASP.NET MVCプレゼンテーション機能に関連するいくつかのポイントに直面しました。 このアプローチをWebプロジェクトに適用できることをすぐに予約しますが、ドピルカが必要になる場合があります。 だから。



まず、最初から、テンプレートからWebアプリケーションのメインオブジェクトのメソッドを参照する必要があります(メインと呼びましょう)。たとえば、構成、テーママネージャー、呼び出し元コントローラーのメソッドなどです。 標準クラスSystem.Web.Mvc.ViewPageは、このための便利な機能を提供しません。 もちろん、ViewContext.Controllerプロパティにアクセスし、型変換を行い、<%=((IndexController)ViewContext.Controller).CurrentTheme.Name%>という形式のコードを使用してテンプレートで作業することもできますが、コードの読みやすさと一般的な記述の利便性の問題が生じます。 System.Web.Mvc.ViewPage(およびSystem.Web.Mvc.MasterPageとSystem.Web.Mvc.UserControlの機能)を拡張し、ControlHelperプロパティを追加するというパスに沿って進み、必要な機能を利用できるヘルパーオブジェクトを返します。



次に、MasterPageFileの直接パスを設定するのではなく、「CurrentTheme.SiteMaster」、「UserTheme.SiteMaster」などの追加タグでマークするビューが必要でした。 残念ながら、 PageディレクティブのMasterPageFile属性に同様の行を書き込むと、ファイル「〜/ Views / {CurrentTheme.SiteMaster}」がないことを誓うパーサーエラーを受け取りました。 見つかった唯一の解決策は、 Pageディレクティブの独自の属性、たとえばMasterPagePathを作成することです。

<%@ Page Language="C#" MasterPagePath="{CurrentTheme.SiteMaster}" Inherits="..." %>
      
      







2番目のポイントに関連して、ニュアンスが生じました。これについては後で説明します。



MVCアプリケーションプロジェクトがあり、1つのIndexメソッドでHomeControllerを作成し、Site1.Masterメインページを作成し、Site1.Masterメインページから継承したViews / Home / Index.aspxビューを作成したとします。

 <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Site1.Master" Inherits="MvcHelperApplication.Inc.Types.ViewPage" %> <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server"><h2>Index</h2></asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="head" runat="server"> </asp:Content>
      
      







次に、要件に従ってPageディレクティブを変更します。

 <%@ Page Title="" Language="C#" MasterPagePath="{CurrentMasterPath}" Inherits="MvcHelperApplication.Inc.Types.ViewPage" %>
      
      







MvcHelperApplication.Inc.Types.ViewPageは将来のページクラスであり、もう少し低めに記述します。



最初に、ページのPreInitイベントを処理し、カスタム属性MasterPagePathに基づいてMasterPageFile属性の値を設定する、たった1つのことを行う単純なヘルパークラスを作成します。



 using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcHelperApplication.Inc { public class ControlHelper { private Types.ViewPage mPage = null; public ControlHelper(Types.ViewPage page) { mPage = page; } public void page_PreInit(object sender, EventArgs e) { try { if (mPage.MasterPagePath != null && mPage.MasterPagePath == "{CurrentMasterPath}") { mPage.MasterPageFile = "~/Views/Site1.Master"; } } catch (Exception ex) { } } } }
      
      







ここではすべてが明確であるはずです。この瞬間には驚きや落とし穴は含まれていません。 PageディレクティブでMasterPagePathタグ​​が見つかった場合は処理され、「{CurrentMasterPath}」と等しい場合、MasterPageFileプロパティは既存のマスターページへのパスに設定されます。



ViewPageクラスを作成します。

 using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Mvc.Html; using System.Web.UI; using System.Diagnostics.CodeAnalysis; namespace MvcHelperApplication.Inc.Types { public class ViewPage : System.Web.Mvc.ViewPage { private string _masterpagepath = ""; public string MasterPagePath { get { return _masterpagepath; } set { _masterpagepath = value; } } public ViewPage() { this.PreInit += new EventHandler(ControlHelper.page_PreInit); } private ControlHelper mControlHelper = null; public ControlHelper ControlHelper { get { if (mControlHelper == null) mControlHelper = new ControlHelper(this); return mControlHelper; } } } }
      
      







順番に解析します。

1)MasterPagePathは、クラスがPageディレクティブからMasterPagePath属性の値を受け取るためのプロパティです。

2)ViewPage-コンストラクター。ヘルパークラスのpage_PreInitメソッドがPreInitイベントにアタッチされます。

3)ControlHelper-実際にはヘルパークラスオブジェクトにアクセスするためのプロパティ。



デバッグを開始すると、ページに美しい単語インデックスが表示されます。 必要に応じて、スクリプトのプロセスに従うことができます。



これで、MasterPageFileへの独自のパスを任意の方法で設定できるようになりました。 さらに、テーマ、構成などへのアクセスに関する問題が解決されました。 -ControlHelperクラスを拡張し、それを通して設定にアクセスするだけで十分です。 たとえば、次のように:

 <%=ControlHelper.Config%> <%=ControlHelper.Themes.Current%>
      
      





など



すべてが素晴らしいようです。 しかし、ASP.NETは非常にい犬を用意しました。そのため、私は1日ネットを掘り当てなければなりませんでした。 実際、ASP.NETパーサーは、デフォルトではオーバーロードされたクラスのジェネリック型をサポートしていません。 これは、Inherits = "MvcHelperApplication.Inc.Types.ViewPage"ではなくIndex.aspxにInherits = "MvcHelperApplication.Inc.Types.ViewPage"を書き込む場合(つまり、実際には、そして、MVCとのすべての大騒ぎが開始されます)、エラーが表示されます:

 :    ,     .            .     :     'masterpagepath':  'System.Web.Mvc.ViewPage'      'masterpagepath'.  :  1: <%@ Page Title="" Language="C#" MasterPagePath="{CurrentMasterPath}" Inherits="MvcHelperApplication.Inc.Types.ViewPage<dynamic>" %>
      
      







ViewPageクラスの説明を次のように静かに変更しましょう。

  public class ViewPage<TModel> : System.Web.Mvc.ViewPage<TModel>
      
      







コンパイル、更新、および同じエラーが発生します。



一般に、ユニバーサルタイプをパーサーにスリップさせるには、pageParserFilterTypeなどを使用する必要があります。 Web.configの<system.web>セクションには、次のように記述されています。

 <pages validateRequest="false" pageParserFilterType="MvcHelperApplication.Inc.ViewTypeParserFilter" pageBaseType="MvcHelperApplication.Inc.Types.ViewPage">
      
      







ViewTypeParserFilterを作成します。



 using System; using System.Collections; using System.Web.UI; using System.Web.Mvc; using System.CodeDom; using System.Web.UI; namespace MvcHelperApplication.Inc { public class ViewTypeParserFilter : 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; switch (directiveName) { case "page": _directiveType = DirectiveType.Page; defaultBaseType = typeof(Types.ViewPage).FullName; break; case "control": _directiveType = DirectiveType.UserControl; defaultBaseType = typeof(System.Web.Mvc.ViewUserControl).FullName; break; case "master": _directiveType = DirectiveType.Master; defaultBaseType = typeof(System.Web.Mvc.ViewMasterPage).FullName; break; } if (_directiveType == DirectiveType.Unknown) return; string inherits = (string)attributes["inherits"]; if (!String.IsNullOrEmpty(inherits)) { if (IsGenericTypeString(inherits)) { attributes["inherits"] = defaultBaseType; _viewBaseType = inherits; } } } private static bool IsGenericTypeString(string typeName) { return typeName.IndexOfAny(new char[] { '<', '(' }) >= 0; } public override void ParseComplete(ControlBuilder rootBuilder) { base.ParseComplete(rootBuilder); ViewPageControlBuilder pageBuilder = rootBuilder as ViewPageControlBuilder; if (pageBuilder != null) { pageBuilder.PageBaseType = _viewBaseType; } } public override bool ProcessCodeConstruct(CodeConstructType codeType, string code) { if (codeType == CodeConstructType.ExpressionSnippet && !_viewTypeControlAdded && _viewBaseType != null && _directiveType == DirectiveType.Master) { Hashtable attribs = new Hashtable(); attribs["typename"] = _viewBaseType; AddControl(typeof(System.Web.Mvc.ViewType), attribs); _viewTypeControlAdded = true; } return base.ProcessCodeConstruct(codeType, code); } 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, } } public sealed class ViewPageControlBuilder : FileLevelPageControlBuilder { public string PageBaseType { get; set; } public override void ProcessGeneratedCode( CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod) { if (PageBaseType != null) { derivedType.BaseTypes[0] = new CodeTypeReference(PageBaseType); } } } }
      
      







ファイルViewPage.csを変更します



 using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Mvc.Html; using System.Web.UI; using System.Diagnostics.CodeAnalysis; namespace MvcHelperApplication.Inc.Types { [FileLevelControlBuilder(typeof(ViewPageControlBuilder))] public class ViewPage : System.Web.Mvc.ViewPage { private string _masterpagepath = ""; public string MasterPagePath { get { return _masterpagepath; } set { _masterpagepath = value; } } public ViewPage() { this.PreInit += new EventHandler(ControlHelper.page_PreInit); } private ControlHelper mControlHelper = null; public ControlHelper ControlHelper { get { if (mControlHelper == null) mControlHelper = new ControlHelper(this); return mControlHelper; } } } [FileLevelControlBuilder(typeof(ViewPageControlBuilder))] public class ViewPage<TModel> : ViewPage 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); } } }
      
      







コンパイル-ブラウザに素晴らしい単語インデックスが表示されます。

ビューフォルダーのWeb.configファイルに関連付けられている小さなニュアンスがあります。 ファイルViews / Web.configは削除するほうがよいでしょう。なぜなら、 メインのWeb.configサイトのpagesタグへの変更を上書きします。



ViewTypeParserFilterの助けを借りて、独自のクラスViewPage、ViewUserControl、ViewMaserPageの動作を保証できます。 念のため、構成ファイルのpagesタグでは、基本タイプpagesBaseTypeおよびuserControlBaseTypeを設定する機会がありますが、masterpageBaseTypeを設定する方法はありません。 恐れてはいけません、それなしで動作します。



All Articles