在 ASP.NET 项目之外使用 System.Web.UI.Page.ParseControl()
Using System.Web.UI.Page.ParseControl() outside of an ASP.NET project
我只想创建一个测试应用程序来动态解析控件。我添加了 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 异常。
异常实际上来自内部 class 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.Page
从 System.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
将被抛出
请考虑以下示例。
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);
}
}
}
您需要将 System.Web
的引用添加到 WinForms 项目并使 WebForms 项目依赖于 WinForms 项目(这种依赖在技术上不是必需的,我将在下面解释)。
您将得到以下结果:
在 WinForms 项目中创建一个 post-build 事件,它将 WinForms 输出复制到 WebForms /bin。
xcopy /y "$(ProjectDir)$(OutDir)*.*" "$(ProjectDir)..\WebFormsTestBed\bin\"
将 WinForms 项目设置为启动项目并运行它。如果一切设置正确,您应该会看到:
它所做的是创建一个 AppDomain
,它基于 WebForms 项目,但在 WinForms 项目的执行上下文中,它提供了一种从 WinForms 项目中触发回调方法的方法新创建的范围AppDomain
。这将使您能够在 WebForms 项目中正确处理 VirtualPath
问题,而不必担心模拟路径变量等的细节。
创建AppDomain
时,它需要能够找到其路径中的所有资源,这就是创建post-build事件以将已编译的WinForms文件复制到WebForms的原因/bin 文件夹。这也是上图中"dependency"从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 项目。
我只想创建一个测试应用程序来动态解析控件。我添加了 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 异常。
异常实际上来自内部 class 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.Page
从 System.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
将被抛出
请考虑以下示例。
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);
}
}
}
您需要将 System.Web
的引用添加到 WinForms 项目并使 WebForms 项目依赖于 WinForms 项目(这种依赖在技术上不是必需的,我将在下面解释)。
您将得到以下结果:
在 WinForms 项目中创建一个 post-build 事件,它将 WinForms 输出复制到 WebForms /bin。
xcopy /y "$(ProjectDir)$(OutDir)*.*" "$(ProjectDir)..\WebFormsTestBed\bin\"
将 WinForms 项目设置为启动项目并运行它。如果一切设置正确,您应该会看到:
它所做的是创建一个 AppDomain
,它基于 WebForms 项目,但在 WinForms 项目的执行上下文中,它提供了一种从 WinForms 项目中触发回调方法的方法新创建的范围AppDomain
。这将使您能够在 WebForms 项目中正确处理 VirtualPath
问题,而不必担心模拟路径变量等的细节。
创建AppDomain
时,它需要能够找到其路径中的所有资源,这就是创建post-build事件以将已编译的WinForms文件复制到WebForms的原因/bin 文件夹。这也是上图中"dependency"从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 项目。