[ASP.NET] Web API kontroler który korzysta z singletona. Jak przechowywać zmeinne globalnie?

0

Hej, zastanawiam się nad tym, czy moje rozwiązanie jest poprawne. Piszę Web API które ma wykonywać cyklicznie pętle, które mają się uruchamiać w określonych porach dnia lub co jakiś czas. Użytkownik ma możliwość sterowania wykonywaniem pętli poprzez klienta, oraz monitorowania postępu wykonywania pętli, bazując na własnościach postępu.

Na chwilę obecną posiadam rozwiązanie, co do którego poprawności nie do końca jestem przekonany, stąd moje pytanie na tym forum.

Kontroler UpdatesController, po wysłaniu żądania przez klienta komunikuje się z serwisem TimedUpdating. W razie potrzeby, czyli żądania z klienta, kontroler też odpytuje serwis o postęp w wykonywaniu pętli, może też poprzez serwis wstrzymać, zatrzymać zupełnie, lub uruchomić wykonywanie pętli. Z racji przechowywania własności postępu serwis jest zarejestrowany jako singleton: services.AddSingleton<ITimedUpdating, TimedUpdating>();. Serwis też aktualizuje bazę danych. Jako że DB nie powinna być singletonem, jest bodajże Transient, to pojawia się konflikt, ponieważ EF Core nie pozwala na korzystanie z takiej zależności singletonowi.

Znalazłem tego obejście, poprzez wykorzystanie IServiceScopeFactory:

using (var scope = _scopeFactory.CreateScope())
            {
                IRepository _repo = scope.ServiceProvider.GetRequiredService<EFRepository>();
                //i tutaj już można korzystać z _repo.
            }

Nie do końca podoba mi się takie rozwiązanie. Jedyna alternatywa jaka przychodzi mi do głowy to korzystanie z TempData i zapisywanie/odczyt w obiekcie wszystkich danych postępu pętli. Wtedy serwis mógłby też pozostać Transient, ale tutaj chyba są potrzebne ciasteczka.

Może macie jakieś lepsze pomysły?

1

Jako że DB nie powinna być singletonem, jest bodajże Transient

A nie powinna być Scoped?

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddTransient<SampleDbContext, SampleDbContext>();
            services.AddSingleton<ITestService, TestService>();
        }
    public class TestService : ITestService
    {
        private readonly SampleDbContext _sampleDbContext;

        public TestService(SampleDbContext sampleDbContext)
        {
            _sampleDbContext = sampleDbContext;
        }
    }

Zrobiłem coś takiego. Działa.

Swoją drogą zgodnie z dokumentacją DbContext dodawany jest w taki sposób:

             public void ConfigureServices(IServiceCollection services)
             {
                 var connectionString = "connection string to database";
  
                 services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));
             }

Pozdrawiam

0
Krzysztof Pe napisał(a):

Jako że DB nie powinna być singletonem, jest bodajże Transient

A nie powinna być Scoped?

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddTransient<SampleDbContext, SampleDbContext>();
            services.AddSingleton<ITestService, TestService>();
        }
    public class TestService : ITestService
    {
        private readonly SampleDbContext _sampleDbContext;

        public TestService(SampleDbContext sampleDbContext)
        {
            _sampleDbContext = sampleDbContext;
        }
    }

Zrobiłem coś takiego. Działa.


Pozdrawiam

Może i działa ale czy jest to bezpieczne? Przecież instancja dbcontext powstanie tylko raz dla singletona. Jak to według Ciebie ma się do thread safe?

2

@bakunet
Ja bym na twoim miejscu to co ma się robić cyklicznie trzymał w jakimś HostedService. Ale tam się nie objedzie bez service providera. Plus do tego jakiś concurent dictionary lub wbudowany InMemory

0
szydlak napisał(a):

@bakunet
Ja bym na twoim miejscu to co ma się robić cyklicznie trzymał w jakimś HostedService. Ale tam się nie objedzie bez service providera. Plus do tego jakiś concurent dictionary lub wbudowany InMemory

Ostatecznie rozwiązanie było łatwiejsze niż mi się wydawało. Zrezygnowałem z TempData ponieważ nie chciałem się bawić z ciasteczkami w kliencie w WPF. Bo pewnie da się prztwarzać sesję, ale jeszcze nie wiem jak :) Poza tym sesja ma się nijak do RESTful, podobno.

Utworzyłem ProgressService, który jest jedynie odpytywany przez inne serwisy o dane postępu, oraz są one nadpisywane w wypadku każdej zmiany w serwisach zależnych. Jako że nie polega on już na _repo, to może sobie być singletonem bez większego problemu.

Cykliczne uruchamianie pętli faktycznie będę chciał wykonywać przy wykorzystaniu IHostedService, ponieważ zwykły Transient kończy swoje życie zbyt szybko :)

Jedyny problem z IHostedService jaki mam, to że wywala mi całą aplikację jak anuluję CancellationTokenSource (graceful shutdown po 5s). Jeszcze nie wiem jak sobie z tym poradzić.

Do końca nie rozumiem po co mi concurent dictionary lub wbudowany InMemory? Choć domyślam się, że tu chodzi o ew dostęp do bazy z singletona?

1
bakunet napisał(a):

Z racji przechowywania własności postępu serwis jest zarejestrowany jako singleton: services.AddSingleton<ITimedUpdating, TimedUpdating>();. Serwis też aktualizuje bazę danych. Jako że DB nie powinna być singletonem, jest bodajże Transient, to pojawia się konflikt, ponieważ EF Core nie pozwala na korzystanie z takiej zależności singletonowi.

Serwis wcale nie musi być zarejestrowany jako singleton. Kolekcję zadań jakie wykonuje można np. przechowywać w statycznym zasobie, który będzie współdzielony przez wszystkie jego instancje. Trzeba tylko pamiętać o potrzebie użycia mechanizmów synchronizacji wątków.

1 użytkowników online, w tym zalogowanych: 0, gości: 1