From 66f61d4fb89a940244b8665922255a5d9817de27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Mon, 14 Nov 2022 19:34:03 +0100 Subject: [PATCH] Refactored DotVVM presenter --- .../Framework/Hosting/DotvvmPresenter.cs | 29 +++++---------- .../Middlewares/DotvvmFileUploadMiddleware.cs | 5 ++- .../Runtime/DefaultOutputRenderer.cs | 16 ++------ .../Framework/Runtime/IOutputRenderer.cs | 8 +--- .../DefaultViewModelSerializer.cs | 37 +++++++++++++++++++ .../Serialization/IViewModelSerializer.cs | 6 +++ .../Serialization/StaticCommandRequest.cs | 19 ++++++++++ 7 files changed, 80 insertions(+), 40 deletions(-) create mode 100644 src/Framework/Framework/ViewModel/Serialization/StaticCommandRequest.cs diff --git a/src/Framework/Framework/Hosting/DotvvmPresenter.cs b/src/Framework/Framework/Hosting/DotvvmPresenter.cs index 181875cdf5..18312bcb99 100644 --- a/src/Framework/Framework/Hosting/DotvvmPresenter.cs +++ b/src/Framework/Framework/Hosting/DotvvmPresenter.cs @@ -21,8 +21,6 @@ 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; @@ -92,11 +90,9 @@ public async Task ProcessRequest(IDotvvmRequestContext context) } 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)); + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; + var json = ViewModelSerializer.SerializeErrorResponse("invalidCsrfToken", ex.Message); + await OutputRenderer.WritePlainJsonResponse(context.HttpContext, json); } catch (DotvvmExceptionBase ex) { @@ -351,15 +347,9 @@ public async Task ProcessStaticCommandRequest(IDotvvmRequestContext context) { try { - JObject postData; - using (var jsonReader = new JsonTextReader(new StreamReader(context.HttpContext.Request.Body))) - { - postData = await JObject.LoadAsync(jsonReader); - } + var request = await ViewModelSerializer.DeserializeStaticCommandRequest(context); - // validate csrf token - context.CsrfToken = postData["$csrfToken"].Value(); - CsrfProtector.VerifyToken(context, context.CsrfToken); + CsrfProtector.VerifyToken(context, context.CsrfToken!); var knownTypes = postData["knownTypeMetadata"].Values().ToArray(); var command = postData["command"].Value(); @@ -370,17 +360,18 @@ public async Task ProcessStaticCommandRequest(IDotvvmRequestContext context) var actionInfo = new ActionInfo( binding: null, - () => { return ExecuteStaticCommandPlan(executionPlan, new Queue(arguments.NotNull()), context); }, + () => { return ExecuteStaticCommandPlan(request.ExecutionPlan, new Queue>(request.ArgumentAccessors.NotNull()), context); }, false ); var filters = context.Configuration.Runtime.GlobalFilters.OfType() - .Concat(executionPlan.GetAllMethods().SelectMany(m => ActionFilterHelper.GetActionFilters(m))) + .Concat(request.ExecutionPlan.GetAllMethods().SelectMany(m => ActionFilterHelper.GetActionFilters(m))) .ToArray(); var result = await ExecuteCommand(actionInfo, context, filters); - await OutputRenderer.WriteStaticCommandResponse( - context, + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; + await OutputRenderer.WritePlainJsonResponse( + context.HttpContext, ViewModelSerializer.BuildStaticCommandResponse(context, result, knownTypes)); } finally diff --git a/src/Framework/Framework/Hosting/Middlewares/DotvvmFileUploadMiddleware.cs b/src/Framework/Framework/Hosting/Middlewares/DotvvmFileUploadMiddleware.cs index 9e76527911..5d8a67a103 100644 --- a/src/Framework/Framework/Hosting/Middlewares/DotvvmFileUploadMiddleware.cs +++ b/src/Framework/Framework/Hosting/Middlewares/DotvvmFileUploadMiddleware.cs @@ -107,12 +107,13 @@ private async Task RenderResponse(IDotvvmRequestContext request, bool isPost, st if (string.IsNullOrEmpty(errorMessage)) { var json = viewModelSerializer.BuildStaticCommandResponse(request, uploadedFiles); - await outputRenderer.RenderPlainJsonResponse(context, json); + context.Response.StatusCode = (int)HttpStatusCode.OK; + await outputRenderer.WritePlainJsonResponse(context, json); } else { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - await outputRenderer.RenderPlainTextResponse(context, errorMessage); + await outputRenderer.WritePlainTextResponse(context, errorMessage); } } diff --git a/src/Framework/Framework/Runtime/DefaultOutputRenderer.cs b/src/Framework/Framework/Runtime/DefaultOutputRenderer.cs index df7158e4db..0d5cb7cc1b 100644 --- a/src/Framework/Framework/Runtime/DefaultOutputRenderer.cs +++ b/src/Framework/Framework/Runtime/DefaultOutputRenderer.cs @@ -91,32 +91,22 @@ public virtual async Task WriteViewModelResponse(IDotvvmRequestContext context, await context.HttpContext.Response.WriteAsync(serializedViewModel); } - public virtual async Task WriteStaticCommandResponse(IDotvvmRequestContext context, string json) + public virtual async Task WritePlainJsonResponse(IHttpContext context, string json) { - context.HttpContext.Response.ContentType = "application/json; charset=utf-8"; - SetCacheHeaders(context.HttpContext); - await context.HttpContext.Response.WriteAsync(json); - } - - public virtual async Task RenderPlainJsonResponse(IHttpContext context, string json) - { - context.Response.StatusCode = (int)HttpStatusCode.OK; context.Response.ContentType = "application/json; charset=utf-8"; SetCacheHeaders(context); await context.Response.WriteAsync(json); } - public virtual async Task RenderHtmlResponse(IHttpContext context, string html) + public virtual async Task WriteHtmlResponse(IHttpContext context, string html) { - context.Response.StatusCode = (int)HttpStatusCode.OK; context.Response.ContentType = "text/html; charset=utf-8"; SetCacheHeaders(context); await context.Response.WriteAsync(html); } - public virtual async Task RenderPlainTextResponse(IHttpContext context, string text) + public virtual async Task WritePlainTextResponse(IHttpContext context, string text) { - context.Response.StatusCode = (int)HttpStatusCode.OK; context.Response.ContentType = "text/plain; charset=utf-8"; SetCacheHeaders(context); await context.Response.WriteAsync(text); diff --git a/src/Framework/Framework/Runtime/IOutputRenderer.cs b/src/Framework/Framework/Runtime/IOutputRenderer.cs index 619475241c..4d28767937 100644 --- a/src/Framework/Framework/Runtime/IOutputRenderer.cs +++ b/src/Framework/Framework/Runtime/IOutputRenderer.cs @@ -14,13 +14,9 @@ public interface IOutputRenderer Task WriteViewModelResponse(IDotvvmRequestContext context, DotvvmView view); - Task WriteStaticCommandResponse(IDotvvmRequestContext context, string json); + Task WritePlainJsonResponse(IHttpContext context, string json); - Task RenderPlainJsonResponse(IHttpContext context, string json); - - Task RenderHtmlResponse(IHttpContext context, string html); - - Task RenderPlainTextResponse(IHttpContext context, string text); + Task WritePlainTextResponse(IHttpContext context, string text); IEnumerable<(string name, string html)> RenderPostbackUpdatedControls(IDotvvmRequestContext context, DotvvmView page); } diff --git a/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs b/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs index 6b11e79882..f8077b2f18 100644 --- a/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs +++ b/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs @@ -17,6 +17,9 @@ using DotVVM.Framework.Binding; using System.Collections.Immutable; using RecordExceptions; +using DotVVM.Framework.Compilation.Binding; +using Microsoft.Extensions.DependencyInjection; +using System.Threading.Tasks; namespace DotVVM.Framework.ViewModel.Serialization { @@ -304,6 +307,17 @@ public string SerializeModelState(IDotvvmRequestContext context) return result.ToString(JsonFormatting); } + /// + /// Serializes the HTTP request error object. + /// + public string SerializeErrorResponse(string action, string errorMessage) + { + // create result object + var result = new JObject(); + result["action"] = action; + result["message"] = errorMessage; + return result.ToString(JsonFormatting); + } /// /// Populates the view model from the data received from the request. @@ -364,6 +378,28 @@ public void PopulateViewModel(IDotvvmRequestContext context, string serializedPo } } + public async Task DeserializeStaticCommandRequest(IDotvvmRequestContext context) + { + JObject postData; + using (var jsonReader = new JsonTextReader(new StreamReader(context.HttpContext.Request.Body))) + { + postData = await JObject.LoadAsync(jsonReader); + } + + // validate csrf token + context.CsrfToken = postData["$csrfToken"].Value(); + + var command = postData["command"].Value(); + var arguments = postData["args"] as JArray; + var executionPlan = + StaticCommandExecutionPlanSerializer.DecryptJson(Convert.FromBase64String(command), context.Services.GetRequiredService()) + .Apply(StaticCommandExecutionPlanSerializer.DeserializePlan); + + var serializer = CreateJsonSerializer(); + var argumentAccessors = arguments.Select(a => (Func)(targetType => a.ToObject(targetType, serializer))); + return new StaticCommandRequest(executionPlan, argumentAccessors); + } + /// /// Resolves the command for the specified post data. /// @@ -415,5 +451,6 @@ public void AddPostBackUpdatedControls(IDotvvmRequestContext context, IEnumerabl context.ViewModelJson["updatedControls"] = result; } + } } diff --git a/src/Framework/Framework/ViewModel/Serialization/IViewModelSerializer.cs b/src/Framework/Framework/ViewModel/Serialization/IViewModelSerializer.cs index 5f57d4ba56..e927577882 100644 --- a/src/Framework/Framework/ViewModel/Serialization/IViewModelSerializer.cs +++ b/src/Framework/Framework/ViewModel/Serialization/IViewModelSerializer.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using DotVVM.Framework.Controls.Infrastructure; using DotVVM.Framework.Hosting; using DotVVM.Framework.Runtime.Filters; @@ -15,11 +16,16 @@ public interface IViewModelSerializer string SerializeModelState(IDotvvmRequestContext context); + string SerializeErrorResponse(string action, string errorMessage); + void PopulateViewModel(IDotvvmRequestContext context, string serializedPostData); + Task DeserializeStaticCommandRequest(IDotvvmRequestContext context); + ActionInfo? ResolveCommand(IDotvvmRequestContext context, DotvvmView view); void AddPostBackUpdatedControls(IDotvvmRequestContext context, IEnumerable<(string name, string html)> postbackUpdatedControls); + void AddNewResources(IDotvvmRequestContext context); } } diff --git a/src/Framework/Framework/ViewModel/Serialization/StaticCommandRequest.cs b/src/Framework/Framework/ViewModel/Serialization/StaticCommandRequest.cs new file mode 100644 index 0000000000..25d86e7f1e --- /dev/null +++ b/src/Framework/Framework/ViewModel/Serialization/StaticCommandRequest.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using DotVVM.Framework.Compilation.Binding; + +namespace DotVVM.Framework.ViewModel.Serialization +{ + public class StaticCommandRequest + { + + public StaticCommandInvocationPlan ExecutionPlan { get; } + public IEnumerable> ArgumentAccessors { get; } + + public StaticCommandRequest(StaticCommandInvocationPlan executionPlan, IEnumerable> argumentAccessors) + { + ExecutionPlan = executionPlan; + ArgumentAccessors = argumentAccessors; + } + } +}