March 2007 - Posts
A coworker (Hey, Evgeny!) came to me with a puzzler today. He has an object that exposes an event, and he wants to know whether he's already subscribed to that event.
Two things immediately came to mind - the first was that he really shouldn't be doing that, and the second was to suggest that if the reason for this is to avoid multiple registrations, he can always call -= and then += afterwards, thus making sure he's subscribed only once. Having established that, I got down to thinking about it.
I started checking out what reflection can do for me. I could easily get the EventInfo object for the event using:
myObj.GetType().GetEvent("MyEvent");
but that proved fruitless - I could get the Add and Remove MethodInfos, add a new handler and even get the delegate type for that event, but not the actual instance of the delegate. So I started thinking again.
An event in the .NET Framework is basically a wrapper around a multicast delegate. Just like a property can be a wrapper around a private data field. Rather than exposing the delegate directly, we expose a more limited subset of functionality (subscribe and unsubscribe) rather than allow my consumers to mess with my internal delegate list.
By that logic, it stands to reason that there should be an internal, private member holding the delegate itself. Probably having the same name as the event. A quick check confirmed that:
myObj.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance):
{Dimensions:[1]}
[0]: {System.EventHandler`1[System.EventArgs] MyEvent}
Right. EventHandler<EventArgs> is my delegate type (that ugly format is the internal representation of generic types) and MyEvent is the name of the event. Using this, it's a piece of cake to get the value of the field and cast it to the multicast delegate:
FieldInfo delegateField = myObj.GetType().GetField("MyEvent", BindingFlags.NonPublic | BindingFlags.Instance);
EventHandler<EventArgs> del = delegateField.GetValue(myObj) as EventHandler<EventArgs>;
And once we had that - it's a simple matter of iterating the invocation list and seeing whether I've registered already:
foreach (EventHandler<EventArgs> handler in del.GetInvocationList())
{
if (handler.Target == this)
return true;
}.
Now, I'm not saying this is a good technique - it's better not to reach a point where I don't know if I've registered or not. But still, it's good to know.
I've attached the full code listing, which is more complete, has comments and a few checks so we don't crash. Enjoy.
UPDATE: This solution is all nice and well, but as Evgeny pointed out in the comments below, this doesn't work with COM Objects. In fact, Reflection as a whole seems to be very limited, always returning __COMObject as the reflected type. Anyone know how to get more information from the RCW?
Finally, we have a full, RTM version of the Visual Studio extensions!
The download page says March 15th, but I haven't seen anyone mention it around yet - could it be just me that missed the boat?
I got it from Mark Bower's blog, but haven't seen it mentioned anywhere else.
On one hand, this is great – the last version was from November ’06, and crashed often. Deployment seemed to work only for empty web-part projects and the Solution Generator crashed when trying to dump a whole site.
On the other hand, there are still many basic scenarios not covered by the tools – we can’t export a Publishing site into a site definition, or any list with a Lookup field. This is a huge drawback. I hope they release a newer version soon, or perhaps a set of MOSS extensions (rather than WSS) that supports these scenarios.
Regardless, congrats to the team for releasing it for us, and keep up the good work.
Get it here:
http://www.microsoft.com/downloads/details.aspx?FamilyID=19f21e5e-b715-4f0c-b959-8c6dcbdc1057&DisplayLang=en
UPDATE: As sral and others reminded me, the main drawback of these extensions is that most of the functionality can only be used when running Visual Studio on the Sharepoint server itself. The most noticeable is the Deployment scenario, which is a pain to do manually.
This forces developers to either forego most of the benefits of the tools or start running Server2k3 on their dev machines, which is a hassle.
The alternative is of course to run a virtual machine for all development work, but this isn't feasible in all situations. I only recently got my development machine upgraded to the point it can run a VM decently, and even now it could do better (2GB RAM, 1.5 of which go to the VM). What did I do earlier? Installed Win2k3 (sometimes on a dual boot) and cursed the slowness.
The current message, which is "You have to buy all your developers a fast machine and a copy of Win2k3" even if you only want to develop for the (freely available) WSS 3.0 is a bit off-putting.
I call for a WSSExpress edition, similiar to the other offerings from Microsoft. It can run on Vista only, I suppose, that wouldn't be too bad. It's time to get up off my lazy ass and upgrade. :)
Ran into a problem with a client today. They installed MOSS 2007 and created a publishing portal, but then decided to erase the Reports Center, since they had no use for it. A few days later, of course, they figured they DID want it after all, and recreated a new site, called Reports, using the Reporting Center template.
However, when trying to access it, they received this error: (click for full-sized image).
We have an error parsing the web.config, complaining that the PartitionResolver cannot be used, since we don't have any session defined as SQLServer or SharedServer. This seems odd, since the very same line that throws the error seems to be defining the session state as SQLServer. It also seemed off since Sharepoint, to the best of my knowledge, didn't even enable session state.
My first hint towards the solution - the file path. Notice that the source file is c:\inetpub\wwwroot\web.config. My portal is homed to the default location, c:\inetpub\wwwroot\wss\VirtualDirectories\80 - so it should be reading the web.config there, not the one at the root. This led me to believe that there was some sort of Managed Path thing going on here.
I verified this by creating a new Report Center site called "ReportCenter", rather than "Reports". Sure enough, it worked just fine.
An even quicker test showed that, as I expected, SQL Server 2005 Reporting Services was installed on the same virtual root, and was hogging the /Reports virtual directory to itself.
Strangely enough, I checked out the Managed Paths screen in the Web Application's Central Administration, but couldn't find Reports listed. Guess it was still a conflict between the explicitly created folder and Sharepoint's HTTP module handling the name.
More Posts