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.Models.PublishedContent; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; using Umbraco.Web.Routing; namespace Umbraco.Web.Common.Controllers { /// /// Represents the default front-end rendering controller. /// [ModelBindingException] [PublishedRequestFilter] public class RenderController : UmbracoController, IRenderController { private readonly ILogger _logger; private readonly ICompositeViewEngine _compositeViewEngine; private readonly IUmbracoContextAccessor _umbracoContextAccessor; private UmbracoRouteValues _umbracoRouteValues; /// /// Initializes a new instance of the class. /// public RenderController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) { _logger = logger; _compositeViewEngine = compositeViewEngine; _umbracoContextAccessor = umbracoContextAccessor; } /// /// Gets the current content item. /// 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; } } /// /// Gets the umbraco context /// protected IUmbracoContext UmbracoContext => _umbracoContextAccessor.UmbracoContext; /// /// Gets the /// protected UmbracoRouteValues UmbracoRouteValues { get { if (_umbracoRouteValues != null) { return _umbracoRouteValues; } if (!ControllerContext.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var def)) { throw new InvalidOperationException($"No route value found with key {Core.Constants.Web.UmbracoRouteDefinitionDataToken}"); } _umbracoRouteValues = (UmbracoRouteValues)def; return _umbracoRouteValues; } } /// /// Ensures that a physical view file exists on disk. /// /// The view name. protected bool EnsurePhsyicalViewExists(string template) { ViewEngineResult result = _compositeViewEngine.FindView(ControllerContext, template, false); if (result.View != null) { return true; } _logger.LogWarning("No physical template file was found for template {Template}", template); return false; } /// /// Gets an action result based on the template name found in the route values and a model. /// /// The type of the model. /// The model. /// The action result. /// If the template found in the route values doesn't physically exist and exception is thrown protected IActionResult CurrentTemplate(T model) { if (EnsurePhsyicalViewExists(UmbracoRouteValues.TemplateName) == false) { throw new InvalidOperationException("No physical template file was found for template " + UmbracoRouteValues.TemplateName); } return View(UmbracoRouteValues.TemplateName, model); } /// /// The default action to render the front-end view. /// public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage)); /// /// Before the controller executes we will handle redirects and not founds /// 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; } } } }