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.
This is the test code applied on every tested method:
Process.Start("iexplore", url);
this.webBrowser1.Navigate(url);
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