Splits PublishedRequest into a builder and a immutable object

This commit is contained in:
Shannon
2021-01-06 17:04:35 +11:00
parent a9e01b8de7
commit 295ab504cd
42 changed files with 1441 additions and 1020 deletions

View File

@@ -22,107 +22,109 @@ namespace Umbraco.Web.Mvc
/// </remarks>
public class EnsurePublishedContentRequestAttribute : ActionFilterAttribute
{
private readonly string _dataTokenName;
private IUmbracoContextAccessor _umbracoContextAccessor;
private readonly int? _contentId;
private IPublishedContentQuery _publishedContentQuery;
// TODO: Need to port this to netcore and figure out if its needed or how this should work (part of a different task)
/// <summary>
/// Constructor - can be used for testing
/// </summary>
/// <param name="umbracoContextAccessor"></param>
/// <param name="contentId"></param>
public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, int contentId)
{
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
_contentId = contentId;
}
//private readonly string _dataTokenName;
//private IUmbracoContextAccessor _umbracoContextAccessor;
//private readonly int? _contentId;
//private IPublishedContentQuery _publishedContentQuery;
/// <summary>
/// A constructor used to set an explicit content Id to the PublishedRequest that will be created
/// </summary>
/// <param name="contentId"></param>
public EnsurePublishedContentRequestAttribute(int contentId)
{
_contentId = contentId;
}
///// <summary>
///// Constructor - can be used for testing
///// </summary>
///// <param name="umbracoContextAccessor"></param>
///// <param name="contentId"></param>
//public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, int contentId)
//{
// _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
// _contentId = contentId;
//}
/// <summary>
/// A constructor used to set the data token key name that contains a reference to a PublishedContent instance
/// </summary>
/// <param name="dataTokenName"></param>
public EnsurePublishedContentRequestAttribute(string dataTokenName)
{
_dataTokenName = dataTokenName;
}
///// <summary>
///// A constructor used to set an explicit content Id to the PublishedRequest that will be created
///// </summary>
///// <param name="contentId"></param>
//public EnsurePublishedContentRequestAttribute(int contentId)
//{
// _contentId = contentId;
//}
/// <summary>
/// Constructor - can be used for testing
/// </summary>
/// <param name="umbracoContextAccessor"></param>
/// <param name="dataTokenName"></param>
public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, string dataTokenName)
{
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
_dataTokenName = dataTokenName;
}
///// <summary>
///// A constructor used to set the data token key name that contains a reference to a PublishedContent instance
///// </summary>
///// <param name="dataTokenName"></param>
//public EnsurePublishedContentRequestAttribute(string dataTokenName)
//{
// _dataTokenName = dataTokenName;
//}
/// <summary>
/// Exposes the UmbracoContext
/// </summary>
protected IUmbracoContext UmbracoContext => _umbracoContextAccessor?.UmbracoContext ?? Current.UmbracoContext;
///// <summary>
///// Constructor - can be used for testing
///// </summary>
///// <param name="umbracoContextAccessor"></param>
///// <param name="dataTokenName"></param>
//public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, string dataTokenName)
//{
// _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
// _dataTokenName = dataTokenName;
//}
// TODO: try lazy property injection?
private IPublishedRouter PublishedRouter => Current.Factory.GetRequiredService<IPublishedRouter>();
///// <summary>
///// Exposes the UmbracoContext
///// </summary>
//protected IUmbracoContext UmbracoContext => _umbracoContextAccessor?.UmbracoContext ?? Current.UmbracoContext;
private IPublishedContentQuery PublishedContentQuery => _publishedContentQuery ?? (_publishedContentQuery = Current.Factory.GetRequiredService<IPublishedContentQuery>());
//// TODO: try lazy property injection?
//private IPublishedRouter PublishedRouter => Current.Factory.GetRequiredService<IPublishedRouter>();
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
//private IPublishedContentQuery PublishedContentQuery => _publishedContentQuery ?? (_publishedContentQuery = Current.Factory.GetRequiredService<IPublishedContentQuery>());
// First we need to check if the published content request has been set, if it has we're going to ignore this and not actually do anything
if (Current.UmbracoContext.PublishedRequest != null)
{
return;
}
//public override void OnActionExecuted(ActionExecutedContext filterContext)
//{
// base.OnActionExecuted(filterContext);
Current.UmbracoContext.PublishedRequest = PublishedRouter.CreateRequest(Current.UmbracoContext);
ConfigurePublishedContentRequest(Current.UmbracoContext.PublishedRequest, filterContext);
}
// // First we need to check if the published content request has been set, if it has we're going to ignore this and not actually do anything
// if (Current.UmbracoContext.PublishedRequest != null)
// {
// return;
// }
/// <summary>
/// This assigns the published content to the request, developers can override this to specify
/// any other custom attributes required.
/// </summary>
/// <param name="request"></param>
/// <param name="filterContext"></param>
protected virtual void ConfigurePublishedContentRequest(IPublishedRequest request, ActionExecutedContext filterContext)
{
if (_contentId.HasValue)
{
var content = PublishedContentQuery.Content(_contentId.Value);
if (content == null)
{
throw new InvalidOperationException("Could not resolve content with id " + _contentId);
}
request.PublishedContent = content;
}
else if (_dataTokenName.IsNullOrWhiteSpace() == false)
{
var result = filterContext.RouteData.DataTokens[_dataTokenName];
if (result == null)
{
throw new InvalidOperationException("No data token could be found with the name " + _dataTokenName);
}
if (result is IPublishedContent == false)
{
throw new InvalidOperationException("The data token resolved with name " + _dataTokenName + " was not an instance of " + typeof(IPublishedContent));
}
request.PublishedContent = (IPublishedContent)result;
}
// Current.UmbracoContext.PublishedRequest = PublishedRouter.CreateRequest(Current.UmbracoContext);
// ConfigurePublishedContentRequest(Current.UmbracoContext.PublishedRequest, filterContext);
//}
PublishedRouter.PrepareRequest(request);
}
///// <summary>
///// This assigns the published content to the request, developers can override this to specify
///// any other custom attributes required.
///// </summary>
///// <param name="request"></param>
///// <param name="filterContext"></param>
//protected virtual void ConfigurePublishedContentRequest(IPublishedRequest request, ActionExecutedContext filterContext)
//{
// if (_contentId.HasValue)
// {
// var content = PublishedContentQuery.Content(_contentId.Value);
// if (content == null)
// {
// throw new InvalidOperationException("Could not resolve content with id " + _contentId);
// }
// request.PublishedContent = content;
// }
// else if (_dataTokenName.IsNullOrWhiteSpace() == false)
// {
// var result = filterContext.RouteData.DataTokens[_dataTokenName];
// if (result == null)
// {
// throw new InvalidOperationException("No data token could be found with the name " + _dataTokenName);
// }
// if (result is IPublishedContent == false)
// {
// throw new InvalidOperationException("The data token resolved with name " + _dataTokenName + " was not an instance of " + typeof(IPublishedContent));
// }
// request.PublishedContent = (IPublishedContent)result;
// }
// PublishedRouter.PrepareRequest(request);
//}
}
}

View File

@@ -219,7 +219,7 @@ namespace Umbraco.Web.Mvc
var defaultControllerType = Current.DefaultRenderMvcControllerType;
var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType);
//creates the default route definition which maps to the 'UmbracoController' controller
// creates the default route definition which maps to the 'UmbracoController' controller
var def = new RouteDefinition
{
ControllerName = defaultControllerName,
@@ -229,28 +229,28 @@ namespace Umbraco.Web.Mvc
HasHijackedRoute = false
};
//check that a template is defined), if it doesn't and there is a hijacked route it will just route
// check that a template is defined), if it doesn't and there is a hijacked route it will just route
// to the index Action
if (request.HasTemplate)
if (request.HasTemplate())
{
//the template Alias should always be already saved with a safe name.
//if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed
// the template Alias should always be already saved with a safe name.
// if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed
// with the action name attribute.
var templateName = request.TemplateAlias.Split('.')[0].ToSafeAlias(_shortStringHelper);
var templateName = request.GetTemplateAlias().Split('.')[0].ToSafeAlias(_shortStringHelper);
def.ActionName = templateName;
}
//check if there's a custom controller assigned, base on the document type alias.
// check if there's a custom controller assigned, base on the document type alias.
var controllerType = _controllerFactory.GetControllerTypeInternal(requestContext, request.PublishedContent.ContentType.Alias);
//check if that controller exists
// check if that controller exists
if (controllerType != null)
{
//ensure the controller is of type IRenderMvcController and ControllerBase
// ensure the controller is of type IRenderMvcController and ControllerBase
if (TypeHelper.IsTypeAssignableFrom<IRenderController>(controllerType)
&& TypeHelper.IsTypeAssignableFrom<ControllerBase>(controllerType))
{
//set the controller and name to the custom one
// set the controller and name to the custom one
def.ControllerType = controllerType;
def.ControllerName = ControllerExtensions.GetControllerName(controllerType);
if (def.ControllerName != defaultControllerName)
@@ -266,12 +266,12 @@ namespace Umbraco.Web.Mvc
typeof(IRenderController).FullName,
typeof(ControllerBase).FullName);
//we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults
// we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults
// that have already been set above.
}
}
//store the route definition
// store the route definition
requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def;
return def;
@@ -284,15 +284,19 @@ namespace Umbraco.Web.Mvc
// missing template, so we're in a 404 here
// so the content, if any, is a custom 404 page of some sort
if (request.HasPublishedContent == false)
if (request.HasPublishedContent() == false)
{
// means the builder could not find a proper document to handle 404
return new PublishedContentNotFoundHandler();
}
if (request.HasTemplate == false)
if (request.HasTemplate() == false)
{
// means the engine could find a proper document, but the document has no template
// at that point there isn't much we can do and there is no point returning
// to Mvc since Mvc can't do much
return new PublishedContentNotFoundHandler("In addition, no template exists to render the custom 404.");
}
return null;
}
@@ -300,8 +304,6 @@ namespace Umbraco.Web.Mvc
/// <summary>
/// this will determine the controller and set the values in the route data
/// </summary>
/// <param name="requestContext"></param>
/// <param name="request"></param>
internal IHttpHandler GetHandlerForRoute(RequestContext requestContext, IPublishedRequest request)
{
if (requestContext == null) throw new ArgumentNullException(nameof(requestContext));
@@ -309,7 +311,7 @@ namespace Umbraco.Web.Mvc
var routeDef = GetUmbracoRouteDefinition(requestContext, request);
//Need to check for a special case if there is form data being posted back to an Umbraco URL
// Need to check for a special case if there is form data being posted back to an Umbraco URL
var postedInfo = GetFormInfo(requestContext);
if (postedInfo != null)
{
@@ -321,10 +323,11 @@ namespace Umbraco.Web.Mvc
// if this is the case we want to return a blank page, but we'll leave that up to the NoTemplateHandler.
// We also check if templates have been disabled since if they are then we're allowed to render even though there's no template,
// for example for json rendering in headless.
if ((request.HasTemplate == false && Features.Disabled.DisableTemplates == false)
&& routeDef.HasHijackedRoute == false)
if (request.HasTemplate() == false && Features.Disabled.DisableTemplates == false && routeDef.HasHijackedRoute == false)
{
request.UpdateToNotFound(); // request will go 404
// TODO: Handle this differently
// request.UpdateToNotFound(); // request will go 404
// HandleHttpResponseStatus returns a value indicating that the request should
// not be processed any further, eg because it has been redirect. then, exit.

View File

@@ -7,11 +7,14 @@ using Umbraco.Web.Models;
using Umbraco.Web.Routing;
using Umbraco.Core;
using Umbraco.Web.Composing;
using System;
namespace Umbraco.Web.Mvc
{
public abstract class UmbracoVirtualNodeRouteHandler : IRouteHandler
{
// TODO: Need to port this to netcore and figure out if its needed or how this should work (part of a different task)
// TODO: try lazy property injection?
private IPublishedRouter PublishedRouter => Current.Factory.GetRequiredService<IPublishedRouter>();
@@ -46,54 +49,56 @@ namespace Umbraco.Web.Mvc
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var umbracoContext = GetUmbracoContext(requestContext);
throw new NotImplementedException();
var found = FindContent(requestContext, umbracoContext);
if (found == null) return new NotFoundHandler();
// var umbracoContext = GetUmbracoContext(requestContext);
var request = PublishedRouter.CreateRequest(umbracoContext);
request.PublishedContent = found;
umbracoContext.PublishedRequest = request;
// var found = FindContent(requestContext, umbracoContext);
// if (found == null) return new NotFoundHandler();
// allows inheritors to change the published content request
PreparePublishedContentRequest(umbracoContext.PublishedRequest);
// var request = PublishedRouter.CreateRequest(umbracoContext);
// request.PublishedContent = found;
// umbracoContext.PublishedRequest = request;
// create the render model
var renderModel = new ContentModel(umbracoContext.PublishedRequest.PublishedContent);
// // allows inheritors to change the published content request
// PreparePublishedContentRequest(umbracoContext.PublishedRequest);
// assigns the required tokens to the request
//requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, renderModel);
//requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, umbracoContext.PublishedRequest);
//requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbracoContext);
// // create the render model
// var renderModel = new ContentModel(umbracoContext.PublishedRequest.PublishedContent);
//// this is used just for a flag that this is an umbraco custom route
//requestContext.RouteData.DataTokens.Add(Core.Constants.Web.CustomRouteDataToken, true);
// // assigns the required tokens to the request
// //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, renderModel);
// //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, umbracoContext.PublishedRequest);
// //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbracoContext);
// Here we need to detect if a SurfaceController has posted
var formInfo = RenderRouteHandler.GetFormInfo(requestContext);
if (formInfo != null)
{
var def = new RouteDefinition
{
ActionName = requestContext.RouteData.GetRequiredString("action"),
ControllerName = requestContext.RouteData.GetRequiredString("controller"),
PublishedRequest = umbracoContext.PublishedRequest
};
// //// this is used just for a flag that this is an umbraco custom route
// //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.CustomRouteDataToken, true);
// set the special data token to the current route definition
requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def;
// // Here we need to detect if a SurfaceController has posted
// var formInfo = RenderRouteHandler.GetFormInfo(requestContext);
// if (formInfo != null)
// {
// var def = new RouteDefinition
// {
// ActionName = requestContext.RouteData.GetRequiredString("action"),
// ControllerName = requestContext.RouteData.GetRequiredString("controller"),
// PublishedRequest = umbracoContext.PublishedRequest
// };
return RenderRouteHandler.HandlePostedValues(requestContext, formInfo);
}
// // set the special data token to the current route definition
// requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def;
return new MvcHandler(requestContext);
// return RenderRouteHandler.HandlePostedValues(requestContext, formInfo);
// }
// return new MvcHandler(requestContext);
}
protected abstract IPublishedContent FindContent(RequestContext requestContext, IUmbracoContext umbracoContext);
protected virtual void PreparePublishedContentRequest(IPublishedRequest request)
{
PublishedRouter.PrepareRequest(request);
}
//protected virtual void PreparePublishedContentRequest(IPublishedRequest request)
//{
// PublishedRouter.PrepareRequest(request);
//}
}
}

View File

@@ -1,4 +1,4 @@
using System.Web;
using System.Web;
using Umbraco.Web.Composing;
namespace Umbraco.Web.Routing
@@ -31,9 +31,9 @@ namespace Umbraco.Web.Routing
var frequest = Current.UmbracoContext.PublishedRequest;
var reason = "Cannot render the page at URL '{0}'.";
if (frequest.HasPublishedContent == false)
if (frequest.HasPublishedContent() == false)
reason = "No umbraco document matches the URL '{0}'.";
else if (frequest.HasTemplate == false)
else if (frequest.HasTemplate() == false)
reason = "No template exists to render the document at URL '{0}'.";
response.Write("<html><body><h1>Page not found</h1>");

View File

@@ -135,16 +135,15 @@ namespace Umbraco.Web
// instantiate, prepare and process the published content request
// important to use CleanedUmbracoUrl - lowercase path-only version of the current URL
var request = _publishedRouter.CreateRequest(umbracoContext);
umbracoContext.PublishedRequest = request;
_publishedRouter.PrepareRequest(request);
var requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl);
var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder);
// HandleHttpResponseStatus returns a value indicating that the request should
// not be processed any further, eg because it has been redirect. then, exit.
if (UmbracoModule.HandleHttpResponseStatus(httpContext, request, _logger))
return;
if (request.HasPublishedContent == false)
if (request.HasPublishedContent() == false)
httpContext.RemapHandler(new PublishedContentNotFoundHandler());
else
RewriteToUmbracoHandler(httpContext, request);

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Web;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
@@ -51,8 +51,8 @@ namespace Umbraco.Web
var response = context.Response;
logger.LogDebug("Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}",
pcr.IsRedirect ? (pcr.IsRedirectPermanent ? "permanent" : "redirect") : "none",
pcr.Is404 ? "true" : "false",
pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none",
pcr.Is404() ? "true" : "false",
pcr.ResponseStatusCode);
if(pcr.CacheabilityNoCache)
@@ -64,15 +64,15 @@ namespace Umbraco.Web
foreach (var header in pcr.Headers)
response.AppendHeader(header.Key, header.Value);
if (pcr.IsRedirect)
if (pcr.IsRedirect())
{
if (pcr.IsRedirectPermanent)
if (pcr.IsRedirectPermanent())
response.RedirectPermanent(pcr.RedirectUrl, false); // do not end response
else
response.Redirect(pcr.RedirectUrl, false); // do not end response
end = true;
}
else if (pcr.Is404)
else if (pcr.Is404())
{
response.StatusCode = 404;
response.TrySkipIisCustomErrors = /*Current.Configs.WebRouting().TrySkipIisCustomErrors; TODO introduce from config*/ false;
@@ -90,7 +90,7 @@ namespace Umbraco.Web
//if (pcr.IsRedirect)
// response.End(); // end response -- kills the thread and does not return!
if (pcr.IsRedirect == false) return end;
if (pcr.IsRedirect() == false) return end;
response.Flush();
// bypass everything and directly execute EndRequest event -- but returns