Использование службы Scoped в Singleton в приложении Asp.Net Core - PullRequest
0 голосов
/ 16 апреля 2019

В моем приложении Asp.Net Core мне нужен одноэлементный сервис, который я могу использовать повторно в течение всего срока службы приложения. Для его создания мне нужен DbContext (из EF Core), но это сервис с ограниченным доступом, а не потокобезопасный.

Поэтому я использую следующий шаблон для создания моего одноэлементного сервиса. Это выглядит довольно странно, поэтому мне было интересно, является ли это приемлемым подходом и не приведет ли он к каким-либо проблемам?

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; }
}

Ответы [ 3 ]

0 голосов
/ 16 апреля 2019

То, что вы делаете, нехорошо и может определенно привести к проблемам. Поскольку это делается при регистрации службы, служба с заданной областью будет получать один раз при первом введении вашего синглтона. Другими словами, этот код здесь будет запускаться только один раз за время существования службы, которую вы регистрируете, что означает, что это будет происходить только один раз, точка. Кроме того, контекст, который вы здесь вводите, существует только в созданной вами области, которая исчезает, как только оператор 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
        }
    }
}

Просто чтобы подчеркнуть, опять же, вам нужно будет делать это в каждом методе, который должен использовать сервис с определенными областями (ваш контекст). Вы не можете сохранить это до ивара или чего-то еще. Если вы отстранены от кода, вы должны быть, так как это антипаттерн. Если вы должны получить услугу с заданной областью действия в одиночном разряде, у вас нет выбора, но чаще всего это признак плохого дизайна. Если сервису необходимо использовать сервисы с определенной областью, он почти всегда должен быть сам по себе, а не синглтоном. Есть только несколько случаев, когда вам действительно нужно время жизни синглтона, и те в основном вращаются вокруг работы с семафорами или другим состоянием, которое должно сохраняться в течение всего жизненного цикла приложения. Если у вас нет очень веских причин для того, чтобы сделать ваш сервис одноразовым, вы должны выбрать область действия во всех случаях; scoped должен быть временем жизни по умолчанию, если у вас нет причин поступать иначе.

0 голосов
/ 23 апреля 2019

Глядя на название этой службы, я думаю, что вам нужен пользовательский поставщик конфигурации, который загружает конфигурацию из базы данных при запуске (только один раз).Почему бы тебе не сделать что-то вроде следующегоЭто лучший дизайн, больше подход, совместимый с фреймворком, а также то, что вы можете создать как общую библиотеку, в которой другие люди также могут извлечь выгоду (или вы можете извлечь выгоду из нескольких проектов).


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>...
    }

}
0 голосов
/ 16 апреля 2019

Хотя Внедрение зависимостей: время жизни службы Документация в ASP.NET Core гласит:

Разрешать выделенную службу из одного объекта опасно.Это может привести к неправильному состоянию службы при обработке последующих запросов.

Но в вашем случае это не проблема.На самом деле вы не разрешаете сервис с определенными областями из синглтона.Это просто получение экземпляра сервиса с ограниченным доступом из синглтона, когда это требуется. Таким образом, ваш код должен работать правильно без какой-либо ошибки удаленного контекста!

Но другое потенциальное решение может использовать IHostedService.Вот подробности об этом:

Использование службы с заданной областью в фоновом режиме (IHostedService)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...