Click here to Skip to main content
15,868,340 members
Articles / Desktop Programming / Windows Forms

Show Dynamic HTML in WinForm Applications

Rate me:
Please Sign up or sign in to vote.
4.86/5 (14 votes)
12 Aug 2010CPOL5 min read 101.1K   3.7K   28   11
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:

C#
// 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.

C#
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:

C#
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.

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Petr Bříza
Czech Republic Czech Republic


Freelance .NET developer from Prague, Czech republic. You have a problem - I am the solution.

Visit me at http://petr.briza.net.


Comments and Discussions

 
GeneralMy vote of 5 Pin
Member 127416372-Feb-24 16:55
Member 127416372-Feb-24 16:55 
QuestionCan We Get HTTP Respones? Pin
voidale11-Oct-12 0:02
voidale11-Oct-12 0:02 
QuestionYou saved my days! Pin
wmjordan7-Jun-12 0:29
professionalwmjordan7-Jun-12 0:29 
Excellent article and codes!
It is so neat and complete!
I can't understand why it is not rated five.
Anyway, I must vote a 5 for your article.
QuestionExcellent, but I am having a problem with something Pin
Mike Nakis18-Jan-12 0:41
Mike Nakis18-Jan-12 0:41 
GeneralMy vote of 5 Pin
MartinSchmidt25-Oct-11 4:33
MartinSchmidt25-Oct-11 4:33 
QuestionAPP and downloding files Pin
sov-20-0721-Jun-11 8:27
sov-20-0721-Jun-11 8:27 
GeneralInteresting. What about .NET 2 Pin
Staffan Sjöstedt16-Aug-10 23:06
Staffan Sjöstedt16-Aug-10 23:06 
GeneralRe: Interesting. What about .NET 2 Pin
Petr Bříza17-Aug-10 2:34
Petr Bříza17-Aug-10 2:34 
AnswerRe: Interesting. What about .NET 2 Pin
Staffan Sjöstedt18-Aug-10 1:31
Staffan Sjöstedt18-Aug-10 1:31 
General[My vote of 2] Doh! Pin
seeblunt12-Aug-10 13:25
seeblunt12-Aug-10 13:25 
GeneralRe: [My vote of 2] Doh! Pin
Petr Bříza12-Aug-10 21:17
Petr Bříza12-Aug-10 21:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.