ServiceDependency not working

Topics: Web Client Software Factory
Apr 30, 2007 at 9:06 AM
Hi everyone.

In my application I have one "Shell" module, several business modules and the website, as usual. In the Shell, I have created a PagePresenter<TView> : Presenter<TView> that for now only has this:

private IHttpContextLocatorService httpContextLocatorService;
 
[ServiceDependency]
public IHttpContextLocatorService HttpContextLocatorService
{
	get { return httpContextLocatorService; }
	set { httpContextLocatorService = value; }
}
 
public HttpContext Context
{
	get { return HttpContextLocatorService.GetCurrentContext(); }
}

In each business module, I have the presenters which inherit from this class. However, everytime I access the Context in a presenter, I get a null reference exception. If I move the ServiceDependency code (shown above) to the code behind in the website, it works without a problem.

I've double- a triple-checked all the project references, web.config for the website and modules, but I still can't figure out what I'm doing wrong. Shouldn't the ServiceDependency work in this layout?

I even tried replacing ServiceDependency with CreateNew, which should get me a new instance of the service, but that doesn't work either. I'm guessing it may not be a problem with WCSF, but with ObjectBuilder instead, but I thought I should ask here first.

I don't know if this is important or not, but I'm only using the Composite Web Application Block. The reason for this is that I'm generating the code for all the projects, inferring the namespace structure from the database and spliting each namespace into its own project.

Thanks in advance for you help. Any hints are more than welcome!

Pedro Sampaio
Coordinator
Apr 30, 2007 at 11:06 PM
Hi Pedro,
The problem isn’t in your configuration, neither a bug in WCSF, but it is in fact the way CompositeWeb looks for the members to inject dependencies. By default, it looks for public properties declared in the Type being built, but not up in the inheritance hierarchy.
Nevertheless, I can think of two ways to implement what you are looking.
  • The simple one:
Instead of using the ServiceDependency attribute in PagePresenter, just add a constructor that receives the IHttpContextLocatorService (without any attribute here either) and let the concrete implementations of the presenters pass it to its base like this:
        public TestViewPresenter([ServiceDependency] IHttpContextLocatorService httpContextLocatorService)
            : base(httpContextLocatorService)
        {
        }
In this solution, you’ll have to add this argument to the constructor in every presenter (you could even modify the Guidance Package templates to do this automatically).


  • The more complex solution:
Override the PropertyReflectionStrategy to look for public members up in the inheritance hierarchy. Add this classes to your solution:
// CustomPropertyReflectionStrategy.cs
 
using System;
using Microsoft.Practices.ObjectBuilder;
using System.Reflection;
 
namespace WebApplication
{
    public class CustomPropertyReflectionStrategy : PropertyReflectionStrategy
    {
        protected override System.Collections.Generic.IEnumerable<IReflectionMemberInfo<System.Reflection.PropertyInfo>>
                       GetMembers(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
        {
            BindingFlags flags = BindingFlags.Public | BindingFlags.FlattenHierarchy;
            foreach (PropertyInfo propInfo in typeToBuild.GetProperties(flags))
                yield return new PropertyReflectionMemberInfo(propInfo);
        }
 
        private class PropertyReflectionMemberInfo : IReflectionMemberInfo<PropertyInfo>
        {
            PropertyInfo prop;
            public PropertyReflectionMemberInfo(PropertyInfo prop)
            {
                this.prop = prop;
            }
            public PropertyInfo MemberInfo
            {
                get { return prop; }
            }
            public string Name
            {
                get { return prop.Name; }
            }
            public object[] GetCustomAttributes(Type attributeType, bool inherit)
            {
                return prop.GetCustomAttributes(attributeType, inherit);
            }
            public ParameterInfo[] GetParameters()
            {
                return new ParameterInfo[] { new CustomPropertyParameterInfo(prop) };
            }
        }
 
        private class CustomPropertyParameterInfo : ParameterInfo
        {
            PropertyInfo prop;
            public CustomPropertyParameterInfo(PropertyInfo prop)
            {
                this.prop = prop;
            }
            public override object[] GetCustomAttributes(Type attributeType, bool inherit)
            {
                return prop.GetCustomAttributes(attributeType, inherit);
            }
            public override Type ParameterType
            {
                get { return prop.PropertyType; }
            }
        }
    }
}

Add a new Application class if you haven’t added one already, that adds this strategy to the chain, replacing the default one:
public class CustomWebClientApplication : WebClientApplication
{
    protected override void AddBuilderStrategies(Microsoft.Practices.ObjectBuilder.IBuilder<Microsoft.Practices.ObjectBuilder.BuilderStage> builder)
    {
        base.AddBuilderStrategies(builder);
        builder.Strategies.AddNew<CustomPropertyReflectionStrategy>(BuilderStage.PreCreation);
    }
}
Reference this new application class in your global.asax

Let me know if this helps,
Julián Domínguez
http://staff.southworks.net/blogs/jdominguez
May 2, 2007 at 2:55 AM
Although perhaps not what you want, you can also just get the service:

container.Services.Get<IHttpContextLocatorService>();

Regards,

Dave

___________________________

David Hayden
Microsoft MVP C#
May 2, 2007 at 9:48 AM
Thanks, Julián and David.

I tried that, but it still didn't solve the problem.

typeToBuild.GetProperties(flags) always returns null, unless I change the binding flags to:
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy;

If I do this, it catches my Presenter property of type TestViewPresent. However, the ServiceDependency is inside the TestViewPresenter class and the HttpContextLocatorService is still null.

I've uploaded a sample solution, in case one of you has some spare time to take a look.
http://www.sharebigfile.com/file/157977/Solution-zip.html

Just another quick question. The paradigm in WCSF and CompositeWeb is to get a modular architecture, where the presenter/controller handle all the logic of the application. This means that the services will be accessed in the presenter/controller, not in the code-behind class. Wouldn't it be better to have the ServiceDepencendy working in the module dlls, like I'm trying to accomplish?

I'm not questioning your decisions, just trying to understand what is the justification for this architecture, so I can better adjust it to my particular needs.

Again, thanks for your help.

Pedro
Coordinator
May 2, 2007 at 3:53 PM
Hi Pedro,
I’ve downloaded your sample solution, and the reason object builder is not injecting the dependencies in your presenter is because you are not building your presenter with ObjectBuilder. If you use the new operator to create an instance of your ClassTestePresenter you are bypassing OB; you should instead use the CreateNew attribute in your code-behind to build the presenter. Use the Add View (with Presenter) recipe to get a sample of how to achieve this. In this way, ObjectBuilder does not only creates an instance of your presenter, but will automatically inject all the dependencies to the built object (ServiceDependency, CreateNew, etc).
On the other hand, I tried the code without adding the BindingFlags.Instance flag, and it did work too, maybe when you tested without it you were having other issues with your solution. Nevertheless, it works with or without it.

Let me know if this gives you a better insight of Composite Web Application Block and WCSF,
Julián Domínguez
http://staff.southworks.net/blogs/jdominguez
Developer
May 2, 2007 at 4:32 PM
Hi Pedro,

For your convenience I made a sample showing how to do it.

Get the sample here.

Please let me know if this helps,
Ezequiel Jadib
http://staff.southworks.net/blogs/ejadib
May 4, 2007 at 10:44 AM
Edited May 4, 2007 at 10:45 AM
Hi guys.

Sorry for the delay in the response. Thanks for your help. It was perfect!

I was under the assumption that, since the WebClientApplication looks for the public properties, it would try to see if any had ServiceDependency. I guess not. :) Only ObjectBuilder does that.

I now understand more how to work with ObjectBuilder. It's documentation is kinda sparse and I didn't have time to play with the source code yet.

Again, thanks for all your help!

Pedro