Exposing Events in User Controls hosted in Internet Explorer
NOTE: This is an old blog post that I obviously never posted. I wanted to send a link to this article to someone and when searching through my archives I realized I never posted it! Enjoy.
Because of architecture restrictions (too difficult to explain in a few sentences), I had a situation where an ASP.NET implementation had to let the client execute a function and return the results for display in ASP.NET. I originally considered a smart-client application with no-touch-deployment since all clients will have the .NET framework, but the application UI had to be in ASP.NET. So I decided to use a WinForms User Control hosted in Internet Explorer.
Getting a sample control up and running is relatively simple. Create the UserControl DLL, copy it to the web directory, add an <OBJECT> tag to the ASPX page and set the CLASSID accordingly (CLASSID="<dllname>#<full-namespace>.<classname>". Now I had the user control doing the processing and I wanted it to raise an event when complete. The client-side javascript code would capture the event, place the data in a hidden form field and submit the form back to the server. Seemed simple enough until I tried to add the event.
No amount of tweaks and hacks would capture the event I raised in the .NET user control. I did some digging and found a couple of newsgroup postings as well as an MSDN article detailing the steps necessary to get a UserControl hosted in IE to raise events properly. It comes down to needing old, COM-style event source sinks. I can't give you a full explanation on this since but you'll find a nice deep article about it on MSDN. However, a little refactoring and a few attributes got me what I needed.
First, you need to define an Interface that contains the names and signature of the events you want to expose. The interface needs two attributes:
1) InterfaceType(ComInterfaceType.InterfaceIsIDispatch) -- define an IDispatch interface
2) Guid(...) -- define a GUID for the interface
In addition, each method in the interface needs a DispId() attribute with a unique value applied to it. Here's a sample interface for an event called "InfoMessage":
using System; using System.Runtime.InteropServices; namespace SimpleControl { [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] [Guid("C89B3E02-E523-422e-8312-D5B8F6F63904")] public interface IClientControlEvents { [DispId(1)] void InfoMessage(string message); } }
Now, to hook this into the UserControl, you need to add two attributes to your class:
1) ClassInterface(ClassInterfaceType.None) -- Prevent .NET from generating a default interface for this class.
2) ComSourceInterfaces(typeof(IClientControlEvents)) -- Define the interface for COM event source sinks
All that's left is to use the regular .NET delegate/event mechanism for event handling. Just make sure the event name matches the name defined in the interface:
public delegate void InfoMessageEventHandler(string data); public event InfoMessageEventHandler InfoMessage;
Capturing the event at the client will depend on which scripting technology you're using. For VBScript, simply create a method with the usual "<id>_<eventName>" scheme. For JavaScript, you need the <script> tag with the "for" and "event" attributes. Here's an example:
<object id="myControl" classid="samplecontrol.dll#MyNamespace.ClientControl" /> <script language="vbscript"> Sub myControl_InfoMessage(data) MsgBox(data) End Sub </script> <script language="javascript" for="myControl" event="InfoMessage(data)"> alert(data); </script>
The only other thing to watch out for is that any public methods you want to call through script need to be done via an interface implemented by your UserControl.
SOURCE: http://msdn.microsoft.com/msdnmag/issues/02/01/UserCtrl/default.aspx