Search code examples
c#wpfinversion-of-controlsimple-injector

How to use DI in WPF to having a new istance without ask to container


i'm trying to develop from scratch a WPF app with the use of Simpleinjector as a IOC container. I'm new on this topic and i have some issue regards lifetime of object and hot use them correctly.

I started the app by following the WPF integration guide on simpleinjector manual. But i don't understand how to receive a new instance every time a service needed it

As i ask in my previous post i need to receive a new unitOfWork every time a service need it.

as @Steven say on my previous post

Do note that transient means "allways a new instance is resolved when it is requested from the container." If you're not requesting it again, you will be operating on the same instance, which might explain the ObjectDisposedException.

In the other post i found a solutin but i think it's a little bit over-complicated and it's to create a factory and inject this instead of the instance because i want to call the container.getInstance only on the startup method and not on the service by passing the container as a dependency

It's the only way i have to achieve this or there is something that i don't understand on how to develop in DI way?

Example of code:

public class HeaderViewModelFactory : IWpfRadDispenserViewModelFactory<HeaderviewModel>
{
    private readonly ProductionService _service;

    public HeaderViewModelFactory(ProductionService service)
    {
        _service = service;
    }

    public HeaderviewModel CreateViewModel()
    {
        return new HeaderviewModel(_service);
    }
}

public class HeaderviewModel : ViewModelBase
{
    private readonly ProductionService _service;
    
    public HeaderviewModel(ProductionService service)
    {
        _service = service;
        CreateData();
    }

    private void CreateData()
    {
        _service.CreateTestCycle();
    }
}


public class CycleService : GenericDataService<Cycle>
{
    private readonly IUnitOfWork<WpfRadDispenserDbContext> _uowContext;
    
    public CycleService(IUnitOfWork<WpfRadDispenserDbContext> uowContext)
        : base(uowContext)
    {
        _uowContext = uowContext;
    }

    public void CreateTestCycle()
    {
        var cycleDataService = new GenericDataService<Cycle>(_uowContext);
        var vialDataService = new GenericDataService<Vial>(_uowContext);

        Cycle c = new Cycle();
        c.BatchName = "test";

        Vial v = new Vial();
        v.Name = "Test Vial";
        c.Vials.Add(v);

        _uowContext.CreateTransaction(IsolationLevel.ReadCommitted);
        try
        {
            vialDataService.Create(v);
            _uowContext.Persist();

            var list = vialDataService.GetAll();

            cycleDataService.Create(c);
            _uowContext.Persist();
            _uowContext.Commit();
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            _uowContext.RollBack();
            throw;
        }
        finally
        {
            _uowContext.Dispose();
        }
    }
}

private static Container Bootstrap()
{
    // Create the container as usual.
    var container = new Container();
    // Register your types:
    // Register your windows and view models:

    container.Register<WpfRadDispenserDbContextFactory>(Lifestyle.Transient);
    container.Register<IUnitOfWork<WpfRadDispenserDbContext>,WpfRadDispenserUOW>();
    container.Register(typeof(CycleService));

    container.Register<IWpfRadDispenserViewModelFactory<ProductionViewModel>,
                            ProductionViewModelFactory>(Lifestyle.Transient);
    container.Register<IWpfRadDispenserViewModelFactory<AnagraphicViewModel>, 
                            AnagraphicsViewModelFactory>(Lifestyle.Transient);

    container.Register<IWpfRadDispenserViewModelFactory<HeaderviewModel>,
        HeaderViewModelFactory>(Lifestyle.Transient);

    container.Register<IViewModelAbstractFactory,
        ViewModelAbstractFactory>(Lifestyle.Transient);

    container.Register<INavigator, Navigator>(Lifestyle.Transient);
    container.Register<MainWindowViewModel>();
    container.Register<MainWindow>();

    //container.Options.EnableAutoVerification = false;
    //container.Verify();
    return container;
}

in this way every time i create a new viewmodel i receive the same service and ovviously the dbcontext it's not present anymore because disposed.

This is not the rela code but only an example that i made to understand how DI works.


Solution

  • Using Abstract Factory pattern is the most common and recommended approach. Using the container in your application directly is widely considered an anti-pattern, like the Service Locator (Service Locator is an Anti-Pattern) for a very good reason.

    Abstract factory allows instantiation of objects without introducing a tight coupling to the actual implementation that knows how to create specific instances.

    Most IoC frameworks support this pattern natively. Most of the time they provide the generic interface for the factory. You register the instance (the product) with the container and the framework will export a ready-to use factory for you. You add the dependency to this framework interface to your object e.g. constructor. Then you register the generic factory interface. The framework will automatically create the instance of the factory and inject it into the relevant instances e.g., via constructor.

    I am not too familiar with Simple Injector, but the framework really keeps things simple. There is no such code generation.
    But the pattern is very simple (that's why this is so easy to automate) and in no way complicated.

    Example

    The interface required to dynamically create the instances of type TInstance:

    interface IFactory<TInstance>
    {
      TInstance Create();
    }
    

    The implementation of this factory:

    class SaveItemFactory : IFactory<ISaveItem>
    {
      ISaveItem Create() => new SaveItem();
    }
    

    The type that needs to create a dependency dynamically:

    interface IItemManager {}
    
    class ItemManager : IItemManager 
    {
      IFactory<ISaveItem> SaveItemFactory { get; }
    
      public ItemManager(IFactory<ISaveItem> itemFactory) => this.SaveItemFactory = itemFactory;
    
      public void SaveData(object data)
      {
        ISaveItem saveItem = this.SaveItemFactory.Create();
        saveItem.SetData(data);
      }
    }
    

    Configure the container:

    public void Run()
    {
      var container = new SimpleInjector.Container();
    
      container.Register<IFactory<ISaveItem>, SaveItemFactory>(Lifestyle.Singleton);
      container.Register<IItemManager, ItemManager>(Lifestyle.Singleton);
    
      IItemManager itemManager = container.GetInstance<IItemManager>();
      itemManager.SaveData("Some Data");
    }