In short: I discuss Sidebar Gadgets, and I show you how to invoke a CardSpace-protected WCF service from a simple Gadget. Full source code is provided, along with detailed commentary on the road I've followed for getting there. Added bonus: the code shows how to apply an arbitrary configuration file to WCF, an issue often encountered when hosting WCF code in processes you don't control.
Sidebar Gadgets are mini applications which live in the Sidebar, a UI element on the Windows Vista desktop. They are extremely handy for keeping an eye on information you are often interested to; they are also very good at providing you a quick-reach UI for tasks you perform often. As you know I wear the server guy hat, so I'm not really the best person for explaining the advanteges of Gadget: I would suggest visiting Michael and Jaime blogs if you want more details on the subject.
When I thought of how the gadget model could be useful for me, I realized that much of the information I'd like to keep an eye on happens to be confidential (like being notified if I received a wire transfer, or getting the access statistics from my website); the actions I want to take when I react to changes in those data are also requiring high security levels (like accessing a portion of my home banking for giving approval for a certain utility bill to be paid). So, would not be great if we could use CardSpace for authenticating the services accessed by a Gadget? I thought for few nights about the issue, devised a strategy and wrote a proof of concept. Yesterday night I finally got it working: below I walk you through the process. In attachment you'll find the source code: it's not very polished, so I am not posting it to the community website just yet.
Sidebar Gadget: a glance to the architecture
I am going to cover a minimal portion of the architecture, to the purpose of understanding how to plug CardSpace in it. Gadgets are so much more that the few things I throw down here, please take the time to take alook at the whole story. That said, let's dive into it.
A sidebar gadget is, in extreme synthesis, an HTML file (Gadget.htm, for example) that will be rendered inside the Windows Vista Sidebar. As such, it can do pretty much everything that DHTML offers you PLUS it takes advantage of some APIs which handle some resource on the local system (some document folder like the pictures, storing and retrieving gadget-specific settings, etc). The gadget application is described by a manifest file, Gadget.xml, which enumerates various metadata such as application name, version, author info, location of the source file, location of images used as icons, platform requirements, permissions required... a classical manifest.
A gadget can also have other HTML files that are shown on specific moments: it is possible to provide a custom UI for changing the gadget settings (typically settings.htm), and a custom UI that pops up from the gadget (typically flyout.htm) and gives more real estate outside of the boundaries of the sidebar. In our walkthrough we don't use those two further UIs, but in a real gadget application you will likely leverage them. All the .HTM files in a gadget can make use of the classical web UI entourage, such as *.JS and *.CSS files; and images, naturally. All those files (and subfolders, if any) should live in the same folder as Gadget.htm and gadget.xml.
Deploying a gadget is super easy: you zip the folder containing the above, you change the .ZIP extension to .GADGET and you obtain a file that can be easily installed. Now, there's a rougher way of deploying a gadget. You take the same folder above, but instead of zipping it you just copy it under %userprofile%\AppData\Local\Microsoft\Windows Sidebar\Gadgets. Then you just click on the plus sign on top of the sidebar (what? You're not running it already? Just type sidebar in the search field in the start menu, the shortcut to it is usually the first result) and pick your gadget for having it instantiated (you can have multiple copies).
How does the typical gadget work? Like a web page from where you cannot leave. All UI updates are made using AJAXy tricks, refreshing only the parts of the UI that need updating. Sometime you can also "cheat" and embed in the page an ActiveX (any flavour you want), or an XBAP. It's that easy.
Injecting CardSpace in a gadget
After I gave the above description, a couple of times I've heard reactions such as "Ah brain dead easy! If this is just HTML, let's shove in it the CardSpace HTM Object tag and we're done!". Non so fast, pal. The HTML in a gadget is hosted and executed on YOUR client machine: this means that you are the subject AND the relying party, which does not really make sense in this situation (in other situations, it may). Besides, the sidebar renders the gadget's HTML without opening any HTTPS session. The object tag makes sense if it rendered in the context of an HTTPS session associated to the SSL certificate of the website you want to authenticate with. So no, we can't use the object tag directly.
What else? Let's think about it for a moment. When a browser invokes CardSpace, it is explicitly passing to it the settings contained in the Object tag found on the page (that is to say the token policy of the RP); the browser also passes implicitly details about the RP, such as the certificate being used. In your gadget you don't get the same information automatically from the context: hence, the solution is force feeding CardSpace with those info in some other way. Since we don't have a way to to that via pure scripting, we have to do it via "real" code (read: compiled): that doesn't worry us, bacause there's the ActiveX venue. Seems promising, let's flesh it out.
There is still a reality check that we need to deal with before implementing our vision. Our Gadget.htm file is instantiated by the process Sidebar.exe, which is already up&running when our gadget starts. This means that our assembly will be instantiated in an already existing AppDomain, which already read its own configuration file. Unfortunately the WCF portion of our code needs a lot of configuration info, and actually we want to keep it on a file that can be modified withour recompiling anything. This is a typical problem when you use WCF in processes you don't own. We cannot put all the relevant configuration in Sidebar.exe.config and still call ourselves architects, so another solution is in order. Actually, fixing this is relatively simple: from our ActiveX we spawn a new AppDomain, then we create & execute our WCF proxy from it. I have made few quick searches on MSDN and articles to see if somebody already did that, and to my surprise I could not find any. Well, now there will be one 🙂 this was the last "architectural" issue, from now on, just implementation bumps. Let's review the solution we devised:
Grocery list style:
- We have a basic sidebar gadget (Gadget.htm) which instantiates a certain ActiveX and invokes it
- We have an ActiveX which creates a new AppDomain, associated to an arbitrary configuration file (in the figure, that is MyActiveX.dll.config)
- Inside the new AppDomain we create a WCF proxy according to the settings in configuration file mentioned above. Such settings will mandate the use of cardspace
- We use the WCF proxy for invoking the service. The cardspace UI pops up, we perform the call, we get back the result, we destroy the new appdomain, we give back the result to the HTM code
Now that we got a good feeling of how it should work, let's walk through the code of the various parts of the solution.
The sample gadget
I am going to describe you a very, very simple gadget: it will turn out pretty ugly from the visual standpoint, but I'm OK with it. The extra code required for the prettification would ruin the signal/noise ratio, and I want to make my best for making you understand the fine points about cardspace and WCF here. You can add cosmetics later 🙂
Our gadget will display a list of roads: by pressing a button, we will get traffic info from a web service and we will change the color of every road name according to the traffic levels (green=good, red=bad, etc). The service that returns the traffic info will be secured via Windows CardSpace. If you're a loyal reader, that may sound familiar to you: this is exactly one of the services featured in my WPF sample. In fact, in the spirit of frugality of this post, the gadget we are creating here will call exactly that service. This means that for testing the gadget you'll need to download the sample from here (detailed blog post here) and you'll need to have TrafficService running when you use the gadget (if you don't, you'll still see cardspace popping up: but afer you select a card the call will fail). You will not need anything else from the WPF sample: also, I won't use any of the token caching technology I introduced at the time even if it actually makes sense.
Now that we know what the gadget is supposed to do and what we need on our "backend", let's finally dive into the details of the implementation.
Let's start with the basic Gadget-ry. What do we need to deploy in %userprofile%\AppData\Local\Microsoft\Windows Sidebar\Gadgets? The following:
- [folder] images
The manifest file is very, very simple:
The only part that really interests us is the one highlighted in yellow, where we specify that the source file is "gadget.htm" and that we need permissions Full. For details on the gadget manifest format, please refer to the gadget development documentation. Also note: all the .PNG files referred in the manifests are what constitutes the content of the folder images.
The file Gadget.htm is much more interesting:
All the parts not highlighted, like the CSS definitions, are of no interest for us in this context and will be ignored.
Starting from the body:
The text highlighted in pink represents the UI elements that will have to change color according to the traffic info: simple DIVs with RoadN as IDs.
Let's take a close look at the definiton of the GetTraffic function.
The text highlighted in yellow indicated code that deal with the ActiveX.
The first line instantiate the ActiveX by its ProgID, WCFCardSpaceActiveX.WCFCardSpaceActiveXClass. No indication that this is a .NET assembly.
The next two lines feed the ActiveX instance with the path of the WCF configuration file and the path of the assembly on your file system. Chances are that you will have to simply substitute "myaccount" with your user alias.
The fourth line invokes the main method of the ActiveX, GetTrafficConditions(), and saves the return value on a local variable.
The green code is just UI update; we go thorugh the list and we change the color of the DIVs accordingly.
That's it! From the Gadget developer perspective, it's so easy it's not even funny. If we take a look to the activex code, though, we'll find that the music changes.
Below you can see the project structure for the custom activex assembly:
The output of the project is a class library, that should be copied in the same folder as gadget.htm, gadget.xml and so on.
THe resulting assembly must be registered as COM object. If you are using visual studio, you can avoid using regasm for registering the assembly under COM. If you go on project properties->build tab, you will find a "Register for COM interop" checkbox; check it, and next time you'll build the project the assembly will be registered. If you want to do everything by hand, you can use regasm from the command line.
As you will read in the <Digression/> block below, for this example you have also to install the assembly in the GAC. THat's easily done via the command line tool gacutil. I am looking at eliminating the GAC requirement.
Let's take a look at some notable file in the solution.
The TrafficServiceContract.cs file it taken verbatim from the WPF sample mentioned above, the one containing the WCF service our gadget will invoke. It just contains the contracts definition for the service we want to invoke. I won't paste it here.
The App.Config is identical to WCFCardSpaceActiveX.dll.config. It is substantially a simplified version of the WPF client config in the WPF sample mentioned above: I have just got rid of all the config for the other service (MeteoService), not used here, and I eliminated all the token caching configuration. Even if it's not very interesting nor new, I will paste it here in case you want to take a look.
If your service listens on a different port, or if it is remote, the highlighted value must be changed accordingly.
We finally got to the real meat of the post, the code behind WCFCardSpaceActivexClass. Since it is longer than the others and somewhat denser, I am going to break down the file in more sections. Let's start with the main ActiveX class declaration:
The yellow code shows the attributes needed for exposing the class as ActiveX. We have seen that ProgId used from the Gadget.htm code for instantiating this class.
The AutoDual interface is useful for being able to use the current class via scripting.
The green and light blue code handle the acquisition of the applicationbase and configuration file paths, respectively. Again, we have seen those methods invoked from Gadget.htm.
The method that Gadget.htm invoked for getting the results was GetTrafficConditions: here there's how it is implemented.
This is the place where we perform the AppDomain trick.
The light blue code retrieves the full name of the current assembly, as loaded in the sidebar root AppDomain: we will need it later, since the class that performs the actual WCF call lives here. I could have hardcoded the assembly full name, but that would not have been funny (and it would break easily every time you change something).
The code highlighted in yellow creates a new AppDomain, assigning to it the config file we specified and forcinf the execution path to the folder where our assembly lives.
The first line of green code creates an instance of our WCF caller class,TrafficServiceCaller, in the new AppDomain. This ensures that the WCF code in it will see the configuration file we specified, as opposed to the sidebar configurations settings.
This line was especially nasty to deal with, because I kept getting an error "cannot cast to transparent proxy". If I hosted the assembly from a console test application, however, everything worked. I know there are some subtleties about assembly resolution that can be fixed by overriding some system function, but I wanted a quick solution. Hence, I just installed my assembly in the GAC and everything magically worked. This is not ideal, since GAC instalaltion requires admin privileges: however we have to make some installation script or MSI anyway, since we need to register an ActiveX; furthermore if the gadget is used for high value transaction it probably makes sense to give the user a feeling that what they're installing is important stuff. That's what MIchael told me today, and I faithfully report it 🙂
Once we obtained the instance, we can call the method that will actually invoke the WCF service. After that, it's just cleaning.
The last thing we need to examine is the TrafficServiceCaller class, where we will finally see some WCF code.
The yellow code enables the create-it-in-one-appdomain-but-call-it-from-another trick. The green code just creates the WCF channel according to the arbitrary configuration, then invokes the service.
That's it. We have seen how we can call a WCF service from one sidebar gadget, and use CardSpace for protecting the operation. The procedure is not necessarily straighforward, but it is not that hard and especially it does not require any hack.
If you are not afraid of some handwork, you can try the above by playing with the code I am attaching to this post. Remember, this is pretty rough and requires some work from your side (downloading the CardSpace+WCF+WPF sample and launch the TrafficService, copying under your %userprofile%\AppData\Local\Microsoft\Windows Sidebar\Gadgets the folder CardSpaceWCFGadgetPoC.Gadget that you will find in the attached ZIP, launching regasm /codebase and gacutil -i on the WCFCardSpaceActiveX.dll file, adding the gadget from the gallery via the "+" button on the sidebar) but it's really not hard, and it actually helps to understand how the various moving parts interact. As usual, if eneough people asks for it I will package it in a full fledged sample. Have fun!