在ASP.NET项目之外使用System.Web.UI.Page.ParseControl()

时间:2015-03-30 10:42:27

标签: asp.net webforms

我只想创建一个动态解析控件的测试应用程序。我添加了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,但它抛出了null异常。

1 个答案:

答案 0 :(得分:2)

该异常实际上来自内部类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.PageParseControl()继承System.Web.UI.TemplateControl。所以你最终打电话......

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


请考虑以下示例。

Default.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>

Default.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


结果:

    Successfully generated control from `HttpContext.Current.Handler` 
    Successfully generated control from `new Page()` with `AppRelativeVirtualPath`
    Failed to generate control from `new Page()` without `AppRelativeVirtualPath` set

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

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

您最终会得到以下结果:

enter image description here enter image description here

在WinForms项目中创建一个post-build事件,该事件将WinForms输出复制到WebForms / bin。

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

将WinForms项目设置为启动项目并运行它。如果你正确设置了一切,你应该看到:

enter image description here

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

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

最后,我不知道对你有多大帮助。可能有办法将这一切都集成到一个项目或两个项目中。如果没有更详细的说明,为什么或如何使用它,我不会再花费更多的时间。

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


另一种选择

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

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项目。