├── .appveyor.yml
├── .editorconfig
├── .gitignore
├── .travis.yml
├── LICENSE
├── NuGet.Config
├── README.md
├── StartupModules.sln
├── src
└── StartupModules
│ ├── ConfigureMiddlewareContext.cs
│ ├── ConfigureServicesContext.cs
│ ├── IApplicationInitializer.cs
│ ├── Internal
│ ├── InlineMiddlewareConfiguration.cs
│ └── ModulesStartupFilter.cs
│ ├── StartupModule.cs
│ ├── StartupModuleRunner.cs
│ ├── StartupModules.csproj
│ ├── StartupModulesExtensions.cs
│ └── StartupModulesOptions.cs
└── test
├── StartupModules.Tests
├── FunctionalTests.cs
├── Properties
│ └── launchSettings.json
├── StartupModuleTests.cs
├── StartupModules.Tests.csproj
├── StartupModulesExtensionsTests.cs
├── StartupModulesOptionsTests.cs
└── StartupRunnerTests.cs
└── TestWebApp
├── FooAppInitializer.cs
├── HangfireStartupModule.cs
├── Program.cs
├── Properties
└── launchSettings.json
└── TestWebApp.csproj
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{build}'
2 | pull_requests:
3 | do_not_increment_build_number: true
4 | image: Visual Studio 2022
5 | nuget:
6 | disable_publish_on_pr: true
7 | build_script:
8 | - cmd: dotnet restore
9 | - cmd: dotnet build
10 | - cmd: dotnet test .\test\StartupModules.Tests
11 | - cmd: dotnet pack .\src\StartupModules -c Release -o .\artifacts
12 | test: off
13 | init:
14 | - git config --global core.autocrlf input
15 | environment:
16 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
17 | DOTNET_CLI_TELEMETRY_OPTOUT: true
18 | CI: true
19 | branches:
20 | only:
21 | - master
22 | artifacts:
23 | - path: .\src\StartupModules\artifacts\**\*.nupkg
24 | name: NuGet
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories
2 | root = true
3 |
4 | # C# files
5 | [*.cs]
6 |
7 | #### Core EditorConfig Options ####
8 |
9 | # Indentation and spacing
10 | indent_size = 4
11 | indent_style = space
12 | tab_width = 4
13 |
14 | # New line preferences
15 | end_of_line = crlf
16 | insert_final_newline = false
17 |
18 | #### .NET Coding Conventions ####
19 |
20 | # Organize usings
21 | dotnet_separate_import_directive_groups = false
22 | dotnet_sort_system_directives_first = true
23 | file_header_template = unset
24 |
25 | # this. and Me. preferences
26 | dotnet_style_qualification_for_event = false:warning
27 | dotnet_style_qualification_for_field = false:silent
28 | dotnet_style_qualification_for_method = false:warning
29 | dotnet_style_qualification_for_property = false:warning
30 |
31 | # Language keywords vs BCL types preferences
32 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
33 | dotnet_style_predefined_type_for_member_access = true:warning
34 |
35 | # Parentheses preferences
36 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
37 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
38 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
39 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
40 |
41 | # Modifier preferences
42 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
43 |
44 | # Expression-level preferences
45 | dotnet_style_coalesce_expression = true:suggestion
46 | dotnet_style_collection_initializer = true:suggestion
47 | dotnet_style_explicit_tuple_names = true:suggestion
48 | dotnet_style_null_propagation = true:suggestion
49 | dotnet_style_object_initializer = true:suggestion
50 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
51 | dotnet_style_prefer_auto_properties = true:silent
52 | dotnet_style_prefer_compound_assignment = true:suggestion
53 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
54 | dotnet_style_prefer_conditional_expression_over_return = true:silent
55 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
56 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
57 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
58 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
59 | dotnet_style_prefer_simplified_interpolation = true:suggestion
60 |
61 | # Field preferences
62 | dotnet_style_readonly_field = true:suggestion
63 |
64 | # Parameter preferences
65 | dotnet_code_quality_unused_parameters = all:suggestion
66 |
67 | #### C# Coding Conventions ####
68 |
69 | # var preferences
70 | csharp_style_var_elsewhere = true:suggestion
71 | csharp_style_var_for_built_in_types = true:suggestion
72 | csharp_style_var_when_type_is_apparent = true:suggestion
73 |
74 | # Expression-bodied members
75 | csharp_style_expression_bodied_accessors = true:silent
76 | csharp_style_expression_bodied_constructors = false:silent
77 | csharp_style_expression_bodied_indexers = true:silent
78 | csharp_style_expression_bodied_lambdas = true:silent
79 | csharp_style_expression_bodied_local_functions = when_on_single_line:silent
80 | csharp_style_expression_bodied_methods = when_on_single_line:silent
81 | csharp_style_expression_bodied_operators = when_on_single_line:silent
82 | csharp_style_expression_bodied_properties = true:silent
83 |
84 | # Pattern matching preferences
85 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
86 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
87 | csharp_style_prefer_switch_expression = true:suggestion
88 |
89 | # Null-checking preferences
90 | csharp_style_conditional_delegate_call = true:suggestion
91 |
92 | # Modifier preferences
93 | csharp_prefer_static_local_function = true:suggestion
94 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
95 |
96 | # Code-block preferences
97 | csharp_prefer_braces = true:suggestion
98 | csharp_prefer_simple_using_statement = true:suggestion
99 |
100 | # Expression-level preferences
101 | csharp_prefer_simple_default_expression = true:suggestion
102 | csharp_style_deconstructed_variable_declaration = true:suggestion
103 | csharp_style_inlined_variable_declaration = true:suggestion
104 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
105 | csharp_style_prefer_index_operator = true:suggestion
106 | csharp_style_prefer_range_operator = true:suggestion
107 | csharp_style_throw_expression = true:suggestion
108 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion
109 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent
110 |
111 | # 'using' directive preferences
112 | csharp_using_directive_placement = outside_namespace:silent
113 |
114 | # Namespace declarations
115 | csharp_style_namespace_declarations=file_scoped:suggestion
116 |
117 | #### C# Formatting Rules ####
118 |
119 | # New line preferences
120 | csharp_new_line_before_catch = true
121 | csharp_new_line_before_else = true
122 | csharp_new_line_before_finally = true
123 | csharp_new_line_before_members_in_anonymous_types = true
124 | csharp_new_line_before_members_in_object_initializers = true
125 | csharp_new_line_before_open_brace = all
126 | csharp_new_line_between_query_expression_clauses = true
127 |
128 | # Indentation preferences
129 | csharp_indent_block_contents = true
130 | csharp_indent_braces = false
131 | csharp_indent_case_contents = true
132 | csharp_indent_case_contents_when_block = false
133 | csharp_indent_labels = one_less_than_current
134 | csharp_indent_switch_labels = true
135 |
136 | # Space preferences
137 | csharp_space_after_cast = false
138 | csharp_space_after_colon_in_inheritance_clause = true
139 | csharp_space_after_comma = true
140 | csharp_space_after_dot = false
141 | csharp_space_after_keywords_in_control_flow_statements = true
142 | csharp_space_after_semicolon_in_for_statement = true
143 | csharp_space_around_binary_operators = before_and_after
144 | csharp_space_around_declaration_statements = false
145 | csharp_space_before_colon_in_inheritance_clause = true
146 | csharp_space_before_comma = false
147 | csharp_space_before_dot = false
148 | csharp_space_before_open_square_brackets = false
149 | csharp_space_before_semicolon_in_for_statement = false
150 | csharp_space_between_empty_square_brackets = false
151 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
152 | csharp_space_between_method_call_name_and_opening_parenthesis = false
153 | csharp_space_between_method_call_parameter_list_parentheses = false
154 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
155 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
156 | csharp_space_between_method_declaration_parameter_list_parentheses = false
157 | csharp_space_between_parentheses = false
158 | csharp_space_between_square_brackets = false
159 |
160 | # Wrapping preferences
161 | csharp_preserve_single_line_blocks = true
162 | csharp_preserve_single_line_statements = true
163 |
164 | #### Naming styles ####
165 |
166 | # Naming rules
167 |
168 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
169 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
170 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
171 |
172 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
173 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types
174 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
175 |
176 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
177 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
178 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
179 |
180 | dotnet_naming_rule.private_constant_should_be_pascal_case.severity = suggestion
181 | dotnet_naming_rule.private_constant_should_be_pascal_case.symbols = private_constant
182 | dotnet_naming_rule.private_constant_should_be_pascal_case.style = pascal_case
183 |
184 | dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.severity = suggestion
185 | dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.symbols = private_or_internal_static_field
186 | dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.style = pascal_case
187 |
188 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.severity = suggestion
189 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.symbols = private_or_internal_field
190 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.style = begins_with__
191 |
192 | # Symbol specifications
193 |
194 | dotnet_naming_symbols.interface.applicable_kinds = interface
195 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal
196 | dotnet_naming_symbols.interface.required_modifiers =
197 |
198 | dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
199 | dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private
200 | dotnet_naming_symbols.private_or_internal_field.required_modifiers =
201 |
202 | dotnet_naming_symbols.private_or_internal_static_field.applicable_kinds = field
203 | dotnet_naming_symbols.private_or_internal_static_field.applicable_accessibilities = internal, private
204 | dotnet_naming_symbols.private_or_internal_static_field.required_modifiers = static
205 |
206 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
207 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal
208 | dotnet_naming_symbols.types.required_modifiers =
209 |
210 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
211 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal
212 | dotnet_naming_symbols.non_field_members.required_modifiers =
213 |
214 | dotnet_naming_symbols.private_constant.applicable_kinds = field, local
215 | dotnet_naming_symbols.private_constant.applicable_accessibilities = private, local
216 | dotnet_naming_symbols.private_constant.required_modifiers = const
217 |
218 | # Naming styles
219 |
220 | dotnet_naming_style.pascal_case.required_prefix =
221 | dotnet_naming_style.pascal_case.required_suffix =
222 | dotnet_naming_style.pascal_case.word_separator =
223 | dotnet_naming_style.pascal_case.capitalization = pascal_case
224 |
225 | dotnet_naming_style.begins_with_i.required_prefix = I
226 | dotnet_naming_style.begins_with_i.required_suffix =
227 | dotnet_naming_style.begins_with_i.word_separator =
228 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
229 |
230 | dotnet_naming_style.begins_with__.required_prefix = _
231 | dotnet_naming_style.begins_with__.required_suffix =
232 | dotnet_naming_style.begins_with__.word_separator =
233 | dotnet_naming_style.begins_with__.capitalization = camel_case
234 |
235 | [*.{xml,csproj}]
236 | indent_size = 2
237 | indent_style = space
238 | tab_width = 2
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | [Oo]bj/
2 | [Bb]in/
3 | .nuget/
4 | packages/
5 | artifacts/
6 | pack/
7 | PublishProfiles/
8 | .vs/
9 | .vscode/
10 | debugSettings.json
11 | project.lock.json
12 | *.user
13 | *.suo
14 | nuget.exe
15 | *.userprefs
16 | *DS_Store
17 | *.*sdf
18 | *.ipch
19 | .settings
20 | *.sln.ide
21 | .build/
22 | *.ldf
23 | /test/StartupModules.Tests/coverage
24 | /test/StartupModules.Tests/coverage.json
25 | test/StartupModules.Tests/coverage.opencover.xml
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | mono: none
3 | dotnet: 6.0
4 | env:
5 | global:
6 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
7 | - DOTNET_CLI_TELEMETRY_OPTOUT: true
8 | branches:
9 | only:
10 | - master
11 | script:
12 | - dotnet restore
13 | - dotnet build
14 | - dotnet test test/StartupModules.Tests
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Henk Mollema
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 |
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Startup Modules
2 | Startup modules for ASP.NET Core.
3 |
4 | Create modular and focused Startup-like classes for each of your application's features/components/modules and keep your startup file sane.
5 |
6 | | Windows | Linux | NuGet |
7 | | ------- | ----- | ----- |
8 | | [](https://ci.appveyor.com/project/henkmollema/startupmodules/branch/master) | [](https://travis-ci.org/henkmollema/StartupModules) | [](https://www.nuget.org/packages/StartupModules)
9 |
10 | ## Installation
11 |
12 | #### StartupModules is [available on NuGet](https://www.nuget.org/packages/StartupModules)
13 |
14 | ## Getting started
15 |
16 | ### Create a startup module
17 |
18 | Creating a startup module is easy. Create a new class and implement the `IStartupModule` interface:
19 |
20 | ```cs
21 | public class MyStartupModule : IStartupModule
22 | {
23 | public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context)
24 | {
25 | }
26 |
27 | public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context)
28 | {
29 | }
30 | }
31 | ```
32 |
33 | You'll notice the familiair `ConfigureServices` and `Configure` methods. A convient context-object is passed to both of them providing useful services while configuring your application, such as `IHostingEnvironment` and `IConfiguration`. A scoped `IServiceProvider` is present in the `ConfigureMiddlewareContext` as well.
34 |
35 | ### Configure startup modules
36 |
37 | You can configure startup modules using the `UseStartupModules` when building the web host in `Program.cs`:
38 |
39 | ```cs
40 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
41 | WebHost.CreateDefaultBuilder(args)
42 | .UseStartupModules()
43 | .UseStartup();
44 | ```
45 |
46 | This will automatically discover startup modules (and application initializers) in the entry assembly of your application. You can also specify an array of assemblies to discover startup modules from:
47 |
48 | ```cs
49 | .UseStartupModules(typeof(Startup).Assembly, typeof(SomeTypeInAnotherAssembly).Assembly)
50 | ```
51 |
52 | You can have more control of the configuration using the overload with the options:
53 |
54 | ```cs
55 | .UseStartupModules(options =>
56 | {
57 | // Discover from entry assembly
58 | o.DiscoverStartupModules();
59 |
60 | // Discover from specific assemblies
61 | o.DiscoverStartupModules(typeof(Startup).Assembly, typeof(SomeTypeInAnotherAssembly).Assembly);
62 |
63 | // Add individual startup modules
64 | o.AddStartupModule();
65 | })
66 | ```
67 |
68 | ### Application Initializers
69 | Application initializers allow you to write asynchronous startup logic for your application, such as configuring your Entity Framework database context and executing migrations. Applications initializers are, just like startup modules, discovered automatically as well.
70 |
71 | ```cs
72 | public class DatabaseInitializer : IApplicationInitializer
73 | {
74 | private readonly AppDbContext _dbContext;
75 |
76 | public DatabaseInitializer(AppDbContext dbContext)
77 | {
78 | _dbContext = dbContext;
79 | }
80 |
81 | public async Task Task Invoke()
82 | {
83 | await _dbContext.MigrateAsync();
84 | }
85 | }
86 | ```
87 |
88 | You can specify any number of dependencies available in your application via the constructor of an application initializer. The dependencies will be resolved from a **scoped** service provider instance and will be disposed after application startup. This prevents common pitfalls such as a resolving a singleton database context and leaking its connection for the lifetime off the application and avoids the hassle of creating a service provider scope yourself.
89 |
--------------------------------------------------------------------------------
/StartupModules.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31912.275
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9BF5D119-F23D-4E38-ACBC-E62FF9459BDC}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartupModules", "src\StartupModules\StartupModules.csproj", "{4D9975C6-C93B-4722-AC88-80412E3CB43C}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{22006F10-F5E1-4F57-A626-6EBDBE4A2AC8}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWebApp", "test\TestWebApp\TestWebApp.csproj", "{8E8BB8C2-7392-4724-BC63-D59F00F84874}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartupModules.Tests", "test\StartupModules.Tests\StartupModules.Tests.csproj", "{1956C266-87A1-4A29-9CAC-C024BE9F087A}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9CE5BB1B-0E26-4A9F-8E32-AEB913E6F6E4}"
17 | ProjectSection(SolutionItems) = preProject
18 | .appveyor.yml = .appveyor.yml
19 | .editorconfig = .editorconfig
20 | .gitignore = .gitignore
21 | .travis.yml = .travis.yml
22 | LICENSE = LICENSE
23 | NuGet.Config = NuGet.Config
24 | README.md = README.md
25 | EndProjectSection
26 | EndProject
27 | Global
28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
29 | Debug|Any CPU = Debug|Any CPU
30 | Debug|x64 = Debug|x64
31 | Debug|x86 = Debug|x86
32 | Release|Any CPU = Release|Any CPU
33 | Release|x64 = Release|x64
34 | Release|x86 = Release|x86
35 | EndGlobalSection
36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
37 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Debug|x64.ActiveCfg = Debug|Any CPU
40 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Debug|x64.Build.0 = Debug|Any CPU
41 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Debug|x86.ActiveCfg = Debug|Any CPU
42 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Debug|x86.Build.0 = Debug|Any CPU
43 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Release|x64.ActiveCfg = Release|Any CPU
46 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Release|x64.Build.0 = Release|Any CPU
47 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Release|x86.ActiveCfg = Release|Any CPU
48 | {4D9975C6-C93B-4722-AC88-80412E3CB43C}.Release|x86.Build.0 = Release|Any CPU
49 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Debug|x64.ActiveCfg = Debug|Any CPU
52 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Debug|x64.Build.0 = Debug|Any CPU
53 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Debug|x86.ActiveCfg = Debug|Any CPU
54 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Debug|x86.Build.0 = Debug|Any CPU
55 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Release|x64.ActiveCfg = Release|Any CPU
58 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Release|x64.Build.0 = Release|Any CPU
59 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Release|x86.ActiveCfg = Release|Any CPU
60 | {8E8BB8C2-7392-4724-BC63-D59F00F84874}.Release|x86.Build.0 = Release|Any CPU
61 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Debug|Any CPU.Build.0 = Debug|Any CPU
63 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Debug|x64.ActiveCfg = Debug|Any CPU
64 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Debug|x64.Build.0 = Debug|Any CPU
65 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Debug|x86.ActiveCfg = Debug|Any CPU
66 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Debug|x86.Build.0 = Debug|Any CPU
67 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Release|Any CPU.ActiveCfg = Release|Any CPU
68 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Release|Any CPU.Build.0 = Release|Any CPU
69 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Release|x64.ActiveCfg = Release|Any CPU
70 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Release|x64.Build.0 = Release|Any CPU
71 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Release|x86.ActiveCfg = Release|Any CPU
72 | {1956C266-87A1-4A29-9CAC-C024BE9F087A}.Release|x86.Build.0 = Release|Any CPU
73 | EndGlobalSection
74 | GlobalSection(SolutionProperties) = preSolution
75 | HideSolutionNode = FALSE
76 | EndGlobalSection
77 | GlobalSection(NestedProjects) = preSolution
78 | {4D9975C6-C93B-4722-AC88-80412E3CB43C} = {9BF5D119-F23D-4E38-ACBC-E62FF9459BDC}
79 | {8E8BB8C2-7392-4724-BC63-D59F00F84874} = {22006F10-F5E1-4F57-A626-6EBDBE4A2AC8}
80 | {1956C266-87A1-4A29-9CAC-C024BE9F087A} = {22006F10-F5E1-4F57-A626-6EBDBE4A2AC8}
81 | EndGlobalSection
82 | GlobalSection(ExtensibilityGlobals) = postSolution
83 | SolutionGuid = {9C162947-6EAC-4F17-821B-977558D7E88B}
84 | EndGlobalSection
85 | EndGlobal
86 |
--------------------------------------------------------------------------------
/src/StartupModules/ConfigureMiddlewareContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 |
5 | namespace StartupModules;
6 |
7 | ///
8 | /// Provides access to useful services during middleware configuration.
9 | ///
10 | public class ConfigureMiddlewareContext
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | public ConfigureMiddlewareContext(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, IServiceProvider serviceProvider, StartupModulesOptions options)
16 | {
17 | Configuration = configuration;
18 | HostingEnvironment = hostingEnvironment;
19 | ServiceProvider = serviceProvider;
20 | Options = options;
21 | }
22 |
23 | ///
24 | /// Gets the application instance.
25 | ///
26 | public IConfiguration Configuration { get; }
27 |
28 | ///
29 | /// Gets the application instance.
30 | ///
31 | public IWebHostEnvironment HostingEnvironment { get; }
32 |
33 | ///
34 | /// Gets the instance scoped for the lifetime of application startup.
35 | ///
36 | public IServiceProvider ServiceProvider { get; }
37 |
38 | ///
39 | /// Gets the .
40 | ///
41 | public StartupModulesOptions Options { get; }
42 | }
43 |
--------------------------------------------------------------------------------
/src/StartupModules/ConfigureServicesContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.Extensions.Configuration;
3 |
4 | namespace StartupModules;
5 |
6 | ///
7 | /// Provides access to useful services during application services configuration.
8 | ///
9 | public class ConfigureServicesContext
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | public ConfigureServicesContext(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, StartupModulesOptions options)
15 | {
16 | Configuration = configuration;
17 | HostingEnvironment = hostingEnvironment;
18 | Options = options;
19 | }
20 |
21 | ///
22 | /// Gets the application instance.
23 | ///
24 | public IConfiguration Configuration { get; }
25 |
26 | ///
27 | /// Gets the application instance.
28 | ///
29 | public IWebHostEnvironment HostingEnvironment { get; }
30 |
31 | ///
32 | /// Gets the .
33 | ///
34 | public StartupModulesOptions Options { get; }
35 | }
36 |
--------------------------------------------------------------------------------
/src/StartupModules/IApplicationInitializer.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace StartupModules;
4 |
5 | ///
6 | /// Represents a class that initializes application services during startup.
7 | ///
8 | public interface IApplicationInitializer
9 | {
10 | ///
11 | /// Invokes the instance.
12 | ///
13 | Task Invoke();
14 | }
15 |
--------------------------------------------------------------------------------
/src/StartupModules/Internal/InlineMiddlewareConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.Extensions.DependencyInjection;
4 |
5 | namespace StartupModules.Internal;
6 |
7 | ///
8 | /// Provides a with inline middleware configuration.
9 | ///
10 | public class InlineMiddlewareConfiguration : IStartupModule
11 | {
12 | private readonly Action _action;
13 |
14 | ///
15 | /// Initializes a new instance of .
16 | ///
17 | ///
18 | public InlineMiddlewareConfiguration(Action action)
19 | {
20 | _action = action;
21 | }
22 |
23 | ///
24 | public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context) => _action(app, context);
25 |
26 | ///
27 | public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context) { }
28 | }
29 |
--------------------------------------------------------------------------------
/src/StartupModules/Internal/ModulesStartupFilter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.AspNetCore.Hosting;
4 | using Microsoft.Extensions.Configuration;
5 |
6 | namespace StartupModules.Internal;
7 |
8 | ///
9 | /// A startup filter that invokes the configured 's and 's.
10 | ///
11 | public class ModulesStartupFilter : IStartupFilter
12 | {
13 | private readonly StartupModuleRunner _runner;
14 | private readonly IConfiguration _configuration;
15 | private readonly IWebHostEnvironment _hostingEnvironment;
16 |
17 | ///
18 | /// Initializes a new instance of the class.
19 | ///
20 | public ModulesStartupFilter(StartupModuleRunner runner, IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
21 | {
22 | _runner = runner;
23 | _configuration = configuration;
24 | _hostingEnvironment = hostingEnvironment;
25 | }
26 |
27 | ///
28 | public Action Configure(Action next) => app =>
29 | {
30 | _runner.Configure(app, _configuration, _hostingEnvironment);
31 | _runner.RunApplicationInitializers(app.ApplicationServices).GetAwaiter().GetResult();
32 | next(app);
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/StartupModules/StartupModule.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.Extensions.DependencyInjection;
3 |
4 | namespace StartupModules;
5 |
6 | ///
7 | /// Defines a startup module to configure application services and middleware during startup.
8 | /// An application can define multiple startup modules for each of its modules/components/features.
9 | ///
10 | public interface IStartupModule
11 | {
12 | ///
13 | /// A callback to configure the application's services.
14 | ///
15 | /// The instance to configure the application's services.
16 | /// The instance which provides access to useful services.
17 | void ConfigureServices(IServiceCollection services, ConfigureServicesContext context);
18 |
19 | ///
20 | /// A callback to configure the middleware pipeline of the application.
21 | ///
22 | /// The instance to configure the middleware pipeline of the application.
23 | /// The instance which provides access to useful services.
24 | void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context);
25 | }
26 |
--------------------------------------------------------------------------------
/src/StartupModules/StartupModuleRunner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 | using Microsoft.AspNetCore.Builder;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.DependencyInjection;
8 |
9 | namespace StartupModules;
10 |
11 | ///
12 | /// A runner for 's discoverd via .
13 | ///
14 | public class StartupModuleRunner
15 | {
16 | private readonly StartupModulesOptions _options;
17 |
18 | ///
19 | /// Initializes a new instance of the class.
20 | ///
21 | /// The to discover 's.
22 | public StartupModuleRunner(StartupModulesOptions options)
23 | {
24 | _options = options;
25 | }
26 |
27 | ///
28 | /// Calls on the
29 | /// discoverd 's.
30 | ///
31 | public void ConfigureServices(IServiceCollection services, IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
32 | {
33 | var ctx = new ConfigureServicesContext(configuration, hostingEnvironment, _options);
34 |
35 | foreach (var cfg in _options.StartupModules)
36 | {
37 | cfg.ConfigureServices(services, ctx);
38 | }
39 | }
40 |
41 | ///
42 | /// Calls on the
43 | /// discovered .
44 | ///
45 | public void Configure(IApplicationBuilder app, IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
46 | {
47 | using var scope = app.ApplicationServices.CreateScope();
48 | var ctx = new ConfigureMiddlewareContext(configuration, hostingEnvironment, scope.ServiceProvider, _options);
49 | foreach (var cfg in _options.StartupModules)
50 | {
51 | cfg.Configure(app, ctx);
52 | }
53 | }
54 |
55 | ///
56 | /// Invokes the discovered instances.
57 | ///
58 | /// The application's root service provider.
59 | public async Task RunApplicationInitializers(IServiceProvider serviceProvider)
60 | {
61 | using var scope = serviceProvider.CreateScope();
62 | var applicationInitializers = _options.ApplicationInitializers
63 | .Select(t =>
64 | {
65 | try
66 | {
67 | return ActivatorUtilities.CreateInstance(scope.ServiceProvider, t);
68 | }
69 | catch (Exception ex)
70 | {
71 | throw new InvalidOperationException($"Failed to create instace of {nameof(IApplicationInitializer)} '{t.Name}'.", ex);
72 | }
73 | })
74 | .Cast();
75 |
76 | foreach (var initializer in applicationInitializers)
77 | {
78 | try
79 | {
80 | await initializer.Invoke();
81 | }
82 | catch (Exception ex)
83 | {
84 | throw new InvalidOperationException($"An exception occured during the execution of {nameof(IApplicationInitializer)} '{initializer.GetType().Name}'.", ex);
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/StartupModules/StartupModules.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | net6.0
5 | Henk Mollema
6 | Startup modules for ASP.NET Core.
7 | Copyright © Henk Mollema 2020
8 | true
9 | aspnetcore;startup;dependency injection;modules;modular
10 | https://github.com/henkmollema/StartupModules
11 | MIT
12 | git
13 | https://github.com/henkmollema/StartupModules
14 | embedded
15 | latest
16 | enable
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/StartupModules/StartupModulesExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.AspNetCore.Hosting;
5 | using Microsoft.Extensions.Configuration;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using StartupModules.Internal;
8 |
9 | namespace StartupModules;
10 |
11 | ///
12 | /// Provides extensions to configure the startup modules infrastructure and configure or discover instances.
13 | ///
14 | public static class StartupModulesExtensions
15 | {
16 | ///
17 | /// Configures startup modules and automatically discovers 's from the applications entry assembly.
18 | ///
19 | /// The instance.
20 | /// The instance.
21 | public static IWebHostBuilder UseStartupModules(this IWebHostBuilder builder) =>
22 | UseStartupModules(builder, options => options.DiscoverStartupModules());
23 |
24 | ///
25 | /// Configures startup modules and automatically discovers 's from the specified assemblies.
26 | ///
27 | /// The instance.
28 | /// The assemblies to discover startup modules from.
29 | /// The instance.
30 | public static IWebHostBuilder UseStartupModules(this IWebHostBuilder builder, params Assembly[] assemblies) =>
31 | UseStartupModules(builder, options => options.DiscoverStartupModules(assemblies));
32 |
33 | ///
34 | /// Configures startup modules with the specified configuration for .
35 | ///
36 | /// The instance.
37 | /// A callback to configure .
38 | /// The instance.
39 | public static IWebHostBuilder UseStartupModules(this IWebHostBuilder builder, Action configure) =>
40 | builder.ConfigureServices((hostContext, services) =>
41 | services.AddStartupModules(hostContext.Configuration, hostContext.HostingEnvironment, configure));
42 |
43 | ///
44 | /// Configures startup modules with the specified configuration for .
45 | ///
46 | /// The instance.
47 | /// The instance.
48 | public static WebApplicationBuilder UseStartupModules(this WebApplicationBuilder builder) =>
49 | builder.UseStartupModules(options => options.DiscoverStartupModules());
50 |
51 | ///
52 | /// Configures startup modules with the specified configuration for .
53 | ///
54 | /// The instance.
55 | /// The assemblies to discover startup modules from.
56 | /// The instance.
57 | public static WebApplicationBuilder UseStartupModules(this WebApplicationBuilder builder, params Assembly[] assemblies) =>
58 | builder.UseStartupModules(options => options.DiscoverStartupModules(assemblies));
59 |
60 | ///
61 | /// Configures startup modules with the specified configuration for .
62 | ///
63 | /// The instance.
64 | /// A callback to configure .
65 | /// The instance.
66 | public static WebApplicationBuilder UseStartupModules(this WebApplicationBuilder builder, Action configure)
67 | {
68 | builder.Services.AddStartupModules(builder.Configuration, builder.Environment, configure);
69 | return builder;
70 | }
71 |
72 | ///
73 | /// Configures startup modules with the specified configuration for .
74 | ///
75 | /// The service collection to add the StartupModules services to.
76 | /// The application's configuration.
77 | /// The application's environment information.
78 | /// A callback to configure .
79 | public static void AddStartupModules(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment environment, Action configure)
80 | {
81 | if (services == null)
82 | {
83 | throw new ArgumentNullException(nameof(services));
84 | }
85 | if (configuration == null)
86 | {
87 | throw new ArgumentNullException(nameof(configuration));
88 | }
89 | if (environment == null)
90 | {
91 | throw new ArgumentNullException(nameof(environment));
92 | }
93 |
94 | var options = new StartupModulesOptions();
95 | configure(options);
96 |
97 | if (options.StartupModules.Count == 0 && options.ApplicationInitializers.Count == 0)
98 | {
99 | // Nothing to do here
100 | return;
101 | }
102 |
103 | var runner = new StartupModuleRunner(options);
104 | services.AddSingleton(sp => ActivatorUtilities.CreateInstance(sp, runner));
105 |
106 | var configureServicesContext = new ConfigureServicesContext(configuration, environment, options);
107 | runner.ConfigureServices(services, configuration, environment);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/StartupModules/StartupModulesOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using Microsoft.AspNetCore.Builder;
6 | using StartupModules.Internal;
7 |
8 | namespace StartupModules;
9 |
10 | ///
11 | /// Specifies options for startup modules.
12 | ///
13 | public class StartupModulesOptions
14 | {
15 | ///
16 | /// Gets a collection of instances to configure the application with.
17 | ///
18 | public ICollection StartupModules { get; } = new List();
19 |
20 | ///
21 | /// Gets a collection of types to initialize the application with.
22 | ///
23 | public ICollection ApplicationInitializers { get; } = new List();
24 |
25 | ///
26 | /// Gets the settings.
27 | ///
28 | public IDictionary Settings { get; set; } = new Dictionary();
29 |
30 | ///
31 | /// Discovers implementations from the entry assembly of the application.
32 | ///
33 | public void DiscoverStartupModules() => DiscoverStartupModules(Assembly.GetEntryAssembly()!);
34 |
35 | ///
36 | /// Discovers implementations from the specified assemblies.
37 | ///
38 | public void DiscoverStartupModules(params Assembly[] assemblies)
39 | {
40 | if (assemblies == null || assemblies.Length == 0 || assemblies.All(a => a == null))
41 | {
42 | throw new ArgumentException("No assemblies to discover startup modules from specified.", nameof(assemblies));
43 | }
44 |
45 | foreach (var type in assemblies.SelectMany(a => a.ExportedTypes))
46 | {
47 | if (typeof(IStartupModule).IsAssignableFrom(type))
48 | {
49 | var instance = Activate(type);
50 | StartupModules.Add(instance);
51 | }
52 | else if (typeof(IApplicationInitializer).IsAssignableFrom(type))
53 | {
54 | ApplicationInitializers.Add(type);
55 | }
56 | }
57 | }
58 |
59 | ///
60 | /// Adds the instance of type .
61 | ///
62 | /// The type of the .
63 | /// The instance.
64 | public void AddStartupModule(T startupModule) where T : IStartupModule
65 | => StartupModules.Add(startupModule);
66 |
67 | ///
68 | /// Adds the implementation of type .
69 | /// The type will be activated using the default, parameterless constructor.
70 | ///
71 | /// The type of the .
72 | public void AddStartupModule() where T : IStartupModule
73 | => AddStartupModule(typeof(T));
74 |
75 | ///
76 | /// Adds a by the specified type.
77 | /// The type will be activated using the default, parameterless constructor.
78 | ///
79 | /// The type of the .
80 | public void AddStartupModule(Type type)
81 | {
82 | if (typeof(IStartupModule).IsAssignableFrom(type))
83 | {
84 | var instance = Activate(type);
85 | StartupModules.Add(instance);
86 | }
87 | else
88 | {
89 | throw new ArgumentException(
90 | $"Specified startup module '{type.Name}' does not implement {nameof(IStartupModule)}.",
91 | nameof(type));
92 | }
93 | }
94 |
95 | ///
96 | /// Adds an inline middleware configuration to the application.
97 | ///
98 | /// A callback to configure the middleware pipeline.
99 | public void ConfigureMiddleware(Action action) =>
100 | StartupModules.Add(new InlineMiddlewareConfiguration(action));
101 |
102 | private IStartupModule Activate(Type type)
103 | {
104 | try
105 | {
106 | return (IStartupModule)Activator.CreateInstance(type)!;
107 | }
108 | catch (Exception ex)
109 | {
110 | throw new InvalidOperationException($"Failed to create instance for {nameof(IStartupModule)} type '{type.Name}'.", ex);
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/test/StartupModules.Tests/FunctionalTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.AspNetCore.Hosting;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.AspNetCore.TestHost;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Xunit;
8 |
9 | namespace StartupModules.Tests;
10 |
11 | public class FunctionalTests
12 | {
13 | [Fact]
14 | public async Task ConfiguresServicesAndMiddleware()
15 | {
16 | var webHostBuilder = new WebHostBuilder()
17 | .UseStartupModules(c => c.AddStartupModule())
18 | .Configure(_ => { });
19 |
20 | using var server = new TestServer(webHostBuilder);
21 | var client = server.CreateClient();
22 | var msg = await client.GetStringAsync("/message");
23 |
24 | Assert.Equal("Hello, World!", msg);
25 | }
26 | }
27 |
28 | public class FooStartupModule : IStartupModule
29 | {
30 | public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context)
31 | {
32 | services.AddSingleton();
33 | }
34 |
35 | public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context)
36 | {
37 | app.Map("/message", sub => sub.Run(async ctx =>
38 | {
39 | var msgProvider = ctx.RequestServices.GetRequiredService();
40 | await ctx.Response.WriteAsync(msgProvider.GetMessage());
41 | }));
42 | }
43 | }
44 |
45 | public class MessageProvider
46 | {
47 | public string GetMessage() => "Hello, World!";
48 | }
49 |
--------------------------------------------------------------------------------
/test/StartupModules.Tests/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:50128/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "StartupModules.Tests": {
19 | "commandName": "Project"
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/test/StartupModules.Tests/StartupModuleTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.AspNetCore.Hosting;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Hosting;
6 | using Xunit;
7 |
8 | namespace StartupModules.Tests;
9 |
10 | public class StartupModuleTests
11 | {
12 | [Fact]
13 | public async Task ConfiguresContext()
14 | {
15 | var hostBuilder = CreateBuilder().UseStartupModules(o => o.AddStartupModule());
16 | using var host = hostBuilder.Build();
17 | await host.StartAsync();
18 | await host.StopAsync();
19 | }
20 |
21 | [Fact]
22 | public async Task DiscoversFromSpecifiedAssembly()
23 | {
24 | var hostBuilder = CreateBuilder().UseStartupModules(o => o.DiscoverStartupModules(typeof(FooStartupModule).Assembly));
25 | using var host = hostBuilder.Build();
26 | await host.StartAsync();
27 | await host.StopAsync();
28 | }
29 |
30 | [Fact]
31 | public async Task DiscoversFromEntryAssembly()
32 | {
33 | // Equivalent of calling UseStartupModules()
34 | var hostBuilder = CreateBuilder().UseStartupModules(o => o.DiscoverStartupModules());
35 | using var host = hostBuilder.Build();
36 | await host.StartAsync();
37 | await host.StopAsync();
38 | }
39 |
40 | [Fact]
41 | public async Task DiscoversFromEntryAssemblyWithDefaultMethod()
42 | {
43 | // Equivalent of calling UseStartupModules()
44 | var hostBuilder = CreateBuilder().UseStartupModules();
45 | using var host = hostBuilder.Build();
46 | await host.StartAsync();
47 | await host.StopAsync();
48 | }
49 |
50 | private IWebHostBuilder CreateBuilder() => new WebHostBuilder().UseKestrel().Configure(_ => { });
51 |
52 | public class FooStartupModule : IStartupModule
53 | {
54 | public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context)
55 | {
56 | Assert.NotNull(services);
57 | Assert.NotNull(context);
58 | Assert.NotNull(context.Configuration);
59 | Assert.NotNull(context.HostingEnvironment);
60 | }
61 |
62 | public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context)
63 | {
64 | Assert.NotNull(app);
65 | Assert.NotNull(context);
66 | Assert.NotNull(context.Configuration);
67 | Assert.NotNull(context.HostingEnvironment);
68 | Assert.NotNull(context.ServiceProvider);
69 | }
70 | }
71 |
72 | public class FooApplicationInitializer : IApplicationInitializer
73 | {
74 | private readonly IHostApplicationLifetime _applicationLifetime;
75 |
76 | public FooApplicationInitializer(IHostApplicationLifetime applicationLifetime)
77 | {
78 | _applicationLifetime = applicationLifetime;
79 | }
80 |
81 | public Task Invoke()
82 | {
83 | Assert.NotNull(_applicationLifetime);
84 | return Task.CompletedTask;
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/test/StartupModules.Tests/StartupModules.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | false
5 |
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/test/StartupModules.Tests/StartupModulesExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Microsoft.AspNetCore.Hosting;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using StartupModules.Internal;
6 | using Xunit;
7 |
8 | namespace StartupModules.Tests;
9 |
10 | public class StartupModulesExtensionsTests
11 | {
12 | [Fact]
13 | public void Noops_WhenNoStartupModulesAndApplicationInitializers()
14 | {
15 | var builder = new WebHostBuilder()
16 | .Configure(_ => { })
17 | .UseStartupModules(_ => { })
18 | .Build();
19 |
20 | Assert.Empty(builder.Services.GetService>().Where(x => x is ModulesStartupFilter));
21 | }
22 |
23 | [Fact]
24 | public void Noops_WhenNoStartupModulesAndApplicationInitializersInEntryAssembly()
25 | {
26 | var builder = new WebHostBuilder()
27 | .Configure(_ => { })
28 | .UseStartupModules()
29 | .Build();
30 |
31 | Assert.Empty(builder.Services.GetService>().Where(x => x is ModulesStartupFilter));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/StartupModules.Tests/StartupModulesOptionsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using StartupModules.Internal;
5 | using Xunit;
6 |
7 | namespace StartupModules.Tests;
8 |
9 | public class StartupModulesOptionsTests
10 | {
11 | [Fact]
12 | public void AddStartupModule_FromGenericType()
13 | {
14 | var options = new StartupModulesOptions();
15 | options.AddStartupModule();
16 |
17 | var module = Assert.Single(options.StartupModules);
18 | Assert.IsType(module);
19 | }
20 |
21 | [Fact]
22 | public void AddStartupModule_FromType()
23 | {
24 | var options = new StartupModulesOptions();
25 | options.AddStartupModule(typeof(FooStartupModule));
26 |
27 | var module = Assert.Single(options.StartupModules);
28 | Assert.IsType(module);
29 | }
30 |
31 | [Fact]
32 | public void AddStartupModule_FromInstance()
33 | {
34 | var options = new StartupModulesOptions();
35 | options.AddStartupModule(new FooStartupModule());
36 |
37 | var module = Assert.Single(options.StartupModules);
38 | Assert.IsType(module);
39 | }
40 |
41 | [Fact]
42 | public void ThrowsException_WhenTypeNotIStartupModule()
43 | {
44 | var options = new StartupModulesOptions();
45 | var ex = Assert.Throws("type", () => options.AddStartupModule(typeof(decimal)));
46 | var expectedEx = new ArgumentException($"Specified startup module '{typeof(decimal).Name}' does not implement {nameof(IStartupModule)}.", "type");
47 | Assert.Equal(expectedEx.Message, ex.Message);
48 | }
49 |
50 | [Fact]
51 | public void ConfigureMiddleware_AddsStartupModule()
52 | {
53 | var options = new StartupModulesOptions();
54 | options.ConfigureMiddleware((app, ctx) => { });
55 |
56 | var module = Assert.Single(options.StartupModules);
57 | Assert.IsType(module);
58 | module.ConfigureServices(null, null);
59 | module.Configure(null, null);
60 | }
61 |
62 | [Fact]
63 | public void ThrowsException_WhenNoParameterlessConstructor()
64 | {
65 | var options = new StartupModulesOptions();
66 | var ex = Assert.Throws(() => options.AddStartupModule());
67 | var expectedMsg = $"Failed to create instance for {nameof(IStartupModule)} type '{typeof(StartupModuleWithCtor).Name}'.";
68 | Assert.Equal(expectedMsg, ex.Message);
69 | }
70 |
71 | [Fact]
72 | public void ThrowsException_WithErrorConstructor()
73 | {
74 | var options = new StartupModulesOptions();
75 | var ex = Assert.Throws(() => options.AddStartupModule());
76 | var expectedMsg = $"Failed to create instance for {nameof(IStartupModule)} type '{typeof(StartupModuleWithErrorCtor).Name}'.";
77 | Assert.Equal(expectedMsg, ex.Message);
78 | }
79 |
80 | // Internal to avoid automatic discovery
81 | internal class FooStartupModule : IStartupModule
82 | {
83 | public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context) => throw new NotImplementedException();
84 | public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context) => throw new NotImplementedException();
85 | }
86 |
87 | // Internal to avoid automatic discovery
88 | internal class StartupModuleWithCtor : IStartupModule
89 | {
90 | public StartupModuleWithCtor(object _) { }
91 |
92 | public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context) => throw new NotImplementedException();
93 | public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context) => throw new NotImplementedException();
94 | }
95 |
96 | // Internal to avoid automatic discovery
97 | internal class StartupModuleWithErrorCtor : IStartupModule
98 | {
99 | public StartupModuleWithErrorCtor()
100 | {
101 | throw new ArgumentException();
102 | }
103 |
104 | public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context) => throw new NotImplementedException();
105 | public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context) => throw new NotImplementedException();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/test/StartupModules.Tests/StartupRunnerTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using Xunit;
5 |
6 | namespace StartupModules.Tests;
7 |
8 | public class StartupRunnerTests
9 | {
10 | [Fact]
11 | public void ConfiguresServices()
12 | {
13 | // Arrange
14 | var options = new StartupModulesOptions();
15 | options.AddStartupModule();
16 | var runner = new StartupModuleRunner(options);
17 | var services = new ServiceCollection();
18 |
19 | // Act
20 | runner.ConfigureServices(services, null, null);
21 |
22 | // Assert
23 | var sd = Assert.Single(services);
24 | Assert.Equal(typeof(MyStartupModule.MyService), sd.ImplementationType);
25 | }
26 |
27 | [Fact]
28 | public void Configures()
29 | {
30 | // Arrange
31 | var options = new StartupModulesOptions();
32 | var startupModule = new MyStartupModule();
33 | options.AddStartupModule(startupModule);
34 | var runner = new StartupModuleRunner(options);
35 |
36 | // Act
37 | runner.Configure(new ApplicationBuilder(new ServiceCollection().BuildServiceProvider()), null, null);
38 |
39 | // Assert
40 | Assert.True(startupModule.Configured);
41 | }
42 |
43 | [Fact]
44 | public async Task RunsApplicationInitializers()
45 | {
46 | // Arrange
47 | var options = new StartupModulesOptions();
48 | options.ApplicationInitializers.Add(typeof(MyAppInitializer));
49 | var runner = new StartupModuleRunner(options);
50 |
51 | // Act
52 | await runner.RunApplicationInitializers(new ServiceCollection().BuildServiceProvider());
53 |
54 | // Assert
55 | // wat do ¯\_(ツ)_/¯
56 | }
57 | }
58 |
59 | public class MyStartupModule : IStartupModule
60 | {
61 | public class MyService { }
62 |
63 | public bool Configured { get; private set; }
64 |
65 | public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context) => services.AddSingleton();
66 |
67 | public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context) => Configured = true;
68 | }
69 |
70 | public class MyAppInitializer : IApplicationInitializer
71 | {
72 | public Task Invoke() => Task.CompletedTask;
73 | }
74 |
--------------------------------------------------------------------------------
/test/TestWebApp/FooAppInitializer.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using StartupModules;
3 |
4 | namespace TestWebApp;
5 |
6 | public class FooAppInitializer : IApplicationInitializer
7 | {
8 | public Task Invoke()
9 | {
10 | return Task.CompletedTask;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/TestWebApp/HangfireStartupModule.cs:
--------------------------------------------------------------------------------
1 | using Hangfire.MemoryStorage;
2 | using StartupModules;
3 | using Webenable.Hangfire.Contrib;
4 |
5 | namespace TestWebApp;
6 |
7 | public class HangfireStartupModule : IStartupModule
8 | {
9 |
10 | public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context)
11 | {
12 | if (context.HostingEnvironment.IsDevelopment())
13 | {
14 | // Do something based on this
15 | }
16 |
17 | if ((bool)context.Options.Settings["AddHangfire"] == true)
18 | {
19 | services.AddHangfireContrib(c => c.UseMemoryStorage());
20 | }
21 | }
22 |
23 | public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context) { }
24 | }
25 |
--------------------------------------------------------------------------------
/test/TestWebApp/Program.cs:
--------------------------------------------------------------------------------
1 | using StartupModules;
2 |
3 | var builder = WebApplication.CreateBuilder(args);
4 | builder.UseStartupModules();
5 | var app = builder.Build();
6 |
7 | app.MapGet("/", () => "Hello, World!");
8 | app.Run();
--------------------------------------------------------------------------------
/test/TestWebApp/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "TestWebApp": {
4 | "commandName": "Project",
5 | "launchBrowser": true,
6 | "applicationUrl": "http://localhost:5000",
7 | "environmentVariables": {
8 | "ASPNETCORE_ENVIRONMENT": "Development"
9 | }
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/test/TestWebApp/TestWebApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | enable
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------