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

6 Comments

  • I've been trying for a day now to get events in a class i wrote in .NET to work in VBScript on my web page. Everything else about the class works fine (methods, properties, etc...) But the one event i have never fires.



    i have:

    public event ReceivedDataHandler OnDataReceived;



    But when i step through the code, OnDataReceived is always null. It's not null in .NET Code that is using it because i actually assign tot he delegate using the +=.



    I followed everything that you have listed above and it's just not working. Is there anyway you can send me a sample file that you have working?

  • I tried doing what you suggested. Setting localhost to trusted site and increased to full trust, but i still have the same issue. I'm completely at a loss. It's still seeing public event ValueSetHandler OnValueChanged; as null.

  • Adam,



    Is it a fairly simple control you could send to me? If not, let me know and I'll try and dig up my sample.

  • One thing to be careful of is your constructor. I was doing something in my constructor that was erroring out. IE hides errors that happen during initialization, so you don't know anything's gone wrong until you try to use your object and it isn't really there.

  • Hi all,

    i've got a similar problem exposing events from an .Net 2.0 user control : the web browser (IE) doesn't receive any event fired from the component.

    The only way to get it to work is to register an interop 'tlb' on each client machine, using such a command :
    C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm.exe MyAssembly.dll /tlb:MyAssembly.tlb

    This works, but is very annoying to deploy !!

    Does anyone have any idea about this strange problem ??

    Thanks

  • Hi,
    i finally resolve this strange issue, just have a look at the following code sample :


    ************ original code ****************
    Public Event ConnectionClear(ByVal i As Integer)

    sub mySub()

    ' your code here
    '

    ' time to raise the event
    RaiseEvent ConnectionClear(123)

    end sub



    actually, there was two problems :
    1) an exception occured on the RaiseEvent BUT was not forwaded to the main thread. Adding an explicit try/catch show me the truth here :

    Exception : "Object does not match target type"
    at System.RuntimeType.InvokeDispMethod(String name, BindingFlags
    invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, Int32
    culture, String[] namedParameters) ...

    2) Searching on the web, i found that this exception is related to a reflection problem in a multi threading context :
    mySub was running from a 'secondary background thread', and not from the main thread of my user control (that's why the exception was not visible if not catched).

    The solution is to raise events from the main thread, using the Invoke method of the user control class, allowing to execute code from the main thread, using delegates.

    Here's the fixed sample code :


    *************** fixed code ****************
    Public Event ConnectionClear(ByVal i As Integer)


    Private Sub DoRaiseEventConnectionClear(ByVal i As Integer)
    Dim d As ConnectionClearDelegate = New ConnectionClearDelegate(AddressOf RaiseEventConnectionClear)

    Dim params(0) As Object
    params(0) = i

    Me.Invoke(d, params)
    End Sub

    Private Delegate Sub ConnectionClearDelegate(ByVal i As Integer)

    Private Sub RaiseEventConnectionClear(ByVal i As Integer)
    RaiseEvent ConnectionClear(i)
    End Sub



    sub mySub()

    ' your code here
    '

    ' time to raise the event
    DoRaiseEventConnectionClear(123)

    end sub


    Now all works fine, without registering any TLB on each client worksation (and without checking 'register fro COM Interop' in the project properties) !

    Hope this helps !

    lzm

Comments have been disabled for this content.