diff --git a/src/Framework/Framework/Hosting/DotvvmPresenter.cs b/src/Framework/Framework/Hosting/DotvvmPresenter.cs index fdcf00c59b..5057cb515f 100644 --- a/src/Framework/Framework/Hosting/DotvvmPresenter.cs +++ b/src/Framework/Framework/Hosting/DotvvmPresenter.cs @@ -478,11 +478,8 @@ async Task RespondWithStaticCommandValidationFailure(ActionInfo action, IDotvvmR async Task ValidateSecFetchHeaders(IDotvvmRequestContext context) { var route = context.Route?.RouteName; - var isPost = context.HttpContext.Request.Method switch { - "POST" => true, - "GET" => false, - _ => throw new NotSupportedException() - }; + 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"]; diff --git a/src/Framework/Framework/Hosting/DotvvmRequestContext.cs b/src/Framework/Framework/Hosting/DotvvmRequestContext.cs index 3a72192853..a0291b62e8 100644 --- a/src/Framework/Framework/Hosting/DotvvmRequestContext.cs +++ b/src/Framework/Framework/Hosting/DotvvmRequestContext.cs @@ -171,6 +171,9 @@ public static DotvvmRequestType DetermineRequestType(IHttpContext context) { return DotvvmRequestType.Command; } + // Unknown POST request is treated as a Navigate request + // it is useful for submitting classic
elements (as a no-JS fallback, login forms, etc.) + return DotvvmRequestType.Navigate; } return DotvvmRequestType.Unknown; } diff --git a/src/Samples/Common/DotVVM.Samples.Common.csproj b/src/Samples/Common/DotVVM.Samples.Common.csproj index d59a3ae001..b66cb67bf2 100644 --- a/src/Samples/Common/DotVVM.Samples.Common.csproj +++ b/src/Samples/Common/DotVVM.Samples.Common.csproj @@ -188,6 +188,7 @@ + diff --git a/src/Samples/Common/ViewModels/FeatureSamples/NoJsForm/NoJsFormViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/NoJsForm/NoJsFormViewModel.cs new file mode 100644 index 0000000000..2a421b88c6 --- /dev/null +++ b/src/Samples/Common/ViewModels/FeatureSamples/NoJsForm/NoJsFormViewModel.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using DotVVM.Framework.ViewModel; + +namespace DotVVM.Samples.BasicSamples.ViewModels.FeatureSamples.NoJsForm +{ + public class NoJsFormViewModel : DotvvmViewModelBase + { + public string Form1Value { get; set; } + public string Form2Value { get; set; } + + public override async Task Load() + { + var req = Context.HttpContext.Request; + if (req.Method == "POST") + { + using var body = new StreamReader(req.Body); + var data = HttpUtility.ParseQueryString(await body.ReadToEndAsync()); + var submit = data["submit"]; + if (submit == "form1") + { + Form1Value = data["text"]; + } + else if (submit == "form2") + { + Form2Value = data["text"]; + } + } + } + } +} + diff --git a/src/Samples/Common/Views/FeatureSamples/NoJsForm/NoJsForm.dothtml b/src/Samples/Common/Views/FeatureSamples/NoJsForm/NoJsForm.dothtml new file mode 100644 index 0000000000..54e1403311 --- /dev/null +++ b/src/Samples/Common/Views/FeatureSamples/NoJsForm/NoJsForm.dothtml @@ -0,0 +1,35 @@ +@viewModel DotVVM.Samples.BasicSamples.ViewModels.FeatureSamples.NoJsForm.NoJsFormViewModel, DotVVM.Samples.Common + + + + + + + +
+
+ Form 1 + + + + +

+ Last submitted value: {{value: Form1Value}} +

+ +
+ +
+ Form 2 +
+ + + +

+ Last submitted value: {{value: Form2Value}} +

+
+
+
+ + diff --git a/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs b/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs index 0d0f7f63cc..a13d0c9227 100644 --- a/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs +++ b/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs @@ -294,6 +294,7 @@ public partial class SamplesRouteUrls public const string FeatureSamples_MarkupControl_StaticCommandInMarkupControl = "FeatureSamples/MarkupControl/StaticCommandInMarkupControl"; public const string FeatureSamples_MarkupControl_StaticCommandInMarkupControlCallingRegularCommand = "FeatureSamples/MarkupControl/StaticCommandInMarkupControlCallingRegularCommand"; public const string FeatureSamples_NestedMasterPages_Content = "FeatureSamples/NestedMasterPages/Content"; + public const string FeatureSamples_NoJsForm_NoJsForm = "FeatureSamples/NoJsForm/NoJsForm"; public const string FeatureSamples_ParameterBinding_OptionalParameterBinding = "FeatureSamples/ParameterBinding/OptionalParameterBinding"; public const string FeatureSamples_ParameterBinding_ParameterBinding = "FeatureSamples/ParameterBinding/ParameterBinding"; public const string FeatureSamples_PostBack_ConfirmPostBackHandler = "FeatureSamples/PostBack/ConfirmPostBackHandler"; diff --git a/src/Samples/Tests/Tests/Feature/NoJsFormTests.cs b/src/Samples/Tests/Tests/Feature/NoJsFormTests.cs new file mode 100644 index 0000000000..2c9e289bc5 --- /dev/null +++ b/src/Samples/Tests/Tests/Feature/NoJsFormTests.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; +using DotVVM.Samples.Tests.Base; +using DotVVM.Testing.Abstractions; +using Riganti.Selenium.Core; +using Riganti.Selenium.DotVVM; +using Xunit; +using Xunit.Abstractions; + +namespace DotVVM.Samples.Tests.Feature +{ + public class NoJsFormTests : AppSeleniumTest + { + [Fact] + public void Feature_NoJsForm_NoJsForm() + { + RunInAllBrowsers(browser => { + browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_NoJsForm_NoJsForm); + + browser.SendKeys("#input1", "Q"); + browser.Click("#submit1"); + AssertUI.InnerTextEquals(browser.First("#result1"), "Q"); + + browser.SendKeys("#input2", "W"); + browser.Click("#submit2"); + AssertUI.InnerTextEquals(browser.First("#result2"), "W"); + }); + } + + public NoJsFormTests(ITestOutputHelper output) : base(output) + { + } + } +}