diff --git a/src/Umbraco.Web.Website/Controllers/SurfaceController.cs b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs
new file mode 100644
index 0000000000..22b50250af
--- /dev/null
+++ b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Collections.Specialized;
+using Umbraco.Core;
+using Umbraco.Core.Cache;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Services;
+using Umbraco.Web.Common.Controllers;
+using Umbraco.Web.Routing;
+using Umbraco.Web.Website.ActionResults;
+
+namespace Umbraco.Web.Website.Controllers
+{
+ ///
+ /// Provides a base class for front-end add-in controllers.
+ ///
+ public abstract class SurfaceController : PluginController
+ {
+ private readonly IPublishedUrlProvider _publishedUrlProvider;
+
+ ///
+ /// Gets the current page.
+ ///
+ protected virtual IPublishedContent CurrentPage
+ {
+ get
+ {
+ var routeDefAttempt = TryGetRouteDefinitionFromAncestorViewContexts();
+ if (routeDefAttempt.Success == false)
+ throw routeDefAttempt.Exception;
+
+ var routeDef = routeDefAttempt.Result;
+ return routeDef.PublishedRequest.PublishedContent;
+ }
+ }
+
+ protected SurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider)
+ : base(umbracoContextAccessor, databaseFactory, services, appCaches, logger, profilingLogger)
+ {
+ _publishedUrlProvider = publishedUrlProvider;
+ }
+
+ ///
+ /// Redirects to the Umbraco page with the given id
+ ///
+ ///
+ ///
+ protected RedirectToUmbracoPageResult RedirectToUmbracoPage(int pageId)
+ {
+ return new RedirectToUmbracoPageResult(pageId, _publishedUrlProvider);
+ }
+
+ ///
+ /// Redirects to the Umbraco page with the given id and passes provided querystring
+ ///
+ ///
+ ///
+ ///
+ protected RedirectToUmbracoPageResult RedirectToUmbracoPage(int pageId, NameValueCollection queryStringValues)
+ {
+ return new RedirectToUmbracoPageResult(pageId, queryStringValues, _publishedUrlProvider);
+ }
+
+ ///
+ /// Redirects to the Umbraco page with the given id and passes provided querystring
+ ///
+ ///
+ ///
+ ///
+ protected RedirectToUmbracoPageResult RedirectToUmbracoPage(int pageId, string queryString)
+ {
+ return new RedirectToUmbracoPageResult(pageId, queryString, _publishedUrlProvider);
+ }
+
+ ///
+ /// Redirects to the Umbraco page with the given published content
+ ///
+ ///
+ ///
+ protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent)
+ {
+ return new RedirectToUmbracoPageResult(publishedContent, _publishedUrlProvider, UmbracoContextAccessor);
+ }
+
+ ///
+ /// Redirects to the Umbraco page with the given published content and passes provided querystring
+ ///
+ ///
+ ///
+ ///
+ protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent, NameValueCollection queryStringValues)
+ {
+ return new RedirectToUmbracoPageResult(publishedContent, queryStringValues, _publishedUrlProvider, UmbracoContextAccessor);
+ }
+
+ ///
+ /// Redirects to the Umbraco page with the given published content and passes provided querystring
+ ///
+ ///
+ ///
+ ///
+ protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent, string queryString)
+ {
+ return new RedirectToUmbracoPageResult(publishedContent, queryString, _publishedUrlProvider, UmbracoContextAccessor);
+ }
+
+ ///
+ /// Redirects to the currently rendered Umbraco page
+ ///
+ ///
+ protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage()
+ {
+ return new RedirectToUmbracoPageResult(CurrentPage, _publishedUrlProvider, UmbracoContextAccessor);
+ }
+
+ ///
+ /// Redirects to the currently rendered Umbraco page and passes provided querystring
+ ///
+ ///
+ ///
+ protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage(NameValueCollection queryStringValues)
+ {
+ return new RedirectToUmbracoPageResult(CurrentPage, queryStringValues, _publishedUrlProvider, UmbracoContextAccessor);
+ }
+
+ ///
+ /// Redirects to the currently rendered Umbraco page and passes provided querystring
+ ///
+ ///
+ ///
+ protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage(string queryString)
+ {
+ return new RedirectToUmbracoPageResult(CurrentPage, queryString, _publishedUrlProvider, UmbracoContextAccessor);
+ }
+
+ ///
+ /// Redirects to the currently rendered Umbraco URL
+ ///
+ ///
+ ///
+ /// This is useful if you need to redirect
+ /// to the current page but the current page is actually a rewritten URL normally done with something like
+ /// Server.Transfer.*
+ ///
+ protected RedirectToUmbracoUrlResult RedirectToCurrentUmbracoUrl()
+ {
+ return new RedirectToUmbracoUrlResult(UmbracoContext);
+ }
+
+ ///
+ /// Returns the currently rendered Umbraco page
+ ///
+ ///
+ protected UmbracoPageResult CurrentUmbracoPage()
+ {
+ return new UmbracoPageResult(ProfilingLogger);
+ }
+
+ ///
+ /// we need to recursively find the route definition based on the parent view context
+ ///
+ ///
+ private Attempt TryGetRouteDefinitionFromAncestorViewContexts()
+ {
+ var currentContext = ControllerContext;
+ while (!(currentContext is null))
+ {
+ var currentRouteData = currentContext.RouteData;
+ if (currentRouteData.DataTokens.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken))
+ return Attempt.Succeed((RouteDefinition)currentRouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken]);
+ }
+
+ return Attempt.Fail(
+ new InvalidOperationException("Cannot find the Umbraco route definition in the route values, the request must be made in the context of an Umbraco request"));
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs
index 877a3c31d8..b9c857a8b3 100644
--- a/src/Umbraco.Web/Mvc/SurfaceController.cs
+++ b/src/Umbraco.Web/Mvc/SurfaceController.cs
@@ -13,6 +13,8 @@ namespace Umbraco.Web.Mvc
///
/// Provides a base class for front-end add-in controllers.
///
+ /// Migrated already to .Net Core without MergeModelStateToChildAction and MergeParentContextViewData action filters
+ /// TODO: Migrate MergeModelStateToChildAction and MergeParentContextViewData action filters
[MergeModelStateToChildAction]
[MergeParentContextViewData]
public abstract class SurfaceController : PluginController