简体   繁体   中英

Using Dependency Injection (Autofac) and avoiding Service Locator pattern

I'm taking a stab at properly implementing DI in my Xamarin Android application using Autofac but I'm having issues understanding how I should handle instantiating objects that require data passed into their constructor. For example one of our viewmodels needs a string and a guid passed in to its constructor. Something that looks promising is Delegate Functions offered by Autofac. This is where the line between Service Locator and DI appears to blur, at least in my mind. In order to use the Delegate Functions you must call container.Resolve, or rather it's recommended to use the IComponentContext.Resolve. Many blogs recommend not using Resolve outside of the bootstapper/main entry point. Is there something I am missing here? Is there a better way to create objects using DI? I am familiar with the Factory pattern to create objects but I feel that I'm losing the benefits of DI going that route since I am back to manually passing in services/objects to the newly created object. Thanks for any feedback!

It is not recommended to call container.Resolve() to use a delegate factory. The correct way is shown on the delegate factories page that you already linked to :

public class Portfolio
{
  Shareholding.Factory ShareholdingFactory { get; set; }
  IList<Shareholding> _holdings = new List<Shareholding>();

  public Portfolio(Shareholding.Factory shareholdingFactory)
  {
    ShareholdingFactory = shareholdingFactory;
  }

  public void Add(string symbol, uint holding)
  {
    _holdings.Add(ShareholdingFactory(symbol, holding));
  }
}

When the docs show an explicit call to container.Resolve() you should realize that they are not showing best practice, they are simply proving that it can be resolved without coding up a whole new class (like Portfolio ) to consume it.

In order to use the Delegate Functions you must call container.Resolve

No, at least not in this case.

Assuming you have registered Shareholding . Now you can ask a dependency on Func<Shareholding> , ie. something hat returns a Shareholding when you call it.

But as the Shareholding constructor has two parameters, it cannot be resolved without supplying those parameters. Just add them to the declaration like this: Func<string, uint, Shareholding> . Now you can resolve the dependency when you supply those parameters.

Here is a better example .

I recently (yesterday) faced the same problem I wound up using the ServiceClient object you see in the code below. This object addresses your question about using the container outside of the bootstrapper. I have read arguments that say not to pass the container around and I think they are mostly valid. In my case however the ServiceClient class represents a single point of entry into my service layer so I thought it was appropriate to pass the container.

The way I use this at the moment is to pass an instance of ServiceClient into my BaseController:

// In Global.asax.cs  
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.RegisterType<ServiceClient>().As<IServiceClient>();

BaseController:

public abstract class BaseController<T> : Controller where T :class
{

    public IServiceClient ServiceClient { get; set; }

    public BaseController(IServiceClient serviceClient)
    {
        ServiceClient = serviceClient;
    }
}

In my controller I can resolve, instantiate, and call a service that uses unmanaged resources with just one line like this:

myViewModel = await ServiceClient.OfType<ICustomerService>().TryAsync(x => x.GetCustomerByID(id));

ServiceClient:

public class ServiceClient : IServiceClient 
{
    private IComponentContext _container;

    public ServiceClient(IComponentContext container)
    {
        _container = container;
    }

    public ServiceCallWrapper<T> OfType<T>() where T : class, IDisposable
    {
        return new ServiceCallWrapper<T>(_container);
    }
}

public class ServiceCallWrapper<T> : IServiceCallWrapper<T> where T : class, IDisposable
{
    private IComponentContext _container;

    internal ServiceCallWrapper(IComponentContext container)
    {
        _container = container;
    }

    public void Try(Action<T> method) 
    {
        // consider try/catch/log/throw here
        using (T client = _container.Resolve<T>())
        {
            method(client);
        }
    }

    public TResult Try<TResult>(Func<T, TResult> method)
    {
        using (T client = _container.Resolve<T>())
        {
            return method(client);
        }
    }

    public async Task TryAsync(Func<T, Task> method)
    {
        using (T client = _container.Resolve<T>())
        {
            await method(client);
        }
    }

    public async Task<TResult> TryAsync<TResult>(Func<T, Task<TResult>> method) 
    {
        using (T client = _container.Resolve<T>())
        {
            return await method(client);
        }
    }
}

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