Source included Contains information about a specific page.

Namespace:  EPiServer.Core
Assembly:  EPiServer (in EPiServer.dll) Version: 6.0.530.0

Syntax

C#
[SerializableAttribute]
public class PageData : IReadOnly<PageData>, 
	IReadOnly, ISecurable

Remarks

The PageData class contains information about a specific page. This includes the name of the page (PageName), reference (PageLink) and URL (LinkURL). All built-in and custom properties defined for the page type are available through the Property property.

PageData is in many respects synonymous with the Web page that it is holding properties for. One example of this is the Changed property, which holds last change date and time for the PageData object, i.e. the Web page.

CurrentPage["PageName"] (type object)
CopyC#
#region Copyright � 1997-2008 EPiServer AB. All Rights Reserved.
/*
This code may only be used according to the EPiServer License Agreement.
The use of this code outside the EPiServer environment, in whole or in
parts, is forbidden without prior written permission from EPiServer AB.

EPiServer is a registered trademark of EPiServer AB. For more information 
see http://www.episerver.com/license or request a copy of the EPiServer 
License Agreement by sending an email to info@episerver.com*/

#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Linq;
using System.Web;

using EPiServer.BaseLibrary;
using EPiServer.DataAbstraction;
using EPiServer.Filters;
using EPiServer.Globalization;
using EPiServer.Security;
using EPiServer.Web;
using EPiServer.Web.Hosting;
using EPiServer.Data.Dynamic;

namespace EPiServer.Core
{
    /// <summary>
    /// Type of page link.
    /// </summary>
    /// <remarks>
    /// Determines how a link to a page is rendered as well as how the page behaves when it is loaded.
    /// </remarks>
    public enum PageShortcutType
    {
        /// <summary>
        /// A normal link. Links to the page will simply show the page itself.
        /// </summary>
        Normal = 0,
        /// <summary>
        /// The page points to a page different than the current, acting as an internal shortcut.
        /// </summary>
        Shortcut = 1,
        /// <summary>
        /// The page points to an external url, e.g http://world.episerver.com/
        /// </summary>
        External = 2,
        /// <summary>
        /// The page is inactive.
        /// </summary>
        Inactive = 3,
        /// <summary>
        /// The page fetches data from from another page for properties that are empty.
        /// </summary>
        FetchData = 4
    }

    /// <summary>
    /// <img src="Icons/CSharp.gif" alt="Source included" border="0" /> 
    /// Contains information about a specific page.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The <b>PageData</b> class contains information about a specific page. This includes the 
    /// name of the page (<b>PageName</b>), reference (<b>PageLink</b>) and URL (<b>LinkURL</b>). 
    /// All built-in and custom properties defined for the page type are available through the 
    /// <b>Property</b> property.
    /// </para>
    /// <para>
    /// <b>PageData</b> is in many respects synonymous with the Web page that it is holding properties 
    /// for. One example of this is the <b>Changed</b> property, which holds last change date and time 
    /// for the <b>PageData</b> object, i.e. the Web page.
    /// </para>
    /// CurrentPage["PageName"]          (type object)
    /// <code source="../EPiServerNET/Core/PageData.cs" lang="cs"/>
    /// </remarks>
    /// <example>
    /// <para>
    /// Often you'll access a <b>PageData</b> object through the <b>CurrentPage</b> on the <b>PageBase</b> 
    /// (and descendants) class.
    /// </para>
    /// <code source="../CodeSamples/EPiServer/Core/PageDataSamples.aspx.cs" region="CurrentPage" />
    /// <para>
    /// The <b>GetPage</b> method (defined in <b>IPageSource</b>) also returns a <b>PageData</b> object.
    /// </para>
    /// <code source="../CodeSamples/EPiServer/Core/PageDataSamples.aspx.cs" region="GetPage" />
    /// </example>
    [Serializable]
    public class PageData : IReadOnly<PageData>, ISecurable
    {
        private PropertyDataCollection _propertyCollection;
        private AccessControlList _acl;
        private bool _isReadOnly;
        private ReadOnlyStringList _languages;
        private Dictionary<string, string> _linkURLs;

        #region Constructors

        /// <summary>
        /// Create an empty PageData object.
        /// </summary>
        public PageData()
            : this(new PageAccessControlList(), new PropertyDataCollection())
        {
            PropertyCategory cat = new PropertyCategory();
            cat.OwnerTab = (int)EditTab.Category;
            _propertyCollection["PageCategory"] = cat;
        }

        /// <summary>
        /// Create an empty PageData object for the given PageReference
        /// </summary>
        /// <param name="pageLink">The PageReference for this page.</param>
        public PageData(PageReference pageLink)
            : this()
        {
            Property["PageLink"] = new PropertyPageReference(pageLink);
        }

        /// <summary>
        /// Creates an empty <b>PageData</b> object for the given <b>RawPage</b>.  
        /// </summary>
        public PageData(RawPage page)
            : this(new PageAccessControlList(page.ACL), new PropertyDataCollection(page.Property))
        {
            ((PageAccessControlList)ACL).PageLink = this.PageLink;
        }

        /// <summary>
        /// Creates an empty <b>PageData</b> object for the given <see cref="EPiServer.Security.AccessControlList"/> and <see cref="EPiServer.Core.PropertyDataCollection"/>.
        /// </summary>
        /// <param name="acl">Access control list</param>
        /// <param name="coll">Property data collection</param>
        public PageData(AccessControlList acl, PropertyDataCollection coll)
        {
            _linkURLs = new Dictionary<string, string>();
            _acl = acl;
            _propertyCollection = coll;
            _propertyCollection.OwnerPage = this;
        }

        /// <summary>
        /// Constructor that creates a new instance by shallow copying the state from another instance.
        /// </summary>
        /// <param name="copy"></param>
        public PageData(PageData copy)
        {
            ShallowCopy(copy);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Access property value. There are special purpose accessor for most values and
        /// the structure of of these accessors are pretty much the same, but one rule...
        /// - Do not call the indexer twice just to check the value as this will double the 
        /// execution time
        /// </summary>
        /// <remarks>
        /// <list type="table">
        /// <listheader>
        /// <term>Meta data name</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>PageParentLink</term>
        /// <description>PageLink to parent page. <see cref="PropertyPageReference"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageTypeID</term>
        /// <description>Page type ID. <see cref="PropertyPageType"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageTypeName</term>
        /// <description>Page type name. <see cref="PropertyString"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageLink</term>
        /// <description>PageLink to page. <see cref="PropertyPageReference"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageLinkURL</term>
        /// <description>Url to page. <see cref="EPiServer.SpecializedProperties.PropertyUrl"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageName</term>
        /// <description>Name of page. <see cref="PropertyString"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageStartPublish</term>
        /// <description>Start publish date. <see cref="PropertyDate"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageStopPublish</term>
        /// <description>Stop publish date. <see cref="PropertyDate"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageVisibleInMenu</term>
        /// <description>If page is set to be visible in menus. <see cref="PropertyBoolean"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageCreated</term>
        /// <description>Date when page was created. <see cref="PropertyDate"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageChildOrderRule</term>
        /// <description>Sort order for child pages. <see cref="EPiServer.Filters.FilterSortOrder"/>.</description>
        /// </item>
        /// <item>
        /// <term>PagePeerOrder</term>
        /// <description>Sorting index for page. <see cref="PropertyNumber"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageLanguageID</term>
        /// <description>Language identifier for page. <see cref="PropertyString"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageArchiveLink</term>
        /// <description>Link to archive. <see cref="PropertyPageReference"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageShortcutType</term>
        /// <description>Type of link behaviour. <see cref="PropertyNumber"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageShortcutLink</term>
        /// <description>Page links to this page, as defined by PageShortcutType. <see cref="PropertyPageReference"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageExternalURL</term>
        /// <description>Simple Url supported by this page. <see cref="EPiServer.SpecializedProperties.PropertyVirtualLink"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageTargetFrame</term>
        /// <description>Frame this page should be displayed in. <see cref="EPiServer.SpecializedProperties.PropertyFrame"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageChanged</term>
        /// <description>Date when this page last was set to changed. <see cref="PropertyDate"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageUseOutputCache</term>
        /// <description>If this page should use output caching. <see cref="PropertyBoolean"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageWorkStatus</term>
        /// <description>Editing status for page. <see cref="PropertyNumber"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageFolderID</term>
        /// <description>Unique folder name in upload. <see cref="PropertyNumber"/>.</description>
        /// </item>
        /// <item>
        /// <term>PageDeleted</term>
        /// <description>If page is in the wastebasket. <see cref="PropertyBoolean"/>.</description>
        /// </item>
        /// </list>
        /// </remarks>
        /// <returns>Returns a collection of all custom properties defined for the page type, and all values.</returns>
        public PropertyDataCollection Property
        {
            get { return _propertyCollection; }
        }

        /// <summary>
        /// The categories that this page belongs to.
        /// </summary>
        /// <returns>A <b>CategoryList</b> with the categories.</returns>
        /// <remarks>
        /// This is used to determine which categories that the page belongs to, it should not 
        /// be confused with a property of type <b>PropertyCategory</b> defined on the page type. 
        /// </remarks>
        public CategoryList Category
        {
            get { return ((PropertyCategory)Property["PageCategory"]).Category; }
        }

        /// <summary>
        /// Display name of page
        /// </summary>
        /// <remarks>
        /// Not many page settings are under absolute editor control, but <b>PageName</b> is one of them. 
        /// PageName is a descriptive name given the page in Edit mode. <b>PageName</b> can be used to great 
        /// benefit in many cases, such as templated controls
        /// </remarks>
        /// <example>
        /// <para>
        /// The following example demonstrates the usage of <b>PageName</b> and <b>LinkURL</b>. The HTML code below 
        /// produces two HTML table rows for every news item page displayed. The first row displays the 
        /// content of the property <b>PageStartPublish</b> and the second row is an HTML anchor tag, where 
        /// <b>PageData.LinkURL</b> is the relative URL for the page and <b>PageData.PageName</b> is used as a new headline. 
        /// Note that <b>PageData.Item</b> is used twice in this HTML code, as the two attributes <b>PageStartPublish</b> 
        /// and <b>MainIntro</b> are accessed using the indexer for the <b>PageData</b> property.
        /// </para>
        /// <code source="../CodeSamples/EPiServer/Core/PageDataSamples.aspx" region="PageName" lang="aspnet" />
        /// </example>
        public string PageName
        {
            get { return (string)this["PageName"] ?? String.Empty; }
            set { this["PageName"] = value; }
        }


        /// <summary>
        /// URL to this page
        /// </summary>
        /// <remarks>
        /// <para>
        /// The <b>LinkURL</b> string property contains root-relative path and query of the URL for the page, 
        /// i.e. to get an absolute URL, prepend <b>LinkURL</b> with the appropriate scheme and host.
        /// </para>
        /// <para>
        /// See <see cref="EPiServer.PageBase.CurrentPage"/> and <see cref="PageName"/> for examples of the usage of <b>LinkURL</b>.
        /// </para>
        /// </remarks>
        public string LinkURL
        {
            get
            {
                string val = (string)GetValue("PageLinkURL");
                if (String.IsNullOrEmpty(val))
                {
                    return String.Empty;
                }

                // If some kind of external link with fixed data, simply return fixed value
                PageShortcutType type = LinkType;
                if (type != PageShortcutType.Normal && type != PageShortcutType.FetchData && type != PageShortcutType.Shortcut)
                {
                    return val;
                }

                // If link to root page, wastebasket or something in the wastebasket, simply return fixed value
                PageReference pageRef = PageLink;
                if (DataFactory.Instance.IsWastebasket(pageRef) || pageRef == PageReference.RootPage || IsDeleted)
                {
                    return val;
                }

                // If requested language (Contentlanguage.PreferredCulture) matches default 
                // language for the host name, return fixed value
                string language = ContentLanguage.PreferredCulture.Name;
                if (LanguageSelection.IsHostLanguageMatch(language))
                {
                    return val;
                }

                // Reduce the need for parsing and building query strings by caching for each langauge
                string cacheVal;
                if (_linkURLs.TryGetValue(language, out cacheVal))
                {
                    return cacheVal;
                }

                val = UriSupport.AddLanguageSelection(val, language);
                lock (_linkURLs)
                {
                    _linkURLs[language] = val;
                }
                return val;
            }

            set
            {
                if (!String.IsNullOrEmpty(value) && value.StartsWith("~/", StringComparison.Ordinal))
                {
                    int queryIndex = value.IndexOf('?');
                    if (queryIndex != -1)
                    {
                        SetValue("PageLinkURL", VirtualPathUtilityEx.ToAbsolute(value.Substring(0, queryIndex)) + "?" + value.Substring(queryIndex + 1, value.Length - queryIndex - 1));
                    }
                    else
                    {
                        SetValue("PageLinkURL", VirtualPathUtilityEx.ToAbsolute(value));
                    }
                }
                else
                {
                    SetValue("PageLinkURL", value);
                }

                lock (_linkURLs)
                {
                    _linkURLs.Clear();
                }
            }
        }

        /// <summary>
        /// The type of URL used
        /// </summary>
        public PageShortcutType LinkType
        {
            get
            {
                object val = this["PageShortcutType"];
                return val == null ? PageShortcutType.Inactive : (PageShortcutType)val;
            }
            set { this["PageShortcutType"] = value; }
        }

        /// <summary>
        /// URL to this page as stored in the database
        /// </summary>
        public string StaticLinkURL
        {
            get { return (string)GetValue("PageLinkURL") ?? String.Empty; }
            set { LinkURL = value; }
        }

        /// <summary>
        /// This page is represented in an URL with this segment
        /// </summary>
        public string URLSegment
        {
            get { return (string)GetValue("PageURLSegment") ?? String.Empty; }
            set { SetValue("PageURLSegment", value); }
        }

        /// <summary>
        /// Being of the type <see cref="PageReference"/>, <b>PageLink</b> is the unique page ID.
        /// </summary>
        /// <remarks>
        /// The use of <b>PageLink</b> is derived from the way in which EPiServer is structured. 
        /// Pages are nothing more than an instance of an EPiServer page type and a collection 
        /// of property settings stored in the database. Individual pages are identified by the 
        /// pkID column in the database table tblPage and transforming this into a URL, we get 
        /// something looking like "templates/emailpagecontainer.aspx?id=n?", where "n" is the 
        /// previously mentioned pkID and thus the unique page identifier.
        /// </remarks>
        /// <example>
        /// <para>
        /// The following example demonstrates the usage of <b>PageLink</b> in HTML.
        /// </para>
        /// <para>
        /// As <b>PageLink</b> is guaranteed to be unique among all pages in a certain EPiServer instances, 
        /// it is used in the following example to create unique IDs and names for HTML check boxes. 
        /// The code is used to present the visitor with a selection of pages and allows them to select 
        /// one or more for further processing.
        /// </para>
        /// <code source="../CodeSamples/EPiServer/Core/PageDataSamples.aspx" region="PageLink" lang="aspnet" />
        /// </example>
        public PageReference PageLink
        {
            get { return (PageReference)GetValue("PageLink") ?? PageReference.EmptyReference; }
            set { SetValue("PageLink", value); }
        }

        /// <summary>
        /// Gets or sets the unique guid based identifier for the page. 
        /// </summary>
        /// <value>The page GUID.</value>
        public Guid PageGuid
        {
            //Currently the guid is stored as a string in property collection
            get
            {
                string pageGuid = (string)GetValue("PageGUID");
                return String.IsNullOrEmpty(pageGuid) ? Guid.Empty : new Guid(pageGuid);
            }
            set { SetValue("PageGUID", value.ToString()); }
        }

        /// <summary>
        /// <see cref="PageReference"/> type that points to the parent page for this page.
        /// </summary>
        /// <remarks>
        /// Parent and childhood is determined by placement in the Web page tree.
        /// </remarks>
        /// <example>
        /// <para>
        /// In the following code example <b>ParentLink</b> is used twice. Firstly to make sure it 
        /// contains a non-empty <b>PageReference</b> and secondly to retrieve the sibling of the 
        /// current page.
        /// </para>
        /// <code source="../CodeSamples/EPiServer/Core/PageDataSamples.aspx.cs" region="CSParentLink" />
        /// <para>
        /// The following code example demonstrates the usage of <b>ParentLink</b>. The example results 
        /// in the name of the parent page being displayed.
        /// </para>
        /// <code source="../CodeSamples/EPiServer/Core/PageDataSamples.aspx" region="ASPNETParentLink" lang="aspnet" />
        /// </example>
        public PageReference ParentLink
        {
            get { return (PageReference)GetValue("PageParentLink") ?? PageReference.EmptyReference; }
            set { SetValue("PageParentLink", value); }
        }

        /// <summary>
        /// Reference to archive page
        /// </summary>
        /// <returns>
        /// A <b>PageReference</b> that indicates where to archive this page when the <b>StopPublish</b> date has passed. 
        /// </returns>
        /// <remarks>
        /// Note that the scheduled job "Archive pages" must run for the page to be archived. 
        /// </remarks>
        public PageReference ArchiveLink
        {
            get { return (PageReference)GetValue("PageArchiveLink") ?? PageReference.EmptyReference; }
            set { this["PageArchiveLink"] = value; }
        }

        /// <summary>
        /// Page type identifier
        /// </summary>
        /// <remarks>
        /// PageTypeID is the same as column fkPageTypeID in the database table tblPage.
        /// </remarks>
        public int PageTypeID
        {
            get
            {
                object val = GetValue("PageTypeID");
                return val == null ? 0 : (int)val;
            }
            set { SetValue("PageTypeID", value); }
        }

        /// <summary>
        /// Name of the page type used to create this page.
        /// </summary>
        /// <returns>A string with the name.</returns>
        /// <remarks>
        /// This is the page type name as entered in Admin mode. It will not be translated in any way. 
        /// <b>PageTypeName</b> is the same as column Name in the database table tblPageType.
        /// </remarks>
        public string PageTypeName
        {
            get { return (string)this["PageTypeName"] ?? String.Empty; }
            set { this["PageTypeName"] = value; }
        }

        /// <summary>
        /// If page has been deleted
        /// </summary>
        /// <returns><b>True</b> if the page is in the Recycle Bin.</returns>
        /// <remarks>
        /// Since deletion of a page moves the page to the Recycle Bin rather than 
        /// deleting it, you can use this property to check if the page is deleted 
        /// or if it is "alive". 
        /// </remarks>
        public bool IsDeleted
        {
            get
            {
                object val = GetValue("PageDeleted");
                return val == null ? false : (bool)val;
            }
        }

        /// <summary>
        /// The date when the page was created
        /// </summary>
        /// <remarks>
        /// <b>Created</b> is maintained by the EPiServer infrastructure and you have no control over them.
        /// </remarks>
        public DateTime Created
        {
            get { return GetDateTimeValue("PageCreated"); }
        }

        /// <summary>
        /// The language id for the page
        /// </summary>
        public string LanguageID
        {
            get { return (string)this["PageLanguageBranch"] ?? String.Empty; }
            set { this["PageLanguageBranch"] = value; }
        }

        /// <summary>
        /// The username of the user that created the page
        /// </summary>
        public string CreatedBy
        {
            get { return (string)this["PageCreatedBy"] ?? String.Empty; }
        }

        /// <summary>
        /// The date when the page was marked as changed
        /// </summary>
        /// <remarks>
        /// Changed is only updated when a page is changed and <c>CurrentPage.Property[ "PageChangedOnPublish" ].Value</c> 
        /// is true.
        /// </remarks>
        /// <example>
        /// The following code example demonstrates the usage of <b>Changed</b>.
        /// <code>
        /// string ChangedDateTime = CurrentPage.Changed.ToString( "r" ); 
        /// </code>
        /// </example>
        public DateTime Changed
        {
            get { return GetDateTimeValue("PageChanged"); }
        }

        /// <summary>
        /// The username of the user that most recently changed the page.
        /// </summary>
        /// <remarks>
        /// For instance, if a Windows account was used to create the page, expect a string 
        /// being returned looking like 'DOMAIN\User Name'.
        /// </remarks>
        /// <example>
        /// The following code example demonstrates the usage of <b>ChangedBy</b>.
        /// <code source="../CodeSamples/EPiServer/Core/PageDataSamples.aspx" region="ChangedBy" lang="aspnet" />
        /// </example>
        public string ChangedBy
        {
            get { return (string)this["PageChangedBy"] ?? String.Empty; }
        }

        /// <summary>
        /// The date when the page was last saved
        /// </summary>
        /// <remarks>
        /// Saved is the sibling of <see cref="Created"/> and is maintained by the EPiServer infrastructure. 
        /// This means that you have no control over them. 
        /// </remarks>
        public DateTime Saved
        {
            get { return GetDateTimeValue("PageSaved"); }
        }

        /// <summary>
        /// The date when the page will be published
        /// </summary>
        public DateTime StartPublish
        {
            get { return GetDateTimeValue("PageStartPublish"); }
            set { SetValue("PageStartPublish", value); }
        }

        /// <summary>
        /// The date when the page will stop to be published. Will be set to <see cref="System.DateTime.MaxValue"/>
        /// if no stop publish date has been set.
        /// </summary>
        public DateTime StopPublish
        {
            get { return GetDateTimeValue("PageStopPublish", DateTime.MaxValue); }
            set { SetValue("PageStopPublish", value); }
        }

        /// <summary>
        /// Indicates whether this page should be visible in menus and tree structures. 
        /// </summary>
        /// <remarks>
        /// <para>
        /// This setting is important for several of the Web custom controls in <see cref="EPiServer.Web.WebControls"/>.
        /// </para>
        /// <para>
        /// Setting <b>VisibleInMenu</b> to <b>false</b> is a preparation for storing the user profile page and making 
        /// sure that it won't be displayed with the other Web pages in the site's Web page tree, which 
        /// would otherwise happen automatically.
        /// </para>
        /// <para>
        /// <b>VisibleInMenu</b> is stored in the column VisibleInMenu in database table tblPage.
        /// </para>
        /// </remarks>
        /// <example>
        /// The following code example demonstrates the usage of <b>VisibleInMenu</b> and is taken from 
        /// code-behind file for the Web user control Profile.ascx, which is shipped with EPiServer.
        /// <code>
        /// CurrentPage.VisibleInMenu = false; 
        /// </code>
        /// </example>
        public bool VisibleInMenu
        {
            get
            {
                object val = this["PageVisibleInMenu"];
                return val == null ? false : (bool)val;
            }
            set { this["PageVisibleInMenu"] = value; }
        }

        /// <summary>
        /// Check if page is in pending state
        /// </summary>
        public bool PendingPublish
        {
            get
            {
                object val = GetValue("PagePendingPublish");
                return val == null ? false : (bool)val;
            }
        }

        /// <summary>
        /// Gets current status.
        /// </summary>
        public VersionStatus Status
        {
            get
            {
                object val = GetValue("PageWorkStatus");
                return val == null ? VersionStatus.NotCreated : (VersionStatus)val;
            }
        }

        /// <summary>
        /// Page version identifier
        /// </summary>
        /// <returns>An <see cref="Int32"/> representing the version ID.</returns>
        /// <remarks>
        /// This property is for internal use. You should not rely on the behavior of this property to remain unchanged. 
        /// </remarks>
        public int WorkPageID
        {
            get { return PageLink.WorkID; }
            set
            {
                ThrowIfReadOnly();
                PageLink.WorkID = value;
            }
        }

        /// <summary>
        /// Check if page has been modified since load
        /// </summary>
        /// <returns>
        /// <b>True</b> if the  page is dirty, i.e. changes have been made to any 
        /// data of the <b>PageData</b> instance. 
        /// </returns>
        /// <remarks>
        /// Primarily used by the <b>Save</b> methods to determine if the page needs to be saved. 
        /// </remarks>
        public bool IsModified
        {
            get
            {
                // If one of the properties is modified, then the page is marked as modified
                for (int i = 0; i < Property.Count; i++)
                {
                    if (Property[i].IsModified)
                    {
                        return true;
                    }
                }

                return false;
            }
            set
            {
                ThrowIfReadOnly();
                // Ignore attempts to set IsModified to true, only handle reset of IsModified
                if (value)
                {
                    return;
                }

                for (int i = 0; i < Property.Count; i++)
                {
                    Property[i].IsModified = false;
                }
            }
        }

        /// <summary>
        /// Access Control List (list of access permissions) of the type <see cref="EPiServer.Security.AccessControlList"/>.
        /// </summary>
        /// <remarks>
        /// <para>
        /// ACL holds the Access Control List for a <b>PageData</b> object. Since <b>PageData</b> has the attribute 
        /// Property which is a <b>PropertyDataCollection</b>, <b>ACL</b> effectively controls access to the Web page. 
        /// Keep in mind that the Access Control List applies to all of the <b>PageData</b> object and its 
        /// attributes. It is not possible to have different access permissions for different properties.
        /// </para>
        /// <para>
        /// The Acccess Control List is comprised of an Access Control Entry, ACE, array and is accessed by 
        /// calling the method ACL.ToRawACEArray.
        /// </para>
        /// </remarks>
        /// <example>
        /// <para>
        /// The following code example demonstrates the usage of <b>ToRawACEArray</b> to enumerate the 
        /// Access Control Entries. The example enumerates the <see cref="EPiServer.Security.RawACE"/> objects, 
        /// which together form the Access Control List and check if one of them is the Create permission.
        /// </para>
        /// <code source="../CodeSamples/EPiServer/Core/PageDataSamples.aspx.cs" region="AclEnumerate" />
        /// <para>
        /// The following code example demonstrates the usage of <b>QueryDistinctAccess</b> to check specific 
        /// access for the current user.
        /// </para>
        /// <code source="../CodeSamples/EPiServer/Core/PageDataSamples.aspx.cs" region="AclAccess" />
        /// </example>
        public AccessControlList ACL
        {
            get { return _acl; }
            set
            {
                ThrowIfReadOnly();
                _acl = value;
            }
        }

        /// <summary>
        /// Indicates if the page should be moved to its archive folder.
        /// </summary>
        /// <returns><b>True</b> if the page should be archived now.</returns>
        /// <remarks>
        /// A utility method that basically checks if the page has an archive folder and 
        /// its <b>StopPublish</b> date has passed. 
        /// </remarks>
        public bool PendingArchive
        {
            get { return !PageReference.IsNullOrEmpty(ArchiveLink) && StopPublish < DateTime.Now; }
        }

        private DateTime GetDateTimeValue(string propertyName)
        {
            return GetDateTimeValue(propertyName, DateTime.Now);
        }

        private DateTime GetDateTimeValue(string propertyName, DateTime defaultValue)
        {
            object val = GetValue(propertyName);
            return val == null ? defaultValue : (DateTime)val;
        }

        #endregion

        /// <summary>
        /// Return the access level that the current user has to this page.
        /// </summary>
        /// <returns>An AccessLevel</returns>
        /// <remarks>Note that this method also checks the published status of the page
        /// to determine the actual access that the user has. I e if the page is not published
        /// the user will not see it unless he has "more" access than Read.</remarks>
        public AccessLevel QueryAccess()
        {
            AccessLevel rawAccess = GetSecurityDescriptor().GetAccessLevel(PrincipalInfo.CurrentPrincipal);

            // Note! Remember that this logic is used by filesystem
            if (!FilterAccess.QueryDistinctAccessEdit(this, AccessLevel.Read))
            {
                rawAccess &= ~AccessLevel.Read;
            }

            return rawAccess;
        }

        /// <summary>
        /// Check for a distinct required access level. This is preferred to use over
        /// <see cref="PageData.QueryAccess"/> for performance reasons.
        /// </summary>
        /// <param name="requestedLevel"></param>
        /// <returns>true if at least the requested access is held by the current user</returns>
        /// <remarks>
        /// This method is optimized for speed, by checking preconditions before calling expensive methods.
        /// </remarks>
        public bool QueryDistinctAccess(AccessLevel requestedLevel)
        {
            return FilterAccess.QueryDistinctAccessEdit(this, requestedLevel);
        }

        /// <summary>
        /// The id for the master language branch
        /// </summary>
        /// <remarks>Returns null if not set.</remarks>
        public string MasterLanguageBranch
        {
            get { return (string)GetValue("PageMasterLanguageBranch"); }
            set { SetValue("PageMasterLanguageBranch", value); }
        }

        /// <summary>
        /// Checks if current object belongs to MasterLanguageBranch
        /// </summary>
        public bool IsMasterLanguageBranch
        {
            get { return PageReference.IsNullOrEmpty(PageLink) || MasterLanguageBranch == null || String.Equals(MasterLanguageBranch, LanguageBranch, StringComparison.OrdinalIgnoreCase); }
        }

        /// <summary>
        /// The id for this language branch
        /// </summary>
        /// <remarks>Returns null if not set.</remarks>
        public string LanguageBranch
        {
            get { return (string)GetValue("PageLanguageBranch"); }
        }

        /// <summary>
        /// Gets the availiable languages for the page. Is set in <see cref="InitializeData"/>.
        /// </summary>
        /// <value>The page languages.</value>
        public virtual ReadOnlyStringList PageLanguages
        {
            get { return _languages; }
        }

        /// <summary>
        /// Access the PropertyData.Value object of properties in the page object.
        /// Return null on get of non-existant property
        /// Throw EPiServerException on set of non-existant property
        /// </summary>
        /// <value>
        /// The Value property of the indexed Page property.
        /// <para>
        /// <b>Note!</b> Using this indexer will use the Pre and Post handlers for property lookup. I e return 
        /// values are not guaranteed to belong to the page, but may be dynamic properties, "fetch-data-from"-data
        /// etc. To get data guaranteed to belong to this page, use the <see cref="GetValue"/> and <see cref="SetValue"/>  methods.
        /// </para>
        /// Also note that setting values with this indexer will only set values that acually belong to the page, i e 
        /// you may get a valid value by reding from the indexer, but trying to set a new value for the same index may
        /// yield an exception since the value does not exist in the page.
        /// </value>
        public object this[string index]
        {
            get
            {
                // Don't use the Exist() method to check for existing property here, since it will require double look-up in the collection
                PropertyData prop = Property[index];
                if (prop != null)
                {
                    return prop.Value;
                }
                return null;
            }
            set
            {
                SetValue(index, value);
            }
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The value of the property data indexed by name.</returns>
        /// <remarks>
        /// This method can be used in the same way as the string indexer, but GetValue will not make use of the Pre and Post handlers.
        /// Any non-null value returned by GetValue is guaranteed to come from the PageData itself.
        /// </remarks>
        public object GetValue(string name)
        {
            // Don't use the Exist() method to check for existing property here, since it will require double look-up in the collection
            PropertyData prop = Property.Get(name);
            if (prop == null)
            {
                return null;
            }
            return prop.Value;
        }

        /// <summary>
        /// Sets the value.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <param name="value">The value.</param>
        /// <exception cref="EPiServer.Core.EPiServerException">Thrown if the property referenced by index does not exist.</exception>
        public void SetValue(string index, object value)
        {
            // Don't use the Exist() method to check for existing property here, since it will require double look-up in the collection
            PropertyData prop = Property.Get(index);
            if (prop == null)
            {
                throw new EPiServerException(String.Format("Property '{0}' does not exist, can only assign values to existing properties", index ?? String.Empty));
            }
            prop.Value = value;
        }

        /// <summary>
        /// Initializes the page with available page languages and initializes the property collection.
        /// </summary>
        public virtual void InitializeData(IList<string> pageLanguages)
        {
            _propertyCollection.InitializeData();
            _languages = new ReadOnlyStringList(pageLanguages);
        }

        /// <summary>
        /// Initializes current instance with predefined <see cref="EPiServer.Core.PropertyDataCollection"/>
        /// </summary>
        /// <param name="props">Initialized property collection.</param>
        internal protected void SetInitializedPropertyCollection(PropertyDataCollection props)
        {
            _propertyCollection = props;
        }

        /// <summary>
        /// Creates a deep copy of the current object, as opposed to a shallow copy.
        /// </summary>
        /// <returns>A new <b>PageData</b>.</returns>
        /// <remarks>
        /// <para>
        /// In most cases you will probably want to use the new <see cref="CreateWritableClone" /> method instead,
        /// since copying a read-only instance will return a new read-only instance.
        /// </para>
        /// <para>
        /// This method performs a deep copy and the copy should therefore have no 
        /// references to the original <b>PageData</b> instance. This is to say that you can 
        /// freely modify the copy without altering the original.
        /// </para>
        /// </remarks>
        public PageData Copy()
        {
            PageData newPage = (PageData)this.MemberwiseClone();
            newPage._propertyCollection = Property.Copy();
            newPage._propertyCollection.OwnerPage = newPage;
            newPage._acl = ACL.Copy();
            newPage._linkURLs = new Dictionary<string, string>();

            return newPage;
        }

        /// <summary>
        /// Shallow copies the state of the passed PageData instance.
        /// </summary>
        /// <param name="copy">The instance to copy state from.</param>
        protected virtual void ShallowCopy(PageData copy)
        {
            this._acl = copy._acl;
            this._isReadOnly = copy._isReadOnly;
            this._languages = copy._languages;
            this._linkURLs = copy._linkURLs;
            this._propertyCollection = copy._propertyCollection;
        }

        /// <summary>
        /// Returns the current instance converted to a RawPage object.
        /// </summary>
        /// <returns>A <b>RawPage</b>.</returns>
        /// <remarks>
        /// Used primarily by the DataFactory Web Service to transfer <b>PageData</b> 
        /// instances to Web service clients.
        /// </remarks>
        public RawPage ToRawPage()
        {
            RawPage rawPage = new RawPage();
            rawPage.Property = Property.ToRawPropertyArray();
            rawPage.ACL = ACL.ToRawACEArray();

            PageObjectManager pom = new PageObjectManager(this);
            IEnumerable<PageObject> pageObjects = pom.LoadAllMetaObjects();
            rawPage.PageObjects = (from po in pageObjects select po.ToRawPageObject()).ToArray();

            return rawPage;
        }

        /// <summary>
        /// Indicates whether the page should be displayed based on publish date.
        /// </summary>
        /// <param name="status">The type of publish status to check against.</param>
        /// <returns><b>True</b> if the page should be displayed.</returns>
        /// <remarks>
        /// Used internally to filter pages in listings. You should usually not need to refer to this method directly.
        /// </remarks>
        public bool CheckPublishedStatus(PagePublishedStatus status)
        {
            if (status == PagePublishedStatus.Ignore)
            {
                return true;
            }

            if (PendingPublish)
            {
                return false;
            }

            if (Status != VersionStatus.Published)
            {
                return false;
            }

            if (status >= PagePublishedStatus.PublishedIgnoreStopPublish && StartPublish > Context.Current.RequestTime)
            {
                return false;
            }

            if (status >= PagePublishedStatus.Published && StopPublish < Context.Current.RequestTime)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Gets the Page in the requested language.
        /// </summary>
        /// <remarks>If page does not exist in requested language null is returned. It is possible 
        /// to use property <see cref="PageLanguages"/> to see which languages the page exist in </remarks>
        /// <param name="languageBranchId">The language branch id.</param>
        /// <returns>Page in requested language</returns>
        public virtual PageData GetPageLanguage(string languageBranchId)
        {
            if (String.Equals(this.LanguageBranch, languageBranchId, StringComparison.OrdinalIgnoreCase))
            {
                return this;
            }

            if (!_languages.Contains(languageBranchId))
            {
                return null;
            }

            //Since to call this method the user must already have got hold to the page and then access checks should have been performed.
            //Since it is currently not possible to have differernt access on different languages we dont bother about access checks here.
            PageReference pageLink = PageLink;
            return DataFactory.Instance.GetPageProvider(pageLink).GetPage(pageLink, new LanguageSelector(languageBranchId));
        }

        /// <summary>
        /// Populates the PageData object with its dynamic properties. This method will always make a call to the database, so use it with care.
        /// </summary>
        /// <remarks>
        /// Dynamic properties are only available when the pageData instance is read-only. If you need the dynamic properties
        /// even when the instance is writable, call this method.
        /// </remarks>
        /// <exception cref="System.NotSupportedException">Thrown if the current instance is read-only.</exception>
        [Obsolete("Not used by the EPiServer Framework any more", false)]
        public void PopulateDynamicProperties()
        {
            // Throws an exception if PageData is readonly.
            ThrowIfReadOnly();

            // Read the DynamicPropertyCollection as it appears for this page.
            DynamicPropertyCollection dynamicProperties = DynamicProperty.ListForPage(PageLink);

            foreach (DynamicProperty property in dynamicProperties)
            {
                DynamicProperty currentProperty = property;
                string propertyName = currentProperty.PropertyValue.Name;

                // If property not defined on the current instance, set if with the dynamic property value
                if (Property[propertyName] == null)
                {
                    // If the dynamic property is inherited from higher up in the tree
                    if (currentProperty.Status == DynamicPropertyStatus.Inherited)
                    {
                        // Read the actual value of dynamic property
                        currentProperty = DynamicProperty.Load(currentProperty.InheritedPageLink, propertyName);
                    }
                    Property.Add(currentProperty.PropertyValue);
                }
            }
        }

        #region File/Directory access

        /// <summary>
        /// Gets the UnifiedDirectory for a page.
        /// </summary>
        /// <param name="createIfNotExist">if set to <c>true</c> directory is created if it not exist</param>
        /// <returns>The directory for the page (or null if createIfNotExist is false and directory does not exist)</returns>
        /// <remarks>If directory does not exist and createIfNotExist is false null is returned.</remarks>
        public virtual UnifiedDirectory GetPageDirectory(bool createIfNotExist)
        {
            string pageDirPath = VirtualPathUtility.AppendTrailingSlash(System.IO.Path.Combine(VirtualPathHandler.PageDirectoryRootVirtualPath, Property["PageFolderID"].ToString()));
            UnifiedDirectory pageDirectory = GenericHostingEnvironment.VirtualPathProvider.GetDirectory(pageDirPath) as UnifiedDirectory;
            if (pageDirectory == null && createIfNotExist)
            {
                UnifiedDirectory pageVppRoot = (UnifiedDirectory)VirtualPathHandler.PageFolderProvider.GetDirectory(VirtualPathHandler.PageDirectoryRootVirtualPath);
                pageDirectory = pageVppRoot.CreateSubdirectory(Property["PageFolderID"].ToString());
            }
            return pageDirectory;
        }

        #endregion

        #region IReadOnly<PageData> Members

        /// <summary>
        /// Creates writable clone of this object.
        /// </summary>
        /// <returns>Writable clone object.</returns>
        public PageData CreateWritableClone()
        {
            PageData newPage = (PageData)this.MemberwiseClone();
            newPage._isReadOnly = false;
            newPage._propertyCollection = Property.CreateWritableClone();
            newPage._propertyCollection.OwnerPage = newPage;
            newPage._acl = ACL.CreateWritableClone();
            newPage._linkURLs = new Dictionary<string, string>();

            return newPage;
        }

        #endregion

        #region IReadOnly Members
        /// <summary>
        /// Protects from modifying this object.
        /// </summary>
        public void MakeReadOnly()
        {
            if (IsReadOnly)
            {
                return;
            }
            Property.MakeReadOnly();
            ACL.MakeReadOnly();
            _isReadOnly = true;
        }

        /// <summary>
        /// Check if current object is read-only.
        /// </summary>
        public bool IsReadOnly
        {
            get { return _isReadOnly; }
        }
        #endregion

        /// <summary>
        /// Throws an exception if Pagedata is readonly.
        /// </summary>
        protected void ThrowIfReadOnly()
        {
            if (IsReadOnly)
            {
                throw new NotSupportedException("The PageData object is read-only.");
            }
        }

        #region ISecurable Members
        /// <summary>
        /// Gets the security descriptor.
        /// </summary>
        /// <returns>
        /// An implementation of ISecurityDescriptor.
        /// </returns>
        public virtual ISecurityDescriptor GetSecurityDescriptor()
        {
            return ACL;
        }
        #endregion
    }
}

Examples

Often you'll access a PageData object through the CurrentPage on the PageBase (and descendants) class.

CopyC#
// Must be called from inside a class that implements IPageSource, 
// e.g. SimplePage or TemplatePage.
Response.Write("<a href=" + this.CurrentPage.LinkURL + ">Click</a>");

The GetPage method (defined in IPageSource) also returns a PageData object.

CopyC#
// Must be called from inside a class that implements IPageSource, 
// e.g. SimplePage Or TemplatePage.
PageData page = this.GetPage(new PageReference(30));
Response.Write(page.PageName);

Inheritance Hierarchy

System..::.Object
  EPiServer.Core..::.PageData

See Also