Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Show Dynamic HTML in WinForm Applications

0.00/5 (No votes)
12 Aug 2010 1  
Use asynchronous pluggable protocol to generate content for WebBrowser control

Introduction

The WebBrowser control is a well-known component which you can use to embed Internet Explorer into your WinForm applications. It can be used to show some content from the Internet, or from the application itself. The first case is very easy, just use the Navigate method to load the desired URL address, but there is a question in the second case: how could we prepare content by ourselves? Yes, we can still have HTML page on disk and point the browser to it, but what if we don't want to work with disk files, or we would like to generate the page from our code? The browser gives us a number of options to manually fill in the web page body, but our web page consists of a number of resources: HTML, CSS, images, etc. It would be nice if there is a way to make browser do callback to our application on every request for a file, so we could process every one according to our needs. This was my starting point of my research.

Background

I did some Googling and searching and I have explored three ways of how I could fulfill my objective. Because the last method worked for me well, I stopped there and here are my results. If you use any other approach, please feel free to share your experience in comments below this article. You can find the sample solution in the attachment. There is a WinForm application with buttons, while each of them tests the corresponding method with the WebBrowser control, WebClient class and independent browser window to compare behavior between them.

Test application screenshot

This is the test code applied on every tested method:

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

Test data are prepared in shared method GetTestData which simply loads content of the files from embedded resources where there is a web page with one JPEG picture.

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

So here are my tested methods in detail description:

WebRequest Prefix

I noticed that there is a RegisterPrefix method in the WebRequest. What does it do? It seems to be capable of replacing or adding some protocol for data requests. I explored it more closely, you can really redirect requests back into your application, but it seems to have effect only for .NET classes and the WebBrowser remains intact. Losing interest, going on.

HttpListener

Of course, how about a simple web server emulator? You can always open socket on some free port and start listening. Well, that is true, but I don't think this is necessary – it depends on network resources of the computer and there is no need to enable access to our data from outside of the application. I have prepared a sample of this solution yet for the case you would be interested. The HttpListener class is quite easy to use for your own web server so you can find sample code here:

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

	this.TestUrl("http://localhost: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);
}

This solution of course works for both WebBrowser and WebClient classes, and is also accessible from outside process.

Embedded Protocol

So is there any way to create direct loopback from Internet Explorer component and keep it for internal use of my application only? There is a way and it's called Asynchronous Pluggable Protocol. This technology enables you to write your own protocol handler for MSIE including manipulating with absolute/relative URL addresses. Using this method, the WebBrowser won't access any network or other resource at all but will simply call your logic to get desired data. Attempt to access the protocol from independent browser window will fail, because the registered protocol is in our case visible only in the current process scope. It is also unavailable from WebClient, because this is exclusively MSIE feature.

If you do want to use this technology, you would have to implement a lot of COM interfaces, but I will not waste your time with comprehensive description of the processes, you may find all information in the MSDN library. My attached test solution contains everything you need, so you can concentrate on your core logic. Classes you should understand in my solution are:

  • EmbeddedProtocol
    Abstract base class for your own protocol, it already includes the most of needed support logic – interfaces IInternetProtocol, IInternetProtocolRoot and IInternetProtocolInfo are implemented. This is the most important class.
  • EmbeddedProtocolFactory
    Instance of this class is used during registration process and it is responsible for creating the EmbeddedProtocol instance when needed.

Using the Code

I will focus only to the embedded protocol solution because I consider it as the best one. Everything you need to do is:

  • Inherit from EmbeddedProtocol class
  • Add unique GUID attribute to your class
  • Write your own request handler (override method GetUrlData)
  • Register protocol during application start using method EmbeddedprotocolFactory.Register

Please be sure your protocol class is very light-weight, because the browser creates many instances of it – the class is used for instantiation of all three related COM interfaces. Maybe you could rewrite the EmbededProtocol class and separate particular interface implementations, if you would like to go deeper into the solution, it's up to you.

You may check out the sample code from my test project. I am registering the "test" protocol here, which means each URL starts with the "test:" prefix. Handler itself is very simple – it just calls the GetTestData method created earlier.

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

Conclusion

I hope this article helped you to reconsider your own usage of the WebBrowser component and if you have any other interesting solution, please do not hesitate to share it in the comments.

History

  • 1.0 - Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here