using System; using System.Collections.Generic; using System.IO; using System.Web; using System.Web.Routing; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; namespace Umbraco.Web { /// /// Class that encapsulates Umbraco information of a specific HTTP request /// public class UmbracoContext : DisposableObjectSlim, IDisposeOnRequestEnd { private readonly IGlobalSettings _globalSettings; private readonly Lazy _publishedSnapshot; private string _previewToken; private bool? _previewing; // initializes a new instance of the UmbracoContext class // internal for unit tests // otherwise it's used by EnsureContext above // warn: does *not* manage setting any IUmbracoContextAccessor internal UmbracoContext(HttpContextBase httpContext, IPublishedSnapshotService publishedSnapshotService, WebSecurity webSecurity, IUmbracoSettingsSection umbracoSettings, IEnumerable urlProviders, IEnumerable mediaUrlProviders, IGlobalSettings globalSettings, IVariationContextAccessor variationContextAccessor) { if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); if (publishedSnapshotService == null) throw new ArgumentNullException(nameof(publishedSnapshotService)); if (webSecurity == null) throw new ArgumentNullException(nameof(webSecurity)); if (umbracoSettings == null) throw new ArgumentNullException(nameof(umbracoSettings)); if (urlProviders == null) throw new ArgumentNullException(nameof(urlProviders)); if (mediaUrlProviders == null) throw new ArgumentNullException(nameof(mediaUrlProviders)); VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); // ensure that this instance is disposed when the request terminates, though we *also* ensure // this happens in the Umbraco module since the UmbracoCOntext is added to the HttpContext items. // // also, it *can* be returned by the container with a PerRequest lifetime, meaning that the // container *could* also try to dispose it. // // all in all, this context may be disposed more than once, but DisposableObject ensures that // it is ok and it will be actually disposed only once. httpContext.DisposeOnPipelineCompleted(this); ObjectCreated = DateTime.Now; UmbracoRequestId = Guid.NewGuid(); HttpContext = httpContext; Security = webSecurity; // beware - we cannot expect a current user here, so detecting preview mode must be a lazy thing _publishedSnapshot = new Lazy(() => publishedSnapshotService.CreatePublishedSnapshot(PreviewToken)); // set the urls... // NOTE: The request will not be available during app startup so we can only set this to an absolute URL of localhost, this // is a work around to being able to access the UmbracoContext during application startup and this will also ensure that people // 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get // the current domain during application startup. // see: http://issues.umbraco.org/issue/U4-1890 // OriginalRequestUrl = GetRequestFromContext()?.Url ?? new Uri("http://localhost"); CleanedUmbracoUrl = UriUtility.UriToUmbraco(OriginalRequestUrl); UrlProvider = new UrlProvider(this, umbracoSettings.WebRouting, urlProviders, mediaUrlProviders, variationContextAccessor); } /// /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this /// object is instantiated which in the web site is created during the BeginRequest phase. /// We can then determine complete rendering time from that. /// internal DateTime ObjectCreated { get; } /// /// This is used internally for debugging and also used to define anything required to distinguish this request from another. /// internal Guid UmbracoRequestId { get; } /// /// Gets the WebSecurity class /// public WebSecurity Security { get; } /// /// Gets the uri that is handled by ASP.NET after server-side rewriting took place. /// internal Uri OriginalRequestUrl { get; } /// /// Gets the cleaned up url that is handled by Umbraco. /// /// That is, lowercase, no trailing slash after path, no .aspx... internal Uri CleanedUmbracoUrl { get; } /// /// Gets the published snapshot. /// public IPublishedSnapshot PublishedSnapshot => _publishedSnapshot.Value; /// /// Gets the published content cache. /// [Obsolete("Use the Content property.")] public IPublishedContentCache ContentCache => PublishedSnapshot.Content; /// /// Gets the published content cache. /// public IPublishedContentCache Content => PublishedSnapshot.Content; /// /// Gets the published media cache. /// [Obsolete("Use the Media property.")] public IPublishedMediaCache MediaCache => PublishedSnapshot.Media; /// /// Gets the published media cache. /// public IPublishedMediaCache Media => PublishedSnapshot.Media; /// /// Gets the domains cache. /// public IDomainCache Domains => PublishedSnapshot.Domains; /// /// Boolean value indicating whether the current request is a front-end umbraco request /// public bool IsFrontEndUmbracoRequest => PublishedRequest != null; /// /// Gets the url provider. /// public UrlProvider UrlProvider { get; } /// /// Gets/sets the PublishedRequest object /// public PublishedRequest PublishedRequest { get; set; } /// /// Exposes the HttpContext for the current request /// public HttpContextBase HttpContext { get; } /// /// Gets the variation context accessor. /// public IVariationContextAccessor VariationContextAccessor { get; } /// /// Gets a value indicating whether the request has debugging enabled /// /// true if this instance is debug; otherwise, false. public bool IsDebug { get { var request = GetRequestFromContext(); //NOTE: the request can be null during app startup! return GlobalSettings.DebugMode && request != null && (string.IsNullOrEmpty(request["umbdebugshowtrace"]) == false || string.IsNullOrEmpty(request["umbdebug"]) == false || string.IsNullOrEmpty(request.Cookies["UMB-DEBUG"]?.Value) == false); } } /// /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) /// public bool InPreviewMode { get { if (_previewing.HasValue == false) DetectPreviewMode(); return _previewing ?? false; } private set => _previewing = value; } #region Urls /// /// Gets the url of a content identified by its identifier. /// /// The content identifier. /// /// The url for the content. public string Url(int contentId, string culture = null) { return UrlProvider.GetUrl(contentId, culture: culture); } /// /// Gets the url of a content identified by its identifier. /// /// The content identifier. /// /// The url for the content. public string Url(Guid contentId, string culture = null) { return UrlProvider.GetUrl(contentId, culture: culture); } /// /// Gets the url of a content identified by its identifier, in a specified mode. /// /// The content identifier. /// The mode. /// /// The url for the content. public string Url(int contentId, UrlMode mode, string culture = null) { return UrlProvider.GetUrl(contentId, mode, culture); } /// /// Gets the url of a content identified by its identifier, in a specified mode. /// /// The content identifier. /// The mode. /// /// The url for the content. public string Url(Guid contentId, UrlMode mode, string culture = null) { return UrlProvider.GetUrl(contentId, mode, culture); } /// /// Gets the absolute url of a content identified by its identifier. /// /// The content identifier. /// /// The absolute url for the content. [Obsolete("Use the Url() method with UrlMode.Absolute.")] public string UrlAbsolute(int contentId, string culture = null) { return UrlProvider.GetUrl(contentId, UrlMode.Absolute, culture); } /// /// Gets the absolute url of a content identified by its identifier. /// /// The content identifier. /// /// The absolute url for the content. [Obsolete("Use the Url() method with UrlMode.Absolute.")] public string UrlAbsolute(Guid contentId, string culture = null) { return UrlProvider.GetUrl(contentId, UrlMode.Absolute, culture); } #endregion private string PreviewToken { get { if (_previewing.HasValue == false) DetectPreviewMode(); return _previewToken; } } private void DetectPreviewMode() { var request = GetRequestFromContext(); if (request?.Url != null && request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath, _globalSettings) == false && Security.CurrentUser != null) { var previewToken = request.GetPreviewCookieValue(); // may be null or empty _previewToken = previewToken.IsNullOrWhiteSpace() ? null : previewToken; } _previewing = _previewToken.IsNullOrWhiteSpace() == false; } // say we render a macro or RTE in a give 'preview' mode that might not be the 'current' one, // then due to the way it all works at the moment, the 'current' published snapshot need to be in the proper // default 'preview' mode - somehow we have to force it. and that could be recursive. internal IDisposable ForcedPreview(bool preview) { InPreviewMode = preview; return PublishedSnapshot.ForcedPreview(preview, orig => InPreviewMode = orig); } private HttpRequestBase GetRequestFromContext() { try { return HttpContext.Request; } catch (HttpException) { return null; } } protected override void DisposeResources() { // DisposableObject ensures that this runs only once Security.DisposeIfDisposable(); // help caches release resources // (but don't create caches just to dispose them) // context is not multi-threaded if (_publishedSnapshot.IsValueCreated) _publishedSnapshot.Value.Dispose(); } } }