ASP.NET Core MVC FromService binding in Controller returns empty collection

落爺英雄遲暮 提交于 2019-12-24 08:22:19

问题


Been trying to get a grip of ASP.NET Core MVC but got stuck with dependency injection returning empty collection in my controller action, despite of registering the service as Singleton in ConfigureService method. Tried two different ways to register but only one of them actually works. Said method is as following:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
    /***THIS DOES NOT WORK***/
    services.AddSingleton<IRepository<Employee>, EmployeeRepository>();
    var empRepo = services.BuildServiceProvider().GetService<IRepository<Employee>>();
    empRepo.Create( "emp001" );

    /***THIS WORKS***/
    var employeeRepository = new EmployeeRepository();
    employeeRepository.Create( "emp001" );
    services.AddSingleton<EmployeeRepository>( employeeRepository );
}

And following is my Controller action:

[HttpGet( "{empID}" )]
public string GetEmployee( string empID, [FromServices] IRepository<Employee> employees **/*THIS IS ALWAYS EMPTY*/**)
{
    ....
}

Kindly point me in the right direction. What is the difference between the two AddSingleton methods? How can I access the service using 1st approach by DI on Controller action? TIA.


回答1:


How your your repository implemented? I assume you inject DbContext in it? Then you can't use new EmployeeRepository() to instantiate it and you can't use singleton. DbContext is resolved per scope and as such, all ervices which use it must do so to.

Also never call services.BuildServiceProvider() within ConfigureServices, because before the call of Configure the ASP.NET Webstack will do that anyways and create a new provider, unrelated to the one you built before.

Third, never resolve scoped services via application scope (by calling app.ApplicationServices.GetService<T>(), this may cause issues with the lifetime of your service. If you have resolve a scoped service (i.e. Data seeding) during application startup, create a new scope, resolve from it, then dispose it once done.




回答2:


Since in your controller action you require an instance of IRepository<Employee>, you can use any of those 2 methods as long as the service being registered is of that type (in your second approach you were registering it as EmployeeRepository):

services.AddSingleton<IRepository<Employee>, EmployeeRepository>();
services.AddSingleton<IRepository<Employee>>(new EmployeeRepository());

As you can read in the docs, the difference between those 2 options is just the point where the instance will be created.

  • When using the first option, the instance will be created the first time that the service needs to be injected in some class.
  • With the second option, the instance is created manually by you in the ConfigureServices.

Which one should you use depends on your needs.

As to why you receive it as null, there must be something else going on in your project, check for errors in the console or try to reproduce the issue in a new minimal project. I tried to reproduce it but both registration methods worked fine:

  • New 1.0.1 web application
  • Then added these types:

    public class Employee { }
    public interface IRepository<T> { }
    public class EmployeeRepository: IRepository<Employee> { }
    
  • Updated the Index action in the HomeController to receive an instance using [FromServices] as in:

    public IActionResult Index([FromServices]IRepository<Employee> emp)
    

EDIT

As @Tseng mentioned, you shouldn't call BuildServiceProvider in the Startup method:

  • Doing so means you create your own service provider completely separated from the one that ASP.Net Core will create.
  • It also means the instance you will get in the Startup method is different from the one your controller will get (As each service provider will have its own singleton)

Either leave the actual creation of the instance to the service provider or use the second AddSingleton method if you need to create the instance yourself. Don't worry if your class also needs services in its constructor, the service provider will need how to resolve them as long as they were registered in the Startup.

For example, update your repo to hold a static list of employees in memory:

public class EmployeeRepository : IRepository<Employee>
{
    private List<string> employees = new List<string>();
    public void Create(string employeeId)
    {
        this.employees.Add(employeeId);
    }
}

Then add a new employee every time your index action is called:

public IActionResult Index([FromServices]IRepository<Employee> repo)
{
    repo.Create($"client-{ Guid.NewGuid() }");
    return View();
}

You will see that no matter which of the 2 ways for registering the singleton you use, your controller will always get the same instance and the employees are added to the same list.

Bear also in mind another of @Tseng's comment, where he mentions that a singleton probably isn't the best idea for a repository. Usually it will depend on some DbContext which is instantiated per request. (Unless you create some form of DbContext provider and your repo depends on it, but that's off-topic)




回答3:


I had to fall back to the only working option for me which is what @Daniel J.G. suggested to use; the AddSingleton<T>() after having initialised the repository (along with some useful tips from him). Though, I would have liked to have the other option work after initialisation.



来源:https://stackoverflow.com/questions/39962402/asp-net-core-mvc-fromservice-binding-in-controller-returns-empty-collection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!