/// <summary> /// Tries to find the document matching the request, by running the IPublishedContentFinder instances. /// </summary> /// <exception cref="InvalidOperationException">There is no finder collection.</exception> internal bool FindPublishedContent(IPublishedRequestBuilder request) { const string tracePrefix = "FindPublishedContent: "; // look for the document // the first successful finder, if any, will set this.PublishedContent, and may also set this.Template // some finders may implement caching using (_profilingLogger.DebugDuration <PublishedRouter>( $"{tracePrefix}Begin finders", $"{tracePrefix}End finders")) { // iterate but return on first one that finds it var found = _contentFinders.Any(finder => { _logger.LogDebug("Finder {ContentFinderType}", finder.GetType().FullName); return(finder.TryFindContent(request)); }); _logger.LogDebug( "Found? {Found}, Content: {PublishedContentId}, Template: {TemplateAlias}, Domain: {Domain}, Culture: {Culture}, StatusCode: {StatusCode}", found, request.HasPublishedContent() ? request.PublishedContent.Id : "NULL", request.HasTemplate() ? request.Template?.Alias : "NULL", request.HasDomain() ? request.Domain.ToString() : "NULL", request.Culture ?? "NULL", request.ResponseStatusCode); return(found); } }
/// <summary> /// Tries to find the document matching the request, by running the IPublishedContentFinder instances. /// </summary> /// <exception cref="InvalidOperationException">There is no finder collection.</exception> internal async Task <bool> FindPublishedContent(IPublishedRequestBuilder request) { const string tracePrefix = "FindPublishedContent: "; // look for the document // the first successful finder, if any, will set this.PublishedContent, and may also set this.Template // some finders may implement caching DisposableTimer?profilingScope = null; try { if (_logger.IsEnabled(LogLevel.Debug)) { profilingScope = _profilingLogger.DebugDuration <PublishedRouter>( $"{tracePrefix}Begin finders", $"{tracePrefix}End finders"); } // iterate but return on first one that finds it var found = false; foreach (IContentFinder contentFinder in _contentFinders) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Finder {ContentFinderType}", contentFinder.GetType().FullName); } found = await contentFinder.TryFindContent(request); if (found) { break; } } if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug( "Found? {Found}, Content: {PublishedContentId}, Template: {TemplateAlias}, Domain: {Domain}, Culture: {Culture}, StatusCode: {StatusCode}", found, request.HasPublishedContent() ? request.PublishedContent?.Id : "NULL", request.HasTemplate() ? request.Template?.Alias : "NULL", request.HasDomain() ? request.Domain?.ToString() : "NULL", request.Culture ?? "NULL", request.ResponseStatusCode); } return(found); } finally { profilingScope?.Dispose(); } }
/// <summary> /// Finds a template for the current node, if any. /// </summary> /// <param name="request">The request builder.</param> /// <param name="contentFoundByFinders"> /// If the content was found by the finders, before anything such as 404, redirect... /// took place. /// </param> private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByFinders) { // TODO: We've removed the event, might need to re-add? // NOTE: at the moment there is only 1 way to find a template, and then ppl must // use the Prepared event to change the template if they wish. Should we also // implement an ITemplateFinder logic? if (request.PublishedContent == null) { request.SetTemplate(null); return; } // read the alternate template alias, from querystring, form, cookie or server vars, // only if the published content is the initial once, else the alternate template // does not apply // + optionally, apply the alternate template on internal redirects var useAltTemplate = contentFoundByFinders || (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirect); var altTemplate = useAltTemplate ? _requestAccessor.GetRequestValue(Constants.Conventions.Url.AltTemplate) : null; if (string.IsNullOrWhiteSpace(altTemplate)) { // we don't have an alternate template specified. use the current one if there's one already, // which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...), // else lookup the template id on the document then lookup the template with that id. if (request.HasTemplate()) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("FindTemplate: Has a template already, and no alternate template."); } return; } // TODO: We need to limit altTemplate to only allow templates that are assigned to the current document type! // if the template isn't assigned to the document type we should log a warning and return 404 var templateId = request.PublishedContent.TemplateId; ITemplate?template = GetTemplate(templateId); request.SetTemplate(template); if (template != null) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug( "FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } } else { _logger.LogWarning("FindTemplate: Could not find template with id {TemplateId}", templateId); } } else { // we have an alternate template specified. lookup the template with that alias // this means the we override any template that a content lookup might have set // so /path/to/page/template1?altTemplate=template2 will use template2 // ignore if the alias does not match - just trace if (request.HasTemplate()) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("FindTemplate: Has a template already, but also an alternative template."); } } if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate); } // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings if (request.PublishedContent.IsAllowedTemplate( _fileService, _contentTypeService, _webRoutingSettings.DisableAlternativeTemplates, _webRoutingSettings.ValidateAlternativeTemplates, altTemplate)) { // allowed, use ITemplate?template = _fileService.GetTemplate(altTemplate); if (template != null) { request.SetTemplate(template); if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug( "FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } } else { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug( "FindTemplate: The alternative template with alias={AltTemplate} does not exist, ignoring.", altTemplate); } } } else { _logger.LogWarning( "FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", altTemplate, request.PublishedContent.Id); // no allowed, back to default var templateId = request.PublishedContent.TemplateId; ITemplate?template = GetTemplate(templateId); request.SetTemplate(template); if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug( "FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template?.Id, template?.Alias); } } } if (!request.HasTemplate()) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("FindTemplate: No template was found."); } // initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true // then reset _pcr.Document to null to force a 404. // // but: because we want to let MVC hijack routes even though no template is defined, we decide that // a missing template is OK but the request will then be forwarded to MVC, which will need to take // care of everything. // // so, don't set _pcr.Document to null here } }
public async Task RenderAsync(int pageId, int?altTemplateId, StringWriter writer) { if (writer == null) { throw new ArgumentNullException(nameof(writer)); } IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); // instantiate a request and process // important to use CleanedUmbracoUrl - lowercase path-only version of the current URL, though this isn't going to matter // terribly much for this implementation since we are just creating a doc content request to modify it's properties manually. IPublishedRequestBuilder requestBuilder = await _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); IPublishedContent?doc = umbracoContext.Content?.GetById(pageId); if (doc == null) { writer.Write("<!-- Could not render template for Id {0}, the document was not found -->", pageId); return; } // in some cases the UmbracoContext will not have a PublishedRequest assigned to it if we are not in the // execution of a front-end rendered page. In this case set the culture to the default. // set the culture to the same as is currently rendering if (umbracoContext.PublishedRequest == null) { ILanguage?defaultLanguage = _languageService.GetAllLanguages().FirstOrDefault(); requestBuilder.SetCulture(defaultLanguage == null ? CultureInfo.CurrentUICulture.Name : defaultLanguage.IsoCode); } else { requestBuilder.SetCulture(umbracoContext.PublishedRequest.Culture); } // set the doc that was found by id requestBuilder.SetPublishedContent(doc); // set the template, either based on the AltTemplate found or the standard template of the doc var templateId = _webRoutingSettings.DisableAlternativeTemplates || !altTemplateId.HasValue ? doc.TemplateId : altTemplateId.Value; if (templateId.HasValue) { requestBuilder.SetTemplate(_fileService.GetTemplate(templateId.Value)); } // if there is not template then exit if (requestBuilder.HasTemplate() == false) { if (altTemplateId.HasValue == false) { writer.Write( "<!-- Could not render template for Id {0}, the document's template was not found with id {0}-->", doc.TemplateId); } else { writer.Write( "<!-- Could not render template for Id {0}, the altTemplate was not found with id {0}-->", altTemplateId); } return; } // First, save all of the items locally that we know are used in the chain of execution, we'll need to restore these // after this page has rendered. SaveExistingItems(out IPublishedRequest? oldPublishedRequest); IPublishedRequest contentRequest = requestBuilder.Build(); try { // set the new items on context objects for this templates execution SetNewItemsOnContextObjects(contentRequest); // Render the template ExecuteTemplateRendering(writer, contentRequest); } finally { // restore items on context objects to continuing rendering the parent template RestoreItems(oldPublishedRequest); } }