#c# #dependency-injection #asp.net-core #singleton #asp.net-core-mvc
#c# #внедрение зависимостей #asp.net-core #синглтон #asp.net-core-mvc
Вопрос:
Пытался разобраться ASP.NET Core MVC, но застрял с внедрением зависимостей, возвращающим пустую коллекцию в моем действии контроллера, несмотря на регистрацию сервиса как одноэлементного в ConfigureService
методе. Пробовал два разных способа регистрации, но на самом деле работает только один из них. Указанный метод выглядит следующим образом:
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 );
}
И ниже приведено мое действие контроллера:
[HttpGet( "{empID}" )]
public string GetEmployee( string empID, [FromServices] IRepository<Employee> employees **/*THIS IS ALWAYS EMPTY*/**)
{
....
}
Пожалуйста, укажите мне правильное направление. В чем разница между этими двумя AddSingleton
методами? Как я могу получить доступ к сервису, используя 1-й подход с помощью DI в действии контроллера? TIA.
Ответ №1:
Как реализован ваш репозиторий? Я полагаю, вы вводите в нее DbContext? Тогда вы не сможете использовать new EmployeeRepository()
ее для создания экземпляра и не сможете использовать singleton. DbContext
разрешается для каждой области, и поэтому все службы, которые ее используют, должны это делать.
Также никогда не вызывайте services.BuildServiceProvider()
внутри ConfigureServices
, потому что перед вызовом Configure
ASP.NET Webstack сделает это в любом случае и создаст нового провайдера, не связанного с тем, который вы создали ранее.
В-третьих, никогда не разрешайте службы с ограниченной областью действия через область приложения (при вызове app.ApplicationServices.GetService<T>()
это может вызвать проблемы с жизненным циклом вашей службы. Если вы разрешили службу с ограниченной областью (т. Е. Заполнение данных) во время запуска приложения, создайте новую область, разрешите из нее, а затем удалите ее после завершения.
Комментарии:
1. Единственная причина, по которой я позвонил
services.BuildServiceProvider()
, — это инициализация определенной службы с некоторыми фиктивными данными, чтобы она присутствовала при вызове действия контроллера.DbContext
Пока во всем проекте ничего не задействовано.
Ответ №2:
Поскольку в вашем действии контроллера вам требуется экземпляр IRepository<Employee>
, вы можете использовать любой из этих 2 методов, если регистрируемая служба относится к этому типу (в вашем втором подходе вы регистрировали ее как EmployeeRepository
):
services.AddSingleton<IRepository<Employee>, EmployeeRepository>();
services.AddSingleton<IRepository<Employee>>(new EmployeeRepository());
Как вы можете прочитать в документации, разница между этими 2 вариантами заключается только в том, где будет создан экземпляр.
- При использовании первого варианта экземпляр будет создан в первый раз, когда сервис необходимо внедрить в какой-либо класс.
- При втором варианте экземпляр создается вами вручную в
ConfigureServices
.
Какой из них вы должны использовать, зависит от ваших потребностей.
Что касается того, почему вы получаете его как null, в вашем проекте должно быть что-то еще, проверьте наличие ошибок в консоли или попытайтесь воспроизвести проблему в новом минимальном проекте. Я попытался воспроизвести ее, но оба метода регистрации работали нормально:
- Новое веб-приложение 1.0.1
-
Затем добавлены эти типы:
public class Employee { } public interface IRepository<T> { } public class EmployeeRepository: IRepository<Employee> { }
-
Обновлено действие Index в HomeController для получения экземпляра с использованием
[FromServices]
as в:public IActionResult Index([FromServices]IRepository<Employee> emp)
Редактировать
Как упоминал @Tseng, вы не должны вызывать BuildServiceProvider
метод запуска:
- Это означает, что вы создаете своего собственного поставщика услуг, полностью отделенного от того, который ASP.Net Ядро будет создавать.
- Это также означает, что экземпляр, который вы получите в методе запуска, отличается от того, который получит ваш контроллер (поскольку у каждого поставщика услуг будет свой собственный синглтон)
Либо оставьте фактическое создание экземпляра поставщику услуг, либо используйте второй AddSingleton
метод, если вам нужно создать экземпляр самостоятельно.Не волнуйтесь, если вашему классу также нужны службы в его конструкторе, поставщику услуг потребуется, как их разрешить, если они были зарегистрированы при запуске.
Например, обновите свой репозиторий, чтобы хранить статический список сотрудников в памяти:
public class EmployeeRepository : IRepository<Employee>
{
private List<string> employees = new List<string>();
public void Create(string employeeId)
{
this.employees.Add(employeeId);
}
}
Затем добавляйте нового сотрудника при каждом вызове вашего индексного действия:
public IActionResult Index([FromServices]IRepository<Employee> repo)
{
repo.Create($"client-{ Guid.NewGuid() }");
return View();
}
Вы увидите, что независимо от того, какой из 2 способов регистрации синглтона вы используете, ваш контроллер всегда будет получать один и тот же экземпляр, а сотрудники добавляются в один и тот же список.
Также имейте в виду другой комментарий @Tseng, где он упоминает, что синглтон, вероятно, не лучшая идея для репозитория. Обычно это будет зависеть от некоторого DbContext, который создается для каждого запроса. (Если вы не создаете какую-либо форму поставщика DbContext, и от этого зависит ваше репозиторий, но это не по теме)
Комментарии:
1. Спасибо, что подчеркнули разницу между ними. Когда я использую первый вариант, я не получаю null , вместо этого я получаю инициализированный, но пустой репозиторий, несмотря на выполнение следующего кода после инициализации с помощью первого варианта.
var empRepo = services.BuildServiceProvider().GetService<IRepository<Employee>>(); empRepo.Create( "emp001" );
2. Добавлены некоторые пояснения
3. J. G. Спасибо за информацию о DbContext, и им не рекомендуется быть одноэлементными. Рассмотрим это далее, где мне нужно будет хранить данные в БД. Для существующей проблемы я использую
AddSingleton<T>()
подход к ее решению.
Ответ №3:
Мне пришлось вернуться к единственному рабочему варианту для меня, который предложил использовать @Daniel J.G.; AddSingleton<T>()
после инициализации репозитория (вместе с некоторыми полезными советами от него). Хотя мне бы хотелось, чтобы после инициализации работал другой вариант.