1+ // This software is part of the LittleBlocks framework
2+ // Copyright (C) 2024 LittleBlocks
3+ //
4+ // This program is free software: you can redistribute it and/or modify
5+ // it under the terms of the GNU Affero General Public License as published by
6+ // the Free Software Foundation, either version 3 of the License, or
7+ // (at your option) any later version.
8+ //
9+ // This program is distributed in the hope that it will be useful,
10+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ // GNU Affero General Public License for more details.
13+ //
14+ // You should have received a copy of the GNU Affero General Public License
15+ // along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+ namespace LittleBlocks . AspNetCore . Bootstrap ;
18+
19+ /// <summary>
20+ /// Bootstrapper for minimal APIs that provides the same configuration capabilities as the traditional AppBootstrapper
21+ /// but without requiring a Startup class.
22+ /// </summary>
23+ public sealed class MinimalApiBootstrapper :
24+ IBootstrapApplication ,
25+ IConfigureContainer ,
26+ IAddExtraConfigSection ,
27+ IHandleAdditionalException ,
28+ ISetDetailsLevel ,
29+ IExtendPipeline ,
30+ IConfigureRequestCorrelation ,
31+ IConfigureAuthentication ,
32+ IConfigureHealthChecks ,
33+ IConfigureApplicationBootstrapper
34+ {
35+ private readonly IConfiguration _configuration ;
36+ private readonly ConfigurationOptionBuilder _configurationOptionBuilder ;
37+ private readonly GlobalErrorHandlerConfigurationBuilder _errorHandlerBuilder ;
38+ private readonly List < Action < IServiceCollection , IConfiguration > > _pipelineExtenders =
39+ new List < Action < IServiceCollection , IConfiguration > > ( ) ;
40+
41+ private readonly IServiceCollection _services ;
42+ private readonly IHealthChecksBuilder _healthChecksBuilder ;
43+ private readonly AppInfo _appInfo ;
44+
45+ private readonly AuthOptions _authOptions ;
46+ private Action < IServiceCollection , IConfiguration > _containerFactory ;
47+ private Func < IExcludeRequests , IBuildOptions > _requestCorrelationExtender = cop => cop . EnforceCorrelation ( ) ;
48+
49+ public MinimalApiBootstrapper (
50+ IServiceCollection services ,
51+ IConfiguration configuration )
52+ {
53+ _services = services ?? throw new ArgumentNullException ( nameof ( services ) ) ;
54+ _configuration = configuration ?? throw new ArgumentNullException ( nameof ( configuration ) ) ;
55+ _configurationOptionBuilder = new ConfigurationOptionBuilder ( services , _configuration ) ;
56+ _errorHandlerBuilder = new GlobalErrorHandlerConfigurationBuilder ( services ) ;
57+
58+ _errorHandlerBuilder . UseStandardMessage ( ) ;
59+ _appInfo = _configuration . GetApplicationInfo ( ) ;
60+ _authOptions = _configuration . GetAuthOptions ( ) ;
61+
62+ _healthChecksBuilder = _services . AddHealthChecks ( ) ;
63+ }
64+
65+ public IAddExtraConfigSection AndSection < TSection > ( )
66+ where TSection : class , new ( )
67+ {
68+ _configurationOptionBuilder . And < TSection > ( ) ;
69+ return this ;
70+ }
71+
72+ public IAddExtraConfigSection AndSection < TSection > ( string section )
73+ where TSection : class , new ( )
74+ {
75+ _configurationOptionBuilder . And < TSection > ( section ) ;
76+ return this ;
77+ }
78+
79+ public IHandleAdditionalException HandleApplicationException < TApplicationBaseException > ( )
80+ where TApplicationBaseException : Exception
81+ {
82+ _errorHandlerBuilder . Handle < TApplicationBaseException > ( ) ;
83+ return this ;
84+ }
85+
86+ public void Bootstrap ( )
87+ {
88+ _configurationOptionBuilder . Build ( ) ;
89+
90+ _services . TryAddSingleton < IDateTimeProvider , DateTimeProvider > ( ) ;
91+ // Don't add IUrlHelper for minimal APIs as it requires MVC ActionContext
92+ _services . TryAddScoped < IArgumentsFormatter , ArgumentsFormatter > ( ) ;
93+ _services . TryAddSingleton ( _ => new ArgumentFormatterOptions ( ) ) ;
94+
95+ _services . AddDatabaseDeveloperPageExceptionFilter ( ) ;
96+ _services . AddHttpRequestContext ( ) ;
97+ _services . AddGlobalExceptionHandler ( _ => _errorHandlerBuilder . UseDefault ( ) ) ;
98+ _services . AddRequestCorrelation ( b => _requestCorrelationExtender ( b . ExcludeDefaultUrls ( ) ) ) ;
99+ _services . AddFeatureFlagging ( _configuration ) ;
100+
101+ // For minimal APIs, we don't add the default MVC services automatically
102+ // Users can still add controllers if needed via AddControllers() on the builder
103+ _services . AddDefaultCorsPolicy ( ) ;
104+ _services . AddAuthentication ( _authOptions ) ;
105+ _services . AddAuthorization ( ) ; // Required for minimal APIs
106+
107+ // Add minimal API specific services for OpenAPI/Swagger
108+ _services . AddEndpointsApiExplorer ( ) ;
109+ _services . AddSwaggerGen ( ) ;
110+
111+ _pipelineExtenders . ForEach ( e => e ( _services , _configuration ) ) ;
112+
113+ _containerFactory ( _services , _configuration ) ;
114+ }
115+
116+ public IAddExtraConfigSection AddConfigSection < TSection > ( )
117+ where TSection : class , new ( )
118+ {
119+ _configurationOptionBuilder . AddSection < TSection > ( ) ;
120+ return this ;
121+ }
122+
123+ public IAddExtraConfigSection AddConfigSection < TSection > ( string section )
124+ where TSection : class , new ( )
125+ {
126+ _configurationOptionBuilder . AddSection < TSection > ( section ) ;
127+ return this ;
128+ }
129+
130+ public IBootstrapApplication UseContainer < TContainer > ( ContainerFactory < TContainer > containerFactory )
131+ where TContainer : class
132+ {
133+ if ( containerFactory == null ) throw new ArgumentNullException ( nameof ( containerFactory ) ) ;
134+
135+ _containerFactory = containerFactory . Create ;
136+ return this ;
137+ }
138+
139+ public IConfigureRequestCorrelation UseStandardMessage ( )
140+ {
141+ _errorHandlerBuilder . UseStandardMessage ( ) ;
142+ return this ;
143+ }
144+
145+ public IConfigureRequestCorrelation UseUserErrors ( )
146+ {
147+ _errorHandlerBuilder . UseUserErrors ( ) ;
148+ return this ;
149+ }
150+
151+ public IConfigureRequestCorrelation UseDetailedErrors ( )
152+ {
153+ _errorHandlerBuilder . UseDetailedErrors ( ) ;
154+ return this ;
155+ }
156+
157+ public IConfigureAuthentication ConfigureCorrelation (
158+ Func < IExcludeRequests , IBuildOptions > optionsProvider )
159+ {
160+ _requestCorrelationExtender = optionsProvider ??
161+ throw new ArgumentNullException ( nameof ( optionsProvider ) ) ;
162+ return this ;
163+ }
164+
165+ public IConfigureAuthentication ConfigureCorrelation ( Func < IExcludeRequests , ICorrelateRequests > optionsProvider )
166+ {
167+ if ( optionsProvider == null ) throw new ArgumentNullException ( nameof ( optionsProvider ) ) ;
168+ _requestCorrelationExtender = r => optionsProvider ( r ) . EnforceCorrelation ( ) ;
169+ return this ;
170+ }
171+
172+ public IExtendPipeline Extend ( Action < IServiceCollection , IConfiguration > pipelineExtender )
173+ {
174+ if ( pipelineExtender == null ) throw new ArgumentNullException ( nameof ( pipelineExtender ) ) ;
175+ _pipelineExtenders . Add ( pipelineExtender ) ;
176+ return this ;
177+ }
178+
179+ public IConfigureHealthChecks ConfigureAuthentication ( Action < ISetAuthenticationMode > configure )
180+ {
181+ if ( configure == null ) throw new ArgumentNullException ( nameof ( configure ) ) ;
182+ configure ( _authOptions ) ;
183+ return this ;
184+ }
185+
186+ public IExtendPipeline ConfigureHealthChecks ( Action < IHealthChecksBuilder > configure )
187+ {
188+ if ( configure == null ) throw new ArgumentNullException ( nameof ( configure ) ) ;
189+
190+ configure ( _healthChecksBuilder ) ;
191+
192+ return this ;
193+ }
194+
195+ public IHandleAdditionalException AndHandle < TThirdPartyBaseException > ( )
196+ where TThirdPartyBaseException : Exception
197+ {
198+ _errorHandlerBuilder . AndHandle < TThirdPartyBaseException > ( ) ;
199+ return this ;
200+ }
201+
202+ public IHandleAdditionalException AndHandle < TThirdPartyBaseException > (
203+ Func < TThirdPartyBaseException , bool > predicate ) where TThirdPartyBaseException : Exception
204+ {
205+ _errorHandlerBuilder . AndHandle ( predicate ) ;
206+ return this ;
207+ }
208+
209+ public IHandleAdditionalException AndHandle < TThirdPartyBaseException > (
210+ Func < ISetErrorBuilder < TThirdPartyBaseException > , IProvideErrorBuilder < TThirdPartyBaseException > >
211+ errorBuilderProvider ) where TThirdPartyBaseException : Exception
212+ {
213+ _errorHandlerBuilder . AndHandle ( errorBuilderProvider ) ;
214+ return this ;
215+ }
216+
217+ public IHandleAdditionalException AndHandle < TThirdPartyBaseException > (
218+ Func < ISetErrorBuilder < TThirdPartyBaseException > , IProvideErrorBuilder < TThirdPartyBaseException > >
219+ errorBuilderProvider , Func < TThirdPartyBaseException , bool > predicate )
220+ where TThirdPartyBaseException : Exception
221+ {
222+ _errorHandlerBuilder . AndHandle ( errorBuilderProvider , predicate ) ;
223+ return this ;
224+ }
225+ }
0 commit comments