Skip to main content

How to show excel files inside the .NET Webbrowser Control

If you are reading this, chances are you been banging your head against the wall for a couple of hours (or even days) trying to show excel files inside the WinForms webbrowser control.
Possible reasons you ended up in here:
  1. You had working code that got broke after upgrading from Win 7.
  2. Your code doesn’t work the same way between machines running different (newer) versions of IE.
  3. A download box pops up every time your app tries to show an excel file inside the webbrowser control (you wanna show the actual content).
  4. You just have no clue on how to get excel working into the .NET embedded webbrowser control.
  5. You are trying to implement IInternetSecurityManager and don’t know where to start. (Or how don’t know how to delegate calls to your security manager).
  6. Among many other, maybe…..

Yes, COM is a PITA, so is ActiveX and IE (Embedded or full for that matter). And no, showing excel files inside the webbrowser control shouldn’t be that hard, but sometimes we have to deal with what we have at hand, so here it goes….

TL;DR; If you are in a rush, go to https://github.com/amiralles/owc to get working code on how to embed excel files into the webbrowser control. Yes, yes... I know... You are welcome ;)!

For those who wanna go deeper, let’s just say we are going to deal with COM, OWC, unsafe code, a custom implementation of IInternetSecurityManager, and of course Excel and IE.

Before start

  1. Make sure you ‘ve Office Web Components on your machine. If you don’t have it, you can download it from here. This component is the one that will take care of Excel interop.
  2. Clone the repo from https://github.com/amiralles/owc and make sure it builds. (No, there is no .sln nor csproj in there. We are programming like real men do ;)). Just run build.bat and if you don’t see any errors, you are ready to go. If you wanna use VS, just create a new project and copy paste the code (it’s all in one file).

Working with the code

As you may guess already, we are going to deal with a combination of C# and HTML. To avoid boring you, I’ll only show/comment the important bits and let you explore the rest of code by yourself.

Crafting the HTML

The first step is to create an HTML page that will hold an instance of  OWC (Office Spreadsheets, more precisely) and configure it to show our excel file. The code will look something like this (let’s call the file “index.html”):

   <object id="excel"
                classid="CLSID:0002E559-0000-0000-C000-000000000046"
                width="100%"
                 height="100%"
     VIEWASTEXT>
       <param name="DisplayColumnHeadings" value="-1">
       <param name="DisplayRowHeadings" value="0">
       <param name="DisplayToolbar" value="0">
       <param name="XMLURL" value="test.xml">
   </object>

Important bits:
  1. CLSID Must be equal to whatever you have on the registry for “Microsoft Office Spreadsheet [version]. As far as I can tell, this value doesn’t among different versions of Office, but who knows… If I were you, I’ll check it anyways. (Open regedit and search for Microsoft Office Spreadsheet “).
  2. Make sure “test.xml” exist and is reachable from where your page is running. (If relative paths are not working for you, try absolut ones, they should work right off the bat).
  3. Make sure you’ve save the excel file as xml. As odd as it sound , OWC can’t handle excel file format!. Repeat after me, it can’t handle Excel files… So be careful.

Quick test:
Open the html page (using full IE, not that crappie Edge thing) and you should get a popup saying “Internet Explorer restricted this webpage from running script or ActiveX Objects”. Click “Allow blocked content”, and you’ll see an excel file “running” inside IE.
If you can’t get this to work, stop here and review everything, paths, owc installation, CLSID, excel file saved as xml, etc… etc…. It has no point to keep going if this step is not right.

Crafting the C# code

Now that we have OWC up and running and a web page to show our content, we are ready to roll, right?

webbrowser.Navigate(Path.GetFullPath(“index.html”));

And…. nope. Is not working. Why is that?

Well, when you previously open the page using full IE, you got a message asking you to allow blocked content. Guess what happens when IE has no way to ask your permission… It assumes you would say: no. (Which is, security wise, 100% correct).
Now is where things get little bit trickier, because there is no way to bypass IE security mechanisms. (Of course you can change the security level for a particular machine, but doing so will put the whole machine at risk because those settings are globals to IE, not just for the embedded browser that you are running inside your app). So you have to implement IInternetSecurityManager interface and allow such and such operations to be completed without asking user’s permission.

Once again, I’ll show the meaningful bits, you can see the whole stuff by checking out the code.

// This class allows us to delegate security aspects to our custom InternetSecurityManager.
class WebBrowserSiteEx :
WebBrowserSite,
IServiceProvider,
IInternetSecurityManager {
public unsafe int MapUrlToZone(string url, int* pdwZone, int dwFlags) {
// ["Local", "Intranet", "Trusted", "Internet", "Restricted"]
// Here we are saying that our site is running under the
// local security policy.
*pdwZone = 0; // <= Local.
return Constants.S_OK;
}
}
public unsafe int ProcessUrlAction(
string url, int dwAction, byte* pPolicy, int cbPolicy,
byte* pContext, int cbContext, int dwFlags,
int dwReserved) {
// And here we say, yes, allow this action….
*((int*)pPolicy) = (int)Constants.UrlPolicy.URLPOLICY_ALLOW;
return Constants.S_OK;
}
}

public class WebBrowserEx : System.Windows.Forms.WebBrowser {
WebBrowserSiteEx _site;

protected override WebBrowserSiteBase CreateWebBrowserSiteBase() {
return _site ?? (_site = new WebBrowserSiteEx(this));
}
}

public static class Constants {
public const int S_OK = 0;
public const int E_NOINTERFACE = unchecked((int)0x80004002);
public const int INET_E_DEFAULT_ACTION = unchecked((int)0x800C0011);
public enum UrlPolicy {
URLPOLICY_ALLOW = 0x00,
URLPOLICY_QUERY = 1,
URLPOLICY_DISALLOW = 3,
}
}

Note: The code assumes that the user didn’t change the IE security settings for local zone. If you have troubles with this, try to restore the settings to factory defaults. Or create a custom zone and change the code accordingly.

Now that we know some security internals, is time to build the code and see what happens. In order to do so, go to the src directory and run build.bat. If everything was OK, you should have a file called program.exe at the bin directory and a working version of Excel running inside the WebBrowser control!

If you are using VS, make sure your build is targeting the x86 platform and that you have the “unsafe” flag turned on (where unsafe means unmanaged code). A really annoying thing about ActiveX Objects inside the Webbrowser control is that they fail silently without giving you any clue about what went wrong. So keep an eye on that, too.

Resources


Credit where credit is due

While none of this post is a complete solution on how to show excel files inside the webbrowser control, they really help me to get off the ground.

Comments

  1. Thank you for sharing information. Wonderful blog & good post.Its really helpful for me, waiting for a more new post. Keep Blogging!


    Outrageous Camelot

    ReplyDelete
  2. Nice article, thanks for the information. You give me some idea's. I will bookmark for next reference.

    Shoretel Atlanta

    ReplyDelete

Post a Comment

Popular posts from this blog

Migrating an ASP.NET MVC 4 app from Azure websites to WinHost

About a week ago I've to migrate an ASP.NET MVC 4/EF5 application from Azure websites to WinHost. While the process was really smooth, there were some caveats related to database connections that I want to share with you. Create and setup the ftp profile on VS and configure the connection string was really easy, WinHost provide you those values and there is nothing special here. But once you deploy your website and try to see it online, you may get the “yellow screen of dead” with the message: "A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)" Assuming you wrote the connection string properly, this happens because you cannot use the default connection name in your web.c

Moving to Medium

It's been a long time since I want to give medium a try, and finally, I made some time to do it. To get started on the new platform, I'll be doing series on "Getting programming concepts, languages and tools". If it sounds interesting to you, please take a look at the first post  Getting AWK  and spread the word if you like it. I'm not going to migrate old entries to the new web site. They will remain here safe and sound! As usual, thanks for reading!