-
Notifications
You must be signed in to change notification settings - Fork 97
/
DotvvmPresenter.cs
590 lines (515 loc) · 28.8 KB
/
DotvvmPresenter.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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using DotVVM.Framework.Binding;
using DotVVM.Framework.Binding.Expressions;
using DotVVM.Framework.Binding.Properties;
using DotVVM.Framework.Compilation;
using DotVVM.Framework.Compilation.Binding;
using DotVVM.Framework.Configuration;
using DotVVM.Framework.Controls;
using DotVVM.Framework.Runtime;
using DotVVM.Framework.Runtime.Filters;
using DotVVM.Framework.Security;
using DotVVM.Framework.Utils;
using DotVVM.Framework.ViewModel;
using DotVVM.Framework.ViewModel.Serialization;
using Microsoft.Extensions.DependencyInjection;
using DotVVM.Framework.Runtime.Tracing;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Security;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using DotVVM.Framework.ViewModel.Validation;
namespace DotVVM.Framework.Hosting
{
[NotAuthorized] // DotvvmPresenter handles authorization itself, allowing authorization on it would make [NotAuthorized] attribute useless on ViewModel, since request would be interrupted earlier that VM is found
public class DotvvmPresenter : IDotvvmPresenter
{
private readonly DotvvmConfiguration configuration;
/// <summary>
/// Initializes a new instance of the <see cref="DotvvmPresenter" /> class.
/// </summary>
public DotvvmPresenter(DotvvmConfiguration configuration, IDotvvmViewBuilder viewBuilder, IViewModelLoader viewModelLoader, IViewModelSerializer viewModelSerializer,
IOutputRenderer outputRender, ICsrfProtector csrfProtector, IViewModelParameterBinder viewModelParameterBinder,
StaticCommandExecutor staticCommandExecutor
)
{
this.configuration = configuration;
DotvvmViewBuilder = viewBuilder;
ViewModelLoader = viewModelLoader;
ViewModelSerializer = viewModelSerializer;
OutputRenderer = outputRender;
CsrfProtector = csrfProtector;
ViewModelParameterBinder = viewModelParameterBinder;
StaticCommandExecutor = staticCommandExecutor;
ApplicationPath = configuration.ApplicationPhysicalPath;
SecurityConfiguration = configuration.Security;
}
public IDotvvmViewBuilder DotvvmViewBuilder { get; }
public IViewModelLoader ViewModelLoader { get; }
public IViewModelSerializer ViewModelSerializer { get; }
public IOutputRenderer OutputRenderer { get; }
public ICsrfProtector CsrfProtector { get; }
public IViewModelParameterBinder ViewModelParameterBinder { get; }
public DotvvmSecurityConfiguration SecurityConfiguration { get; }
public StaticCommandExecutor StaticCommandExecutor { get; }
public string ApplicationPath { get; }
/// <summary>
/// Processes the request.
/// </summary>
public async Task ProcessRequest(IDotvvmRequestContext context)
{
try
{
await ProcessRequestCore(context);
}
catch (UnauthorizedAccessException)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
catch (CorruptedCsrfTokenException ex)
{
// TODO this should be done by IOutputRender or something like that. IOutputRenderer does not support that, so should we make another IJsonErrorOutputWriter?
context.HttpContext.Response.StatusCode = 400;
context.HttpContext.Response.ContentType = "application/json; charset=utf-8";
var settings = DefaultSerializerSettingsProvider.Instance.Settings;
await context.HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(new { action = "invalidCsrfToken", message = ex.Message }, settings));
}
catch (DotvvmExceptionBase ex)
{
if (ex.GetLocation() is { FileName: not null } location)
{
ex.Location = location with { FileName = Path.Combine(ApplicationPath, location.FileName) };
}
throw;
}
}
/// <summary>
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task ProcessRequestCore(IDotvvmRequestContext context)
{
if (context.RequestType == DotvvmRequestType.Unknown)
{
await context.InterruptRequestAsMethodNotAllowedAsync();
}
await ValidateSecFetchHeaders(context);
var requestTracer = context.Services.GetRequiredService<AggregateRequestTracer>();
if (context.RequestType == DotvvmRequestType.StaticCommand)
{
await ProcessStaticCommandRequest(context);
await requestTracer.TraceEvent(RequestTracingConstants.StaticCommandExecuted, context);
return;
}
var isPostBack = context.RequestType == DotvvmRequestType.Command;
// build the page view
var page = DotvvmViewBuilder.BuildView(context);
page.SetValue(Internal.RequestContextProperty, context);
context.View = page;
await requestTracer.TraceEvent(RequestTracingConstants.ViewInitialized, context);
// locate and create the view model
context.ViewModel = ViewModelLoader.InitializeViewModel(context, page);
// get action filters
var viewModelFilters = ActionFilterHelper.GetActionFilters<IViewModelActionFilter>(context.ViewModel.GetType())
.Concat(context.Configuration.Runtime.GlobalFilters.OfType<IViewModelActionFilter>());
var requestFilters = ActionFilterHelper.GetActionFilters<IPageActionFilter>(context.ViewModel.GetType())
.Concat(context.Configuration.Runtime.GlobalFilters.OfType<IPageActionFilter>());
foreach (var f in requestFilters)
{
await f.OnPageInitializedAsync(context);
}
try
{
// run the preinit phase in the page
DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.PreInit, context);
page.DataContext = context.ViewModel;
// run OnViewModelCreated on action filters
foreach (var filter in viewModelFilters)
{
await filter.OnViewModelCreatedAsync(context);
}
await requestTracer.TraceEvent(RequestTracingConstants.ViewModelCreated, context);
// perform parameter binding
if (context.ViewModel is DotvvmViewModelBase dotvvmViewModelBase)
{
dotvvmViewModelBase.ExecuteOnViewModelRecursive(v => ViewModelParameterBinder.BindParameters(context, v));
}
else
{
ViewModelParameterBinder.BindParameters(context, context.ViewModel);
}
// init the view model lifecycle
if (context.ViewModel is IDotvvmViewModel viewModel)
{
viewModel.Context = context;
ChildViewModelsCache.SetViewModelClientPath(viewModel, ChildViewModelsCache.RootViewModelPath);
await viewModel.Init();
}
// run the init phase in the page
DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.Init, context);
await requestTracer.TraceEvent(RequestTracingConstants.InitCompleted, context);
object? commandResult = null;
if (!isPostBack)
{
// perform standard get
if (context.ViewModel is IDotvvmViewModel)
{
await ((IDotvvmViewModel)context.ViewModel).Load();
}
// run the load phase in the page
DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.Load, context);
await requestTracer.TraceEvent(RequestTracingConstants.LoadCompleted, context);
}
else
{
// perform the postback
string postData;
using (var sr = new StreamReader(ReadRequestBody(context.HttpContext.Request, context.Route?.RouteName)))
{
postData = await sr.ReadToEndAsync();
}
ViewModelSerializer.PopulateViewModel(context, postData);
// run OnViewModelDeserialized on action filters
foreach (var filter in viewModelFilters)
{
await filter.OnViewModelDeserializedAsync(context);
}
await requestTracer.TraceEvent(RequestTracingConstants.ViewModelDeserialized, context);
// validate CSRF token
try
{
CsrfProtector.VerifyToken(context, context.CsrfToken.NotNull());
}
catch (SecurityException exc)
{
await context.InterruptRequestAsync(HttpStatusCode.BadRequest, exc.Message);
}
if (context.ViewModel is IDotvvmViewModel)
{
await ((IDotvvmViewModel)context.ViewModel).Load();
}
// run the load phase in the page
DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.Load, context);
await requestTracer.TraceEvent(RequestTracingConstants.LoadCompleted, context);
// invoke the postback command
var actionInfo = ViewModelSerializer.ResolveCommand(context, page).NotNull("Command not found?");
// get filters
var methodFilters = context.Configuration.Runtime.GlobalFilters.OfType<ICommandActionFilter>()
.Concat(ActionFilterHelper.GetActionFilters<ICommandActionFilter>(context.ViewModel.GetType()));
if (actionInfo.Binding?.GetProperty<ActionFiltersBindingProperty>(ErrorHandlingMode.ReturnNull) is ActionFiltersBindingProperty filters)
methodFilters = methodFilters.Concat(filters.Filters.OfType<ICommandActionFilter>());
var commandTimer = ValueStopwatch.StartNew();
try
{
commandResult = await ExecuteCommand(actionInfo, context, methodFilters);
}
finally
{
DotvvmMetrics.CommandInvocationDuration.Record(
commandTimer.ElapsedSeconds,
new KeyValuePair<string, object?>("command", actionInfo.Binding!.ToString()),
new KeyValuePair<string, object?>("result", context.CommandException is null ? "Ok" :
context.IsCommandExceptionHandled ? "HandledException" :
"UnhandledException"));
}
await requestTracer.TraceEvent(RequestTracingConstants.CommandExecuted, context);
}
if (context.ViewModel is IDotvvmViewModel)
{
await ((IDotvvmViewModel)context.ViewModel).PreRender();
}
// run the prerender phase in the page
DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.PreRender, context);
// run the prerender complete phase in the page
DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.PreRenderComplete, context);
await requestTracer.TraceEvent(RequestTracingConstants.PreRenderCompleted, context);
// generate CSRF token if required
if (string.IsNullOrEmpty(context.CsrfToken) && !context.Configuration.ExperimentalFeatures.LazyCsrfToken.IsEnabledForRoute(context.Route?.RouteName))
{
context.CsrfToken = CsrfProtector.GenerateToken(context);
}
// run OnViewModelSerializing on action filters
foreach (var filter in viewModelFilters)
{
await filter.OnViewModelSerializingAsync(context);
}
await requestTracer.TraceEvent(RequestTracingConstants.ViewModelSerialized, context);
ViewModelSerializer.BuildViewModel(context, commandResult);
if (context.RequestType == DotvvmRequestType.Navigate)
{
await OutputRenderer.WriteHtmlResponse(context, page);
}
else
{
Debug.Assert(context.RequestType is DotvvmRequestType.Command or DotvvmRequestType.SpaNavigate);
// postback or SPA content
var postBackUpdates = OutputRenderer.RenderPostbackUpdatedControls(context, page);
ViewModelSerializer.AddPostBackUpdatedControls(context, postBackUpdates);
// resources must be added after the HTML is rendered - some controls may request resources in the render phase
ViewModelSerializer.AddNewResources(context);
await OutputRenderer.WriteViewModelResponse(context, page);
}
await requestTracer.TraceEvent(RequestTracingConstants.OutputRendered, context);
foreach (var f in requestFilters) await f.OnPageRenderedAsync(context);
}
catch (CorruptedCsrfTokenException) { throw; }
catch (DotvvmInterruptRequestExecutionException ex) when (ex.InterruptReason == InterruptReason.CachedViewModelMissing)
{
// the client needs to repeat the postback and send the full viewmodel
await context.SetCachedViewModelMissingResponse();
throw;
}
catch (DotvvmInterruptRequestExecutionException) { throw; }
catch (DotvvmHttpException) { throw; }
catch (Exception ex)
{
// run OnPageException on action filters
foreach (var filter in requestFilters)
{
await filter.OnPageExceptionAsync(context, ex);
if (context.IsPageExceptionHandled)
{
context.InterruptRequest();
}
}
throw;
}
finally
{
if (context.ViewModel != null)
{
ViewModelLoader.DisposeViewModel(context.ViewModel);
}
StaticCommandExecutor.DisposeServices(context);
}
}
public async Task ProcessStaticCommandRequest(IDotvvmRequestContext context)
{
try
{
JObject postData;
using (var jsonReader = new JsonTextReader(new StreamReader(ReadRequestBody(context.HttpContext.Request, routeName: null))))
{
postData = await JObject.LoadAsync(jsonReader);
}
// validate csrf token
context.CsrfToken = (postData["$csrfToken"]?.Value<string>()).NotNull("$csrfToken is required");
CsrfProtector.VerifyToken(context, context.CsrfToken);
var knownTypes = postData["knownTypeMetadata"]?.Values<string>().WhereNotNull().ToArray() ?? Array.Empty<string>();
var argumentPaths = postData["argumentPaths"]?.Values<string?>().ToArray();
var command = (postData["command"]?.Value<string>()).NotNull("command is required");
var arguments = (postData["args"] as JArray).NotNull("args is required");
var executionPlan = StaticCommandExecutor.DecryptPlan(command);
var actionInfo = new ActionInfo(
binding: null,
() => { return StaticCommandExecutor.Execute(executionPlan, arguments, argumentPaths, context); },
false,
executionPlan.Method,
argumentPaths
);
var filters = context.Configuration.Runtime.GlobalFilters.OfType<ICommandActionFilter>()
.Concat(executionPlan.GetAllMethods().SelectMany(m => ActionFilterHelper.GetActionFilters<ICommandActionFilter>(m)))
.ToArray();
var commandTimer = ValueStopwatch.StartNew();
object? result = null;
try
{
result = await ExecuteCommand(actionInfo, context, filters);
}
finally
{
DotvvmMetrics.StaticCommandInvocationDuration.Record(
commandTimer.ElapsedSeconds,
new KeyValuePair<string, object?>("command", executionPlan.ToString()),
new KeyValuePair<string, object?>("result", context.CommandException is null ? "Ok" :
context.IsCommandExceptionHandled ? "HandledException" :
"UnhandledException"));
}
await OutputRenderer.WriteStaticCommandResponse(
context,
ViewModelSerializer.BuildStaticCommandResponse(context, result, knownTypes));
}
finally
{
StaticCommandExecutor.DisposeServices(context);
}
}
protected async Task<object?> ExecuteCommand(ActionInfo action, IDotvvmRequestContext context, IEnumerable<ICommandActionFilter> methodFilters)
{
// run OnCommandExecuting on action filters
foreach (var filter in methodFilters)
{
await filter.OnCommandExecutingAsync(context, action);
}
try
{
var commandResultOrNotYetComputedAwaitable = action.Action();
return await TaskUtils.ToObjectTask(commandResultOrNotYetComputedAwaitable);
}
catch (Exception ex)
{
if (ex is TargetInvocationException && ex.InnerException is object)
{
ex = ex.InnerException;
}
if (ex is DotvvmInvalidStaticCommandModelStateException { StaticCommandModelState: {} staticCommandModelState })
{
if (context.RequestType != DotvvmRequestType.StaticCommand)
throw new InvalidOperationException($"The StaticCommandModelState type may only be used in StaticCommand requests. Please use Context.ModelState in Commands.");
await RespondWithStaticCommandValidationFailure(action, context, staticCommandModelState);
context.IsCommandExceptionHandled = true;
}
else if (ex is DotvvmInterruptRequestExecutionException)
{
throw new DotvvmInterruptRequestExecutionException("The request execution was interrupted in the command!", ex);
}
context.CommandException = ex;
}
finally
{
// run OnCommandExecuted on action filters
foreach (var filter in methodFilters.Reverse())
{
await filter.OnCommandExecutedAsync(context, action, context.CommandException);
}
if (context.CommandException != null && !context.IsCommandExceptionHandled)
{
throw new Exception("Unhandled exception occurred in the command!", context.CommandException);
}
}
return null;
}
async Task RespondWithStaticCommandValidationFailure(ActionInfo action, IDotvvmRequestContext context, StaticCommandModelState staticCommandModelState)
{
var invokedMethod = action.InvokedMethod!;
var staticCommandAttribute = invokedMethod.GetCustomAttribute<AllowStaticCommandAttribute>();
if (staticCommandAttribute?.Validation == StaticCommandValidation.None)
throw new Exception($"Could not respond with validation failure, validation is disabled on method {ReflectionUtils.FormatMethodInfo(invokedMethod)}. Use [AllowStaticCommand(StaticCommandValidation.Manual)] to allow validation.");
if (staticCommandModelState.Errors.FirstOrDefault(e => !e.IsResolved) is {} unresolvedError)
throw new Exception("Could not respond with validation failure, some errors have unresolved paths: " + unresolvedError);
DotvvmMetrics.ValidationErrorsReturned.Record(
staticCommandModelState.ErrorsInternal.Count,
context.RouteLabel(),
context.RequestTypeLabel()
);
var jObject = new JObject
{
[ "modelState" ] = JArray.FromObject(staticCommandModelState.Errors),
[ "action" ] = "validationErrors"
};
var result = jObject.ToString();
context.HttpContext.Response.ContentType = "application/json";
await context.HttpContext.Response.WriteAsync(result);
throw new DotvvmInterruptRequestExecutionException(InterruptReason.ArgumentsValidationFailed, "Argument contain validation errors!");
}
async Task ValidateSecFetchHeaders(IDotvvmRequestContext context)
{
var route = context.Route?.RouteName;
var requestType = DotvvmRequestContext.DetermineRequestType(context.HttpContext);
var isPost = requestType is DotvvmRequestType.Command or DotvvmRequestType.StaticCommand;
var checksAllowed = (isPost ? SecurityConfiguration.VerifySecFetchForCommands : SecurityConfiguration.VerifySecFetchForPages).IsEnabledForRoute(route);
var dest = context.HttpContext.Request.Headers["Sec-Fetch-Dest"];
var site = context.HttpContext.Request.Headers["Sec-Fetch-Site"];
if (SecurityConfiguration.RequireSecFetchHeaders.IsEnabledForRoute(route))
if (string.IsNullOrEmpty(dest) || string.IsNullOrEmpty(site))
await context.RejectRequest("Sec-Fetch-Dest header is required. Please, use a web browser with security in mind: https://www.mozilla.org/en-US/firefox/new/");
// if the request has Dest: iframe, check if we allow iframes. Otherwise, we can throw an error right away, since the iframe will not load anyway due to the X-Frame-Options header
if (dest is "frame" or "iframe")
{
if (SecurityConfiguration.FrameOptionsCrossOrigin.IsEnabledForRoute(route))
{ // fine
}
else if (SecurityConfiguration.FrameOptionsSameOrigin.IsEnabledForRoute(route) && site == "same-origin")
{ // samesite allowed - also fine
}
else
{
if (site == "same-origin")
await context.RejectRequest($"""
Same site iframe are disabled in this application.
If you are the developer, you can enable iframes by setting DotvvmConfiguration.Security.FrameOptionsSameOrigin.IncludeRoute("{route}")
""");
else
await context.RejectRequest($"""
Cross site iframe are disabled in this application.
If you are the developer, you can enable cross-site iframes by setting DotvvmConfiguration.Security.FrameOptionsCrossOrigin.IncludeRoute("{route}"). Note that it's not recommended to enable cross-site iframes for sites / pages where security is important (due to Clickjacking)
""");
}
}
if (!checksAllowed || string.IsNullOrEmpty(dest) || string.IsNullOrEmpty(site))
return;
if (isPost)
{
if (site != "same-origin")
await context.RejectRequest($"Cross site postbacks are disabled.");
if (dest != "empty")
await context.RejectRequest($"Postbacks must have Sec-Fetch-Dest: empty");
}
else
{
if (dest is "document" or "frame" or "iframe")
{ // fine, this is allowed even cross-site
}
// if SPA is used, dest will be empty, since it's initiated from JS
// we only allow this with the X-DotVVM-SpaContentPlaceHolder header
// we "trust" the client - as if he lies about it being a SPA request,
// he'll will just get a redirect response, not anything useful
else if (dest is "empty")
{
if (context.RequestType is not DotvvmRequestType.SpaNavigate)
await context.RejectRequest($"""
Pages can not be loaded using Javascript for security reasons.
Try refreshing the page to get rid of the error.
If you are the developer, you can disable this check by setting DotvvmConfiguration.Security.VerifySecFetchForPages.ExcludeRoute("{route}"). [dest: {dest}, site: {site}]
""");
if (site != "same-origin")
await context.RejectRequest($"Cross site SPA requests are disabled.");
}
else
await context.RejectRequest($"Cannot load a DotVVM page with Sec-Fetch-Dest: {dest}.");
}
}
Stream ReadRequestBody(IHttpRequest request, string? routeName)
{
request.Headers.TryGetValue("Content-Encoding", out var encodingValue);
var encoding = encodingValue?.FirstOrDefault();
var limitLengthHelp = "To increase the maximum request size, use the DotvvmConfiguration.Runtime.MaxPostbackSizeBytes option.";
if (encoding is null)
return LimitLengthStream.LimitLength(request.Body, configuration.Runtime.MaxPostbackSizeBytes, limitLengthHelp);
if (encoding is "gzip")
{
var enabled = routeName is null ? this.configuration.Runtime.CompressPostbacks.IsEnabledForAnyRoute(defaultValue: true) : this.configuration.Runtime.CompressPostbacks.IsEnabledForRoute(routeName, defaultValue: true);
if (!enabled)
throw new Exception($"Content-Encoding: gzip must be enabled in DotvvmConfiguration.Runtime.CompressPostbacks.");
var gzipStream = new System.IO.Compression.GZipStream(request.Body, System.IO.Compression.CompressionMode.Decompress);
return LimitLengthStream.LimitLength(gzipStream, configuration.Runtime.MaxPostbackSizeBytes, limitLengthHelp);
}
else
throw new Exception($"Unsupported Content-Encoding {encoding}");
}
[Obsolete("Use context.RequestType == DotvvmRequestType.StaticCommand")]
public static bool DetermineIsStaticCommand(IDotvvmRequestContext context) =>
context.RequestType == DotvvmRequestType.StaticCommand;
[Obsolete("Use context.RequestType == DotvvmRequestType.Command")]
public static bool DetermineIsPostBack(IHttpContext context) =>
DotvvmRequestContext.DetermineRequestType(context) == DotvvmRequestType.Command;
[Obsolete("Use context.RequestType == DotvvmRequestType.SpaGet")]
public static bool DetermineSpaRequest(IHttpContext context) =>
DotvvmRequestContext.DetermineRequestType(context) == DotvvmRequestType.SpaNavigate;
[Obsolete("Use context.RequestType is DotvvmRequestType.Command or DotvvmRequestType.SpaGet")]
public static bool DeterminePartialRendering(IHttpContext context) =>
DotvvmRequestContext.DetermineRequestType(context) is DotvvmRequestType.Command or DotvvmRequestType.SpaNavigate;
public static string? DetermineSpaContentPlaceHolderUniqueId(IHttpContext context)
{
return context.Request.Headers[HostingConstants.SpaContentPlaceHolderHeaderName];
}
}
}