Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asp.net core MVC Dependency injection - required properties in constructor? [closed]

In an asp.net core mvc project, i have an abstract ViewModel class, for a layout, which requires some properties to function properly, and each concrete page's ViewModel derives from it:

public abstract class Layout
{
    // required to function properly
    public string Prop1 { get; set; }

    // required to function properly
    public IEnumerable<Foo> FooList { get; set; }

    protected Layout()
    {
        // Populate FooList from an util caching class?
    }
}

public class ViewModelHome : Layout {}
public class ViewModelProducts : Layout {}

The layout requires a FooList, which is populated from a database or a cache, since it is data not often changed.

I would like to avoid to putting each required field in the constructor in order to make it not too verbose, and I would like to avoid the following for each derived class:

var model = new ViewModelHome();
model.FooList = .....

In asp.net core caching is available through DI of IMemoryCache, so I cannot write something like this:

    public abstract class Layout
    {           
       protected Layout()
       {
           var cacheClass = new MyCacheClass(...IMemoryCache??...);
           this.FooList = cacheClass.GetFooList();        
       }        
    }

This is because I'm creating Layout on my own in a service, in turn called from controllers.

public class MainController : Controller
{
    private readonly IMainService _service;

    public MainController(IMainService service)
    {
       _service = service;
    }


    public IActionResult Home()
    {              
       return View(_service.GetHomeViewModel());
    }
    public IActionResult Products()
    {
        return View(_service.GetProductsViewModel());
    }
 }

My question is: should i put in the abstract class constructor the logic for getting data from database or cache?

Thanks

like image 325
ʞᴉɯ Avatar asked Jul 01 '19 06:07

ʞᴉɯ


2 Answers

This is a bad design to have dependencies in view models. My understanding is that you want to setup some common properties for all your view models. In order to resolve this I would recommend you to implement some kind of initializer (e.g. IViewModelInitializer) which then can be injected in a controller and then called for all view models in order to initialize them. You can inject all the dependencies (caching, repositories etc) in this initializer. See the code sample below:

public class Foo { }

public class Bar { }

public abstract class Layout
{
    public Bar Bar { get; set; }

    public IEnumerable<Foo> FooList { get; set; }
}

public class HomeViewModel : Layout
{

}

public interface IFooRepository
{
    IEnumerable<Foo> GetList();
}

public interface IBarRepository
{
    Bar GetSingle();
}

public interface ILayoutInitializer
{
    void Initialize(Layout layout);
}

public class LayoutInitializer : ILayoutInitializer
{
    private readonly IFooRepository _fooRepository;
    private readonly IBarRepository _barRepository;

    public LayoutInitializer(IFooRepository fooRepository, IBarRepository barRepository)
    {
        _fooRepository = fooRepository;
        _barRepository = barRepository;
    }

    public void Initialize(Layout layout)
    {
        if (layout is null) throw new ArgumentNullException(nameof(layout));

        layout.FooList = _fooRepository.GetList();
        layout.Bar = _barRepository.GetSingle();
    }
}

public class MainController : Controller
{
    private readonly ILayoutInitializer _layoutInitializer;

    public MainController(ILayoutInitializer layoutInitializer)
    {
        _layoutInitializer = layoutInitializer;
    }

    public IActionResult Home()
    {
        var homeViewModel = new HomeViewModel();

        _layoutInitializer.Initialize(homeViewModel);

        return View(homeViewModel);
    }
}
like image 72
Alexey Andrushkevich Avatar answered Sep 20 '22 09:09

Alexey Andrushkevich


I guess you have a couple of options here:

  1. Property Injection - Inject your IMemoryCache into the property via DI (most containers support it), i would not recommend this approach.
public class Foo {
   [Inject] // or other marker attribute specific to your IoC
   public readonly IMemoryCache;
....
}
  1. Service Locator - Use a thread safe, singleton reference to your container and resolve the dependency. I'd also avoid this option.
public Foo(): base()
{
  base.MemoryCache = IoC.Resolve<IMemoryCache>();
}
  1. Ctor injection from derived to parent - Although this might be verbose its actually the right thing to do, as you will be explicit about your contracts and dependencies.
public Foo(IMemoryCache cache): base(cache)
  1. Pass dependency in the method signature - Very explicit but honours functional programming principles. Eg: Ask yourself is it the whole class which needs that dependency or just one of its methods?
public void Foo(IMemoryCache cache, int bar)
  1. Ctor injection but pass the container to parent if you have too many dependencies
public Foo(IBar bar, IBaz baz, IContainer container): base(container)
{
  ...
}

I had instances where option 5 worked best during heavy refactorings.

like image 26
Cristian E. Avatar answered Sep 22 '22 09:09

Cristian E.