#c# #asp.net-core #.net-core
#c# #asp.net-ядро #.net-ядро
Вопрос:
В моем Asp.Net Основное приложение Мне нужен одноэлементный сервис, который я мог бы повторно использовать в течение всего срока службы приложения. Для его создания мне нужен DbContext
(из ядра EF), но это служба с ограниченной областью действия, а не потокобезопасная.
Поэтому я использую следующий шаблон для создания моей одноэлементной службы. Это выглядит довольно банально, поэтому мне было интересно, является ли это приемлемым подходом и не приведет ли к каким-либо проблемам?
services.AddScoped<IPersistedConfigurationDbContext, PersistedConfigurationDbContext>();
services.AddSingleton<IPersistedConfigurationService>(s =>
{
ConfigModel currentConfig;
using (var scope = s.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<IPersistedConfigurationDbContext>();
currentConfig = dbContext.retrieveConfig();
}
return new PersistedConfigurationService(currentConfig);
});
...
public class ConfigModel
{
string configParam { get; set; }
}
Комментарии:
1. Есть много дубликатов, которые показывают, как это сделать. То же самое относится и к документации для фоновых служб.
PersistedConfigurationService
IServiceProvider
в его кострукторе должен быть параметр. При необходимости оно должно создавать область видимости.2. A
ServiceProvider
— это не что иное, как «корневая» область; поэтому вы можете просто использовать его напрямую (т.Е. Без необходимости создания области).3. Проверьте использование службы с ограниченной областью действия в фоновой задаче . Это показывает, что
IServiceProvider
вводится в конструктор одноэлементной службы. Метод, которому действительно нужна служба с ограниченной областью,DoWork
, создает область и запрашивает службу только тогда, когда это действительно необходимо.4. @PanagiotisKanavos, спасибо, что поделились ссылкой с примером. Оставшийся у меня вопрос заключается в том, хорош ли тогда мой подход, поскольку он делает то же самое, что и ваш, но в другом месте (когда происходит создание сервисов, а не внутри сервиса)
5. @TanvirArjel Да, это верно. Легко неправильно истолковать это и подумать, что у нас есть синглтон, пытающийся использовать службу с ограниченной областью действия, но это не так.
Ответ №1:
То, что вы делаете, нехорошо и определенно может привести к проблемам. Поскольку это делается при регистрации службы, служба с ограниченной областью действия будет извлекаться один раз при первом введении вашего синглтона. Другими словами, этот код здесь будет выполняться только один раз за время существования регистрируемой вами службы, что, поскольку это одноэлементный код, означает, что это произойдет только один раз, точка. Кроме того, контекст, который вы вводите здесь, существует только в созданной вами области, которая исчезает, как только оператор using закрывается. Таким образом, к тому времени, когда вы на самом деле попытаетесь использовать контекст в своем синглтоне, он будет удален, и вы получите ObjectDisposedException
.
Если вам нужно использовать службу с ограниченной областью действия внутри одноэлементного элемента, то вам нужно внедрить IServiceProvider
в одноэлементный элемент. Затем вам нужно создать область видимости и извлекать свой контекст, когда вам нужно его использовать, и это нужно будет делать каждый раз, когда вам нужно его использовать. Например:
public class PersistedConfigurationService : IPersistedConfigurationService
{
private readonly IServiceProvider _serviceProvider;
public PersistedConfigurationService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task Foo()
{
using (var scope = _serviceProvider.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<IPersistedConfigurationDbContext>();
// do something with context
}
}
}
Просто чтобы подчеркнуть, еще раз, вам нужно будет сделать это в каждом методе, который должен использовать службу с ограниченной областью действия (ваш контекст). Вы не можете сохранить это в ivar или что-то в этом роде. Если вас отпугивает код, так и должно быть, поскольку это антипаттерн. Если вы должны получить службу с ограниченной областью действия в одноэлементном, у вас нет выбора, но чаще всего это признак плохого дизайна. Если службе необходимо использовать службы с ограниченной областью действия, она почти всегда должна быть сама по себе, а не одноэлементная. Существует всего несколько случаев, когда вам действительно нужно время жизни одного элемента, и они в основном связаны с семафорами или другим состоянием, которое должно сохраняться на протяжении всего срока службы приложения. Если нет очень веской причины сделать ваш сервис одноэлементным, вы должны выбрать scoped во всех случаях; scoped должно быть время жизни по умолчанию, если у вас нет причин поступать иначе.
Комментарии:
1. Спасибо за такой подробный ответ, но я думаю, вы неправильно поняли мой вопрос. Приношу свои извинения за то, что не был ясен. Я обновил вопрос частями, которые, как я думал, неявно понятны.
2. И я считаю, что ваше предложение в основном совпадает с тем, что у меня есть в моем вопросе 🙂
3. Нет. Я не верю, что я вообще неправильно понял. Если вам нужно получить доступ к службе с ограниченной областью действия в одноэлементном, метод в моем ответе выше — единственный способ сделать это . Код, который у вас есть, в корне поврежден и вообще не будет работать. Итак, на самом деле вопрос даже не в том, является ли это «приемлемым» или нет, это вообще не сработает.
4. Я не передаю службу с ограниченной областью действия в качестве аргумента в конструкторе синглтона. Я создаю экземпляр службы с ограниченной областью действия, использую его для извлечения некоторых данных, затем удаляю его и больше никогда не вижу объект с ограниченной областью действия.
5. Итак
retrieveConfig
, возвращает статический объект, который никогда не меняется?
Ответ №2:
Хотя внедрение зависимостей: документация по срокам службы в ASP.NET Ядро говорит:
Опасно разрешать службу с ограниченной областью действия из синглтона. Это может привести к неправильному состоянию службы при обработке последующих запросов.
Но в вашем случае это не проблема. На самом деле вы не разрешаете службу с ограниченной областью из singleton. Он просто получает экземпляр службы с ограниченной областью действия из singleton всякий раз, когда это требуется. Таким образом, ваш код должен работать правильно, без каких-либо ошибок в удаленном контексте!
Но другим потенциальным решением может быть использование IHostedService
. Вот подробная информация об этом:
Использование службы с ограниченной областью действия в фоновой задаче (IHostedService)
Комментарии:
1. В документации также показано, как это сделать правильно
2. @PanagiotisKanavos Ладно, понял! Вы о чем говорите
IHostedService
?3. IHostedService — это всего лишь пример одноэлементного сервиса.
4. @PanagiotisKanavos Да! Я улучшаю свой ответ. Еще раз спасибо.
Ответ №3:
Глядя на название этой службы — я думаю, что вам нужен пользовательский поставщик конфигурации, который загружает конфигурацию из базы данных при запуске (только один раз). Почему бы вам вместо этого не сделать что-нибудь вроде следования? Это лучший дизайн, более совместимый с фреймворком подход, а также то, что вы можете создать как общую библиотеку, от которой другие люди также могут извлечь выгоду (или вы можете извлечь выгоду из нескольких проектов).
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
var persistentConfigBuilder = new ConfigurationBuilder();
var connectionString = builtConfig["ConnectionString"];
persistentStorageBuilder.AddPersistentConfig(connectionString);
var persistentConfig = persistentConfigBuilder.Build();
config.AddConfiguration(persistentConfig);
});
}
Здесь — AddPersistentConfig
это метод расширения, построенный в виде библиотеки, которая выглядит следующим образом.
public static class ConfigurationBuilderExtensions
{
public static IConfigurationBuilder AddPersistentConfig(this IConfigurationBuilder configurationBuilder, string connectionString)
{
return configurationBuilder.Add(new PersistentConfigurationSource(connectionString));
}
}
class PersistentConfigurationSource : IConfigurationSource
{
public string ConnectionString { get; set; }
public PersistentConfigurationSource(string connectionString)
{
ConnectionString = connectionString;
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new PersistentConfigurationProvider(new DbContext(ConnectionString));
}
}
class PersistentConfigurationProvider : ConfigurationProvider
{
private readonly DbContext _context;
public PersistentConfigurationProvider(DbContext context)
{
_context = context;
}
public override void Load()
{
// Using _dbContext
// Load Configuration as valuesFromDb
// Set Data
// Data = valuesFromDb.ToDictionary<string, string>...
}
}