65.9K
CodeProject 正在变化。 阅读更多。
Home

在 WinForm 应用程序中显示动态 HTML

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (13投票s)

2010年8月12日

CPOL

5分钟阅读

viewsIcon

104699

downloadIcon

3792

使用异步可插拔协议为 WebBrowser 控件生成内容

引言

WebBrowser 控件是一个众所周知的组件,您可以使用它将 Internet Explorer 嵌入到您的 WinForm 应用程序中。它可以用于显示来自 Internet 或应用程序本身的内容。第一种情况非常简单,只需使用 Navigate 方法加载所需的 URL 地址,但在第二种情况下存在一个问题:我们如何自己准备内容?是的,我们仍然可以在磁盘上有一个 HTML 页面并将浏览器指向它,但如果我们不想处理磁盘文件,或者想从代码中生成页面怎么办?浏览器为我们提供了许多手动填充网页主体的方法,但我们的网页由多个资源组成:HTML、CSS、图像等。如果有一种方法可以在每次请求文件时让浏览器回调到我们的应用程序,以便我们可以根据需要处理每一个文件,那将是很棒的。这是我研究的起点。

背景

我进行了一些 Google 搜索和查找,并探索了三种实现目标的方法。由于最后一种方法对我来说效果很好,我便停在那里,以下是我的研究结果。如果您使用其他方法,请随时在本文下方的评论中分享您的经验。您可以在附件中找到示例解决方案。其中有一个 WinForm 应用程序,带有按钮,每个按钮都使用 WebBrowser 控件、WebClient 类和独立的浏览器窗口来测试相应的方法,以便比较它们之间的行为。

Test application screenshot

这是应用于每种测试方法上的测试代码

// outer Internet Explorer process test
Process.Start("iexplore", url);

// WebBrowser test
this.webBrowser1.Navigate(url);

// WebClient test
var client = new WebClient();
try
{
    var str = client.DownloadString(url);
    MessageBox.Show(str);
}
catch (Exception exc)
{
    MessageBox.Show(exc.Message);
}

测试数据在共享方法 GetTestData 中准备,该方法简单地从嵌入的资源中加载文件内容,其中包含一个带有单个 JPEG 图片的网页。

private static byte[] GetTestData(string url, out string contentType)
{
	var fileName = url.Substring(url.LastIndexOf("/") + 1);
	var fileExt = url.Substring(url.LastIndexOf(".") + 1);

	var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream
		("IEPrefixTest.data." + fileName);
	if (stream == null)
	{
		contentType = "text/html";
		return Encoding.UTF8.GetBytes(@"Page not found!");
	}
	var data = new byte[stream.Length];
	stream.Read(data, 0, data.Length);
	switch (fileExt)
	{
		case "htm":
			contentType = "text/html";
			break;
		case "jpg":
			contentType = "image/jpeg";
			break;
		default:
			contentType = "application/octet-stream";
			break;
	}
	return data;
}

因此,以下是我测试方法的详细描述

WebRequest 前缀

我注意到 WebRequest 中有一个 RegisterPrefix 方法。它有什么作用?它似乎能够替换或添加一些数据请求的协议。我对此进行了更仔细的探索,您确实可以将会话重定向回您的应用程序,但这似乎只对 .NET 类有效,而 WebBrowser 保持不变。失去兴趣,继续前进。

HttpListener

当然,怎么会有一个简单的 Web 服务器模拟器呢?您始终可以在某个空闲端口上打开套接字并开始监听。好吧,这是真的,但我认为这没有必要 - 它取决于计算机的网络资源,并且没有必要从应用程序外部访问我们的数据。我已经为有兴趣的情况下准备了一个此解决方案的示例。HttpListener 类对于您自己的 Web 服务器来说非常易于使用,因此您可以在此处找到示例代码

private void test2Btn_Click(object sender, EventArgs e)
{
	var srv = new HttpListener();
	srv.Prefixes.Add("https://:12345/");
	srv.Start();
	srv.BeginGetContext(HttpListener_ContextReceived, srv);

	this.TestUrl("https://:12345/page.htm");

	srv.Close();
}

private static void HttpListener_ContextReceived(IAsyncResult ar)
{
	var srv = (HttpListener) ar.AsyncState;
	var ctx = srv.EndGetContext(ar);
	string contentType;
	var buff = GetTestData(ctx.Request.Url.ToString(), out contentType);
	ctx.Response.ContentLength64 = buff.Length;
	ctx.Response.ContentType = contentType;
	var output = ctx.Response.OutputStream;
	output.Write(buff, 0, buff.Length);
	output.Close();
	srv.BeginGetContext(HttpListener_ContextReceived, srv);
}

此解决方案当然适用于 WebBrowserWebClient 类,并且也可以从外部进程访问。

嵌入式协议

那么,有没有办法从 Internet Explorer 组件创建直接的本地回传,并仅将其用于应用程序内部使用?有一种方法,它被称为 Asynchronous Pluggable Protocol(异步可插入协议)。这项技术使您能够为 MSIE 编写自己的协议处理程序,包括操作绝对/相对 URL 地址。使用此方法,WebBrowser 根本不会访问任何网络或其他资源,而只会调用您的逻辑来获取所需的数据。尝试从独立的浏览器窗口访问该协议将会失败,因为在这种情况下注册的协议仅在当前进程范围内可见。它也无法从 WebClient 访问,因为这是 MSIE 独有的功能。

如果您确实想使用此技术,则需要实现许多 COM 接口,但我不会浪费您的时间进行详尽的流程描述,您可以在 MSDN 库中找到所有信息。我附加的测试解决方案包含了您需要的所有内容,因此您可以专注于您的核心逻辑。您应该在我的解决方案中理解的类是

  • EmbeddedProtocol
    Abstract 您的自定义协议的基类,它已经包含了大部分所需的辅助逻辑 - 实现了 IInternetProtocolIInternetProtocolRootIInternetProtocolInfo 接口。这是最重要的类。
  • EmbeddedProtocolFactory
    该类的实例在注册过程中使用,并且负责在需要时创建 EmbeddedProtocol 实例。

Using the Code

我将仅关注嵌入式协议解决方案,因为它被认为是最好的。您需要做的就是

  • 继承自 EmbeddedProtocol
  • 为您的类添加唯一的 GUID 属性
  • 编写您自己的请求处理程序(覆盖 GetUrlData 方法)
  • 在应用程序启动期间使用 EmbeddedprotocolFactory.Register 方法注册协议

请确保您的协议类非常轻量级,因为浏览器会创建许多它的实例 - 该类用于实例化所有三个相关的 COM 接口。如果您想深入研究解决方案,也许您可以重写 EmbededProtocol 类并分离各个接口实现,这取决于您。

您可以查看我的测试项目中的示例代码。我在这里注册了“test”协议,这意味着每个以“test:”前缀开头的 URL。处理程序本身非常简单 - 它只是调用之前创建的 GetTestData 方法。

private void test3Btn_Click(object sender, EventArgs e)
{
	if (!embededProtocolRegistered)
	{
		EmbededProtocolFactory.Register("test", () => new TestProtocol("test"));
		embededProtocolRegistered = true;
	}
	
	this.TestUrl("test://app/page.htm");
}

private static bool embededProtocolRegistered;

[Guid("E00957BE-D0E1-4eb9-A025-7743FDC8B27F")]
public class TestProtocol : EmbededProtocol.EmbededProtocol
{
	public TestProtocol(string protocolPrefix) : base(protocolPrefix, "/")
	{
	}

	public override Stream GetUrlData(string url, out string contentType)
	{
		return new MemoryStream(GetTestData(url, out contentType));
	}
}
#endregion

结论

我希望本文能帮助您重新考虑您自己对 WebBrowser 组件的使用,如果您有其他有趣的解决方案,请不要犹豫在评论中分享。

历史

  • 1.0 - 初始版本
© . All rights reserved.