zaterdag, juli 14, 2007

.NET User Control en JScript in Internet Explorer

Ben weer verder gegaan met het client-side invoegen van een usercontrol in IE.
Als uitgangspunt ben ik de code aan het bestuderen afkomstig uit MSDN Magazine van januari 2002, artikel DHTML and .NET: Host Secure, Lightweight Client-Side Controls in Microsoft Internet Explorer van de hand van Jay Allen.

Jay heeft een stuk html opgesteld met daarin een aantal JScript eventhandlers:

<script event="BeginUpload" for="upload1">
window.status = "uploading files...please wait";
</script>

<script event="UploadComplete" for="upload1">
window.alert("Upload complete");
</script>

Er komt nogal wat bij kijken om deze eventhandlers te koppelen aan code in je usercontrol. Een hoop COM interface gedoe.

De events zijn in c# netjes gedefinieerd:
public event UploadCompleteHandler UploadComplete;
public event BeginUploadHandler BeginUpload;

De signatuur van de usercontrol ziet er al wat minder transparant uit:

[ClassInterface(ClassInterfaceType.None), ComSourceInterfaces(typeof(IMultiUploadCtrlCOMEvents))]
public class MultiUploadCtrl : System.Windows.Forms.UserControl, IMultiUploadCtrlCOMIncoming

De interface IMultiUploadCtrlCOMIncoming is in de code als volgt gedefinieerd:

public interface IMultiUploadCtrlCOMIncoming
{
void UploadFiles();
bool FilesPending {get;}
int MaxSessionUpload {get; set; }
int BytesUploaded {get;}
string FileUploadURL {get; set; }
}

Op deze manier kan JScript middels deze properties en methods communiceren met onze usercontrol.

De interface IMultiUploadCtrlCOMEvents is in de code als volgt gedefinieerd:

[Guid("A59B958D-B363-454b-88AA-BE8626A131FB")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IMultiUploadCtrlCOMEvents
{
[DispId(0x60020000)]
void UploadComplete();
[DispId(0x60020001)]
void BeginUpload();
}

Zoals Jay zelf schrijft in het commentaar bij deze definitie: "Disgusting, but it works."

Het COM event BeginUpload() wordt netjes aangeroepen in de volgende code:
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();

// Assert ability to call unmanaged code.
BeginUpload();
CodeAccessPermission.RevertAssert();

Het COM event UploadComplete() moet pas worden uitgevoerd als de upload-thread klaar is. Nu is het zo dat COM events alleen kunnen worden uitgevoerd door de thread waarin deze zijn gekoppeld.
De methode die Jay heeft uitgewerkt maakt gebruik van een Timer object om op een sneaky manier terug te komen in de main thread. In mijn configuratie werkt deze truc helaas niet: het Timer object triggert het elapsed-event pas als de usercontrol wordt afgebroken door bijvoorbeeld het afsluiten van het IE venster.

Ik probeer daarom middels Control.Invoke te springen naar de main thread:

this.Invoke(new TimerElapsedDelegate(timer1_Elapsed), new object[] { null, null });

(even voor het gemak een delegate TimerElapsedDelegate gedefinieerd)

Maar helaas, als ik hierdoorheen step met de debugger en ik voer deze Invoke uit, dan gebeurt er een paar secondes helemaal niets. En ineens verschijnt een SecurityException!
Erg onduidelijk waarom.

Maar goed, met mijn lompe boeren verstand probeer ik het volgende:

new SecurityPermission(SecurityPermissionFlag.AllFlags).Assert();
this.Invoke(new TimerElapsedDelegate(timer1_Elapsed), new object[] { null, null });
SecurityPermission.RevertAssert();

// geen zin om uit te zoeken welke flag precies nodig is voor een invoke!

En ja hoor, het werkt! Geweldig.

Geen opmerkingen: