asp.net-mvc-5inversion-of-controlstructuremapstructuremap4setter-injection

Using StructureMap[4.7.0] Setter Injection in my MVC5 Controller


I am trying to inject the IApplicationConfigurationSection implementation into this MVC5 Controller, so that I can have access to some of the information (various strings) from my web.config custom section in all of my views:

public class BaseController : Controller
{
    public IApplicationConfigurationSection AppConfig { get; set; }

    public BaseController()
    {
        ViewBag.AppConfig = AppConfig; // AppConfig is always null
    }
}

I want to use setter injection so I don't have to clutter up my derived Controller constructors with parameters that they don't really care about.

Note: If there is a better way to inject base class dependencies, please let me know. I admit I may not be on the right track here.

In my Global.asax I load my StructureMap configurations:

private static IContainer _container;

protected void Application_Start()
{
    _container = new Container();

    StructureMapConfig.Configure(_container, () => Container ?? _container);
    // redacted other registrations
}

My StructureMapConfig class loads my registries:

public class StructureMapConfig
{
    public static void Configure(IContainer container, Func<IContainer> func)
    {
        DependencyResolver.SetResolver(new StructureMapDependencyResolver(func));

        container.Configure(cfg =>
        {
            cfg.AddRegistries(new Registry[]
            {
                new MvcRegistry(),
                // other registries redacted
            });
        });
    }
}

My MvcRegistry provides the mapping for StructureMap:

public class MvcRegistry : Registry
{
    public MvcRegistry()
    {
        For<BundleCollection>().Use(BundleTable.Bundles);
        For<RouteCollection>().Use(RouteTable.Routes);
        For<IPrincipal>().Use(() => HttpContext.Current.User);
        For<IIdentity>().Use(() => HttpContext.Current.User.Identity);
        For<ICurrentUser>().Use<CurrentUser>();
        For<HttpSessionStateBase>()
            .Use(() => new HttpSessionStateWrapper(HttpContext.Current.Session));
        For<HttpContextBase>()
            .Use(() => new HttpContextWrapper(HttpContext.Current));
        For<HttpServerUtilityBase>()
            .Use(() => new HttpServerUtilityWrapper(HttpContext.Current.Server));
        For<IApplicationConfigurationSection>()
            .Use(GetConfig());

        Policies.SetAllProperties(p => p.OfType<IApplicationConfigurationSection>());
    }

    private IApplicationConfigurationSection GetConfig()
    {
        var config = ConfigurationManager.GetSection("application") as ApplicationConfigurationSection;
        return config; // this always returns a valid instance
    }
}

I have also "thrown my hands up" and tried using the [SetterProperty] attribute on the BaseController - that technique failed as well.


Despite my best efforts to find a solution, the AppConfig property in my controller's constructor is always null. I thought that

`Policies.SetAllProperties(p => p.OfType<IApplicationConfigurationSection>());` 

would do the trick, but it didn't.

I have found that if I discard setter injection and go with constructor injection, it works as advertised. I'd still like to know where I'm going wrong, but I'd like to stress that I'm not a StructureMap guru - there may be a better way to avoid having to constructor-inject my base class dependencies. If you know how I should be doing this but am not, please share.


Solution

  • While constructor injection in this scenario appears to be the better solution to the stated problem as it follows The Explicit Dependencies Principle

    Methods and classes should explicitly require (typically through method parameters or constructor parameters) any collaborating objects they need in order to function correctly.

    The mention of only needing to access the AppConfig in your views leads me to think that this is more of an XY problem and a cross cutting concern.

    It appears that the controllers themselves have no need to use the dependency so stands to reason that there is no need to be injecting them into the controller explicitly just so that the dependency is available to the View.

    Consider using an action filter that can resolve the dependency and make it available to the View via the same ViewBag as the request goes through the pipeline.

    public class AccessesAppConfigAttribute : ActionFilterAttribute {
        public override void OnActionExecuting(ActionExecutingContext filterContext) {
            var resolver = DependencyResolver.Current;
            var appConfig = (IApplicationConfigurationSection)resolver.GetService(typeof(IApplicationConfigurationSection));
            filterContext.Controller.ViewBag.AppConfig = appConfig;
        }
    }
    

    This now makes the required information available to the views with out tight coupling of the controllers that may have a use for it. Removing the need to inject the dependency into derived classes.

    Either via adorning Controller/Action with the filter attribute

    [AccessesAppConfig] //available to all its actions
    public class HomeController : Controller {
    
        //[AccessesAppConfig] //Use directly if want to isolate to single action/view
        public ActionResult Index() {
            //...
            return View();
        }
    }
    

    or globally for all requests.

    public class FilterConfig {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
            filters.Add(new AccessesAppConfigAttribute());
        }
    }
    

    At this point it really does not matter which IoC container is used. Once the dependency resolver has been configured, Views should have access to the required information in the ViewBag