异常其实是来自内部类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
请考虑以下示例。
默认.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 项目。