removes ResponseStatusDescription and others that aren't used, ports the not found handler, ports redirects, headers, etc...

This commit is contained in:
Shannon
2021-01-06 20:03:49 +11:00
parent f5bd53b223
commit 333479666c
26 changed files with 351 additions and 331 deletions

View File

@@ -60,14 +60,7 @@ namespace Umbraco.Web.Routing
/// </summary>
/// <remarks>Does not actually set the http response status code, only registers that the response
/// should use the specified code. The code will or will not be used, in due time.</remarks>
int ResponseStatusCode { get; }
/// <summary>
/// Gets the content request http response status description.
/// </summary>
/// <remarks>Does not actually set the http response status description, only registers that the response
/// should use the specified description. The description will or will not be used, in due time.</remarks>
string ResponseStatusDescription { get; }
int? ResponseStatusCode { get; }
/// <summary>
/// Gets a list of Extensions to append to the Response.Cache object.

View File

@@ -39,7 +39,7 @@ namespace Umbraco.Web.Routing
/// <summary>
/// Gets the content request http response status code.
/// </summary>
int ResponseStatusCode { get; }
int? ResponseStatusCode { get; }
/// <summary>
/// Gets the current <see cref="IPublishedContent"/> assigned (if any)
@@ -77,7 +77,7 @@ namespace Umbraco.Web.Routing
/// <param name="content">The requested content.</param>
/// <remarks>Depending on <c>UmbracoSettings.InternalRedirectPreservesTemplate</c>, will
/// preserve or reset the template, if any.</remarks>
IPublishedRequestBuilder SetInternalRedirectPublishedContent(IPublishedContent content);
IPublishedRequestBuilder SetInternalRedirectPublishedContent(IPublishedContent content); // TODO: Need to figure this one out
/// <summary>
/// Tries to set the template to use to display the requested content.
@@ -97,11 +97,6 @@ namespace Umbraco.Web.Routing
/// <remarks>Setting the template does refresh <c>RenderingEngine</c>.</remarks>
IPublishedRequestBuilder SetTemplate(ITemplate template);
/// <summary>
/// Resets the template.
/// </summary>
IPublishedRequestBuilder ResetTemplate();
/// <summary>
/// Indicates that the content request should trigger a permanent redirect (301).
/// </summary>
@@ -123,11 +118,10 @@ namespace Umbraco.Web.Routing
/// Sets the http response status code, along with an optional associated description.
/// </summary>
/// <param name="code">The http status code.</param>
/// <param name="description">The description.</param>
/// <remarks>Does not actually set the http response status code and description, only registers that
/// the response should use the specified code and description. The code and description will or will
/// not be used, in due time.</remarks>
IPublishedRequestBuilder SetResponseStatus(int code, string description = null);
IPublishedRequestBuilder SetResponseStatus(int code);
IPublishedRequestBuilder SetCacheabilityNoCache(bool cacheability);
@@ -140,16 +134,5 @@ namespace Umbraco.Web.Routing
/// Sets a dictionary of Headers to append to the Response object.
/// </summary>
IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary<string, string> headers);
/// <summary>
/// Sets a value indicating that the requested content could not be found.
/// </summary>
/// <remarks>This is set in the <c>PublishedContentRequestBuilder</c> and can also be used in
/// custom content finders or <c>Prepared</c> event handlers, where we want to allow developers
/// to indicate a request is 404 but not to cancel it.</remarks>
IPublishedRequestBuilder SetIs404(bool is404);
// TODO: This seems to be the same as is404?
//IPublishedRequestBuilder UpdateToNotFound();
}
}

View File

@@ -16,20 +16,19 @@ namespace Umbraco.Web.Routing
/// <summary>
/// Initializes a new instance of the <see cref="PublishedRequest"/> class.
/// </summary>
public PublishedRequest(Uri uri, /*bool ignorePublishedContentCollisions, */IPublishedContent publishedContent, bool isInternalRedirectPublishedContent, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int responseStatusCode, string responseStatusDescription, IReadOnlyList<string> cacheExtensions, IReadOnlyDictionary<string, string> headers, bool cacheabilityNoCache)
public PublishedRequest(Uri uri, /*bool ignorePublishedContentCollisions, */IPublishedContent publishedContent, bool isInternalRedirectPublishedContent, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int? responseStatusCode, IReadOnlyList<string> cacheExtensions, IReadOnlyDictionary<string, string> headers, bool cacheabilityNoCache)
{
Uri = uri ?? throw new ArgumentNullException(nameof(uri));
//IgnorePublishedContentCollisions = ignorePublishedContentCollisions;
PublishedContent = publishedContent ?? throw new ArgumentNullException(nameof(publishedContent));
PublishedContent = publishedContent;
IsInternalRedirectPublishedContent = isInternalRedirectPublishedContent;
Template = template ?? throw new ArgumentNullException(nameof(template));
Domain = domain ?? throw new ArgumentNullException(nameof(domain));
Template = template;
Domain = domain;
Culture = culture ?? throw new ArgumentNullException(nameof(culture));
RedirectUrl = redirectUrl ?? throw new ArgumentNullException(nameof(redirectUrl));
RedirectUrl = redirectUrl;
ResponseStatusCode = responseStatusCode;
ResponseStatusDescription = responseStatusDescription ?? throw new ArgumentNullException(nameof(responseStatusDescription));
CacheExtensions = cacheExtensions ?? throw new ArgumentNullException(nameof(cacheExtensions));
Headers = headers ?? throw new ArgumentNullException(nameof(headers));
CacheExtensions = cacheExtensions;
Headers = headers;
CacheabilityNoCache = cacheabilityNoCache;
}
@@ -42,9 +41,6 @@ namespace Umbraco.Web.Routing
/// <inheritdoc/>
public IPublishedContent PublishedContent { get; }
/// <inheritdoc/>
public IPublishedContent InitialPublishedContent { get; }
/// <inheritdoc/>
public bool IsInternalRedirectPublishedContent { get; }
@@ -61,10 +57,7 @@ namespace Umbraco.Web.Routing
public string RedirectUrl { get; }
/// <inheritdoc/>
public int ResponseStatusCode { get; }
/// <inheritdoc/>
public string ResponseStatusDescription { get; }
public int? ResponseStatusCode { get; }
/// <inheritdoc/>
public IReadOnlyList<string> CacheExtensions { get; }

View File

@@ -17,19 +17,19 @@ namespace Umbraco.Web.Routing
private IReadOnlyList<string> _cacheExtensions;
private IPublishedContent _internalRedirectContent;
private string _redirectUrl;
private HttpStatusCode _responseStatus = HttpStatusCode.NotFound;
private string _responseDesc;
private HttpStatusCode? _responseStatus;
/// <summary>
/// Initializes a new instance of the <see cref="PublishedRequestBuilder"/> class.
/// </summary>
public PublishedRequestBuilder(IFileService fileService)
public PublishedRequestBuilder(Uri uri, IFileService fileService)
{
Uri = uri;
_fileService = fileService;
}
/// <inheritdoc/>
public Uri Uri { get; private set; }
public Uri Uri { get; }
/// <inheritdoc/>
public DomainAndUri Domain { get; private set; }
@@ -44,7 +44,7 @@ namespace Umbraco.Web.Routing
public bool IsInternalRedirectPublishedContent { get; private set; } // TODO: Not sure what this is yet
/// <inheritdoc/>
public int ResponseStatusCode => (int)_responseStatus;
public int? ResponseStatusCode => _responseStatus.HasValue ? (int?)_responseStatus : null;
/// <inheritdoc/>
public IPublishedContent PublishedContent { get; private set; }
@@ -58,19 +58,11 @@ namespace Umbraco.Web.Routing
Domain,
Culture,
_redirectUrl,
(int)_responseStatus,
_responseDesc,
_responseStatus.HasValue ? (int?)_responseStatus : null,
_cacheExtensions,
_headers,
_cacheability);
/// <inheritdoc/>
public IPublishedRequestBuilder ResetTemplate()
{
Template = null;
return this;
}
/// <inheritdoc/>
public IPublishedRequestBuilder SetCacheabilityNoCache(bool cacheability)
{
@@ -113,13 +105,6 @@ namespace Umbraco.Web.Routing
return this;
}
/// <inheritdoc/>
public IPublishedRequestBuilder SetIs404(bool is404)
{
_responseStatus = HttpStatusCode.NotFound;
return this;
}
/// <inheritdoc/>
public IPublishedRequestBuilder SetPublishedContent(IPublishedContent content)
{
@@ -144,10 +129,9 @@ namespace Umbraco.Web.Routing
}
/// <inheritdoc/>
public IPublishedRequestBuilder SetResponseStatus(int code, string description = null)
public IPublishedRequestBuilder SetResponseStatus(int code)
{
_responseStatus = (HttpStatusCode)code;
_responseDesc = description;
return this;
}

View File

@@ -2,14 +2,42 @@ using System.Net;
namespace Umbraco.Web.Routing
{
public static class PublishedRequestExtensions
{
/// <summary>
/// Gets the <see cref="UmbracoRouteResult"/>
/// </summary>
public static UmbracoRouteResult GetRouteResult(this IPublishedRequest publishedRequest)
{
if (publishedRequest.IsRedirect())
{
return UmbracoRouteResult.Redirect;
}
if (!publishedRequest.HasPublishedContent())
{
return UmbracoRouteResult.NotFound;
}
return UmbracoRouteResult.Success;
}
/// <summary>
/// Gets a value indicating whether the request was successfully routed
/// </summary>
public static bool Success(this IPublishedRequest publishedRequest)
=> !publishedRequest.IsRedirect() && publishedRequest.HasPublishedContent();
/// <summary>
/// Sets the response status to be 404 not found
/// </summary>
public static IPublishedRequestBuilder SetIs404(this IPublishedRequestBuilder publishedRequest)
{
publishedRequest.SetResponseStatus((int)HttpStatusCode.NotFound);
return publishedRequest;
}
/// <summary>
/// Gets a value indicating whether the content request has a content.
/// </summary>

View File

@@ -72,7 +72,7 @@ namespace Umbraco.Web.Routing
}
/// <inheritdoc />
public IPublishedRequestBuilder CreateRequest(Uri uri) => new PublishedRequestBuilder(_fileService);
public IPublishedRequestBuilder CreateRequest(Uri uri) => new PublishedRequestBuilder(uri, _fileService);
/// <inheritdoc />
public bool TryRouteRequest(IPublishedRequestBuilder request)
@@ -106,6 +106,10 @@ namespace Umbraco.Web.Routing
/// <inheritdoc />
public IPublishedRequest RouteRequest(IPublishedRequestBuilder request)
{
//// trigger the Preparing event - at that point anything can still be changed
//// the idea is that it is possible to change the uri
//request.OnPreparing();
// find domain
FindDomain(request);
@@ -411,7 +415,7 @@ namespace Umbraco.Web.Routing
// handle not found
if (request.PublishedContent == null)
{
request.SetIs404(true);
request.SetIs404();
_logger.LogDebug("HandlePublishedContent: No document, try last chance lookup");
// if it fails then give up, there isn't much more that we can do

View File

@@ -0,0 +1,20 @@
namespace Umbraco.Web.Routing
{
public enum UmbracoRouteResult
{
/// <summary>
/// Routing was successful and a content item was matched
/// </summary>
Success,
/// <summary>
/// A redirection took place
/// </summary>
Redirect,
/// <summary>
/// Nothing matched
/// </summary>
NotFound
}
}

View File

@@ -111,7 +111,7 @@ namespace Umbraco.Web.Routing
frequest
.SetPublishedContent(content)
.SetIs404(true);
.SetIs404();
return content != null;
}

View File

@@ -12,9 +12,11 @@ using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using Umbraco.Web.Common.ModelBinders;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.Models;
using Umbraco.Web.Routing;
namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders
{
@@ -211,9 +213,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders
/// </summary>
private ModelBindingContext CreateBindingContextForUmbracoRequest(Type modelType, IPublishedContent publishedContent)
{
var builder = new PublishedRequestBuilder(new Uri("https://example.com"), Mock.Of<IFileService>());
builder.SetPublishedContent(publishedContent);
IPublishedRequest publishedRequest = builder.Build();
var httpContext = new DefaultHttpContext();
var routeData = new RouteData();
routeData.Values.Add(Constants.Web.UmbracoRouteDefinitionDataToken, new UmbracoRouteValues(publishedContent));
routeData.Values.Add(Constants.Web.UmbracoRouteDefinitionDataToken, new UmbracoRouteValues(publishedRequest));
{
}

View File

@@ -162,8 +162,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
IPublishedContent content = Mock.Of<IPublishedContent>(publishedContent => publishedContent.Id == 12345);
var builder = new PublishedRequestBuilder(umbracoContext.CleanedUmbracoUrl, Mock.Of<IFileService>());
builder.SetPublishedContent(content);
IPublishedRequest publishedRequest = builder.Build();
var routeDefinition = new UmbracoRouteValues(content);
var routeDefinition = new UmbracoRouteValues(publishedRequest);
var routeData = new RouteData();
routeData.Values.Add(CoreConstants.Web.UmbracoRouteDefinitionDataToken, routeDefinition);

View File

@@ -0,0 +1,60 @@
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Common.ActionsResults
{
/// <summary>
/// Returns the Umbraco not found result
/// </summary>
public class PublishedContentNotFoundResult : IActionResult
{
private readonly IUmbracoContext _umbracoContext;
private readonly string _message;
/// <summary>
/// Initializes a new instance of the <see cref="PublishedContentNotFoundResult"/> class.
/// </summary>
public PublishedContentNotFoundResult(IUmbracoContext umbracoContext, string message = null)
{
_umbracoContext = umbracoContext;
_message = message;
}
/// <inheritdoc/>
public async Task ExecuteResultAsync(ActionContext context)
{
HttpResponse response = context.HttpContext.Response;
response.Clear();
response.StatusCode = StatusCodes.Status404NotFound;
IPublishedRequest frequest = _umbracoContext.PublishedRequest;
var reason = "Cannot render the page at URL '{0}'.";
if (frequest.HasPublishedContent() == false)
{
reason = "No umbraco document matches the URL '{0}'.";
}
else if (frequest.HasTemplate() == false)
{
reason = "No template exists to render the document at URL '{0}'.";
}
await response.WriteAsync("<html><body><h1>Page not found</h1>");
await response.WriteAsync("<h2>");
await response.WriteAsync(string.Format(reason, WebUtility.HtmlEncode(_umbracoContext.OriginalRequestUrl.PathAndQuery)));
await response.WriteAsync("</h2>");
if (string.IsNullOrWhiteSpace(_message) == false)
{
await response.WriteAsync("<p>" + _message + "</p>");
}
await response.WriteAsync("<p>This page can be replaced with a custom 404. Check the documentation for \"custom 404\".</p>");
await response.WriteAsync("<p style=\"border-top: 1px solid #ccc; padding-top: 10px\"><small>This page is intentionally left ugly ;-)</small></p>");
await response.WriteAsync("</body></html>");
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Common.Controllers
{
/// <summary>
/// Deals with custom headers for the umbraco request
/// </summary>
internal class PublishedRequestFilterAttribute : ResultFilterAttribute
{
/// <summary>
/// Gets the <see cref="UmbracoRouteValues"/>
/// </summary>
protected UmbracoRouteValues GetUmbracoRouteValues(ResultExecutingContext context)
{
if (!context.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var def))
{
throw new InvalidOperationException($"No route value found with key {Core.Constants.Web.UmbracoRouteDefinitionDataToken}");
}
return (UmbracoRouteValues)def;
}
/// <summary>
/// Deals with custom headers for the umbraco request
/// </summary>
public override void OnResultExecuting(ResultExecutingContext context)
{
UmbracoRouteValues routeVals = GetUmbracoRouteValues(context);
IPublishedRequest pcr = routeVals.PublishedRequest;
// now we can deal with headers, etc...
if (pcr.ResponseStatusCode.HasValue)
{
// set status code -- even for redirects
context.HttpContext.Response.StatusCode = pcr.ResponseStatusCode.Value;
}
AddCacheControlHeaders(context, pcr);
if (pcr.Headers != null)
{
foreach (KeyValuePair<string, string> header in pcr.Headers)
{
context.HttpContext.Response.Headers.Append(header.Key, header.Value);
}
}
}
private void AddCacheControlHeaders(ResultExecutingContext context, IPublishedRequest pcr)
{
var cacheControlHeaders = new List<string>();
if (pcr.CacheabilityNoCache)
{
cacheControlHeaders.Add("no-cache");
}
if (pcr.CacheExtensions != null)
{
foreach (var cacheExtension in pcr.CacheExtensions)
{
cacheControlHeaders.Add(cacheExtension);
}
}
if (cacheControlHeaders.Count > 0)
{
context.HttpContext.Response.Headers["Cache-Control"] = string.Join(", ", cacheControlHeaders);
}
}
}
}

View File

@@ -1,9 +1,11 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Common.ActionsResults;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.Models;
@@ -11,30 +13,50 @@ using Umbraco.Web.Routing;
namespace Umbraco.Web.Common.Controllers
{
/// <summary>
/// Represents the default front-end rendering controller.
/// </summary>
[ModelBindingException]
[PublishedRequestFilter]
public class RenderController : UmbracoController, IRenderController
{
private readonly ILogger<RenderController> _logger;
private readonly ICompositeViewEngine _compositeViewEngine;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private UmbracoRouteValues _umbracoRouteValues;
/// <summary>
/// Initializes a new instance of the <see cref="RenderController"/> class.
/// </summary>
public RenderController(ILogger<RenderController> logger, ICompositeViewEngine compositeViewEngine)
public RenderController(ILogger<RenderController> logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor)
{
_logger = logger;
_compositeViewEngine = compositeViewEngine;
_umbracoContextAccessor = umbracoContextAccessor;
}
/// <summary>
/// Gets the current content item.
/// </summary>
protected IPublishedContent CurrentPage => UmbracoRouteValues.PublishedContent;
protected IPublishedContent CurrentPage
{
get
{
if (!UmbracoRouteValues.PublishedRequest.HasPublishedContent())
{
// This will never be accessed this way since the controller will handle redirects and not founds
// before this can be accessed but we need to be explicit.
throw new InvalidOperationException("There is no published content found in the request");
}
return UmbracoRouteValues.PublishedRequest.PublishedContent;
}
}
/// <summary>
/// Gets the umbraco context
/// </summary>
protected IUmbracoContext UmbracoContext => _umbracoContextAccessor.UmbracoContext;
/// <summary>
/// Gets the <see cref="UmbracoRouteValues"/>
@@ -95,5 +117,41 @@ namespace Umbraco.Web.Common.Controllers
/// The default action to render the front-end view.
/// </summary>
public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage));
/// <summary>
/// Before the controller executes we will handle redirects and not founds
/// </summary>
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
IPublishedRequest pcr = UmbracoRouteValues.PublishedRequest;
_logger.LogDebug(
"Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}",
pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none",
pcr.Is404() ? "true" : "false",
pcr.ResponseStatusCode);
UmbracoRouteResult routeStatus = pcr.GetRouteResult();
switch (routeStatus)
{
case UmbracoRouteResult.Redirect:
// set the redirect result and do not call next to short circuit
context.Result = pcr.IsRedirectPermanent()
? RedirectPermanent(pcr.RedirectUrl)
: Redirect(pcr.RedirectUrl);
break;
case UmbracoRouteResult.NotFound:
// set the redirect result and do not call next to short circuit
context.Result = new PublishedContentNotFoundResult(UmbracoContext);
break;
case UmbracoRouteResult.Success:
default:
// continue normally
await next();
break;
}
}
}
}

View File

@@ -27,7 +27,7 @@ namespace Umbraco.Web.Common.ModelBinders
return Task.CompletedTask;
}
BindModel(bindingContext, umbracoRouteValues.PublishedContent, bindingContext.ModelType);
BindModel(bindingContext, umbracoRouteValues.PublishedRequest.PublishedContent, bindingContext.ModelType);
return Task.CompletedTask;
}

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Web.Common.Routing
/// Initializes a new instance of the <see cref="UmbracoRouteValues"/> class.
/// </summary>
public UmbracoRouteValues(
IPublishedContent publishedContent,
IPublishedRequest publishedRequest,
string controllerName = null,
Type controllerType = null,
string actionName = DefaultActionName,
@@ -29,7 +29,7 @@ namespace Umbraco.Web.Common.Routing
{
ControllerName = controllerName ?? ControllerExtensions.GetControllerName<RenderController>();
ControllerType = controllerType ?? typeof(RenderController);
PublishedContent = publishedContent;
PublishedRequest = publishedRequest;
HasHijackedRoute = hasHijackedRoute;
ActionName = actionName;
TemplateName = templateName;
@@ -56,9 +56,9 @@ namespace Umbraco.Web.Common.Routing
public Type ControllerType { get; }
/// <summary>
/// Gets the <see cref="IPublishedContent"/>
/// Gets the <see cref="IPublishedRequest"/>
/// </summary>
public IPublishedContent PublishedContent { get; }
public IPublishedRequest PublishedRequest { get; }
/// <summary>
/// Gets a value indicating whether the current request has a hijacked route/user controller routed for it

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Specialized;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
@@ -12,6 +12,7 @@ using Umbraco.Web.Routing;
namespace Umbraco.Web.Website.ActionResults
{
/// <summary>
/// Redirects to an Umbraco page by Id or Entity
/// </summary>

View File

@@ -44,7 +44,7 @@ namespace Umbraco.Web.Website.Controllers
}
var routeDef = routeDefAttempt.Result;
return routeDef.PublishedContent;
return routeDef.PublishedRequest.PublishedContent;
}
}

View File

@@ -23,8 +23,6 @@ namespace Umbraco.Web.Website.DependencyInjection
/// </summary>
public static IUmbracoBuilder AddWebsite(this IUmbracoBuilder builder)
{
builder.Services.AddUnique<NoContentRoutes>();
builder.WithCollectionBuilder<SurfaceControllerTypeCollectionBuilder>()
.Add(builder.TypeLoader.GetSurfaceControllers());

View File

@@ -37,9 +37,6 @@ namespace Umbraco.Extensions
{
app.UseEndpoints(endpoints =>
{
NoContentRoutes noContentRoutes = app.ApplicationServices.GetRequiredService<NoContentRoutes>();
noContentRoutes.CreateRoutes(endpoints);
endpoints.MapDynamicControllerRoute<UmbracoRouteValueTransformer>("/{**slug}");
});

View File

@@ -1,59 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
using Umbraco.Web.Common.Routing;
namespace Umbraco.Web.Website.Routing
{
/// <summary>
/// Creates route for the no content page
/// </summary>
public class NoContentRoutes : IAreaRoutes
{
private readonly IRuntimeState _runtimeState;
private readonly string _umbracoPathSegment;
/// <summary>
/// Initializes a new instance of the <see cref="NoContentRoutes"/> class.
/// </summary>
public NoContentRoutes(
IOptions<GlobalSettings> globalSettings,
IHostingEnvironment hostingEnvironment,
IRuntimeState runtimeState)
{
_runtimeState = runtimeState;
_umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment);
}
/// <inheritdoc/>
public void CreateRoutes(IEndpointRouteBuilder endpoints)
{
switch (_runtimeState.Level)
{
case RuntimeLevel.Install:
break;
case RuntimeLevel.Upgrade:
break;
case RuntimeLevel.Run:
// TODO: I don't really think this is working AFAIK the code has just been migrated but it's not really enabled
// yet. Our route handler needs to be aware that there is no content and redirect there. Though, this could all be
// managed directly in UmbracoRouteValueTransformer. Else it could actually do a 'redirect' but that would need to be
// an internal rewrite.
endpoints.MapControllerRoute(
Constants.Web.NoContentRouteName, // named consistently
_umbracoPathSegment + "/UmbNoContent",
new { controller = "RenderNoContent", action = "Index" });
break;
case RuntimeLevel.BootFailed:
case RuntimeLevel.Unknown:
case RuntimeLevel.Boot:
break;
}
}
}
}

View File

@@ -93,14 +93,19 @@ namespace Umbraco.Web.Website.Routing
return values;
}
bool routed = RouteRequest(_umbracoContextAccessor.UmbracoContext, out IPublishedRequest publishedRequest);
if (!routed)
// Check if there is no existing content and return the no content controller
if (!_umbracoContextAccessor.UmbracoContext.Content.HasContent())
{
return values;
// TODO: Deal with it not being routable, perhaps this should be an enum result?
values["controller"] = ControllerExtensions.GetControllerName<RenderNoContentController>();
values["action"] = nameof(RenderNoContentController.Index);
return await Task.FromResult(values);
}
RouteRequest(_umbracoContextAccessor.UmbracoContext, out IPublishedRequest publishedRequest);
UmbracoRouteValues routeDef = GetUmbracoRouteDefinition(httpContext, values, publishedRequest);
values["controller"] = routeDef.ControllerName;
if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false)
{
@@ -134,7 +139,6 @@ namespace Umbraco.Web.Website.Routing
var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType);
string customActionName = null;
var customControllerName = request.PublishedContent.ContentType.Alias; // never null
// check that a template is defined), if it doesn't and there is a hijacked route it will just route
// to the index Action
@@ -143,17 +147,31 @@ namespace Umbraco.Web.Website.Routing
// 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.
customActionName = request.GetTemplateAlias().Split('.')[0].ToSafeAlias(_shortStringHelper);
customActionName = request.GetTemplateAlias()?.Split('.')[0].ToSafeAlias(_shortStringHelper);
}
// creates the default route definition which maps to the 'UmbracoController' controller
var def = new UmbracoRouteValues(
request.PublishedContent,
request,
defaultControllerName,
defaultControllerType,
templateName: customActionName);
IReadOnlyList<ControllerActionDescriptor> candidates = FindControllerCandidates(customControllerName, customActionName, def.ActionName);
var customControllerName = request.PublishedContent?.ContentType.Alias;
if (customControllerName != null)
{
def = DetermineHijackedRoute(def, customControllerName, customActionName, request);
}
// store the route definition
values.TryAdd(Constants.Web.UmbracoRouteDefinitionDataToken, def);
return def;
}
private UmbracoRouteValues DetermineHijackedRoute(UmbracoRouteValues routeValues, string customControllerName, string customActionName, IPublishedRequest request)
{
IReadOnlyList<ControllerActionDescriptor> candidates = FindControllerCandidates(customControllerName, customActionName, routeValues.ActionName);
// check if there's a custom controller assigned, base on the document type alias.
var customControllerCandidates = candidates.Where(x => x.ControllerName.InvariantEquals(customControllerName)).ToList();
@@ -170,11 +188,12 @@ namespace Umbraco.Web.Website.Routing
// now check if the custom action matches
var customActionExists = customActionName != null && customControllerCandidates.Any(x => x.ActionName.InvariantEquals(customActionName));
def = new UmbracoRouteValues(
request.PublishedContent,
// it's a hijacked route with a custom controller, so return the the values
return new UmbracoRouteValues(
request,
controllerDescriptor.ControllerName,
controllerDescriptor.ControllerTypeInfo,
customActionExists ? customActionName : def.ActionName,
customActionExists ? customActionName : routeValues.ActionName,
customActionName,
true); // Hijacked = true
}
@@ -192,10 +211,7 @@ namespace Umbraco.Web.Website.Routing
}
}
// store the route definition
values.TryAdd(Constants.Web.UmbracoRouteDefinitionDataToken, def);
return def;
return routeValues;
}
/// <summary>
@@ -228,7 +244,7 @@ namespace Umbraco.Web.Website.Routing
// Maybe could be a one-time Set method instead?
publishedRequest = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder);
return publishedRequest.Success() && publishedRequest.HasPublishedContent();
return publishedRequest.Success();
// // HandleHttpResponseStatus returns a value indicating that the request should
// // not be processed any further, eg because it has been redirect. then, exit.

View File

@@ -284,19 +284,22 @@ 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)
{
// means the builder could not find a proper document to handle 404
return new PublishedContentNotFoundHandler();
}
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.");
}
// TODO: Handle this differently in netcore....
//if (request.HasPublishedContent() == false)
//{
// // means the builder could not find a proper document to handle 404
// return new PublishedContentNotFoundHandler();
//}
//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;
}
@@ -318,6 +321,7 @@ namespace Umbraco.Web.Mvc
return HandlePostedValues(requestContext, postedInfo);
}
// TODO: Surely this check is part of the PublishedRouter?
// Here we need to check if there is no hijacked route and no template assigned,
// if this is the case we want to return a blank page, but we'll leave that up to the NoTemplateHandler.
@@ -326,13 +330,14 @@ namespace Umbraco.Web.Mvc
if (request.HasTemplate() == false && Features.Disabled.DisableTemplates == false && routeDef.HasHijackedRoute == false)
{
// TODO: Handle this differently
// TODO: Handle this differently in netcore....
// 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.
if (UmbracoModule.HandleHttpResponseStatus(requestContext.HttpContext, request, Current.Logger))
return null;
//if (UmbracoModule.HandleHttpResponseStatus(requestContext.HttpContext, request, Current.Logger))
// return null;
var handler = GetHandlerOnMissingTemplate(request);

View File

@@ -1,52 +0,0 @@
using System.Web;
using Umbraco.Web.Composing;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Gets executed when no document can be found in Umbraco
/// </summary>
internal class PublishedContentNotFoundHandler : IHttpHandler
{
private readonly string _message;
public PublishedContentNotFoundHandler()
{ }
public PublishedContentNotFoundHandler(string message)
{
_message = message;
}
public void ProcessRequest(HttpContext context)
{
WriteOutput(context);
}
internal void WriteOutput(HttpContext context)
{
var response = context.Response;
response.Clear();
var frequest = Current.UmbracoContext.PublishedRequest;
var reason = "Cannot render the page at URL '{0}'.";
if (frequest.HasPublishedContent() == false)
reason = "No umbraco document matches the URL '{0}'.";
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>");
response.Write("<h2>");
response.Write(string.Format(reason, HttpUtility.HtmlEncode(Current.UmbracoContext.OriginalRequestUrl.PathAndQuery)));
response.Write("</h2>");
if (string.IsNullOrWhiteSpace(_message) == false)
response.Write("<p>" + _message + "</p>");
response.Write("<p>This page can be replaced with a custom 404. Check the documentation for \"custom 404\".</p>");
response.Write("<p style=\"border-top: 1px solid #ccc; padding-top: 10px\"><small>This page is intentionally left ugly ;-)</small></p>");
response.Write("</body></html>");
}
public bool IsReusable => false;
}
}

View File

@@ -233,7 +233,6 @@
<Compile Include="RouteCollectionExtensions.cs" />
<Compile Include="UmbracoHelper.cs" />
<Compile Include="Mvc\ViewDataContainerExtensions.cs" />
<Compile Include="Routing\PublishedContentNotFoundHandler.cs" />
<Compile Include="Mvc\RenderActionInvoker.cs" />
<Compile Include="Mvc\RenderRouteHandler.cs" />
<Compile Include="Mvc\RouteDefinition.cs" />

View File

@@ -138,15 +138,15 @@ namespace Umbraco.Web
var requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl);
var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder);
// NOTE: This has been ported to netcore
// 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)
httpContext.RemapHandler(new PublishedContentNotFoundHandler());
else
RewriteToUmbracoHandler(httpContext, request);
//if (UmbracoModule.HandleHttpResponseStatus(httpContext, request, _logger))
// return;
//if (request.HasPublishedContent() == false)
// httpContext.RemapHandler(new PublishedContentNotFoundHandler());
//else
// RewriteToUmbracoHandler(httpContext, request);
}
#endregion
@@ -257,40 +257,6 @@ namespace Umbraco.Web
urlRouting.PostResolveRequestCache(context);
}
/// <summary>
/// Rewrites to the Umbraco handler - we always send the request via our MVC rendering engine, this will deal with
/// requests destined for webforms.
/// </summary>
/// <param name="context"></param>
/// <param name="pcr"> </param>
private void RewriteToUmbracoHandler(HttpContextBase context, IPublishedRequest pcr)
{
// NOTE: we do not want to use TransferRequest even though many docs say it is better with IIS7, turns out this is
// not what we need. The purpose of TransferRequest is to ensure that .net processes all of the rules for the newly
// rewritten URL, but this is not what we want!
// read: http://forums.iis.net/t/1146511.aspx
var query = pcr.Uri.Query.TrimStart('?');
// GlobalSettings.Path has already been through IOHelper.ResolveUrl() so it begins with / and vdir (if any)
var rewritePath = _globalSettings.GetBackOfficePath(_hostingEnvironment).TrimEnd('/') + "/RenderMvc";
// rewrite the path to the path of the handler (i.e. /umbraco/RenderMvc)
context.RewritePath(rewritePath, "", query, false);
//if it is MVC we need to do something special, we are not using TransferRequest as this will
//require us to rewrite the path with query strings and then re-parse the query strings, this would
//also mean that we need to handle IIS 7 vs pre-IIS 7 differently. Instead we are just going to create
//an instance of the UrlRoutingModule and call it's PostResolveRequestCache method. This does:
// * Looks up the route based on the new rewritten URL
// * Creates the RequestContext with all route parameters and then executes the correct handler that matches the route
//we also cannot re-create this functionality because the setter for the HttpContext.Request.RequestContext is internal
//so really, this is pretty much the only way without using Server.TransferRequest and if we did that, we'd have to rethink
//a bunch of things!
var urlRouting = new UrlRoutingModule();
urlRouting.PostResolveRequestCache(context);
}
#endregion

View File

@@ -41,65 +41,5 @@ namespace Umbraco.Web
{
EndRequest?.Invoke(sender, args);
}
// returns a value indicating whether redirection took place and the request has
// been completed - because we don't want to Response.End() here to terminate
// everything properly.
internal static bool HandleHttpResponseStatus(HttpContextBase context, IPublishedRequest pcr, ILogger logger)
{
var end = false;
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.ResponseStatusCode);
if(pcr.CacheabilityNoCache)
response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);
foreach (var cacheExtension in pcr.CacheExtensions)
response.Cache.AppendCacheExtension(cacheExtension);
foreach (var header in pcr.Headers)
response.AppendHeader(header.Key, header.Value);
if (pcr.IsRedirect())
{
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())
{
response.StatusCode = 404;
response.TrySkipIisCustomErrors = /*Current.Configs.WebRouting().TrySkipIisCustomErrors; TODO introduce from config*/ false;
if (response.TrySkipIisCustomErrors == false)
logger.LogWarning("Status code is 404 yet TrySkipIisCustomErrors is false - IIS will take over.");
}
if (pcr.ResponseStatusCode > 0)
{
// set status code -- even for redirects
response.StatusCode = pcr.ResponseStatusCode;
response.StatusDescription = pcr.ResponseStatusDescription;
}
//if (pcr.IsRedirect)
// response.End(); // end response -- kills the thread and does not return!
if (pcr.IsRedirect() == false) return end;
response.Flush();
// bypass everything and directly execute EndRequest event -- but returns
context.ApplicationInstance.CompleteRequest();
// though some say that .CompleteRequest() does not properly shutdown the response
// and the request will hang until the whole code has run... would need to test?
logger.LogDebug("Response status: redirecting, complete request now.");
return end;
}
}
}