diff --git a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs index aa68922f21..c6af13e874 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs @@ -1,4 +1,6 @@ -using Umbraco.Core; +using System.Collections.Generic; +using System.Web; +using Umbraco.Core; using Umbraco.Core.Logging; namespace Umbraco.Web.Routing @@ -27,6 +29,14 @@ namespace Umbraco.Web.Routing var service = contentRequest.RoutingContext.UmbracoContext.Application.Services.RedirectUrlService; var redirectUrl = service.GetMostRecentRedirectUrl(route); + // From: http://stackoverflow.com/a/22468386/5018 + // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 + // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads + // to problems if you rename a page back to it's original name or create a new page with the original name + contentRequest.Cacheability = HttpCacheability.NoCache; + contentRequest.CacheExtensions = new List { "no-store, must-revalidate" }; + contentRequest.Headers = new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }; + if (redirectUrl == null) { LogHelper.Debug("No match for route: \"{0}\".", () => route); diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs index 1ed0cf8150..2081fd69b5 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; +using System.Web; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -14,14 +15,14 @@ using RenderingEngine = Umbraco.Core.RenderingEngine; namespace Umbraco.Web.Routing { - /// - /// Represents a request for one specified Umbraco IPublishedContent to be rendered - /// by one specified template, using one specified Culture and RenderingEngine. - /// - public class PublishedContentRequest - { - private bool _readonly; - private bool _readonlyUri; + /// + /// Represents a request for one specified Umbraco IPublishedContent to be rendered + /// by one specified template, using one specified Culture and RenderingEngine. + /// + public class PublishedContentRequest + { + private bool _readonly; + private bool _readonlyUri; /// /// Triggers before the published content request is prepared. @@ -31,65 +32,65 @@ namespace Umbraco.Web.Routing /// that might have been modified by an in-between equipement such as a load-balancer. public static event EventHandler Preparing; - /// - /// Triggers once the published content request has been prepared, but before it is processed. - /// - /// When the event triggers, preparation is done ie domain, culture, document, template, - /// rendering engine, etc. have been setup. It is then possible to change anything, before - /// the request is actually processed and rendered by Umbraco. - public static event EventHandler Prepared; + /// + /// Triggers once the published content request has been prepared, but before it is processed. + /// + /// When the event triggers, preparation is done ie domain, culture, document, template, + /// rendering engine, etc. have been setup. It is then possible to change anything, before + /// the request is actually processed and rendered by Umbraco. + public static event EventHandler Prepared; - // the engine that does all the processing - // because in order to keep things clean and separated, - // the content request is just a data holder - private readonly PublishedContentRequestEngine _engine; + // the engine that does all the processing + // because in order to keep things clean and separated, + // the content request is just a data holder + private readonly PublishedContentRequestEngine _engine; // the cleaned up uri // the cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. private Uri _uri; - /// - /// Initializes a new instance of the class with a specific Uri and routing context. - /// - /// The request Uri. - /// A routing context. + /// + /// Initializes a new instance of the class with a specific Uri and routing context. + /// + /// The request Uri. + /// A routing context. /// A callback method to return the roles for the provided login name when required /// - public PublishedContentRequest(Uri uri, RoutingContext routingContext, IWebRoutingSection routingConfig, Func> getRolesForLogin) - { + public PublishedContentRequest(Uri uri, RoutingContext routingContext, IWebRoutingSection routingConfig, Func> getRolesForLogin) + { if (uri == null) throw new ArgumentNullException("uri"); if (routingContext == null) throw new ArgumentNullException("routingContext"); Uri = uri; RoutingContext = routingContext; - GetRolesForLogin = getRolesForLogin; + GetRolesForLogin = getRolesForLogin; - _engine = new PublishedContentRequestEngine( + _engine = new PublishedContentRequestEngine( routingConfig, this); RenderingEngine = RenderingEngine.Unknown; - } + } [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use the constructor specifying all dependencies instead")] - public PublishedContentRequest(Uri uri, RoutingContext routingContext) + public PublishedContentRequest(Uri uri, RoutingContext routingContext) : this(uri, routingContext, UmbracoConfig.For.UmbracoSettings().WebRouting, s => Roles.Provider.GetRolesForUser(s)) - { - } + { + } - /// - /// Gets the engine associated to the request. - /// - internal PublishedContentRequestEngine Engine { get { return _engine; } } + /// + /// Gets the engine associated to the request. + /// + internal PublishedContentRequestEngine Engine { get { return _engine; } } - /// - /// Prepares the request. - /// + /// + /// Prepares the request. + /// public void Prepare() - { - _engine.PrepareRequest(); - } + { + _engine.PrepareRequest(); + } /// /// Called to configure the request @@ -104,56 +105,57 @@ namespace Umbraco.Web.Routing _engine.ConfigureRequest(); } - /// - /// Updates the request when there is no template to render the content. - /// - internal void UpdateOnMissingTemplate() - { + /// + /// Updates the request when there is no template to render the content. + /// + internal void UpdateOnMissingTemplate() + { var __readonly = _readonly; - _readonly = false; - _engine.UpdateRequestOnMissingTemplate(); - _readonly = __readonly; - } + _readonly = false; + _engine.UpdateRequestOnMissingTemplate(); + _readonly = __readonly; + } /// /// Triggers the Preparing event. /// internal void OnPreparing() - { - var handler = Preparing; + { + var handler = Preparing; if (handler != null) handler(this, EventArgs.Empty); - _readonlyUri = true; - } + _readonlyUri = true; + } - /// - /// Triggers the Prepared event. - /// - internal void OnPrepared() - { + /// + /// Triggers the Prepared event. + /// + internal void OnPrepared() + { var handler = Prepared; if (handler != null) handler(this, EventArgs.Empty); - if (HasPublishedContent == false) + if (HasPublishedContent == false) Is404 = true; // safety - _readonly = true; - } + _readonly = true; + } - /// - /// Gets or sets the cleaned up Uri used for routing. - /// - /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - public Uri Uri { - get - { - return _uri; - } - set - { + /// + /// Gets or sets the cleaned up Uri used for routing. + /// + /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. + public Uri Uri + { + get + { + return _uri; + } + set + { if (_readonlyUri) throw new InvalidOperationException("Cannot modify Uri after Preparing has triggered."); - _uri = value; - } + _uri = value; + } } private void EnsureWriteable() @@ -162,37 +164,37 @@ namespace Umbraco.Web.Routing throw new InvalidOperationException("Cannot modify a PublishedContentRequest once it is read-only."); } - #region PublishedContent + #region PublishedContent - /// - /// The requested IPublishedContent, if any, else null. - /// - private IPublishedContent _publishedContent; + /// + /// The requested IPublishedContent, if any, else null. + /// + private IPublishedContent _publishedContent; - /// - /// The initial requested IPublishedContent, if any, else null. - /// - /// The initial requested content is the content that was found by the finders, - /// before anything such as 404, redirect... took place. - private IPublishedContent _initialPublishedContent; + /// + /// The initial requested IPublishedContent, if any, else null. + /// + /// The initial requested content is the content that was found by the finders, + /// before anything such as 404, redirect... took place. + private IPublishedContent _initialPublishedContent; - /// - /// Gets or sets the requested content. - /// - /// Setting the requested content clears Template. - public IPublishedContent PublishedContent - { - get { return _publishedContent; } - set - { + /// + /// Gets or sets the requested content. + /// + /// Setting the requested content clears Template. + public IPublishedContent PublishedContent + { + get { return _publishedContent; } + set + { EnsureWriteable(); - _publishedContent = value; + _publishedContent = value; IsInternalRedirectPublishedContent = false; - TemplateModel = null; - } - } + TemplateModel = null; + } + } - /// + /// /// Sets the requested content, following an internal redirect. /// /// The requested content. @@ -200,8 +202,8 @@ namespace Umbraco.Web.Routing /// preserve or reset the template, if any. public void SetInternalRedirectPublishedContent(IPublishedContent content) { - if (content == null) throw new ArgumentNullException("content"); - EnsureWriteable(); + if (content == null) throw new ArgumentNullException("content"); + EnsureWriteable(); // unless a template has been set already by the finder, // template should be null at that point. @@ -225,7 +227,7 @@ namespace Umbraco.Web.Routing // set published content - this resets the template, and sets IsInternalRedirect to false PublishedContent = content; - IsInternalRedirectPublishedContent = isInternalRedirect; + IsInternalRedirectPublishedContent = isInternalRedirect; // must restore the template if it's an internal redirect & the config option is set if (isInternalRedirect && UmbracoConfig.For.UmbracoSettings().WebRouting.InternalRedirectPreservesTemplate) @@ -243,16 +245,16 @@ namespace Umbraco.Web.Routing /// before anything such as 404, redirect... took place. public IPublishedContent InitialPublishedContent { get { return _initialPublishedContent; } } - /// - /// Gets value indicating whether the current published content is the initial one. - /// - public bool IsInitialPublishedContent - { - get - { - return _initialPublishedContent != null && _initialPublishedContent == _publishedContent; - } - } + /// + /// Gets value indicating whether the current published content is the initial one. + /// + public bool IsInitialPublishedContent + { + get + { + return _initialPublishedContent != null && _initialPublishedContent == _publishedContent; + } + } /// /// Indicates that the current PublishedContent is the initial one. @@ -282,19 +284,19 @@ namespace Umbraco.Web.Routing get { return PublishedContent != null; } } - #endregion + #endregion - #region Template + #region Template /// /// The template model, if any, else null. /// private ITemplate _template; - /// + /// /// Gets or sets the template model to use to display the requested content. /// - internal ITemplate TemplateModel + internal ITemplate TemplateModel { get { @@ -316,9 +318,9 @@ namespace Umbraco.Web.Routing /// public string TemplateAlias { - get - { - return _template == null ? null : _template.Alias; + get + { + return _template == null ? null : _template.Alias; } } @@ -348,7 +350,7 @@ namespace Umbraco.Web.Routing if (model == null) return false; - TemplateModel = model; + TemplateModel = model; return true; } @@ -368,10 +370,10 @@ namespace Umbraco.Web.Routing /// /// The RenderingEngine becomes unknown. public void ResetTemplate() - { - EnsureWriteable(); - TemplateModel = null; - } + { + EnsureWriteable(); + TemplateModel = null; + } /// /// Gets a value indicating whether the content request has a template. @@ -381,15 +383,15 @@ namespace Umbraco.Web.Routing get { return _template != null; } } - #endregion + #endregion - #region Domain and Culture + #region Domain and Culture - [Obsolete("Do not use this property, use the non-legacy UmbracoDomain property instead")] - public Domain Domain - { - get { return new Domain(UmbracoDomain); } - } + [Obsolete("Do not use this property, use the non-legacy UmbracoDomain property instead")] + public Domain Domain + { + get { return new Domain(UmbracoDomain); } + } //TODO: Should we publicize the setter now that we are using a non-legacy entity?? /// @@ -397,85 +399,85 @@ namespace Umbraco.Web.Routing /// public IDomain UmbracoDomain { get; internal set; } - /// - /// Gets or sets the content request's domain Uri. - /// - /// The Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". - public Uri DomainUri { get; internal set; } + /// + /// Gets or sets the content request's domain Uri. + /// + /// The Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". + public Uri DomainUri { get; internal set; } - /// - /// Gets a value indicating whether the content request has a domain. - /// - public bool HasDomain - { - get { return UmbracoDomain != null; } - } + /// + /// Gets a value indicating whether the content request has a domain. + /// + public bool HasDomain + { + get { return UmbracoDomain != null; } + } - private CultureInfo _culture; + private CultureInfo _culture; - /// - /// Gets or sets the content request's culture. - /// - public CultureInfo Culture - { + /// + /// Gets or sets the content request's culture. + /// + public CultureInfo Culture + { get { return _culture; } set { EnsureWriteable(); _culture = value; } - } + } - // note: do we want to have an ordered list of alternate cultures, + // note: do we want to have an ordered list of alternate cultures, // to allow for fallbacks when doing dictionnary lookup and such? - #endregion + #endregion - #region Rendering + #region Rendering - /// - /// Gets or sets whether the rendering engine is MVC or WebForms. - /// - public RenderingEngine RenderingEngine { get; internal set; } + /// + /// Gets or sets whether the rendering engine is MVC or WebForms. + /// + public RenderingEngine RenderingEngine { get; internal set; } - #endregion + #endregion - /// - /// Gets or sets the current RoutingContext. - /// - public RoutingContext RoutingContext { get; private set; } + /// + /// Gets or sets the current RoutingContext. + /// + public RoutingContext RoutingContext { get; private set; } - internal Func> GetRolesForLogin { get; private set; } + internal Func> GetRolesForLogin { get; private set; } - /// - /// The "umbraco page" object. - /// - private page _umbracoPage; + /// + /// The "umbraco page" object. + /// + private page _umbracoPage; - /// - /// Gets or sets the "umbraco page" object. - /// - /// - /// This value is only used for legacy/webforms code. - /// - internal page UmbracoPage - { - get - { - if (_umbracoPage == null) - throw new InvalidOperationException("The UmbracoPage object has not been initialized yet."); + /// + /// Gets or sets the "umbraco page" object. + /// + /// + /// This value is only used for legacy/webforms code. + /// + internal page UmbracoPage + { + get + { + if (_umbracoPage == null) + throw new InvalidOperationException("The UmbracoPage object has not been initialized yet."); - return _umbracoPage; - } - set { _umbracoPage = value; } - } + return _umbracoPage; + } + set { _umbracoPage = value; } + } - #region Status + #region Status - /// + /// /// Gets or sets a value indicating whether the requested content could not be found. /// - /// This is set in the PublishedContentRequestBuilder. + /// This is set in the PublishedContentRequestBuilder. public bool Is404 { get; internal set; } /// @@ -581,7 +583,40 @@ namespace Umbraco.Web.Routing ResponseStatusCode = code; ResponseStatusDescription = description; } - - #endregion + + #endregion + + /// + /// Gets or sets the System.Web.HttpCacheability + /// + /// Is set to System.Web.HttpCacheability.Private by default, which is the ASP.NET default. + private HttpCacheability _cacheability = HttpCacheability.Private; + internal HttpCacheability Cacheability + { + get { return _cacheability; } + set { _cacheability = value; } + } + + /// + /// Gets or sets a list of Extensions to append to the Response.Cache object + /// + private List _cacheExtensions = new List(); + internal List CacheExtensions + { + get { return _cacheExtensions; } + set { _cacheExtensions = value; } + } + + /// + /// Gets or sets a dictionary of Headers to append to the Response object + /// + private Dictionary _headers = new Dictionary(); + internal Dictionary Headers + { + get { return _headers; } + set { _headers = value; } + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 762a5f3e67..517fd112c9 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -323,6 +323,14 @@ namespace Umbraco.Web LogHelper.Debug("Response status: Redirect={0}, Is404={1}, StatusCode={2}", () => pcr.IsRedirect ? (pcr.IsRedirectPermanent ? "permanent" : "redirect") : "none", () => pcr.Is404 ? "true" : "false", () => pcr.ResponseStatusCode); + + response.Cache.SetCacheability(pcr.Cacheability); + + foreach (var cacheExtension in pcr.CacheExtensions) + response.Cache.AppendCacheExtension(cacheExtension); + + foreach (var header in pcr.Headers) + response.Cache.AppendCacheExtension(string.Format("{0}, {1}", header.Key, header.Value)); if (pcr.IsRedirect) {