├── SpawnDev.BackgroundServices.Demo
├── Pages
│ ├── Home.razor
│ ├── Counter.razor
│ └── Weather.razor
├── wwwroot
│ ├── favicon.png
│ ├── icon-192.png
│ ├── sample-data
│ │ └── weather.json
│ ├── index.html
│ └── css
│ │ └── app.css
├── Services
│ └── MyAutoStartingService.cs
├── Layout
│ ├── MainLayout.razor
│ ├── NavMenu.razor
│ ├── MainLayout.razor.css
│ └── NavMenu.razor.css
├── _Imports.razor
├── App.razor
├── SpawnDev.BackgroundServices.Demo.csproj
├── Program.cs
└── Properties
│ └── launchSettings.json
├── SpawnDev.BackgroundServices
├── Images
│ └── icon-128.png
├── IBackgroundService.cs
├── IGlobalScopeSource.cs
├── IAsyncBackgroundService.cs
├── IAsyncService.cs
├── AsyncGlobalScopeSource.cs
├── SpawnDev.BackgroundServices.csproj
├── IBackgroundServiceManager.cs
├── GlobalScope.cs
├── ServiceInformation.cs
├── BackgroundServiceManager.cs
└── IServiceCollectionExtensions.cs
├── README.md
├── LICENSE.txt
├── SpawnDev.BackgroundServices.sln
├── .gitattributes
└── .gitignore
/SpawnDev.BackgroundServices.Demo/Pages/Home.razor:
--------------------------------------------------------------------------------
1 | @page "/"
2 |
3 | Home
4 |
5 |
Hello, world!
6 |
7 | Welcome to your new app.
8 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/Images/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BackgroundServices/master/SpawnDev.BackgroundServices/Images/icon-128.png
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/wwwroot/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BackgroundServices/master/SpawnDev.BackgroundServices.Demo/wwwroot/favicon.png
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/wwwroot/icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BackgroundServices/master/SpawnDev.BackgroundServices.Demo/wwwroot/icon-192.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SpawnDev.BackgroundServices
2 | [](https://www.nuget.org/packages/SpawnDev.BackgroundServices)
3 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/IBackgroundService.cs:
--------------------------------------------------------------------------------
1 | namespace SpawnDev
2 | {
3 | ///
4 | /// IBackgroundServices are started at app startup by default
5 | /// or started based on GlobalScope settings when registered and the current global scope
6 | ///
7 | public interface IBackgroundService { }
8 | }
9 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/Services/MyAutoStartingService.cs:
--------------------------------------------------------------------------------
1 | namespace SpawnDev.BackgroundServices.Demo.Services
2 | {
3 | public class MyAutoStartingService : IBackgroundService
4 | {
5 | public MyAutoStartingService()
6 | {
7 | Console.WriteLine("MyAutoStartingService()");
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/IGlobalScopeSource.cs:
--------------------------------------------------------------------------------
1 | namespace SpawnDev
2 | {
3 | ///
4 | /// Scope provider
5 | ///
6 | public interface IGlobalScopeSource
7 | {
8 | ///
9 | /// Scope source method
10 | ///
11 | Task GetGlobalScope();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/IAsyncBackgroundService.cs:
--------------------------------------------------------------------------------
1 | namespace SpawnDev
2 | {
3 | ///
4 | /// IAsyncBackgroundServices are IAsyncServices started at app startup by default
5 | /// or started based on GlobalScope settings when registered and the current global scope
6 | ///
7 | public interface IAsyncBackgroundService : IBackgroundService, IAsyncService { }
8 | }
9 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/Pages/Counter.razor:
--------------------------------------------------------------------------------
1 | @page "/counter"
2 |
3 | Counter
4 |
5 | Counter
6 |
7 | Current count: @currentCount
8 |
9 | Click me
10 |
11 | @code {
12 | private int currentCount = 0;
13 |
14 | private void IncrementCount()
15 | {
16 | currentCount++;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/Layout/MainLayout.razor:
--------------------------------------------------------------------------------
1 | @inherits LayoutComponentBase
2 |
3 |
6 |
7 |
8 |
11 |
12 |
13 | @Body
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/IAsyncService.cs:
--------------------------------------------------------------------------------
1 | namespace SpawnDev
2 | {
3 | ///
4 | /// IAsyncServices have a Ready Task property that completes successfully when the service is ready for use.
5 | ///
6 | public interface IAsyncService
7 | {
8 | ///
9 | /// Completes successfully when asynchronous initialization has completed
10 | ///
11 | Task Ready { get; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using System.Net.Http
2 | @using System.Net.Http.Json
3 | @using Microsoft.AspNetCore.Components.Forms
4 | @using Microsoft.AspNetCore.Components.Routing
5 | @using Microsoft.AspNetCore.Components.Web
6 | @using Microsoft.AspNetCore.Components.Web.Virtualization
7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http
8 | @using Microsoft.JSInterop
9 | @using SpawnDev.BackgroundServices.Demo
10 | @using SpawnDev.BackgroundServices.Demo.Layout
11 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/App.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Not found
8 |
9 | Sorry, there's nothing at this address.
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/wwwroot/sample-data/weather.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "date": "2022-01-06",
4 | "temperatureC": 1,
5 | "summary": "Freezing"
6 | },
7 | {
8 | "date": "2022-01-07",
9 | "temperatureC": 14,
10 | "summary": "Bracing"
11 | },
12 | {
13 | "date": "2022-01-08",
14 | "temperatureC": -13,
15 | "summary": "Freezing"
16 | },
17 | {
18 | "date": "2022-01-09",
19 | "temperatureC": -16,
20 | "summary": "Balmy"
21 | },
22 | {
23 | "date": "2022-01-10",
24 | "temperatureC": -2,
25 | "summary": "Chilly"
26 | }
27 | ]
28 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/SpawnDev.BackgroundServices.Demo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components.Web;
2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
3 | using SpawnDev;
4 | using SpawnDev.BackgroundServices.Demo;
5 | using SpawnDev.BackgroundServices.Demo.Services;
6 |
7 | var builder = WebAssemblyHostBuilder.CreateDefault(args);
8 |
9 | builder.Services.AddBackgroundServiceManager();
10 | builder.Services.AddSingleton(GlobalScope.Window);
11 |
12 | builder.RootComponents.Add("#app");
13 | builder.RootComponents.Add("head::after");
14 |
15 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
16 | var host = builder.Build();
17 |
18 | // start background services. specify the current globalScope to only auto-start services set for specific scopes
19 | await host.Services.StartBackgroundServices();
20 |
21 | await host.RunAsync();
22 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/wwwroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | SpawnDev.BackgroundServices.Demo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | An unhandled error has occurred.
26 |
Reload
27 |
🗙
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [year] [fullname]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/Layout/NavMenu.razor:
--------------------------------------------------------------------------------
1 |
9 |
10 |
29 |
30 | @code {
31 | private bool collapseNavMenu = true;
32 |
33 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
34 |
35 | private void ToggleNavMenu()
36 | {
37 | collapseNavMenu = !collapseNavMenu;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:52147",
8 | "sslPort": 44320
9 | }
10 | },
11 | "profiles": {
12 | "http": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
17 | "applicationUrl": "http://localhost:5076",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | },
22 | "https": {
23 | "commandName": "Project",
24 | "dotnetRunMessages": true,
25 | "launchBrowser": true,
26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
27 | "applicationUrl": "https://localhost:7290;http://localhost:5076",
28 | "environmentVariables": {
29 | "ASPNETCORE_ENVIRONMENT": "Development"
30 | }
31 | },
32 | "IIS Express": {
33 | "commandName": "IISExpress",
34 | "launchBrowser": true,
35 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
36 | "environmentVariables": {
37 | "ASPNETCORE_ENVIRONMENT": "Development"
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/Pages/Weather.razor:
--------------------------------------------------------------------------------
1 | @page "/weather"
2 | @inject HttpClient Http
3 |
4 | Weather
5 |
6 | Weather
7 |
8 | This component demonstrates fetching data from the server.
9 |
10 | @if (forecasts == null)
11 | {
12 | Loading...
13 | }
14 | else
15 | {
16 |
17 |
18 |
19 | Date
20 | Temp. (C)
21 | Temp. (F)
22 | Summary
23 |
24 |
25 |
26 | @foreach (var forecast in forecasts)
27 | {
28 |
29 | @forecast.Date.ToShortDateString()
30 | @forecast.TemperatureC
31 | @forecast.TemperatureF
32 | @forecast.Summary
33 |
34 | }
35 |
36 |
37 | }
38 |
39 | @code {
40 | private WeatherForecast[]? forecasts;
41 |
42 | protected override async Task OnInitializedAsync()
43 | {
44 | forecasts = await Http.GetFromJsonAsync("sample-data/weather.json");
45 | }
46 |
47 | public class WeatherForecast
48 | {
49 | public DateOnly Date { get; set; }
50 |
51 | public int TemperatureC { get; set; }
52 |
53 | public string? Summary { get; set; }
54 |
55 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/AsyncGlobalScopeSource.cs:
--------------------------------------------------------------------------------
1 | namespace SpawnDev
2 | {
3 | ///
4 | /// Scope provider from a method
5 | ///
6 | public class GlobalScopeSource : IGlobalScopeSource
7 | {
8 | ///
9 | /// Scope source method
10 | ///
11 | private Func> ScopeSource { get; set; }
12 | ///
13 | /// Get the global scope from the current source
14 | ///
15 | ///
16 | public Task GetGlobalScope() => ScopeSource();
17 | ///
18 | /// New instance
19 | ///
20 | ///
21 | public GlobalScopeSource(Func> source)
22 | {
23 | ScopeSource = source;
24 | }
25 | ///
26 | /// New instance
27 | ///
28 | ///
29 | public GlobalScopeSource(Task source)
30 | {
31 | ScopeSource = () => source;
32 | }
33 | ///
34 | /// New instance
35 | ///
36 | ///
37 | public GlobalScopeSource(Func source)
38 | {
39 | ScopeSource = () => Task.FromResult(source());
40 | }
41 | ///
42 | /// New instance
43 | ///
44 | ///
45 | public GlobalScopeSource(GlobalScope source)
46 | {
47 | ScopeSource = () => Task.FromResult(source);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/Layout/MainLayout.razor.css:
--------------------------------------------------------------------------------
1 | .page {
2 | position: relative;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 |
7 | main {
8 | flex: 1;
9 | }
10 |
11 | .sidebar {
12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
13 | }
14 |
15 | .top-row {
16 | background-color: #f7f7f7;
17 | border-bottom: 1px solid #d6d5d5;
18 | justify-content: flex-end;
19 | height: 3.5rem;
20 | display: flex;
21 | align-items: center;
22 | }
23 |
24 | .top-row ::deep a, .top-row ::deep .btn-link {
25 | white-space: nowrap;
26 | margin-left: 1.5rem;
27 | text-decoration: none;
28 | }
29 |
30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | .top-row ::deep a:first-child {
35 | overflow: hidden;
36 | text-overflow: ellipsis;
37 | }
38 |
39 | @media (max-width: 640.98px) {
40 | .top-row {
41 | justify-content: space-between;
42 | }
43 |
44 | .top-row ::deep a, .top-row ::deep .btn-link {
45 | margin-left: 0;
46 | }
47 | }
48 |
49 | @media (min-width: 641px) {
50 | .page {
51 | flex-direction: row;
52 | }
53 |
54 | .sidebar {
55 | width: 250px;
56 | height: 100vh;
57 | position: sticky;
58 | top: 0;
59 | }
60 |
61 | .top-row {
62 | position: sticky;
63 | top: 0;
64 | z-index: 1;
65 | }
66 |
67 | .top-row.auth ::deep a:first-child {
68 | flex: 1;
69 | text-align: right;
70 | width: 0;
71 | }
72 |
73 | .top-row, article {
74 | padding-left: 2rem !important;
75 | padding-right: 1.5rem !important;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.14.36705.20
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpawnDev.BackgroundServices", "SpawnDev.BackgroundServices\SpawnDev.BackgroundServices.csproj", "{EB002CD1-83BD-F135-19BE-0CB2649A5DA6}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpawnDev.BackgroundServices.Demo", "SpawnDev.BackgroundServices.Demo\SpawnDev.BackgroundServices.Demo.csproj", "{C5422B91-79E3-470D-8722-A4E79B9F1B88}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {EB002CD1-83BD-F135-19BE-0CB2649A5DA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {EB002CD1-83BD-F135-19BE-0CB2649A5DA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {EB002CD1-83BD-F135-19BE-0CB2649A5DA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {EB002CD1-83BD-F135-19BE-0CB2649A5DA6}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {C5422B91-79E3-470D-8722-A4E79B9F1B88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {C5422B91-79E3-470D-8722-A4E79B9F1B88}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {C5422B91-79E3-470D-8722-A4E79B9F1B88}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {C5422B91-79E3-470D-8722-A4E79B9F1B88}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {CE648F6E-D522-4692-9CA1-E21503C48D38}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/SpawnDev.BackgroundServices.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0;net7.0;net8.0;net9.0
4 | latest
5 | enable
6 | enable
7 | 1.0.2
8 | True
9 | true
10 | true
11 | Embedded
12 | SpawnDev.BackgroundServices
13 | LostBeard
14 | Add support for autostarting services based on the running scope.
15 | https://github.com/LostBeard/SpawnDev.BackgroundServices
16 | README.md
17 | LICENSE.txt
18 | icon-128.png
19 | https://github.com/LostBeard/SpawnDev.BackgroundServices.git
20 | git
21 | DependencyInjection
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/Layout/NavMenu.razor.css:
--------------------------------------------------------------------------------
1 | .navbar-toggler {
2 | background-color: rgba(255, 255, 255, 0.1);
3 | }
4 |
5 | .top-row {
6 | height: 3.5rem;
7 | background-color: rgba(0,0,0,0.4);
8 | }
9 |
10 | .navbar-brand {
11 | font-size: 1.1rem;
12 | }
13 |
14 | .bi {
15 | display: inline-block;
16 | position: relative;
17 | width: 1.25rem;
18 | height: 1.25rem;
19 | margin-right: 0.75rem;
20 | top: -1px;
21 | background-size: cover;
22 | }
23 |
24 | .bi-house-door-fill-nav-menu {
25 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
26 | }
27 |
28 | .bi-plus-square-fill-nav-menu {
29 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
30 | }
31 |
32 | .bi-list-nested-nav-menu {
33 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
34 | }
35 |
36 | .nav-item {
37 | font-size: 0.9rem;
38 | padding-bottom: 0.5rem;
39 | }
40 |
41 | .nav-item:first-of-type {
42 | padding-top: 1rem;
43 | }
44 |
45 | .nav-item:last-of-type {
46 | padding-bottom: 1rem;
47 | }
48 |
49 | .nav-item ::deep a {
50 | color: #d7d7d7;
51 | border-radius: 4px;
52 | height: 3rem;
53 | display: flex;
54 | align-items: center;
55 | line-height: 3rem;
56 | }
57 |
58 | .nav-item ::deep a.active {
59 | background-color: rgba(255,255,255,0.37);
60 | color: white;
61 | }
62 |
63 | .nav-item ::deep a:hover {
64 | background-color: rgba(255,255,255,0.1);
65 | color: white;
66 | }
67 |
68 | @media (min-width: 641px) {
69 | .navbar-toggler {
70 | display: none;
71 | }
72 |
73 | .collapse {
74 | /* Never collapse the sidebar for wide screens */
75 | display: block;
76 | }
77 |
78 | .nav-scrollable {
79 | /* Allow sidebar to scroll for tall menus */
80 | height: calc(100vh - 3.5rem);
81 | overflow-y: auto;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/IBackgroundServiceManager.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 |
3 | namespace SpawnDev
4 | {
5 | ///
6 | /// Manages starting background services
7 | ///
8 | public interface IBackgroundServiceManager
9 | {
10 | ///
11 | /// Startup scope overrides for by service type.
12 | ///
13 | Dictionary AutoStartModes { get; }
14 | ///
15 | /// Service descriptors
16 | ///
17 | IServiceCollection Descriptors { get; }
18 | ///
19 | /// The scope used when background services were started (if they have been started)
20 | ///
21 | GlobalScope GlobalScope { get; }
22 | ///
23 | /// When background services are started this will be populated with service startup information used to determine what services to start
24 | ///
25 | List ServiceInformation { get; }
26 | ///
27 | /// Service provider
28 | ///
29 | IServiceProvider? Services { get; }
30 | ///
31 | /// Returns true if background services have already been started
32 | ///
33 | bool Started { get; }
34 | ///
35 | /// Fires after background service have started
36 | ///
37 | event Func OnStarted;
38 | ///
39 | /// Get service type startup scope override (if any)
40 | ///
41 | ///
42 | ///
43 | GlobalScope? GetAutoStartMode(Type type);
44 | ///
45 | /// Get keyed service
46 | ///
47 | ///
48 | ///
49 | ///
50 | (Type serviceType, object? serviceKey, ServiceDescriptor descriptor)? GetRegisteredServiceInfo(Type serviceType, object? serviceKey);
51 | ///
52 | /// Returns a list of registered services represented as a Tuple - ServiceType, ServiceKey, ServiceDescriptor
53 | ///
54 | /// Tuple (ServiceType, ServiceKey, ServiceDescriptor)
55 | List<(Type serviceType, object? serviceKey, ServiceDescriptor descriptor)> GetRegisteredServices();
56 | ///
57 | /// Clears any set auto start modes
58 | ///
59 | void ResetAutoStartMode();
60 | ///
61 | /// Resets the service type's startup mode
62 | ///
63 | ///
64 | ///
65 | void ResetAutoStartMode(Type serviceType);
66 | ///
67 | /// Sets the service type's startup mode
68 | ///
69 | ///
70 | ///
71 | ///
72 | void SetAutoStartMode(Type serviceType, GlobalScope autoStartMode);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices.Demo/wwwroot/css/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | }
4 |
5 | h1:focus {
6 | outline: none;
7 | }
8 |
9 | a, .btn-link {
10 | color: #0071c1;
11 | }
12 |
13 | .btn-primary {
14 | color: #fff;
15 | background-color: #1b6ec2;
16 | border-color: #1861ac;
17 | }
18 |
19 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
20 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
21 | }
22 |
23 | .content {
24 | padding-top: 1.1rem;
25 | }
26 |
27 | .valid.modified:not([type=checkbox]) {
28 | outline: 1px solid #26b050;
29 | }
30 |
31 | .invalid {
32 | outline: 1px solid red;
33 | }
34 |
35 | .validation-message {
36 | color: red;
37 | }
38 |
39 | #blazor-error-ui {
40 | background: lightyellow;
41 | bottom: 0;
42 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
43 | display: none;
44 | left: 0;
45 | padding: 0.6rem 1.25rem 0.7rem 1.25rem;
46 | position: fixed;
47 | width: 100%;
48 | z-index: 1000;
49 | }
50 |
51 | #blazor-error-ui .dismiss {
52 | cursor: pointer;
53 | position: absolute;
54 | right: 0.75rem;
55 | top: 0.5rem;
56 | }
57 |
58 | .blazor-error-boundary {
59 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
60 | padding: 1rem 1rem 1rem 3.7rem;
61 | color: white;
62 | }
63 |
64 | .blazor-error-boundary::after {
65 | content: "An error has occurred."
66 | }
67 |
68 | .loading-progress {
69 | position: relative;
70 | display: block;
71 | width: 8rem;
72 | height: 8rem;
73 | margin: 20vh auto 1rem auto;
74 | }
75 |
76 | .loading-progress circle {
77 | fill: none;
78 | stroke: #e0e0e0;
79 | stroke-width: 0.6rem;
80 | transform-origin: 50% 50%;
81 | transform: rotate(-90deg);
82 | }
83 |
84 | .loading-progress circle:last-child {
85 | stroke: #1b6ec2;
86 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
87 | transition: stroke-dasharray 0.05s ease-in-out;
88 | }
89 |
90 | .loading-progress-text {
91 | position: absolute;
92 | text-align: center;
93 | font-weight: bold;
94 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
95 | }
96 |
97 | .loading-progress-text:after {
98 | content: var(--blazor-load-percentage-text, "Loading");
99 | }
100 |
101 | code {
102 | color: #c02d76;
103 | }
104 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/GlobalScope.cs:
--------------------------------------------------------------------------------
1 | namespace SpawnDev
2 | {
3 | ///
4 | /// GlobalScope is used in calls to IServiceCollection.AddSingleton() to control the scopes in which the service is automatically started.
5 | /// And used in the call to IServiceProvider.StartBackgroundServices() to select which startup scopes to start.
6 | ///
7 | [Flags]
8 | public enum GlobalScope
9 | {
10 | ///
11 | /// Default
12 | /// Descriptor: Start in All scopes if the service implements IBackgroundService or IAsyncBackgroundService.
13 | /// Startup: Start all scopes
14 | ///
15 | Default = 0,
16 | ///
17 | /// Browser Window global scope
18 | /// Descriptor: Start in this scope.
19 | /// Startup: Start this scope
20 | ///
21 | Window = 1,
22 | ///
23 | /// Browser DedicatedWorker global scope
24 | /// Descriptor: Start in this scope.
25 | /// Startup: Start this scope
26 | ///
27 | DedicatedWorker = 2,
28 | ///
29 | /// Browser SharedWorker global scope
30 | /// Descriptor: Start in this scope.
31 | /// Startup: Start this scope
32 | ///
33 | SharedWorker = 4,
34 | ///
35 | /// Browser ServiceWorker global scope
36 | /// Descriptor: Start in this scope.
37 | /// Startup: Start this scope
38 | ///
39 | ServiceWorker = 8,
40 | ///
41 | /// Non-browser scope
42 | /// Descriptor: Start in this scope.
43 | /// Startup: Start this scope
44 | ///
45 | NonBrowser = 16,
46 | ///
47 | /// Browser other scope
48 | /// Descriptor: Start in this scope.
49 | /// Startup: Start this scope
50 | ///
51 | BrowserOther = 32,
52 | ///
53 | /// DedicatedWorker or SharedWorker global scope
54 | /// Descriptor: Start in any of these scopes.
55 | /// Startup: Start all of these scopes
56 | ///
57 | DedicatedAndSharedWorkers = DedicatedWorker | SharedWorker,
58 | ///
59 | /// DedicatedWorker, SharedWorker, and ServiceWorker global scopes
60 | /// Descriptor: Start in any of these scopes.
61 | /// Startup: Start all of these scopes
62 | ///
63 | AllWorkers = DedicatedWorker | SharedWorker | ServiceWorker,
64 | ///
65 | /// All browser scopes
66 | /// Descriptor: Start in any of these scopes.
67 | /// Startup: Start all of these scopes
68 | ///
69 | AllBrowser = Window | DedicatedWorker | SharedWorker | ServiceWorker | BrowserOther,
70 | ///
71 | /// All scopes
72 | /// Descriptor: Start in any of these scopes.
73 | /// Startup: Start all of these scopes
74 | ///
75 | AllTypical = Window | NonBrowser,
76 | ///
77 | /// All scopes
78 | /// Descriptor: Start in any scope.
79 | /// Startup: Start all scopes
80 | ///
81 | All = AllBrowser | NonBrowser,
82 | ///
83 | /// Auto scope.
84 | /// Descriptor: Not supported. Use AllTypical.
85 | /// Startup: Start OperatingSystem.IsBrowser() ? Window : NonBrowser
86 | ///
87 | AllTypicalAuto = 64,
88 | ///
89 | /// Auto scope.
90 | /// Descriptor: Not supported. Use All.
91 | /// Startup: Start OperatingSystem.IsBrowser() ? AllBrowser : NonBrowser
92 | ///
93 | AllAuto = 128,
94 | ///
95 | /// None. Ignored if combined with other flags.
96 | /// Descriptor: Do not auto start in any scope.
97 | /// Startup: Do not start any scopes.
98 | ///
99 | None = 256,
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/ServiceInformation.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 |
3 | namespace SpawnDev
4 | {
5 | ///
6 | /// Scoped service information
7 | ///
8 | public class ServiceInformation
9 | {
10 | ///
11 | /// Service type
12 | ///
13 | public Type ServiceType { get; private set; }
14 | ///
15 | /// The implementation type (if found)
16 | ///
17 | public Type? ImplementationType { get; private set; }
18 | ///
19 | /// The service key (if IsKeyedService)
20 | ///
21 | public object? ServiceKey { get; private set; } = null;
22 | ///
23 | /// True if this is a keyed service
24 | ///
25 | public bool IsKeyedService { get; private set; }
26 | ///
27 | /// This service
28 | ///
29 | public ServiceDescriptor ServiceDescriptor { get; private set; }
30 | ///
31 | /// Returns the service instance if found
32 | ///
33 | public object? ImplementationInstance
34 | {
35 | get
36 | {
37 | #if NET8_0_OR_GREATER
38 | return ServiceDescriptor.IsKeyedService ? ServiceDescriptor.KeyedImplementationInstance : null;
39 | #else
40 | return ServiceDescriptor.ImplementationInstance;
41 | #endif
42 | }
43 | }
44 | ///
45 | /// The scope(s) started
46 | ///
47 | public GlobalScope CurrentGlobalScope { get; set; }
48 | ///
49 | /// The scopes this service should auto start during
50 | ///
51 | public GlobalScope ServiceAutoStartScope { get; set; }
52 | ///
53 | /// True if the service implements IBackgroundService
54 | ///
55 | public bool ImplementsIBackgroundService { get; private set; }
56 | ///
57 | /// True if the service implements IAsyncBackgroundService
58 | ///
59 | public bool ImplementsIAsyncBackgroundService { get; private set; }
60 | ///
61 | /// Returns true if this service be started with background services
62 | ///
63 | public bool CurrentScopeAutoStart { get; private set; }
64 | ///
65 | /// Returns the service from the given service provider, or null if not found
66 | ///
67 | ///
68 | ///
69 | public object? GetService(IServiceProvider serviceProvider)
70 | {
71 | object? service = null;
72 | if (IsKeyedService)
73 | {
74 | #if NET8_0_OR_GREATER
75 | service = serviceProvider.GetRequiredKeyedService(ServiceType, ServiceKey);
76 | #endif
77 | }
78 | else
79 | {
80 | service = serviceProvider.GetRequiredService(ServiceType);
81 | }
82 | return service;
83 | }
84 | ///
85 | /// Returns the service from the given service provider, or null if not found.
86 | /// If the service implements IAsync its Ready property will be awaited
87 | ///
88 | ///
89 | ///
90 | public async Task GetServiceAsync(IServiceProvider serviceProvider)
91 | {
92 | object? service = GetService(serviceProvider);
93 | if (service is IAsyncService asyncBackgroundService)
94 | {
95 | await asyncBackgroundService.Ready;
96 | }
97 | return service;
98 | }
99 | internal ServiceInformation(ServiceDescriptor serviceDescriptor, GlobalScope currentGlobalScope, GlobalScope? serviceAutoStartScopeSet)
100 | {
101 | ServiceDescriptor = serviceDescriptor;
102 | ServiceType = ServiceDescriptor.ServiceType;
103 | #if NET8_0_OR_GREATER
104 | IsKeyedService = ServiceDescriptor.IsKeyedService;
105 | if (ServiceDescriptor.IsKeyedService)
106 | {
107 | ServiceKey = ServiceDescriptor.ServiceKey;
108 | ImplementationType = ServiceDescriptor.KeyedImplementationType ?? ServiceDescriptor.KeyedImplementationInstance?.GetType();
109 | }
110 | #endif
111 | if (!IsKeyedService)
112 | {
113 | ImplementationType = ServiceDescriptor.ImplementationType ?? ServiceDescriptor.ImplementationInstance?.GetType();
114 | }
115 | ImplementsIBackgroundService = typeof(IBackgroundService).IsAssignableFrom(ServiceType)
116 | || (ImplementationType != null && typeof(IBackgroundService).IsAssignableFrom(ImplementationType));
117 | ImplementsIAsyncBackgroundService = typeof(IAsyncBackgroundService).IsAssignableFrom(ServiceType)
118 | || (ImplementationType != null && typeof(IAsyncBackgroundService).IsAssignableFrom(ImplementationType));
119 | GlobalScope serviceAutoStartScope;
120 | if (serviceAutoStartScopeSet == null || serviceAutoStartScopeSet.Value == GlobalScope.Default)
121 | {
122 | serviceAutoStartScope = ImplementsIBackgroundService ? GlobalScope.All : GlobalScope.None;
123 | }
124 | else
125 | {
126 | serviceAutoStartScope = serviceAutoStartScopeSet.Value;
127 | }
128 | // a couple GlobalScopes are only used when calling StartBackgroundServices
129 | switch (serviceAutoStartScope)
130 | {
131 | case GlobalScope.AllTypicalAuto:
132 | throw new Exception($"Invalid service startup scope: GlobalScope.AllTypicalAuto. Use GlobalScope.AllTypical");
133 | case GlobalScope.AllAuto:
134 | throw new Exception($"Invalid service startup scope: GlobalScope.AllAuto. Use GlobalScope.All");
135 | }
136 | var lifetimeCanAutoStart = false;
137 | if (OperatingSystem.IsBrowser())
138 | {
139 | // in the browser scoped and singleton have the same lifespan
140 | lifetimeCanAutoStart = ServiceDescriptor.Lifetime == ServiceLifetime.Singleton || ServiceDescriptor.Lifetime == ServiceLifetime.Scoped;
141 | }
142 | else
143 | {
144 | lifetimeCanAutoStart = ServiceDescriptor.Lifetime == ServiceLifetime.Singleton;
145 | }
146 | var shouldStart = currentGlobalScope != GlobalScope.None && lifetimeCanAutoStart && (currentGlobalScope & serviceAutoStartScope) != 0;
147 | ServiceAutoStartScope = serviceAutoStartScope;
148 | CurrentScopeAutoStart = shouldStart;
149 | CurrentGlobalScope = currentGlobalScope;
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/BackgroundServiceManager.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using Microsoft.Extensions.DependencyInjection.Extensions;
3 |
4 | namespace SpawnDev
5 | {
6 |
7 | ///
8 | /// Manages starting background services
9 | ///
10 | public class BackgroundServiceManager : IBackgroundServiceManager
11 | {
12 | List> StartupCallbacks = new List>();
13 | ///
14 | /// Fires after background service have started
15 | ///
16 | public event Func OnStarted
17 | {
18 | add => StartupCallbacks.Add(value);
19 | remove => StartupCallbacks.Remove(value);
20 | }
21 | ///
22 | /// The scope used when background services were started (if they have been started)
23 | ///
24 | public GlobalScope GlobalScope { get; internal set; } = GlobalScope.Default;
25 | ///
26 | /// Service provider
27 | ///
28 | public IServiceProvider? Services { get; private set; }
29 | ///
30 | /// Service descriptors
31 | ///
32 | public IServiceCollection Descriptors { get; }
33 | ///
34 | /// Returns true if background services have already been started
35 | ///
36 | public bool Started { get; internal set; }
37 | ///
38 | /// When background services are started this will be populated with service startup information used to determine what services to start
39 | ///
40 | public List ServiceInformation { get; internal set; } = new List();
41 | ///
42 | /// Startup scope overrides for by service type.
43 | ///
44 | public Dictionary AutoStartModes { get; private set; } = new Dictionary();
45 | internal ServiceInformation? GetServiceInfo(Type serviceType, object? serviceKey)
46 | {
47 | return ServiceInformation.Where(o => o.ServiceType == serviceType && o.ServiceKey == serviceKey).LastOrDefault();
48 | }
49 | internal List GetServiceInfos(Type serviceType, object? serviceKey)
50 | {
51 | return ServiceInformation.Where(o => o.ServiceType == serviceType && o.ServiceKey == serviceKey).ToList();
52 | }
53 | ///
54 | /// New instance
55 | ///
56 | ///
57 | internal BackgroundServiceManager(IServiceCollection serviceCollection)
58 | {
59 | Descriptors = serviceCollection;
60 | serviceCollection.TryAddSingleton(Descriptors);
61 | serviceCollection.TryAddSingleton(sp => sp);
62 | }
63 | ///
64 | /// Returns a list of registered services represented as a Tuple - ServiceType, ServiceKey, ServiceDescriptor
65 | ///
66 | /// Tuple (ServiceType, ServiceKey, ServiceDescriptor)
67 | public List<(Type serviceType, object? serviceKey, ServiceDescriptor descriptor)> GetRegisteredServices()
68 | {
69 | return Descriptors.Select(x =>
70 | {
71 | object? serviceKey = null;
72 | #if NET8_0_OR_GREATER
73 | serviceKey = x.ServiceKey;
74 | #endif
75 | return (x.ServiceType, serviceKey, x);
76 | }).ToList();
77 | }
78 | ///
79 | /// Get keyed service
80 | ///
81 | ///
82 | ///
83 | ///
84 | public (Type serviceType, object? serviceKey, ServiceDescriptor descriptor)? GetRegisteredServiceInfo(Type serviceType, object? serviceKey)
85 | {
86 | var ret = GetRegisteredServices().Where(o => o.Item1 == serviceType && o.Item2 == serviceKey).LastOrDefault();
87 | return ret;
88 | }
89 | ///
90 | /// Get service type startup scope override (if any)
91 | ///
92 | ///
93 | ///
94 | public GlobalScope? GetAutoStartMode(Type type)
95 | {
96 | return AutoStartModes.TryGetValue(type, out var mode) ? mode : null;
97 | }
98 | ///
99 | /// Starts background service using the registered IGlobalScopeSource service if one.
100 | /// GlobalScope.AllTypicalAuto will be used if no source is found
101 | ///
102 | ///
103 | ///
104 | internal async Task StartBackgroundServices(IServiceProvider serviceProvider)
105 | {
106 | GlobalScope? scope = null;
107 | var scopeProvider = serviceProvider.GetService();
108 | if (scopeProvider != null)
109 | {
110 | scope = await scopeProvider.GetGlobalScope();
111 | }
112 | scope ??= GlobalScope.AllTypicalAuto;
113 | await StartBackgroundServices(serviceProvider, scope.Value);
114 | }
115 | ///
116 | /// Starts background services.
117 | /// This is called by internally by the extension method: IServiceProvider.StartBackgroundServices((GlobalScope)globalScope)
118 | ///
119 | ///
120 | ///
121 | ///
122 | ///
123 | ///
124 | internal async Task StartBackgroundServices(IServiceProvider serviceProvider, GlobalScope globalScope)
125 | {
126 | switch (globalScope)
127 | {
128 | case GlobalScope.Default:
129 | globalScope = GlobalScope.All;
130 | break;
131 | case GlobalScope.AllTypicalAuto:
132 | globalScope = OperatingSystem.IsBrowser() ? GlobalScope.Window : GlobalScope.NonBrowser;
133 | break;
134 | case GlobalScope.AllAuto:
135 | globalScope = OperatingSystem.IsBrowser() ? GlobalScope.AllBrowser : GlobalScope.NonBrowser;
136 | break;
137 | }
138 | if (serviceProvider == null) throw new ArgumentNullException(nameof(serviceProvider));
139 | Services = serviceProvider;
140 | Started = true;
141 | var hasWork = (GlobalScope & globalScope) != globalScope;
142 | if (!hasWork)
143 | {
144 | // already started with these flags
145 | return;
146 | }
147 | GlobalScope |= globalScope;
148 | var serviceCollection = Services.GetRequiredService();
149 | ServiceInformation = serviceCollection!.Select(o => new ServiceInformation(o, globalScope, GetAutoStartMode(o.ServiceType))).ToList();
150 | var shouldStartInfos = ServiceInformation.Where(o => o.CurrentScopeAutoStart).ToList();
151 | var services = new List();
152 | foreach (var shouldStartInfo in shouldStartInfos)
153 | {
154 | var service = shouldStartInfo.GetService(Services)!;
155 | services.Add(service);
156 | }
157 | var asyncServiceReadyTasks = new List();
158 | foreach (var service in services)
159 | {
160 | if (service is IAsyncBackgroundService asyncBackgroundService)
161 | {
162 | asyncServiceReadyTasks.Add(asyncBackgroundService.Ready);
163 | }
164 | }
165 | await Task.WhenAll(asyncServiceReadyTasks);
166 | await Task.WhenAll(StartupCallbacks.Select(o => o(this, globalScope)).ToList());
167 | }
168 | ///
169 | /// Resets the service type's startup mode
170 | ///
171 | ///
172 | ///
173 | public void ResetAutoStartMode(Type serviceType)
174 | {
175 | AutoStartModes.Remove(serviceType);
176 | }
177 | ///
178 | /// Clears any set auto start modes
179 | ///
180 | public void ResetAutoStartMode()
181 | {
182 | AutoStartModes.Clear();
183 | }
184 | ///
185 | /// Sets the service type's startup mode
186 | ///
187 | ///
188 | ///
189 | ///
190 | public void SetAutoStartMode(Type serviceType, GlobalScope autoStartMode)
191 | {
192 | switch (autoStartMode)
193 | {
194 | case GlobalScope.AllTypicalAuto:
195 | throw new Exception($"Invalid service startup scope: GlobalScope.AllTypicalAuto. Use GlobalScope.AllTypical");
196 | case GlobalScope.AllAuto:
197 | throw new Exception($"Invalid service startup scope: GlobalScope.AllAuto. Use GlobalScope.All");
198 | }
199 | AutoStartModes[serviceType] = autoStartMode;
200 | }
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/SpawnDev.BackgroundServices/IServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using Microsoft.Extensions.DependencyInjection.Extensions;
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | namespace SpawnDev
6 | {
7 | ///
8 | /// SpawnDev.BlazorJS IServiceCollection extension methods
9 | ///
10 | public static class IServiceCollectionExtensions
11 | {
12 | ///
13 | /// Starts background services using the specified scope.
14 | ///
15 | ///
16 | ///
17 | ///
18 | public static async Task StartBackgroundServices(this IServiceProvider _this, GlobalScope globalScope)
19 | {
20 | var bgServiceManager = (BackgroundServiceManager)_this.GetRequiredService();
21 | await bgServiceManager.StartBackgroundServices(_this, globalScope);
22 | }
23 | ///
24 | /// Starts background services using a registered IGlobalScopeSource service.
25 | /// If no IGlobalScopeSource service is found, GlobalScope.AllTypicalAuto will be used
26 | ///
27 | ///
28 | ///
29 | public static async Task StartBackgroundServices(this IServiceProvider _this)
30 | {
31 | var bgServiceManager = (BackgroundServiceManager)_this.GetRequiredService();
32 | await bgServiceManager.StartBackgroundServices(_this);
33 | }
34 | ///
35 | /// Adds IBackgroundServiceManager service.
36 | /// BackgroundServiceManager handles starting IBackgroundService services automatically when IServiceProvider.StartBackgroundServices is called(),
37 | /// optionally based on scope.
38 | ///
39 | ///
40 | ///
41 | public static IServiceCollection AddBackgroundServiceManager(this IServiceCollection _this)
42 | {
43 | _this.GetBackgroundServiceManager(true);
44 | return _this;
45 | }
46 | ///
47 | /// Set GlobalScope source
48 | ///
49 | ///
50 | ///
51 | ///
52 | public static IServiceCollection TrySetGlobalScope(this IServiceCollection _this, IGlobalScopeSource globalScopeSource)
53 | {
54 | _this.TryAddSingleton(globalScopeSource);
55 | return _this;
56 | }
57 | ///
58 | /// Set GlobalScope source
59 | ///
60 | ///
61 | ///
62 | ///
63 | public static IServiceCollection TrySetGlobalScope(this IServiceCollection _this, GlobalScope globalScopeSource)
64 | {
65 | _this.TryAddSingleton(new GlobalScopeSource(globalScopeSource));
66 | return _this;
67 | }
68 | ///
69 | /// Set GlobalScope source
70 | ///
71 | ///
72 | ///
73 | ///
74 | public static IServiceCollection TrySetGlobalScope(this IServiceCollection _this, Func globalScopeSource)
75 | {
76 | _this.TryAddSingleton(new GlobalScopeSource(globalScopeSource));
77 | return _this;
78 | }
79 | ///
80 | /// Set GlobalScope source
81 | ///
82 | ///
83 | ///
84 | ///
85 | public static IServiceCollection TrySetGlobalScope(this IServiceCollection _this, Func> globalScopeSource)
86 | {
87 | _this.TryAddSingleton(new GlobalScopeSource(globalScopeSource));
88 | return _this;
89 | }
90 | ///
91 | /// Set GlobalScope source
92 | ///
93 | ///
94 | ///
95 | ///
96 | public static IServiceCollection TrySetGlobalScope(this IServiceCollection _this, Task globalScopeSource)
97 | {
98 | _this.TryAddSingleton(new GlobalScopeSource(globalScopeSource));
99 | return _this;
100 | }
101 | ///
102 | /// Set GlobalScope source
103 | ///
104 | ///
105 | ///
106 | ///
107 | public static IServiceCollection SetGlobalScope(this IServiceCollection _this, IGlobalScopeSource globalScopeSource)
108 | => _this.AddSingleton(globalScopeSource);
109 | ///
110 | /// Set GlobalScope source
111 | ///
112 | ///
113 | ///
114 | ///
115 | public static IServiceCollection SetGlobalScope(this IServiceCollection _this, GlobalScope globalScopeSource)
116 | => _this.AddSingleton(new GlobalScopeSource(globalScopeSource));
117 | ///
118 | /// Set GlobalScope source
119 | ///
120 | ///
121 | ///
122 | ///
123 | public static IServiceCollection SetGlobalScope(this IServiceCollection _this, Func globalScopeSource)
124 | => _this.AddSingleton(new GlobalScopeSource(globalScopeSource));
125 | ///
126 | /// Set GlobalScope source
127 | ///
128 | ///
129 | ///
130 | ///
131 | public static IServiceCollection SetGlobalScope(this IServiceCollection _this, Func> globalScopeSource)
132 | => _this.AddSingleton(new GlobalScopeSource(globalScopeSource));
133 | ///
134 | /// Set GlobalScope source
135 | ///
136 | ///
137 | ///
138 | ///
139 | public static IServiceCollection SetGlobalScope(this IServiceCollection _this, Task globalScopeSource)
140 | => _this.AddSingleton(new GlobalScopeSource(globalScopeSource));
141 | ///
142 | /// Gets IBackgroundServiceManager from the current IServiceCollection, adding it if it is not found.
143 | ///
144 | ///
145 | ///
146 | public static IBackgroundServiceManager GetBackgroundServiceManager(this IServiceCollection _this) => GetBackgroundServiceManager(_this, true)!;
147 | ///
148 | /// Gets IBackgroundServiceManager from the current IServiceCollection, adding it if it is not found and allowAdd == true.
149 | ///
150 | ///
151 | ///
152 | ///
153 | public static IBackgroundServiceManager? GetBackgroundServiceManager(this IServiceCollection _this, bool allowAdd)
154 | {
155 | var existing = _this.FirstOrDefault(o => o.ServiceType == typeof(IBackgroundServiceManager));
156 | var ret = existing?.ImplementationInstance as IBackgroundServiceManager;
157 | if (ret == null && allowAdd)
158 | {
159 | ret = new BackgroundServiceManager(_this);
160 | _this.AddSingleton(ret);
161 | }
162 | return ret;
163 | }
164 | ///
165 | /// Returns the service of the given service type.
166 | /// If the service is implements IAsyncService, IAsyncService.Ready will be awaited.
167 | ///
168 | public static async Task GetServiceAsync(this IServiceProvider _this)
169 | {
170 | return (TService?)await _this.GetServiceAsync(typeof(TService));
171 | }
172 | ///
173 | /// Returns the service of the given service type.
174 | /// If the service is implements IAsyncService, IAsyncService.Ready will be awaited.
175 | ///
176 | ///
177 | ///
178 | ///
179 | public static async Task GetServiceAsync(this IServiceProvider _this, Type type)
180 | {
181 | var service = _this.GetService(type);
182 | if (service is IAsyncService asyncBackgroundService)
183 | {
184 | await asyncBackgroundService.Ready;
185 | }
186 | return service;
187 | }
188 | #if NET8_0_OR_GREATER
189 | ///
190 | /// Returns the service of the given service type.
191 | /// If the service is implements IAsyncService, IAsyncService.Ready will be awaited.
192 | ///
193 | public static async Task GetServiceAsync(this IServiceProvider _this, Type type, object key)
194 | {
195 | try
196 | {
197 | var service = _this.GetRequiredKeyedService(type, key);
198 | if (service is IAsyncService asyncBackgroundService)
199 | {
200 | await asyncBackgroundService.Ready;
201 | }
202 | return service;
203 | }
204 | catch { }
205 | return null;
206 | }
207 | #else
208 | ///
209 | /// Returns the service of the given service type.
210 | /// If the service is implements IAsyncService, IAsyncService.Ready will be awaited.
211 | ///
212 | public static Task GetServiceAsync(this IServiceProvider _this, Type type, object key)
213 | {
214 | return Task.FromResult(null);
215 | }
216 | #endif
217 | ///
218 | /// Get a keyed service
219 | ///
220 | ///
221 | ///
222 | ///
223 | ///
224 | public static object? GetService(this IServiceProvider _this, Type type, object key)
225 | {
226 | #if NET8_0_OR_GREATER
227 | try
228 | {
229 | return _this.GetRequiredKeyedService(type, key);
230 | }
231 | catch { }
232 | #endif
233 | return null;
234 | }
235 | ///
236 | /// Get a keyed service
237 | ///
238 | ///
239 | ///
240 | ///
241 | ///
242 | public static TService? GetService(this IServiceProvider _this, object key)
243 | {
244 | #if NET8_0_OR_GREATER
245 | try
246 | {
247 | return (TService?)_this.GetRequiredKeyedService(typeof(TService), key);
248 | }
249 | catch { }
250 | #endif
251 | return default;
252 | }
253 | ///
254 | /// Get service descriptor
255 | ///
256 | ///
257 | ///
258 | ///
259 | public static ServiceDescriptor? GetServiceDescriptor(this IServiceProvider _this, Type type)
260 | {
261 | var descriptors = _this.GetRequiredService();
262 | return descriptors.LastOrDefault(o => o.ServiceType == type);
263 | }
264 | #region AddSingleton
265 | ///
266 | /// Adds a singleton service of the type specified in with an
267 | /// implementation type specified in to the
268 | /// specified .
269 | ///
270 | /// The type of the service to add.
271 | /// The type of the implementation to use.
272 | /// The to add the service to.
273 | /// GlobalScope flags indicating in which scopes the service should be started automatically
274 | /// A reference to this instance after the operation has completed.
275 | ///
276 | public static IServiceCollection AddSingleton(this IServiceCollection services, GlobalScope autoStartMode) where TService : class where TImplementation : class, TService
277 | {
278 | ThrowIfNull(services);
279 | var bgServiceManager = services.GetBackgroundServiceManager();
280 | bgServiceManager.SetAutoStartMode(typeof(TService), autoStartMode);
281 | return services.AddSingleton(typeof(TService), typeof(TImplementation));
282 | }
283 | ///
284 | /// Adds a singleton service of the type specified in to the
285 | /// specified .
286 | ///
287 | /// The to add the service to.
288 | /// The type of the service to register and the implementation to use.
289 | /// GlobalScope flags indicating in which scopes the service should be started automatically
290 | /// A reference to this instance after the operation has completed.
291 | ///
292 | public static IServiceCollection AddSingleton(this IServiceCollection services, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type serviceType, GlobalScope autoStartMode)
293 | {
294 | ThrowIfNull(services);
295 | ThrowIfNull(serviceType);
296 | var bgServiceManager = services.GetBackgroundServiceManager();
297 | bgServiceManager.SetAutoStartMode(serviceType, autoStartMode);
298 | return services.AddSingleton(serviceType, serviceType);
299 | }
300 | ///
301 | /// Adds a singleton service of the type specified in to the
302 | /// specified .
303 | ///
304 | /// The type of the service to add.
305 | /// The to add the service to.
306 | /// GlobalScope flags indicating in which scopes the service should be started automatically
307 | /// A reference to this instance after the operation has completed.
308 | ///
309 | public static IServiceCollection AddSingleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this IServiceCollection services, GlobalScope autoStartMode) where TService : class
310 | {
311 | ThrowIfNull(services);
312 | var bgServiceManager = services.GetBackgroundServiceManager();
313 | bgServiceManager.SetAutoStartMode(typeof(TService), autoStartMode);
314 | return services.AddSingleton(typeof(TService));
315 | }
316 | ///
317 | /// Adds a singleton service of the type specified in with a
318 | /// factory specified in to the
319 | /// specified .
320 | ///
321 | /// The type of the service to add.
322 | /// The to add the service to.
323 | /// The factory that creates the service.
324 | /// GlobalScope flags indicating in which scopes the service should be started automatically
325 | /// A reference to this instance after the operation has completed.
326 | ///
327 | public static IServiceCollection AddSingleton(this IServiceCollection services, Func implementationFactory, GlobalScope autoStartMode) where TService : class
328 | {
329 | ThrowIfNull(services);
330 | ThrowIfNull(implementationFactory);
331 | var bgServiceManager = services.GetBackgroundServiceManager();
332 | bgServiceManager.SetAutoStartMode(typeof(TService), autoStartMode);
333 | return services.AddSingleton(typeof(TService), implementationFactory);
334 | }
335 | ///
336 | /// Adds a singleton service of the type specified in with an
337 | /// implementation type specified in using the
338 | /// factory specified in to the
339 | /// specified .
340 | ///
341 | /// The type of the service to add.
342 | /// The type of the implementation to use.
343 | /// The to add the service to.
344 | /// The factory that creates the service.
345 | /// GlobalScope flags indicating in which scopes the service should be started automatically
346 | /// A reference to this instance after the operation has completed.
347 | ///
348 | public static IServiceCollection AddSingleton(this IServiceCollection services, Func implementationFactory, GlobalScope autoStartMode) where TService : class where TImplementation : class, TService
349 | {
350 | ThrowIfNull(services);
351 | ThrowIfNull(implementationFactory);
352 | var bgServiceManager = services.GetBackgroundServiceManager();
353 | bgServiceManager.SetAutoStartMode(typeof(TService), autoStartMode);
354 | return services.AddSingleton(typeof(TService), implementationFactory);
355 | }
356 | #endregion
357 | static void ThrowIfNull(T? value)
358 | {
359 | if (value == null) throw new ArgumentNullException($"{typeof(T).Name} cannot be null");
360 | }
361 | #region Find service
362 | ///
363 | /// Find keyed service descriptor
364 | ///
365 | ///
366 | ///
367 | ///
368 | ///
369 | ///
370 | public static ServiceDescriptor? FindKeyedServiceDescriptor(this IServiceCollection _this, Type type, object key, bool strict = false)
371 | {
372 | ServiceDescriptor? services = null;
373 | if (type == null) return services;
374 | #if NET8_0_OR_GREATER
375 | services = _this.FirstOrDefault(o => o.IsKeyedService && Object.Equals(o.ServiceKey, key) && o.ServiceType == type);
376 | if (services != null || strict) return services;
377 | services = _this.FirstOrDefault(o => o.IsKeyedService && Object.Equals(o.ServiceKey, key) && o.ImplementationType == type);
378 | if (services != null) return services;
379 | services = _this.FirstOrDefault(o => o.IsKeyedService && Object.Equals(o.ServiceKey, key) && o.ServiceType.IsAssignableFrom(type));
380 | #endif
381 | return services;
382 | }
383 | ///
384 | /// Find service descriptor
385 | ///
386 | ///
387 | ///
388 | ///
389 | ///
390 | public static ServiceDescriptor? FindServiceDescriptor(this IServiceCollection _this, Type type, bool strict = false)
391 | {
392 | if (type == null) return null;
393 | var services = _this.FirstOrDefault(o => o.ServiceType == type);
394 | if (services != null || strict) return services;
395 | services = _this.FirstOrDefault(o => o.ImplementationType == type);
396 | if (services != null) return services;
397 | services = _this.FirstOrDefault(o => o.ServiceType.IsAssignableFrom(type));
398 | return services;
399 | }
400 |
401 | ///
402 | /// Returns all ServiceDescriptors for services registered with the given service type
403 | /// Type can be the type or the implementing type
404 | ///
405 | ///
406 | ///
407 | ///
408 | public static List FindServiceDescriptors(this IServiceCollection _this, Type type)
409 | {
410 | if (type == null) return new List();
411 | var services = _this.Where(o => o.ServiceType == type).ToList();
412 | if (services.Count > 0) return services;
413 | services = _this.Where(o => o.ImplementationType == type).ToList();
414 | if (services.Count > 0) return services;
415 | services = _this.Where(o => o.ServiceType.IsAssignableFrom(type)).ToList();
416 | return services;
417 | }
418 | ///
419 | /// Find service descriptors
420 | ///
421 | ///
422 | ///
423 | ///
424 | ///
425 | public static List FindServiceDescriptors(this IServiceCollection _this, object key)
426 | {
427 | return _this.FindServiceDescriptors(typeof(TService), key);
428 | }
429 | ///
430 | /// Find service descriptors
431 | ///
432 | ///
433 | ///
434 | ///
435 | ///
436 | public static List FindServiceDescriptors(this IServiceCollection _this, Type type, object key)
437 | {
438 | #if NET8_0_OR_GREATER
439 | if (type == null) return new List();
440 | var services = _this.Where(o => o.ServiceType == type && Object.Equals(o.ServiceKey, key)).ToList();
441 | if (services.Count > 0) return services;
442 | services = _this.Where(o => o.ImplementationType == type && Object.Equals(o.ServiceKey, key)).ToList();
443 | if (services.Count > 0) return services;
444 | services = _this.Where(o => o.ServiceType.IsAssignableFrom(type) && Object.Equals(o.ServiceKey, key)).ToList();
445 | return services;
446 | #else
447 | return new List();
448 | #endif
449 | }
450 | ///
451 | /// Find a service and await its IAsync.Ready property if it has one
452 | ///
453 | ///
454 | ///
455 | ///
456 | public static async Task FindServiceAsync(this IServiceProvider _this)
457 | {
458 | return (TService?)await _this.FindServiceAsync(typeof(TService));
459 | }
460 | ///
461 | /// Find a service and await its IAsync.Ready property if it has one
462 | ///
463 | ///
464 | ///
465 | ///
466 | public static async Task FindServiceAsync(this IServiceProvider _this, Type? serviceType)
467 | {
468 | if (serviceType == null) return null;
469 | var serviceCollection = _this.GetService();
470 | if (serviceCollection == null) return await _this.GetServiceAsync(serviceType);
471 | var info = serviceCollection.FindServiceDescriptors(serviceType)!.FirstOrDefault();
472 | return await _this.GetServiceAsync(info == null ? serviceType : info.ServiceType);
473 | }
474 | ///
475 | /// Find a keyed service and await its IAsync.Ready property if it has one
476 | ///
477 | ///
478 | ///
479 | ///
480 | ///
481 | public static async Task FindServiceAsync(this IServiceProvider _this, object key)
482 | {
483 | return (TService?)await _this.FindServiceAsync(typeof(TService), key);
484 | }
485 | ///
486 | /// Find a keyed service and await its IAsync.Ready property if it has one
487 | ///
488 | ///
489 | ///
490 | ///
491 | ///
492 | public static async Task FindServiceAsync(this IServiceProvider _this, Type serviceType, object key)
493 | {
494 | if (serviceType == null) return null;
495 | var serviceCollection = _this.GetService();
496 | if (serviceCollection == null) return await _this.GetServiceAsync(serviceType, key);
497 | var info = serviceCollection.FindServiceDescriptors(serviceType, key)!.FirstOrDefault();
498 | return await _this.GetServiceAsync(info == null ? serviceType : info.ServiceType, key);
499 | }
500 | #endregion
501 | }
502 | }
503 |
--------------------------------------------------------------------------------