Ответ 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>