Geeks With Blogs
MightyZot

Recently I was building a DNN (DotNetNuke) module for a hobby project that I’m working on.  We use DNN at PaperWise (www.paperwise.com) as a portal for our customers and our employees.  I’m trying to do just enough research into the architecture that I can direct future resources on how to integrate with our other systems.  I found a module for this hobby project of mine and, naturally, it stores data based upon the module ID, like many modules do.  Because the data is tied to the module ID, instead of an ID that gets passed into the module, I can’t use the module to display stuff based upon the dynamic content I’m showing in another module.  Since the module ID is related to the page that the module is inserted into, or the instance of the module, I’d need to create a new DNN page for each piece of dynamic content I want to associate with the new module.  The new module isn’t mine, and I don’t have source, so I had to find a way to get it to show the content I desire without modifying the module.

The first thing I did was email the author of the module to see if the module might implement the IModuleListener interface, or if he had any ideas for how I could tie the two modules together.  While Oliver was very helpful, he confirmed that the module stores data based upon the page it’s in and that I was out of luck.  As those who know me will attest, I don’t take “no” very well!  :)  After a small bit of thought, it occurred to me that I could get Oliver’s module to do what I wanted it to do if I could somehow fake it into believing that it was on different pages.  So, I set out to fool the module into believing it was somewhere that it wasn’t.

My first inclination was to find a reference to the module by searching the hierarchy of controls.  I thought I might be able to change the module ID to the ID of the content shown in my own module.  Finding Oliver’s module wasn’t difficult…I simply started referencing this.Parent.Parent.Parent…until I reached a parent that I thought might contain his module.  With a little luck, and a few debug messages, I located a reference to his module.  After some evolution, I came up with the following routine to make finding the target module easier.

Control MyFindControl(Control parent, string s)
{
    Control c = parent.FindControl(s);
    if (c == null)
    {
        foreach (Control cc in parent.Controls)
        {
            if (cc.ID != null && string.Compare(cc.ID, s, true) == 0)
            {
                c = cc;
                break;
            }

            if (string.Compare(cc.ClientID, s, true) == 0)
            {
                c = cc;
                break;
            }
        }
    }
    return c;
}

While I could make this routine more efficient by refactoring out the recursion, the recursive solution works quite well and finds the control fast enough for my purposes.  (Besides, recursion is insanely cool when you stop and think about it.)  Calling MyFindControl with this.Page and the module name returns a reference to the module that I’m after.  Ok…uh…what now?

I’m sure that many of you sit and code while you’re watching movies, TV, or listening to the radio.  The distraction, believe it or not, helps me think.  Well, I was sitting there ready to go on to the next step of changing the module ID and then it occurred to me that changing the module ID affects the settings for the module.  Crap.  This isn’t going to work.  I can’t change the module ID because then the module won’t know which settings to load and would most likely use its defaults.  It’ll get all kinds of confused.  I decided to take a look at the view and see if I could infer anything useful about the architecture.

Opening up WhateverModuleView.ascx, yes the name is fictitious, I can see that Oliver created an ObjectDataSource and everything is nicely bound to this data source.  Excellent work Oliver (and lucky me!)  What this means is that I can get a reference to that object and possibly fake it into believing it is on a different page for each of the items in my dynamic content.  Upon inspection, the ObjectDataSource object throws events for Selecting, Inserting, Deleting, and Updating!  Getting a reference to the WhateverModuleView and then the ObjectDataSource looks something like this…

private void WhateverModuleNotification(int itemid)
{
    // Search through the controls in the parent...parent.Controls collection and burrow
    // to find the WhateverModuleView control.
    Control c = MyFindControl(this.Page, "WhateverModuleView");
    if (c != null)
    {

        ObjectDataSource source = c.FindControl("dataWhateverModule") as ObjectDataSource;
        if (source != null)
        {
            ItemId = itemid;                           // This is a class-level property that the handlers can see.

            source.Selecting += new ObjectDataSourceSelectingEventHandler(Whatever_Selecting);
            source.Inserting += new ObjectDataSourceMethodEventHandler(Whatever_CRUDing);
            source.Deleting += new ObjectDataSourceMethodEventHandler(Whatever_CRUDing);
            source.Updating += new ObjectDataSourceMethodEventHandler(Whatever_CRUDing);
        }
    }
}

There are two different handlers because the selecting delegate has a different prototype.  Since inserting, deleting, and updating share the same prototype, I’m just using one handler to service all three of those.  The item ID for the item displayed by my module is stored in a class-level property called ItemId to give the handlers access to the ID.  My event handlers are shown below.  ModuleID is a parameter that’s passed through the controller and provider into the stored procedures that Oliver (the module author) uses to select, insert, delete, and update the related data.  These handlers pass my item ID as the module ID fooling the target module into believing it’s on a page associated with my item instead of a page created through administration.  The result is an “instance” of the target module tied to the data displayed in my own module!

void Whatever_CRUDing(object sender, ObjectDataSourceMethodEventArgs e)
{
    WhateverModuleInfo info = e.InputParameters["objWhatever"] as WhateverModuleInfo;
    if (info != null) info.ModuleId = ItemId;
}

void Whatever_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
{
    e.InputParameters["ModuleId"] = ItemId;
}

A key difference between these two handlers, as you’ll notice, is that the inserting, deleting, and updating handlers get passed a reference to the object representing the entity serviced by the module.  The selecting event just receives a collection of parameters.  No matter, in the first case I just cast out the object and modify its ModuleId property, which I found through a small bit of reflection.  (You may also use IL Disassembler to look through the MSIL.)  In the second case, I modified the “ModuleId” parameter and set it to my item ID.  In any case, the new module now does what I need it to do, displaying content based upon an item displayed in my module.

Now, this technique isn’t always going to work.  I got lucky because Oliver took good advantage of data binding.  Had he not done that, I would have had to be a little more creative.  My point here is that…if you need something bad enough, you shouldn’t take “no” for an answer!  You wouldn’t believe how many times I’ve proven to less experienced developers that they can do something that they thought was impossible.

Well…that’s my story for this article.  If nothing else, you’ve got at least two helpful hints that might help you solve some problems:

   - Using recursion to find controls in the page hierarchy.

   - Overriding the behavior of a data source by pre-empting select, insert, delete, and update.

Posted on Friday, July 17, 2009 5:44 PM DotNetNuke (DNN) | Back to top


Comments on this post: Don’t Take “No” for an Answer

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © MightyZot | Powered by: GeeksWithBlogs.net