Voodoo?

Mar 27, 2008 at 3:04 AM
Edited Mar 27, 2008 at 3:05 AM
Voodoo - that's what our team has labeled the "behind-the-scenes" Microsoft magic that we can't see, as in the case of our discussions surrounding bindingsources, binding navigators, etc.

There is some serious Voodoo, good mojo, going on with Prism - nothing in the StockTraderRI to indicate the view is instantiating a presenter and then injecting itself into it. I had to plug in the Unity/Objectbuilder source to get some insight into the process and was hoping some blanks could be filled in.

My best guess is that the RegionManager.Region statements within the XAML are self-registering Workspaces (in SCSF terms); registering themselves during InitializeComponents. I noted there is some dynamic IL buildplan mojo going on during the Buildup process - eventually we end up in the Presenter's contructor with a view reference - very cool voodo to say the least. I'm very curious as to how the buildplan knew to utilize the IWatchListView mapping for the WatchListViewPresenter.

Can't wait to see how this evolves!





Mar 27, 2008 at 3:41 AM
It's quite easy actually:

  • Bootstrapper loads modules
  • PositionModule creates PositionSummaryPresenter which (with the aid of UnityContainer) will create/resolve the view
  • PositionSummaryPresenter.ShowViews() create ITrendLinePresenter which in turn creates/resolves TrendLineView

This could go one and one but it's actually pretty straight forward.

In order to understand dependency injection under the hood, you should look at the Unity project on codeplex.
Mar 27, 2008 at 1:57 PM
Edited Mar 27, 2008 at 2:43 PM

francois_tanguay wrote:

  • PositionModule creates PositionSummaryPresenter which (with the aid of UnityContainer) will create/resolve the view

In order to understand dependency injection under the hood, you should look at the Unity project on codeplex.


I remember back in the day I could look under the hood of my 70 Mustang and see my spark plug wires and coil; today my 99 mustang has fuel injection and more wires than I can trace. I just about had my arms around the ObjectBuilder but with WCSF and Prism I'm seeing a lot of IL-code emitting - "under the hood" just got more complex - I havn't grasped it (yet).

Your response confirmed my suspicion that the Presenter's (Unity) buildplan creates/resolves the view - what wasn't apparent to me, because of the IL-code emitting, was how it was resolved, i.e, when I create my view and presenter where/how do I establish the dependency?
Mar 27, 2008 at 3:21 PM
By default, Unity will automatically pick the Presenter's longest constructor. It will then look every parameter type through reflection and establish dependencies to inject this way.

If those dependencies represent interfaces or abstract types, it will look into the registered types to find a match and then instantiate the concrete type to fulfill the dependency.
Mar 27, 2008 at 3:36 PM


francois_tanguay wrote:
By default, Unity will automatically pick the Presenter's longest constructor. It will then look every parameter type through reflection and establish dependencies to inject this way.

If those dependencies represent interfaces or abstract types, it will look into the registered types to find a match and then instantiate the concrete type to fulfill the dependency.


Very nice .....

Thanks! That clarifies a lot.
Apr 1, 2008 at 10:24 PM
@Bill

Views and Presenters are being registered at the module level. This is how views are able to be discovered. Look into any of the module classes and you will see methods that do this registration.

As far as regions, we are doing something different. We have a RegionManagerService that holds pointers to all the different regions that are created. To build up regions we are leveraging some WPF Voodoo :) We've created an attached Region property which can be attached to any region. A region from a WPF perspective is simply a wrapper to a UIElement that is a container. When you declare the region in XAML, the RegionManager will get invoked, and willl throw an event. The RegionManagerService listens to this event and then adds the Region to it's internal collection. The service is hard coded so it creates one of two classes, either an ItemRegion or a PanelRegion depending on the type of control.

The downside to this approach is that we've hardcoded to allow creating only one of two regions. In the next drop this is going to get a face lift. Instead of hardcoding the handlers for regions, we've created an IRegion<T> interface, with T representing a UIElement. When a region is added, the RegionManagerService will look at the UIElement type and then attempt to resolve an IRegion<> for that type from the IPrismContainer. If it does not find one, it will recursively walk the inheritance tree until it does find one. Once it finds one, then it will manufacture an instance and register it. In this model, by using IPrismContainer we're externalizing the handlers for regions to being registered through config rather than hard-coding. Look for more on this in our wiki.



BillKrat wrote:
Voodoo - that's what our team has labeled the "behind-the-scenes" Microsoft magic that we can't see, as in the case of our discussions surrounding bindingsources, binding navigators, etc.

There is some serious Voodoo, good mojo, going on with Prism - nothing in the StockTraderRI to indicate the view is instantiating a presenter and then injecting itself into it. I had to plug in the Unity/Objectbuilder source to get some insight into the process and was hoping some blanks could be filled in.

My best guess is that the RegionManager.Region statements within the XAML are self-registering Workspaces (in SCSF terms); registering themselves during InitializeComponents. I noted there is some dynamic IL buildplan mojo going on during the Buildup process - eventually we end up in the Presenter's contructor with a view reference - very cool voodo to say the least. I'm very curious as to how the buildplan knew to utilize the IWatchListView mapping for the WatchListViewPresenter.

Can't wait to see how this evolves!







Apr 3, 2008 at 2:11 AM
@Glenn

Thank you for taking the time to clarify these very important areas to understanding Prism (under the hood). For the benefit of those, like me, who are new to WPF and Dependency Injection (I was introduced to DI with the ObjectBuilder in SCSF and now WCSF - WPF is totally new) I will regurgitate what you noted above with the applicable source code and a laymans view. This should make it easier for developers to visualize your comments while verifying that I understood the information you provided (I trust if I missed something someone will let us know).

I provide the following within the context of a developer new to Prism and Unity (not in any way complaining about the architecture); the gurus may be to far removed from our ignorance (haven't learned - not can't learn) so it may be more difficult to provide us what we need to know to get off the ground without much patience; what is quite obvious to the gurus may have us totally lost. Perhaps raising the awareness of this gap may make communications easier?

So I say again - THANK YOU - your and francois_tanquays time and patience is deeply appreciated!
----

Views and Presenters are being registered at the module level. This is how views are able to be discovered. Look into any of the module classes and you will see methods that do this registration.



The following is very straight-forward and easy to understand

Bootstrapper.cs
        private void InitializeModules()
        {
            IModule newsModule = new NewsModule(container);
            IModule watchListModule = new WatchListModule(container);
            IModule marketModule = new MarketModule(container);
            IModule positionModule = new PositionModule(container);
 
            newsModule.Initialize();
            marketModule.Initialize();
            watchListModule.Initialize();
            positionModule.Initialize();
        }

Where it got very confusing is that outside of the presenter's constructor parameter list, the following class is the only reference to the IWatchListView and WatchListView. The DI folks will chuckle at this confusion, but for us MVP folks we're looking for where the view instantiates the presenter and places a reference to itself into it. I suspect that developers not familiar with MVP and DI will most likely be totally lost.

The following Initialize() method will seem foreign to MVP folks (almost backwards) leaving us scratching our head trying to figure out how the view get's instantiated. Fortunately francois_tanquay did a great job of clearing this up above.

WatchListModule.cs
    public class WatchListModule : IModule
    {
        private IUnityContainer container;
 
        public WatchListModule(IUnityContainer container)
        {
            this.container = container;
        }
 
        public void Initialize()
        {
            RegisterViewsAndServices();
 
            WatchListViewPresenter presenter = container.Resolve<WatchListViewPresenter>();
            presenter.ShowViews();
        }
 
        public void RegisterViewsAndServices()
        {
            container.RegisterType<IWatchListView, WatchListView>();
            container.RegisterType<IWatchListService, WatchListService>();
        }
 
        #endregion
    }

Immediately after the Unity container is instantiated and initialized the RegisterGlobalServices() method is called which ensures our RegionManagerService is available.

BootStrapper.cs
    public class Bootstrapper : IDisposable
    {
        IUnityContainer container;
 
        public void Initialize()
        {
            InitializeContainer();
==>     RegisterGlobalServices();
            InitializeShell();
            InitializeModules();
 
        }
 
        private void RegisterGlobalServices()
        {
==>         container.RegisterInstance<IRegionManagerService>(new RegionManagerService());
        }


The constructor for the manager service will subscribe to the RegionManager's static RegionPropertyChanged event. When the event is raised the RegionManager_RegionPropertyChanged event handler will call the SetRegion method which wraps the panel or ItemsControl (later IRegion<T>) and adds it to the RegionManagerService _regions dictionary.

RegionManagerService.cs
    public class RegionManagerService : IRegionManagerService, IDisposable
    {
        private Dictionary<string, IRegion> _regions = new Dictionary<string, IRegion>();
 
        public RegionManagerService()
        {
            RegionManager.RegionPropertyChanged += RegionManager_RegionPropertyChanged;
        }
 
        void RegionManager_RegionPropertyChanged(object sender, RegionPropertyChangedEventArgs e)
        {
            SetRegion(e.ContainerElement, e.RegionName);
        }
 
        public void SetRegion(DependencyObject containerElement, string regionName)
        {
            IRegion region = null;
 
            if (containerElement is Panel)
            {
                region = new PanelRegion((Panel)containerElement);
            }
            else if (containerElement is ItemsControl)
            {
                region = new ItemsControlRegion((ItemsControl)containerElement);
            }
 
            if (region != null)
                _regions.Add(regionName, region);
        }

Shell.xaml
<StackPanel Grid.Row="1" Grid.Column="0">
   <Grid prism:RegionManager.Region="mainRegion"></Grid>
   <TabControl Name="newsTab" prism:RegionManager.Region="ResearchArticlesRegion" 
 
ItemContainerStyle="{StaticResource HeaderStyle}" />
</StackPanel>

The above is where your comments shed the most light and revealed some pretty powerful mojo. If I understood you correctly the DependencyProperty.RegisterAttached(...) method is the Voodoo that you were referring to. My assumption is that <Grid prism:RegionManager.Region="mainRegion"></Grid>* is a dynamically generated property created by the RegisterAttached process - very very nice. My second assumption is that the prism:RegionManager.Region setter is responsible for executing the callback method, i.e., OnSetRegionCallBack which in turn raises the RegionPropertyChanged event identified above.

RegionManager.cs
namespace Prism
{
    public static class RegionManager
    {
        public static readonly DependencyProperty RegionProperty = DependencyProperty.RegisterAttached(
            "Region",
            typeof(string),
            typeof(RegionManager),
            new PropertyMetadata(OnSetRegionCallback));
 
        public static event EventHandler<RegionPropertyChangedEventArgs> RegionPropertyChanged;
 
        public static void SetRegion(DependencyObject containerElement, string regionName)
        {
            containerElement.SetValue(RegionProperty, regionName);
        }
 
        public static void OnSetRegionCallback(DependencyObject containerElement, 
 
DependencyPropertyChangedEventArgs args)
        {
            if (RegionPropertyChanged != null)
            {
                RegionPropertyChanged(null, new RegionPropertyChangedEventArgs(containerElement, 
 
args.NewValue.ToString()));
            }
        }
    }
}