【问题标题】:Using System.Web.UI.Page.ParseControl() outside of an ASP.NET project在 ASP.NET 项目之外使用 System.Web.UI.Page.ParseControl()
【发布时间】:2015-03-30 10:42:26
【问题描述】:

我只想创建一个测试应用程序来动态解析控件。我添加了new Page().ParseControl。我得到了,

System.ArgumentNullException
Value cannot be null.
Parameter name: virtualPath

at System.Web.VirtualPath.Create(String virtualPath, VirtualPathOptions options) 
at System.Web.UI.TemplateControl.ParseControl(String content) 

也尝试了BuildManager.CreateInstanceFromVirtualPath,但它抛出了空异常。

【问题讨论】:

  • 我在做new Page().ParseControl
  • 您需要改用this.Page.ParseControl
  • @MichaelLiu,我在 WebForms 中使用此代码。
  • ParseControl 有严重的局限性。它不能完全像 ASP.NET 在 HTTP 上下文中所做的那样。另外,您的堆栈不完整,控制解析和 VirtualPath 处理之间存在一些“距离”。你到底在做什么?你将什么传递给 ParseControl 方法?尝试逐步消除那里的东西以确定失败的原因。
  • @SimonMourier,我在做new Page().ParseControl 没有别的事

标签: asp.net webforms


【解决方案1】:

异常其实是来自内部类System.Web.VirtualPath

// Default Create method
public static VirtualPath Create(string virtualPath) {
    return Create(virtualPath, VirtualPathOptions.AllowAllPath);
}

...

public static VirtualPath Create(string virtualPath, VirtualPathOptions options) {
    ...

    // If it's empty, check whether we allow it
    if (String.IsNullOrEmpty(virtualPath)) {
        if ((options & VirtualPathOptions.AllowNull) != 0) // <- nope
            return null;

        throw new ArgumentNullException("virtualPath"); // <- source of exception
    }

    ...
}

System.Web.UI.PageSystem.Web.UI.TemplateControl 继承 ParseControl()。所以你最终是在打电话...

public Control ParseControl(string content) {
    return ParseControl(content, true);
}

public Control ParseControl(string content, bool ignoreParserFilter) {
    return TemplateParser.ParseControl(content, VirtualPath.Create(AppRelativeVirtualPath), ignoreParserFilter);
}

供参考(来自VirtualPathOptions):

internal enum VirtualPathOptions
{
    AllowNull = 1,
    EnsureTrailingSlash = 2,
    AllowAbsolutePath = 4,
    AllowAppRelativePath = 8,
    AllowRelativePath = 16,
    FailIfMalformed = 32,
    AllowAllPath = AllowRelativePath | AllowAppRelativePath | AllowAbsolutePath,
}

由于VirtualPathOptions.AllowAllPath 被传递给VirtualPath.Create()...

return Create(virtualPath, VirtualPathOptions.AllowAllPath);

这...

options & VirtualPathOptions.AllowNull

...计算结果为0,将抛出ArgumentNullException


请考虑以下示例。

默认.aspx:

<%@ Page Title="Home Page" Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebFormsTestBed._Default" %>

<html>
<head>
    <title></title>
</head>
<body>
    <form id="formMain" runat="server">
        <asp:Label ID="lblResults" runat="server"></asp:Label>
    </form>
</body>
</html>

默认.aspx.cs:

using System;
using System.Web;
using System.Web.UI;

namespace WebFormsTestBed {
    public partial class _Default : Page {
        protected void Page_Load(object sender, EventArgs e) {
            Control ctl;
            var page = HttpContext.Current.Handler as Page;

            // First, using `HttpContext.Current.Handler as Page`,
            // - already has `AppRelativeVirtualPath` set to `~\Default.aspx`
            if (page != null) {
                ctl = page.ParseControl(@"<asp:TextBox ID=""txtFromCurrentHandler"" runat=""server"" Text=""Generated from `HttpContext.Current.Handler`""></asp:TextBox>");

                if (ctl != null) lblResults.Text = "Successfully generated control from `HttpContext.Current.Handler`";
            }

            // Next, using `new Page()`, setting `AppRelativeVirtualPath`
            // - set `AppRelativeVirtualPath` to `~\`
            var tmpPage = new Page() {
                AppRelativeVirtualPath = "~\\"
            };

            ctl = tmpPage.ParseControl(@"<asp:TextBox ID=""txtFromNewPageWithAppRelativeVirtualPathSet"" runat=""server"" Text=""Generated from `new Page()` with `AppRelativeVirtualPath` set""></asp:TextBox>", true);

            if (ctl != null)
                lblResults.Text +=
                    string.Format("{0}Successfully generated control from `new Page()` with `AppRelativeVirtualPath` set",
                                  lblResults.Text.Length > 0 ? "<br/>" : "");

            // Last, using `new Page()`, without setting `AppRelativeVirtualPath`
            try {
                ctl = new Page().ParseControl(@"<asp:TextBox ID=""txtFromNewPageWithoutAppRelativeVirtualPathSet"" runat=""server"" Text=""Generated from `new Page()` without `AppRelativeVirtualPath` set""></asp:TextBox>", true);

                if (ctl != null)
                    lblResults.Text +=
                        string.Format("{0}Successfully generated control from `new Page()` without `AppRelativeVirtualPath` set",
                                      lblResults.Text.Length > 0 ? "<br/>" : "");
            } catch (ArgumentNullException) {
                lblResults.Text +=
                    string.Format("{0}Failed to generate control from `new Page()` without `AppRelativeVirtualPath` set",
                                  lblResults.Text.Length > 0 ? "<br/>" : "");
            }
        }
    }
}

您可以阅读有关此行的信息...

var page = HttpContext.Current.Handler as Page;

在这个here


结果:

从 `HttpContext.Current.Handler` 成功生成控件 使用 AppRelativeVirtualPath 从 `new Page()` 成功生成控件 在没有设置“AppRelativeVirtualPath”的情况下无法从“new Page()”生成控件

WebForms 项目的使用示例

此 hack 基于 this SO answer,它基于将非 WebForms 测试工具附加到 WebForms 应用程序。

从为上述示例创建的 WebForms 项目开始,添加一个新的 WinForms 项目。

对于最简单的情况,我们只修改Program.cs

using System;
using System.IO;
using System.Linq;
using System.Web.Hosting;
using System.Windows.Forms;
using System.Web.UI;

namespace WinFormsTestBed {
    public class AppDomainUnveiler : MarshalByRefObject {
        public AppDomain GetAppDomain() {
            return AppDomain.CurrentDomain;
        }
    }

    internal static class Program {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        private static void Main() {
            var appDomain = ((AppDomainUnveiler)ApplicationHost.CreateApplicationHost(
                    typeof(AppDomainUnveiler), "/", Path.GetFullPath("../../../WebFormsTestBed")))
                .GetAppDomain();

            try {
                appDomain.DoCallBack(StartApp);
            } catch (ArgumentNullException ex) {
                MessageBox.Show(ex.Message);
            } finally {
                AppDomain.Unload(appDomain);
            }
        }

        private static void StartApp() {
            var tmpPage = new Page() {
                AppRelativeVirtualPath = "~/Default.aspx"
            };
            var ctl = tmpPage.ParseControl(@"<asp:TextBox ID=""txtFromNewPageWithAppRelativeVirtualPathSet"" runat=""server"" Text=""Generated from `new Page()` with `AppRelativeVirtualPath` set""></asp:TextBox>");

            ctl = ctl == null ||
                  (ctl = ctl.Controls.OfType<System.Web.UI.WebControls.TextBox>().FirstOrDefault()) == null
                ? null
                : ctl;

            MessageBox.Show(ctl == null ? "Failed to generate asp:TextBox"  : "Generated asp:TextBox with ID = " + ctl.ID);
        }
    }
}

您需要将System.Web 的引用添加到 WinForms 项目,并使 WebForms 项目依赖于 WinForms 项目(这种依赖在技术上不是必需的,我将在下面解释)。

你最终会得到以下结果:

在 WinForms 项目中创建一个构建后事件,它将 WinForms 输出复制到 WebForms /bin。

xcopy /y "$(ProjectDir)$(OutDir)*.*" "$(ProjectDir)..\WebFormsTestBed\bin\"

将WinForms项目设置为启动项目并运行。如果您正确设置了所有内容,您应该会看到:

它的作用是创建一个AppDomain,它基于WebForms 项目,但在WinForms 项目的执行上下文中,它提供了一种方法,用于在WinForms 项目的范围内触发回调方法新创建的AppDomain。这将允许您在 WebForms 项目中正确处理VirtualPath 问题,而无需担心模拟路径变量等细节。

AppDomain 创建时,它需要能够找到其路径中的所有资源,这就是为什么创建构建后事件将编译后的 WinForms 文件复制到 WebForms /bin 文件夹的原因。这就是上图中从 WebForms 项目到 WinForms 项目设置“依赖”的原因。

最后我不知道这对你有多大帮助。可能有一种方法可以将这一切整合到一个项目或两个项目中。如果不详细说明您为什么或如何使用它,我不会再花时间在这上面。

注意:从 ParseControl() 返回的 ctl 现在是一个包装器,其中的 Controls 集合实际上包含 asp:TextBox - 我没有费心去想为什么呢


另一种选择

您可以尝试完全模拟AppDomain,而不是保留一个虚拟的WebForms 项目,这样在new Page() 上设置AppRelativeVirtualPath 不会导致...

System.Web.HttpException The application relative virtual path '~/' cannot be made absolute, because the path to the application is not known.

要开始执行此操作,您可能需要先参考我上面引用的 SO 答案使用的 source。我引用的 SO 答案实际上是这种方法的一种解决方法,这就是我首先建议这样做的原因,但它需要与 WinForms 项目位于同一主机上的有效 WebForms 项目。

【讨论】:

  • 在我收到 System.Web.HttpException The application relative virtual path '~/' cannot be made absolute, because the path to the application is not known. 之前尝试过,注意我正在使用这个外部网络来源。
  • @user960567 请参阅关于在 WebForms 项目之外使用 ParseControl 的补充说明
  • 谢谢,我有时间会检查的。还是谢谢
  • 我正在尝试做类似的事情:在没有 ASP.NET Page 对象的情况下使用 ParseControl “独立”。我想解析一些 ASP.NET 标记并将其转储回 HTML 字符串。该解决方案有效,但不会为用户控件调用诸如“OnLoad”之类的事件。有没有什么技巧可以用这样的页面“虚拟”调用那些?
  • @Tobias81 从ParseControl 生成的控件不知道如何在 ASP.NET WebForm 生命周期中使用/解释它。我已经有大约一年没有使用 WebForms 了,也不打算研究您的问题(抱歉)。如果我是你,我会首先尝试将生成的控件添加到 ControlCollectionPage 并允许它运行其生命周期。如果你能做到这一点,那么你从Page.Response 中提取关键标记。但这似乎需要整个 ASP.NET 引擎,因此不会很“独立”。祝你好运!
猜你喜欢
  • 2013-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-22
  • 1970-01-01
  • 2011-06-07
  • 1970-01-01
相关资源
最近更新 更多