-
-
Notifications
You must be signed in to change notification settings - Fork 122
/
WolverineHttpOptions.cs
309 lines (260 loc) · 10.2 KB
/
WolverineHttpOptions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
using System.Text.Json;
using JasperFx.CodeGeneration.Frames;
using JasperFx.Core;
using JasperFx.Core.Reflection;
using Lamar;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Wolverine.Configuration;
using Wolverine.Http.CodeGen;
using Wolverine.Http.Policies;
using Wolverine.Http.Resources;
using Wolverine.Http.Runtime;
using Wolverine.Http.Runtime.MultiTenancy;
using Wolverine.Middleware;
namespace Wolverine.Http;
public enum JsonUsage
{
SystemTextJson,
NewtonsoftJson
}
public interface ITenantDetectionPolicies
{
/// <summary>
/// Try to detect the tenant id from the named route argument
/// </summary>
/// <param name="routeArgumentName"></param>
void IsRouteArgumentNamed(string routeArgumentName);
/// <summary>
/// Try to detect the tenant id from an expected query string value
/// </summary>
/// <param name="key"></param>
void IsQueryStringValue(string key);
/// <summary>
/// Try to detect the tenant id from a request header
/// if it exists
/// </summary>
/// <param name="headerKey"></param>
void IsRequestHeaderValue(string headerKey);
/// <summary>
/// Try to detect the tenant id from the ClaimsPrincipal for the
/// current request
/// </summary>
/// <param name="claimType"></param>
void IsClaimTypeNamed(string claimType);
/// <summary>
/// Simplistic tenant id detection that uses the sub domain name of the current
/// request location as the tenant id
/// </summary>
void IsSubDomainName();
/// <summary>
/// Assert that the tenant id was successfully detected, and if no tenant id
/// is found, return a ProblemDetails with a 400 status code
/// </summary>
void AssertExists();
/// <summary>
/// Register a custom tenant detection strategy. Be away though, this object
/// will be resolved from your application container, but will be done as a Singleton
/// scoping
/// </summary>
/// <typeparam name="T"></typeparam>
void DetectWith<T>() where T : ITenantDetection;
/// <summary>
/// Register a custom tenant detection strategy
/// </summary>
/// <param name="detection"></param>
void DetectWith(ITenantDetection detection);
/// <summary>
/// If no tenant id is detected, this value should be used for the Tenant Id
/// </summary>
/// <param name="defaultTenantId"></param>
void DefaultIs(string defaultTenantId);
}
[Singleton]
public class WolverineHttpOptions
{
public WolverineHttpOptions()
{
Policies.Add(new HttpAwarePolicy());
Policies.Add(new RequestIdPolicy());
Policies.Add(new RequiredEntityPolicy());
Policies.Add(TenantIdDetection);
}
public async ValueTask<string?> TryDetectTenantId(HttpContext httpContext)
{
foreach (var strategy in TenantIdDetection.Strategies)
{
var tenantId = await strategy.DetectTenant(httpContext);
if (tenantId.IsNotEmpty()) return tenantId;
}
return null;
}
public string? TryDetectTenantIdSynchronously(HttpContext httpContext)
{
return TenantIdDetection
.Strategies
.OfType<ISynchronousTenantDetection>()
.Select(strategy => strategy.DetectTenantSynchronously(httpContext))
.FirstOrDefault(tenantId => tenantId.IsNotEmpty());
}
internal TenantIdDetection TenantIdDetection { get; } = new();
internal Lazy<JsonSerializerOptions> JsonSerializerOptions { get; set; } = new(() => new JsonSerializerOptions());
internal JsonSerializerSettings NewtonsoftSerializerSettings { get; set; } = new();
internal HttpGraph? Endpoints { get; set; }
internal MiddlewarePolicy Middleware { get; } = new();
public List<IHttpPolicy> Policies { get; } = new();
public List<IResourceWriterPolicy> ResourceWriterPolicies { get; } = new();
/// <summary>
/// Configure built in tenant id detection strategies
/// </summary>
public ITenantDetectionPolicies TenantId => TenantIdDetection;
/// <summary>
/// Opt into using Newtonsoft.Json for all JSON serialization in the Wolverine
/// Http handlers
/// </summary>
/// <param name="configure"></param>
public void UseNewtonsoftJsonForSerialization(Action<JsonSerializerSettings>? configure = null)
{
configure?.Invoke(NewtonsoftSerializerSettings);
Endpoints.UseNewtonsoftJson();
}
/// <summary>
/// Customize Wolverine's handling of parameters to HTTP endpoint methods
/// </summary>
/// <typeparam name="T"></typeparam>
public void AddParameterHandlingStrategy<T>() where T : IParameterStrategy, new()
{
AddParameterHandlingStrategy(new T());
}
/// <summary>
/// Customize Wolverine's handling of parameters to HTTP endpoint methods
/// </summary>
/// <param name="strategy"></param>
/// <exception cref="NotImplementedException"></exception>
public void AddParameterHandlingStrategy(IParameterStrategy strategy)
{
Endpoints!.InsertParameterStrategy(strategy);
}
#region sample_RequireAuthorizeOnAll
/// <summary>
/// Equivalent of calling RequireAuthorization() on all wolverine endpoints
/// </summary>
public void RequireAuthorizeOnAll()
{
ConfigureEndpoints(e => e.RequireAuthorization());
}
#endregion
/// <summary>
/// Add a new IEndpointPolicy for the Wolverine endpoints
/// </summary>
/// <typeparam name="T"></typeparam>
public void AddPolicy<T>() where T : IHttpPolicy, new()
{
Policies.Add(new T());
}
/// <summary>
/// Add a new IResourceWriterPolicy for the Wolverine endpoints
/// </summary>
/// <typeparam name="T"></typeparam>
public void AddResourceWriterPolicy<T>() where T : IResourceWriterPolicy, new()
{
ResourceWriterPolicies.Add(new T());
}
/// <summary>
/// Add a new IResourceWriterPolicy for the Wolverine endpoints
/// </summary>
/// <typeparam name="T"></typeparam>
public void AddResourceWriterPolicy<T>(T policy) where T : IResourceWriterPolicy
{
ResourceWriterPolicies.Add(policy);
}
/// <summary>
/// Apply user-defined customizations to how endpoints are handled
/// by Wolverine
/// </summary>
/// <param name="configure"></param>
public void ConfigureEndpoints(Action<HttpChain> configure)
{
var policy = new LambdaHttpPolicy((c, _, _) => configure(c));
Policies.Add(policy);
}
/// <summary>
/// Add middleware only on handlers where the message type can be cast to the message
/// type of the middleware type
/// </summary>
/// <param name="middlewareType"></param>
public void AddMiddlewareByMessageType(Type middlewareType)
{
Middleware.AddType(middlewareType, chain => chain is HttpChain).MatchByMessageType = true;
}
/// <summary>
/// Add Wolverine middleware to message handlers
/// </summary>
/// <param name="filter">If specified, limits the applicability of the middleware to certain message types</param>
/// <typeparam name="T">The actual middleware type</typeparam>
public void AddMiddleware<T>(Func<HttpChain, bool>? filter = null)
{
AddMiddleware(typeof(T), filter);
}
/// <summary>
/// Add Wolverine middleware to message handlers
/// </summary>
/// <param name="middlewareType">The actual middleware type</param>
/// <param name="filter">If specified, limits the applicability of the middleware to certain message types</param>
public void AddMiddleware(Type middlewareType, Func<HttpChain, bool>? filter = null)
{
Func<IChain, bool> chainFilter = c => c is HttpChain;
if (filter != null)
{
chainFilter = c => c is HttpChain e && filter(e);
}
Middleware.AddType(middlewareType, chainFilter);
}
/// <summary>
/// From this url, forward a JSON serialized message by publishing through Wolverine
/// </summary>
/// <param name="httpMethod"></param>
/// <param name="url"></param>
/// <param name="customize">Optionally customize the HttpChain handling for elements like validation</param>
/// <typeparam name="T"></typeparam>
public RouteHandlerBuilder PublishMessage<T>(HttpMethod httpMethod, string url, Action<HttpChain>? customize = null)
{
#pragma warning disable CS4014
var method = MethodCall.For<PublishingEndpoint<T>>(x => x.PublishAsync(default!, null!, null!));
#pragma warning restore CS4014
var chain = Endpoints!.Add(method, httpMethod, url);
chain.MapToRoute(httpMethod.ToString(), url);
chain.DisplayName = $"Forward {typeof(T).FullNameInCode()} to Wolverine";
chain.OperationId = $"Publish:{typeof(T).FullNameInCode()}";
customize?.Invoke(chain);
return chain.Metadata;
}
public RouteHandlerBuilder PublishMessage<T>(string url, Action<HttpChain>? customize = null)
{
return PublishMessage<T>(HttpMethod.Post, url, customize);
}
/// <summary>
/// From this url, forward a JSON serialized message by sending through Wolverine
/// </summary>
/// <param name="httpMethod"></param>
/// <param name="url"></param>
/// <param name="customize">Optionally customize the HttpChain handling for elements like validation</param>
/// <typeparam name="T"></typeparam>
public RouteHandlerBuilder SendMessage<T>(HttpMethod httpMethod, string url, Action<HttpChain>? customize = null)
{
#pragma warning disable CS4014
var method = MethodCall.For<SendingEndpoint<T>>(x => x.SendAsync(default!, null!, null!));
#pragma warning restore CS4014
var chain = Endpoints!.Add(method, httpMethod, url);
chain.MapToRoute(httpMethod.ToString(), url);
chain.DisplayName = $"Forward {typeof(T).FullNameInCode()} to Wolverine";
chain.OperationId = $"Send:{typeof(T).FullNameInCode()}";
customize?.Invoke(chain);
return chain.Metadata;
}
public RouteHandlerBuilder SendMessage<T>(string url, Action<HttpChain>? customize = null)
{
return SendMessage<T>(HttpMethod.Post, url, customize);
}
}