2

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.

Scott Baker
  • 10,013
  • 17
  • 56
  • 102
  • constructor injection is the better way to go. I was going to answer with that but just saw your update stating that you have already done that. Second I believe the `ViewBag` is only populated with an action, unlike the example you presented in the constructor. – Nkosi Jul 23 '18 at 15:49
  • On a completely separate note I just read that `StructureMap has been sunsetted.` – Nkosi Jul 23 '18 at 16:02
  • ViewBag is easily accessible from the constructor. I hadn't heard that about StructureMap - I'll have to see if I can find a link. – Scott Baker Jul 23 '18 at 16:19
  • Ok well I stand corrected on the constructor access to the ViewBag. As for sunset it is on the main site for the framework https://structuremap.github.io/ – Nkosi Jul 23 '18 at 16:20
  • (...)[https://vignette.wikia.nocookie.net/peanuts/images/1/1f/Charliebrown-1-.jpg/revision/latest?cb=20130411035507] – Scott Baker Jul 23 '18 at 16:22
  • quick question. is this dependency only needed to be accessed by views? I am thinking of a cross cutting concern where a global action filter can set the view bag property. Your thoughts? – Nkosi Jul 23 '18 at 16:53
  • Yeah, that's the end goal - put the config info in the views so I can display it. Post an answer so I can see what you're thinking - if it works, you will have met the requirement. – Scott Baker Jul 23 '18 at 16:59

1 Answers1

0

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

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Excellent solution. Decoupling from the controllers is very clever. Can't award the bounty yet, but it's yours. – Scott Baker Jul 23 '18 at 21:37
  • @ScottBaker Glad to help. – Nkosi Jul 23 '18 at 21:43
  • @ScottBaker One of the cool things to look forward to in Asp.Net Core is that Views allow strongly typed Dependency Injection. ie `@inject IApplicationConfigurationSection AppConfig` Reference [Dependency injection into views in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/mvc/views/dependency-injection?view=aspnetcore-2.1) – Nkosi Jul 23 '18 at 21:47