Sample: CustomWebClientApplication Injecting Dependencies into MasterPages and UserControls

Jun 1, 2007 at 1:59 AM
I added a sample to the WCSFContrib Project that is a CustomWebClientApplication Class that will inject dependencies into MasterPages and UserControls. This is something that is currently not done in WCSF 1.0 and 1.1. You can read the documentation.

If you find any issues with the sample or can improve upon it, please let me know.

Regards,

Dave

___________________________________

David Hayden
Microsoft MVP C#
Jun 13, 2007 at 7:14 PM
Edited Jun 13, 2007 at 7:18 PM
Hi David,

I have found a bug in your CustomWebClientApplication class. The BuildControls recursive method is only able to inject dependencies into UserControls located on the first level of Controls hierarchy on the page. If the UserControl is inside a container like Placeholder or Multiview, for example, the dependency won't be built for this UserControl. It happens because the BuildControls only calls itself recursively if a control is a UserControl, so it only goes through the first levels of controls.

In order to make the approach working for unlimited levels of Controls hierarchy a small change needs to be applied. The BuildControls method should look like this:

private void BuildControls(ICompositionContainer moduleContainer, ControlCollection controls)
{
foreach (Control currentControl in controls)
{
if (currentControl is UserControl)
{
CompositionContainer.BuildItem(PageBuilder, moduleContainer.Locator, currentControl);
}
BuildControls(moduleContainer, currentControl.Controls);
}
}

Alexander Turlov
MCSD.NET
Jun 13, 2007 at 8:19 PM
Alexander,

I purposely put that BuildControls call within the if block to save some cycles because we end up traversing controls looking for dependencies that don't make sense, but it looks like it was a bad choice.

Thanks for catching it. I will modify the code this evening to remove it from the if block.

Regards,

Dave

____________________________

David Hayden
Microsoft MVP C#
Jun 20, 2007 at 6:34 PM
We have tried the solution and are experiencing problems. If I use a usercontrol with a ServiceDependency and add it to a page that does not use a masterpage it works great. However, if the control is contained in within a Content tag it would not work. Changing the condition to the following seems to fix the problem.

if (currentControl is UserControl
|| currentControl is System.Web.UI.HtmlControls.HtmlForm
|| currentControl is System.Web.UI.WebControls.ContentPlaceHolder)
Jun 23, 2007 at 1:58 AM
Using a CustomWebClientApplication class influences on how regular ASP.NET control behaves. I can not mention all the cases but the one that specifically concerned me was Repeater behavior. If you used A CustomWebclientApplication and has a repeater control on a page that contains button or textbox with event handlers attached to them those event handlers would never be called. This case is 100% reproducable. I could not be able to find a real cause of this fenomena but if I get rid of the recursive call of BuildControls method everything works fine. Nevertheless I was able to find a workaround for the problem. If I use PreLoad event instead of InitCompete everything works fine. The following code demonstrates the solution:

protected override void PrePageExecute(Page page)
{
base.PrePageExecute(page);
page.PreLoad += new EventHandler(this.OnPagePreLoad);
}

private void OnPagePreLoad(object sender, EventArgs e)
{
Page page = (Page)sender;
page.PreLoad -= new EventHandler(this.OnPagePreLoad);
ICompositionContainer moduleContainer = GetModuleContainer(new HttpContext(System.Web.HttpContext.Current));
BuildControls(moduleContainer, page.Controls);
}
Jun 27, 2007 at 6:47 AM
Edited Jun 27, 2007 at 6:50 AM
Traversing the code hierarchy passing the module container so that the control in question can act on the container seems to be a bit dot-net-nuke-ish.

Isnt there a more elegant solution that we could come up with? Maybe something using an AOP approach?

Is it feasable that we could create a custom attribute, apply the apply the attribute to an interface or view. Then couldnt we use reflection to get a reference to the classes / interfaces that have been decorated with the attribute and use the values in the attribute to determine which dependencies to inject into the module container?

I may be a bit off base because I havent been able to find where to get the code for the CustomWebClientApplication class, but I can guarantee that my organization will never use WCSF in any real world applications if there isnt a way to get dependencies into a user control like you can with a page, as most all of our functionality lives in user controls and pages are simply dumb containers.
Sep 8, 2007 at 9:57 AM
If you check out the latest weekly drop of the software factory they have added a solution to this problem.
Here's a snippet from the readme:

========
Changes
========

The major change this week is to how CWAB does dependency injection. Previously, dependency injection was performed on ASPX pages automatically as they were created. Unfortunately, that approach doesn't work for master pages, controls, or web services. In each of those cases, there is no consistent hook to intercept construction of these objects. In order to enable dependency injection, we've changed how the DI container is accessed.

Now, instead of happening automatically, an object that wishes to be injected must call a single static method:

Microsoft.Practices.CompositeWeb.WebClientApplication.BuildItemWithCurrentContext(this);

This will cause the DI container to walk the given instance, and resolve any dependencies. The disadvantage is that this isn't really dependency injection anymore; the object must know that there is a DI container and invoke it. However, it has the major advantage that getting dependencies resolved now works in exactly the same manner across pages, master pages, controls, and web services. Among other things, this enables building user controls and master pages using the MVP pattern.

Please let us know what you think of this change on our forum.

----------------

Doesn't really feel like the final solution but I'm going with it for now, I really want to use WCSF but I need my user controls, and this is the most stable solution yet.
Dec 9, 2007 at 3:24 AM
Hi David,

We are developing an application based on WCSF June 2007 Version 1.1. The application dynamically loads the user controls into the content page of a particular Master page. At first we had the problem in loading the user controls because presenter class couldn’t get instantiated. I looked into this Blog and added the CustomWebClientApplication class to our application as advised by you and Alexander Turlov. Now we are able to load the user controls, but during the post back to the User Control we are loosing the data related to the Master Page. Please let me know for any additional code changes that will resolve this problem. Thanks in advance.


Best regards,
sidd
Dec 9, 2007 at 3:25 AM
Hi David,

We are developing an application based on WCSF June 2007 Version 1.1. The application dynamically loads the user controls into the content page of a particular Master page. At first we had the problem in loading the user controls because presenter class couldn’t get instantiated. I looked into this Blog and added the CustomWebClientApplication class to our application as advised by you and Alexander Turlov. Now we are able to load the user controls, but during the post back to the User Control we are loosing the data related to the Master Page. Please let me know for any additional code changes that will resolve this problem. Thanks in advance.


Best regards,
sidd
May 6, 2008 at 7:17 PM
Hi David,

Could you, or somebody else, comment on all the issues in this thread and get some kind of final recomendation/solution.

What's the down side of iterating recursively through the entire .Controls tree down to the very last control? I would sacrifice some CPU cycles vs. coupling my views with the DI container.

Any potential issues doing this:

private void BuildControls(ICompositionContainer moduleContainer, ControlCollection controls)
{
__foreach (Control currentControl in controls)
__{
____if (currentControl is UserControl)
____{
______CompositionContainer.BuildItem(PageBuilder, moduleContainer.Locator, currentControl);
______BuildControls(moduleContainer, currentControl.Controls);
____}
____if (currentControl.Controls != null)
______BuildControls(moduleContainer, currentControl.Controls);
__}
}
May 7, 2008 at 12:52 AM
Colleagues,

I'd say this discussion thread does not make sense any more. Let's just close it: it's already not relevant. There is the only solution for all the questions above: switch to WCSF 2.0 and forget all the troubles. It's not that hard. I personally have a solution originally developed with WCSF Jul 2007 that includes 26 projects and 6 of them are WCSF business modules. Switching to .NET 3.5 and WCSF only took 6 hours from me from which 2 hours I was removing/installing the prerequisite software. There were no underwater surprises everything went smooth.
May 7, 2008 at 1:13 PM
Edited May 7, 2008 at 1:13 PM
1. Is VS2008 required to implement WCSF2.0?
2. Could you share more details regarding the conversion, please.
May 7, 2008 at 3:28 PM
WCSF 2.0 does require VS2008. If you are still using VS2005 you can use one of the previous WCSF Bundles (Dec 2007) as they implement the same model of the code injection as the WCSF 2.0 so your struggle with User controls and Master pages will be resolved too.
You can read more details about the conversion process in my blog here http://webaspnet.blogspot.com/2008/05/upgrading-your-wcsf-solution-from-june.html
Aug 6, 2008 at 5:28 PM
Just for the record, even though the Oct 2007 release does support DI in UC and Mastert Pages, it is still possible that you'd need to do what this thread is about. The reason being the fact that DI in Oct 2007 release happens somewhat late in the UC's lifecycle. this is post from another thread:

From: pbolduc

The user controls inherit from Microsoft.Practices.CompositeWeb.Web.UI.UserControl.  In the Init event, it calls WebClientApplication.BuildItemWithCurrentContext(this);

public class UserControl : UserControl
{
    // Methods
    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        WebClientApplication.BuildItemWithCurrentContext(this);
    }
}

You must ensure your user control has finished the OnInit before accessing injected dependencies.