Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored DotVVM presenter #1514

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 10 additions & 19 deletions src/Framework/Framework/Hosting/DotvvmPresenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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<string>();
CsrfProtector.VerifyToken(context, context.CsrfToken);
CsrfProtector.VerifyToken(context, context.CsrfToken!);

var knownTypes = postData["knownTypeMetadata"].Values<string>().ToArray();
var command = postData["command"].Value<string>();
Expand All @@ -370,17 +360,18 @@ public async Task ProcessStaticCommandRequest(IDotvvmRequestContext context)

var actionInfo = new ActionInfo(
binding: null,
() => { return ExecuteStaticCommandPlan(executionPlan, new Queue<JToken>(arguments.NotNull()), context); },
() => { return ExecuteStaticCommandPlan(request.ExecutionPlan, new Queue<Func<Type, object>>(request.ArgumentAccessors.NotNull()), context); },
false
);
var filters = context.Configuration.Runtime.GlobalFilters.OfType<ICommandActionFilter>()
.Concat(executionPlan.GetAllMethods().SelectMany(m => ActionFilterHelper.GetActionFilters<ICommandActionFilter>(m)))
.Concat(request.ExecutionPlan.GetAllMethods().SelectMany(m => ActionFilterHelper.GetActionFilters<ICommandActionFilter>(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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
16 changes: 3 additions & 13 deletions src/Framework/Framework/Runtime/DefaultOutputRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 2 additions & 6 deletions src/Framework/Framework/Runtime/IOutputRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -304,6 +307,17 @@ public string SerializeModelState(IDotvvmRequestContext context)
return result.ToString(JsonFormatting);
}

/// <summary>
/// Serializes the HTTP request error object.
/// </summary>
public string SerializeErrorResponse(string action, string errorMessage)
{
// create result object
var result = new JObject();
result["action"] = action;
result["message"] = errorMessage;
return result.ToString(JsonFormatting);
}

/// <summary>
/// Populates the view model from the data received from the request.
Expand Down Expand Up @@ -364,6 +378,28 @@ public void PopulateViewModel(IDotvvmRequestContext context, string serializedPo
}
}

public async Task<StaticCommandRequest> 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<string>();

var command = postData["command"].Value<string>();
var arguments = postData["args"] as JArray;
var executionPlan =
StaticCommandExecutionPlanSerializer.DecryptJson(Convert.FromBase64String(command), context.Services.GetRequiredService<IViewModelProtector>())
.Apply(StaticCommandExecutionPlanSerializer.DeserializePlan);

var serializer = CreateJsonSerializer();
var argumentAccessors = arguments.Select(a => (Func<Type, object>)(targetType => a.ToObject(targetType, serializer)));
return new StaticCommandRequest(executionPlan, argumentAccessors);
}

/// <summary>
/// Resolves the command for the specified post data.
/// </summary>
Expand Down Expand Up @@ -415,5 +451,6 @@ public void AddPostBackUpdatedControls(IDotvvmRequestContext context, IEnumerabl

context.ViewModelJson["updatedControls"] = result;
}

}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,11 +16,16 @@ public interface IViewModelSerializer

string SerializeModelState(IDotvvmRequestContext context);

string SerializeErrorResponse(string action, string errorMessage);

void PopulateViewModel(IDotvvmRequestContext context, string serializedPostData);

Task<StaticCommandRequest> DeserializeStaticCommandRequest(IDotvvmRequestContext context);

ActionInfo? ResolveCommand(IDotvvmRequestContext context, DotvvmView view);

void AddPostBackUpdatedControls(IDotvvmRequestContext context, IEnumerable<(string name, string html)> postbackUpdatedControls);

void AddNewResources(IDotvvmRequestContext context);
}
}
Original file line number Diff line number Diff line change
@@ -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<Func<Type, object>> ArgumentAccessors { get; }

public StaticCommandRequest(StaticCommandInvocationPlan executionPlan, IEnumerable<Func<Type, object>> argumentAccessors)
{
ExecutionPlan = executionPlan;
ArgumentAccessors = argumentAccessors;
}
}
}