diff --git a/src/Umbraco.Web.Common/Constants/Constants.cs b/src/Umbraco.Web.Common/Constants/Constants.cs
new file mode 100644
index 0000000000..1acbe761c1
--- /dev/null
+++ b/src/Umbraco.Web.Common/Constants/Constants.cs
@@ -0,0 +1,12 @@
+namespace Umbraco.Web.Common.Constants
+{
+ ///
+ /// constants
+ ///
+ internal static class Constants
+ {
+ internal const string ViewLocation = "~/Views";
+
+ internal const string DataTokenCurrentViewContext = "umbraco-current-view-context";
+ }
+}
diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs
new file mode 100644
index 0000000000..43058616de
--- /dev/null
+++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs
@@ -0,0 +1,9 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace Umbraco.Web.Common.Controllers
+{
+ public abstract class RenderController : Controller
+ {
+
+ }
+}
diff --git a/src/Umbraco.Web.Common/Filters/EnsurePartialViewMacroViewContextFilterAttribute.cs b/src/Umbraco.Web.Common/Filters/EnsurePartialViewMacroViewContextFilterAttribute.cs
new file mode 100644
index 0000000000..bddb8b3653
--- /dev/null
+++ b/src/Umbraco.Web.Common/Filters/EnsurePartialViewMacroViewContextFilterAttribute.cs
@@ -0,0 +1,87 @@
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Mvc.ViewEngines;
+using Microsoft.AspNetCore.Mvc.ViewFeatures;
+using Umbraco.Web.Common.Controllers;
+
+namespace Umbraco.Web.Common.Filters
+{
+ ///
+ /// This is a special filter which is required for the RTE to be able to render Partial View Macros that
+ /// contain forms when the RTE value is resolved outside of an MVC view being rendered
+ ///
+ ///
+ /// The entire way that we support partial view macros that contain forms isn't really great, these forms
+ /// need to be executed as ChildActions so that the ModelState,ViewData,TempData get merged into that action
+ /// so the form can show errors, viewdata, etc...
+ /// Under normal circumstances, macros will be rendered after a ViewContext is created but in some cases
+ /// developers will resolve the RTE value in the controller, in this case the Form won't be rendered correctly
+ /// with merged ModelState from the controller because the special DataToken hasn't been set yet (which is
+ /// normally done in the UmbracoViewPageOfModel when a real ViewContext is available.
+ /// So we need to detect if the currently rendering controller is IRenderController and if so we'll ensure that
+ /// this DataToken exists before the action executes in case the developer resolves an RTE value that contains
+ /// a partial view macro form.
+ ///
+ internal class EnsurePartialViewMacroViewContextFilterAttribute : ActionFilterAttribute
+ {
+
+ ///
+ /// Ensures the custom ViewContext datatoken is set before the RenderController action is invoked,
+ /// this ensures that any calls to GetPropertyValue with regards to RTE or Grid editors can still
+ /// render any PartialViewMacro with a form and maintain ModelState
+ ///
+ ///
+ public override void OnActionExecuting(ActionExecutingContext context)
+ {
+ if (!(context.Controller is Controller controller)) return;
+
+ //ignore anything that is not IRenderController
+ if (!(controller is RenderController)) return;
+
+ SetViewContext(context, controller);
+ }
+
+ ///
+ /// Ensures that the custom ViewContext datatoken is set after the RenderController action is invoked,
+ /// this ensures that any custom ModelState that may have been added in the RenderController itself is
+ /// passed onwards in case it is required when rendering a PartialViewMacro with a form
+ ///
+ /// The filter context.
+ public override void OnResultExecuting(ResultExecutingContext context)
+ {
+ if (!(context.Controller is Controller controller)) return;
+
+ //ignore anything that is not IRenderController
+ if (!(controller is RenderController)) return;
+
+ SetViewContext(context, controller);
+ }
+
+ private void SetViewContext(ActionContext context, Controller controller)
+ {
+ var viewCtx = new ViewContext(
+ context,
+ new DummyView(),
+ controller.ViewData,
+ controller.TempData,
+ new StringWriter(),
+ new HtmlHelperOptions());
+
+ //set the special data token
+ context.RouteData.DataTokens[Constants.Constants.DataTokenCurrentViewContext] = viewCtx;
+ }
+
+ private class DummyView : IView
+ {
+ public Task RenderAsync(ViewContext context)
+ {
+ return Task.CompletedTask;
+ }
+
+ public string Path { get; }
+ }
+ }
+}