{"id":17078525,"url":"https://github.com/dazinator/dazinator.responsivecore","last_synced_at":"2025-09-12T19:33:12.696Z","repository":{"id":56436338,"uuid":"301742053","full_name":"dazinator/Dazinator.ResponsiveCore","owner":"dazinator","description":"Enable your .net applications to be more responsive to changes at runtime, so you don't have to restart the application in order for changes to take effect. It does this whilst trying to be as minimally invasive in your code base as possible.","archived":false,"fork":false,"pushed_at":"2021-11-11T15:51:24.000Z","size":200,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"develop","last_synced_at":"2024-04-13T21:44:35.075Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dazinator.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-06T13:48:22.000Z","updated_at":"2024-03-14T06:43:48.000Z","dependencies_parsed_at":"2022-08-15T18:40:41.373Z","dependency_job_id":null,"html_url":"https://github.com/dazinator/Dazinator.ResponsiveCore","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dazinator%2FDazinator.ResponsiveCore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dazinator%2FDazinator.ResponsiveCore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dazinator%2FDazinator.ResponsiveCore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dazinator%2FDazinator.ResponsiveCore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dazinator","download_url":"https://codeload.github.com/dazinator/Dazinator.ResponsiveCore/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239971385,"owners_count":19727153,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-14T12:22:29.048Z","updated_at":"2025-02-22T03:30:32.360Z","avatar_url":"https://github.com/dazinator.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Features\n\n`ResponsiveCore` provides features to enable your .net core applications to be more responsive to changes at runtime, so you don't have to restart the application in order for changes to take effect. \nIt does this whilst trying to be as minimally invasive in your code base as possible.\n\nFeatures:\n\n- Middleware: Define middleware pipelines that can be dynamically reloaded / rebuilt from latest config at runtime, without dropping requests.\n- Background Services: Allow your existing `IHostedService`s to be dynamically started and stopped based on your own logic to determine whether they should be currently enabled or not.\n\n[![Build Status](https://darrelltunnell.visualstudio.com/Public%20Projects/_apis/build/status/dazinator.Dazinator.ResponsiveCore?branchName=develop)](https://darrelltunnell.visualstudio.com/Public%20Projects/_build/latest?definitionId=16\u0026branchName=develop)\n\nThanks:\n\n- [Changify](https://github.com/dazinator/Changify) - easily build composite change token producers.\n\n## Reloadable Middleware Pipelines\n\n1. Add the `Dazinator.ResponsiveCore.ReloadablePipeline` nuget package to your project.\n2. Configure an `Options` class, and then build a middleware pipeline that from it that will be rebuilt whenever `IOptionsMonitor` detects a change:\n \n```csharp\n\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940\n        public void ConfigureServices(IServiceCollection services)\n        {\n            var configSection = Configuration.GetSection(\"Pipeline\");\n            services.Configure\u003cPipelineOptions\u003e(configSection);\n        }\n\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            // Note: Use vs Run (latter is terminal, former is not)\n            // make a change to appsettings.json \"Pipelines\" section and watch log output in console on furture requests.\n             app.UseReloadablePipeline((options) =\u003e\n            {\n                var monitor = app.ApplicationServices.GetRequiredService\u003cIOptionsMonitor\u003cPipelineOptions\u003e\u003e();\n                var tokenProducer = new ChangeTokenProducerBuilder() // I am choosing to use the Changify library to build a Func\u003cIChangeToken\u003e as is easier - you don't have to.\n                                  .IncludeSubscribingHandlerTrigger((trigger) =\u003e monitor.OnChange((o, n) =\u003e trigger()))\n                                  .Build(out var disposable);\n\n                options.RespondsTo(tokenProducer, disposable)\n                       .WithPipelineRebuild((app) =\u003e\n                       {\n                           ConfigureReloadablePipeline(app, env, monitor.CurrentValue);\n                       });\n            });      \n\n            app.UseWelcomePage();\n        }\n\n        private void ConfigureReloadablePipeline(IApplicationBuilder appBuilder, IWebHostEnvironment environment, PipelineOptions options)\n        {\n            var logger = appBuilder.ApplicationServices.GetRequiredService\u003cILogger\u003cStartup\u003e\u003e();\n            logger.LogInformation(\"Building reloadable pipeline from current options!\");\n\n            if (options.UseDeveloperExceptionPage)\n            {\n                appBuilder.Use(async (context, onNext) =\u003e\n                {\n                    var logger = context.RequestServices.GetRequiredService\u003cILogger\u003cStartup\u003e\u003e();\n                    logger.LogInformation(\"Using dev middleware!\");\n                    await onNext();\n                });\n            }\n            else\n            {\n                appBuilder.Use(async (context, onNext) =\u003e\n                {\n                    var logger = context.RequestServices.GetRequiredService\u003cILogger\u003cStartup\u003e\u003e();\n                    logger.LogInformation(\"Not using dev middleware..\");\n                    await onNext();\n                });\n            }\n        }\n    }\n\n\n```\n\n### How do I signal the pipeline to rebuild for other sorts of changes - for example a button click?\n\nYou can supply a `Func\u003cIChangeToken\u003e` to the `RespondsTo()` api.\nWhenever a change token signalled it will trigger a pipeline rebuild to occur as per the `WithPipelineRebuild` delegate.\n\n### Rebuild Strategies\n\nThe default strategy for pipeline rebuilds, is to rebuild it within a lock at the point its invalidated. \nOnce the new pipeline has been build, the new instance is swapped in for the old instance (no locking), and new requests are now pushed through the new instance of the pipeline instead of the old.\nThis strategy means there is no downtime for requests - requests continue to be processed through the old pipeline until the new one is swapped in without any lock delaying requests.\nAnother optional strategy that is provided out of the box, allows instead for the pipeline to be lazily re-built in line with the next request. This means other requests that occur whilst a rebuild is in progress may queue behind the lock until the rebuild completes, and for this reason - its not the default strategy - however it might prove useful if for example you'd like access to a current httpcontext (request) during the pipeline build itself - which is ordinarly not achievable.\n\nTo override the default strategy, set the `RebuildStrategy` on the `options` to an instance of `IRebuildStrategy`.\n\n\n```csharp\n  app.UseReloadablePipeline((options) =\u003e\n            {\n              options.RebuildStragety = new RebuildOnDemandStrategy();\n              ...\n```\n\n## Responsive Hosted Services\n\nSuppose you have an `IHostedService` that you want to be able to stop and start at runtime without restarting the application.\n\n1. Add the `Dazinator.ResponsiveCore.ResponsiveHostedService` package to your project.\n\n2. Create an `Options` class that you can then bind to config to represent whether the service is enabled or disabled.\n\n```csharp\n    public class HostedServiceOptions\n    {\n        public bool Enabled { get; set; }\n    }\n```\n\nand\n\n```csharp\n    services.Configure\u003cHostedServiceOptions\u003e(Configuration.GetSection(\"HostedService\"));\n```\n\n3. Replace your call to `services.AddHostedService` with  `AddResponsiveHostedService`:\n\n```csharp\n     services.AddResponsiveHostedService\u003cHostedService\u003e(o =\u003e\n            {\n                var monitor = o.Services.GetRequiredService\u003cIOptionsMonitor\u003cHostedServiceOptions\u003e\u003e();\n                var tokenProducer = new ChangeTokenProducerBuilder()\n                                   .IncludeSubscribingHandlerTrigger((trigger) =\u003e monitor.OnChange((o, n) =\u003e trigger()))\n                                   .Build(out var disposable);\n\n                o.RespondsTo(tokenProducer, disposable)\n                 .ShouldBeRunning(() =\u003e monitor.CurrentValue?.Enabled ?? false);\n\n            });\n\n```\n\nNote: This is very similar to reloadable pipelines, we configure a change token producer and a delegate to run when a change token is signalled.\nIn this case the result of that delegate indicates whether the background service should now be running or not. Based on that it will either start or stop or do nothing if it's already in the desired state.\n\nI am using the `Changify` library in the sample and in the tests to build the token producer, but you can provide a `Func\u003cIChangeToken\u003e` however you see fit.\n\n4. Start your application with that config setting as `false`. Your service will not start. Whilst the application is running, change the config to be `true` and save that change. The change is detected and your service will now start. \nAgain, whilst your application is running, change the config back to `false` - your service will be stopped. \nRepeat this ad infinitum - if you have the time, all the while basking in the glory of this responsivity.\n\n### Alternatively, express service requirements\n\nRather than passing in a single `Func` to be run via the `WithShouldBeRunningCheck` API, which dictates whether your service should now be running or not,\nyou can optionally used the `IRequirement` based api.\n\nSo rather than this:\n\n```\n  o.RespondsTo(tokenProducer, disposable)\n   .ShouldBeRunning(() =\u003e monitor.CurrentValue?.Enabled ?? false);\n```\n\nYou can include multiple requirements / conditions like this:\n\n```\n o.RespondsTo(tokenProducer, disposable)\n  .Requires((b) =\u003e\n           {\n               // if any return false, the service will not start (or be stopped if its running).\n               b.IncludeFunc(cancelToken =\u003e true)\n                .Include\u003cMyRequirement\u003e();\n                .Include\u003cMyOtherRequirement\u003e(sp=\u003enew MyOtherRequirement());                                   \n           });\n\n```\n\nThis makes things a little more modular.\n\nYou can implement a requirement for use with this API as per the following. It will be instantiated and injected with any dependencies.\nIt does not need to be registered for DI.\n\n```csharp\npublic class MyRequirement : IRequirement        \n{\n        private readonly IFoo _someServiceYouNeed;\n\n        public StatusServiceRequirement(IFoo someServiceYouNeed)\n        {\n            _someServiceYouNeed = someServiceYouNeed;\n        }\n\n        public async Task\u003cbool\u003e IsSatisfied(CancellationToken cancellationToken)\n        {        \n            return await _someServiceYouNeed.IsUpAndRunningAsync();            \n        }\n}\n```\n\nYou should take care to handle exceptions within the `IsSatisfied` method, and return true or false.      ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdazinator%2Fdazinator.responsivecore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdazinator%2Fdazinator.responsivecore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdazinator%2Fdazinator.responsivecore/lists"}