0

I had tried to separate DAL and inject it to DI in Program.cs like below

services.AddScoped<IDataLoadServices, DataLoadServices>(); 

Then to get data from DB and put it into Model (maybe can call it ViewModel) I need to get these services from the constructor of Model class like below

class MyModel{
    public IList Countries{get;set;}
    
    public int SelectedCountryCode{get;set;
    
    //default Ctor
    //call When creating by Model Binding
    public MyModel(){
      
    }
    //call when creating by Factory
    [ActivatorUtilitiesConstructor]
    public MyModel(IDataLoadServices ser){
         Countries = ser.LoadMyListFromDB();           
    }
}

In Controller Action I can create a MyModel instance by using FactoryServices.(not shown here) after that passing to View successfully.

@model MyModel
<form method="post" asp-controller="MyController">
  ...
  <select asp-for="SelectedCountryCode" asp-items="@(new SelectList(Model.Countries, "CountryCode", "CountryName"))">            
  </select>
  <input type="hidden" asp-for="SelectedCountryCode" />
 ...

In Controller Action for post-Submit

[HttpsPost]
public IActionResult Submited(MyModel model){
   //model.Countries == null
   //model.SelectedCountryCode == 0 ????
}

The problem I think that is MyModel was created in Model Binding Stage with Default Contructor so that It cant get Countries list and somehow can't resolve SelectedCountryCode.

  1. Is that Right ?
  2. Any idea for bypassing this problem ?
dellos
  • 327
  • 5
  • 15

3 Answers3

0

Most of the data your views display should be passed in from the controller. I would keep the model as bare as possible. Instead, inject the IDataLoadServices into the controller.

public class WorldController : Controller
{
    private readonly IDataLoadServices _service;

    public WorldController(IDataLoadServices service)
    {
         _service = service          
    }
}

Check out the docs on this. Dependency injection into controllers in ASP.NET Core

steepestascent
  • 153
  • 2
  • 7
0
  1. Is that Right ? --> It could be, but you could remove that default ctor from the model. Cause it will be created by default.

  2. Any idea for bypassing this problem ? --> There are many ways to do this.

  • You can load the list of Countries in the startup.cs class using Options pattern (say you have a class with country code and country name) and then use that class inside the model constructor. Option Pattern
  • Inject the IDataLoadServices in the controller class constructor.
  • with no default ctor, you get compliant because Model Binding can not resolves IDataLoadServices. And Put IDataServices in Controller will work with Get Action (put mode to View) but not work with Post Action ( get model form Model Binding) – dellos Dec 05 '21 at 13:24
  • Did you try with the option patter I mentioned above. ? And how did you register your services? – Avantha Siriwardana Dec 05 '21 at 14:12
  • I am reading Option Pattern but have not understood yet. I registered IDataLoadServices in ConfigureServices method of Progarm.cs like this "services.AddTransient()" – dellos Dec 05 '21 at 14:51
  • Check this out. https://learn.microsoft.com/en-us/aspnet/core/mvc/views/dependency-injection?view=aspnetcore-6.0 – Avantha Siriwardana Dec 05 '21 at 17:36
0
  1. It is Right --> ModelBininding create new MyModel use Default Ctor

  2. This should not become a problem. MyModel instance I received in Control Action should have SelectedCountryCode != 0. => I was fail in Mapping class.

    [HttpsPost]
    
    public IActionResult Submited(MyModel model){
    
      //model.Countries == null => ok. because cant resolve IDataLoadServices
    
      //model.SelectedCountryCode != 0 
    }
    

In case someOne needs ModelBining call Parameters Ctor:

  1. Create Factory to inject DI into Model Ctor.

    public class FactoryServices : IFactoryServices
    {
       private readonly IServiceProvider _sp;
    
       public FactoryServices(IServiceProvider serviceProvider)
      { 
         _sp = serviceProvider;
      }
    
       public T Create<T>(params object[] p) 
       {
    
         return ActivatorUtilities.CreateInstance<T>(_sp, p);
       }
    }
    
  2. Create Custom Binder

  3. Create Custom BinderProvider

  4. Regist to DI

For 1., 3., 4. refer to this answer

dellos
  • 327
  • 5
  • 15