简体   繁体   中英

Is there any way to register an Action into a DI Container (Autofac)?

I have a situation where I need to inject an Action into the constructor of a class. Because of this need, I'm currently stuck using the Service Locator Pattern throughout my app instead of being able to simply use the DI container for it's intended purpose.

Here's the example

RootPage.cs (master detail page)

public RootPage()
{
    this.Master = new NavigationPage();
    this.Detail = new DetailPage(OnToggleRequest);
}
private Action OnToggleRequest()
{
    IsPresented = !IsPresented;
}

now what I'd like to do is register the NavigationPage and the DetailPage into the container, and then just use some custom logic to resolve the generic.

PseudoRootPage.cs

public RootPage()
{
    this.Master = MyApp.PageBuilder < NavigationPage > ();
    this.Detail = MyApp.PageBuilder < DetailPage > ();
}

The problem comes with the Action that I need in the DetailPage to allow me to toggle the navigation menu.

Is there any way to register an Action into a DI Container?

The other option is to make the OnToggleRequest an internal static .

It sounds like what you need is a Component Factory .

Inside your DetailPage , you create a delegate property that will act as a factory for your page. I haven't done this, but you could probably make this static (as a factory method/delegate should be). I am essentially providing the example given from Autofac, tailored to your Types.

public delegate Shareholding Factory(Action toggleRequest);

Then you register your Detail Page with your IoC container.

var builder = new ContainerBuilder();
builder.RegisterType<DetailPage>();
var container = builder.Build();

and Finally resolve your DetailPage in your RootPage .

var DetailPageFactory = container.Resolve<DetailPage.Factory>();
this.Detail = detailPageFactory.Invoke(OnToggleRequest);

Hopefully this is kind of what you were looking for.

Not sure I 100% understand all the complexities around this being related to Master Detail Pages , but how about using AutoFac Dynamic Instantiation ?

public class RootPage
{
   public RootPage(INavigationPage navigationPage,
      Func<Action, IDetailPage> detailPageFactory)
   {
      var detailPage = detailPageFactory(myAction);
   }
}

When AutoFac sees the Func<Action, IDetailPage> constructor parameter it passes in a delegate that acts as a factory method for IDetailPage.

Ie it will pass the Action parameter that you send to the factory through to the constructor of the IDetailPage type.

More details can be found here: http://nblumhardt.com/2010/01/the-relationship-zoo/

If the implementation of the Action you want to inject is static, that's really easy. Normally we wouldn't thing of static methods and injection together, but you're depending on an abstraction which can be replaced, so it doesn't matter at all.

I recommend declaring a delegate corresponding to your action. Otherwise if you have to actions you want to inject and both have the same signature, they're indistinguishable. It also makes it clearer what it is that you're registering.

builder.Register<DelegateForSomeAction>(context => MyStaticClass.MyStaticMethod);

If the action we want to inject belongs to a class which must be resolved, we can do that too:

First declare the delegate you want to use. This is analogous to declaring an interface, except it's just for the action:

public delegate int DoMath(Single value1, Single value2);

For the sake of illustration, here's a class with a method that implements the delegate:

public class AddsNumbers 
{
    public Single Add(Single value1, Single value2)
    {
        return value1 + value2;
    }
}

Then register the type that contains the method you want to inject:

builder.RegisterType<AddsNumbers>();

Then register the implementation of the delegate:

builder.RegisterType<AddsNumbers>();
builder.Register<DoMath>(c =>
{
    var componentContext = c.Resolve<IComponentContext>();
    var addsNumbers = componentContext.Resolve<AddsNumbers>();
    return addsNumbers.Add;
});

This tells the container that when you need an instance of the DoMath delegate, it should resolve an instance of AddsNumbers and return its Add method.

That's a little messy. Here's an extension for Autofac that keeps you from writing that same code:

{ 
    public static IRegistrationBuilder<TDelegate, SimpleActivatorData, SingleRegistrationStyle> RegisterDelegate<TDelegate, TSource>( 
        this ContainerBuilder builder,  
        Func<TSource, TDelegate> extractDelegate,  
        string sourceComponentName = null,  
        string registeredComponentName = null)  
        where TDelegate : class
    {
        var registrationFunction = new Func<IComponentContext, TDelegate>(context => 
        { 
            var c = context.Resolve<IComponentContext>(); 
            var source = sourceComponentName == null 
                ? c.Resolve<TSource>() 
                : c.ResolveNamed<TSource>(sourceComponentName); 
            return extractDelegate(source); 
        }); 

        return registeredComponentName == null ? 
            builder.Register(registrationFunction) : 
            builder.Register(registrationFunction) 
                .Named<TDelegate>(registeredComponentName); 
    } 
}

Now the actual registration code is much simpler:

builder.RegisterDelegate<DoMath, AddsNumbers>(addsNumbers => addsNumbers.Add);

The extension method also has some extra parameters in case you need to

  • Resolved a named instance of the component where the delegate resides
  • Name the delegate component you're registering
  • Both

I prefer this approach because if your class depends on an action (delegate) it should just depend on that, not on some weird interface required by Autofac to create a factory. All of that magic should be moved to the composition root, and then the class just gets exactly what it's asking for.

More details and examples

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM