CurrentPage in Context
For the majority of EPiServer developers, CurrentPage is a fairly straightforward concept. When in a page template or user control, it refers to the current page, and provided that we use the EPiServer Property web control, it intelligently handles our CurrentPage context even in a templated control like a repeater (there is some ‘magic’ inside the Property control that detects if it is in a data bound context and tries to locate a PageData object to use as it’s page data). Even if we don’t use the EPiServer Property web control, the EPiServer templated web controls help us because we can use Container.CurrentPage instead.
It gets a little trickier if we are using a non-EPiServer templated control like a repeater and are not using the EPiServer Property control because we don’t have the EPiServer Property ‘magic’ nor do we have the Container.CurrentPage, so we need to get a little more creative and use a bit of casting like (EPiServer.Core.PageData)Container.DataItem. This is still hardly an issue.
The following table shows what can be used when:
| EPiServer Property Control | CurrentPage | Container.CurrentPage | (EPiServer.Core.PageData)Container.DataItem | |
| Directly on page/user control | Will detect whether it is inside a data bound control somewhere in the control hierarchy that provides a PageData object, otherwise will use current page | Will always use the current page | Not available | Not available |
| In an EPiServer templated control | Will use the page in the data item | Will always use the current page | Will use the page in the data item | Will use the page in the data item |
| In a non-EPiServer templated control | Will detect whether it is inside a data bound control somewhere in the control hierarchy that provides a PageData object, otherwise will use current page | Will always use the current page | Not available | Will use the page in the data item |
It is increasingly common to want to create a formatted user control (for example, a news item summary) and use it either singly or in a templated controls such as repeaters across a site. if using only EPiServer Property controls inside the user control this isn’t an issue, because they will detect whether the user control is inside a PageData data item somewhere in the control hierarchy (see table above). However, it is unlikely that this will be useful enough. Nearly always the developer will want to use other controls and inline statements to render property values . The user control itself has no concept of what current page ‘context’ it is in and trying to ‘walk up the tree’ to find a data item context that can be used is tricky – the code inside the EPiServer Property control to do that is fairly complex.
What would therefore be handy is to design our user control in such a way that when it is data bound it ‘knows’ what page context it is in. To do this, I have created a base class that can be used to achieve this. Simply inherit from it and ensure that the ‘PageLink’ property is always set when the control is data bound. Any data-bound controls in your user control will then have access to a CurrentPage object that is always the bound page context. There are versions both for a site with PageTypeBuilder or without (using this base control with PageTypeBuilder sites will also sidestep that nasty not-of-expected-page-type error that you can get when trying to call CurrentPage in a control that inherits from the PageTypeBuilder.UI.UserControlBase<T> class).
Without PageTypeBuilder:
1: public class BoundBase : UserControlBase
2: {
3: public PageReference PageLink { get; set; }
4: private PageData _pageData;
5:
6: public override PageData CurrentPage
7: {
8: get
9: {
10: if (PageLink == null)
11: {
12: throw new Exception("You cannot use BoundBase before the PageLink is set");
13: }
14:
15: if (_pageData == null)
16: {
17: _pageData = GetPage(PageLink);
18: }
19:
20: return _pageData;
21: }
22: set
23: {
24: _pageData = value;
25: }
26: }
27: }
With PageTypeBuilder:
1: public class BoundBase<T> : UserControlBase where T : TypedPageData
2: {
3: public PageReference PageLink { get; set; }
4: private T _pageData;
5:
6: public new T CurrentPage
7: {
8: get
9: {
10: if (PageLink == null)
11: {
12: throw new Exception("You cannot use BoundBase before the PageLink is set");
13: }
14:
15: if (_pageData == null)
16: {
17: var pd = PageTypeBuilder.PageTypeResolver.Instance.ConvertToTyped(GetPage(PageLink));
18:
19: if (pd is T)
20: {
21: _pageData = pd as T;
22: }
23: else
24: {
25: throw new Exception("BoundBase found a page of type " + pd.GetType().Name + " when " + typeof(T).Name + " was expected");
26: }
27: }
28:
29: return _pageData;
30: }
31: set
32: {
33: _pageData = value;
34: }
35: }
36: }
Just a couple of ‘gotchas’ with this:
- Firstly, this technique depends on the PageLink being set. If you are only setting this on data bind (which is most likely), then any attempt to access CurrentPage directly before that in your user control will cause an exception. For example, using <%= CurrentPage.PageName %> would fail. You would need to use <%# CurrentPage.PageName %> instead.
- Secondly, if you are using EPiServer Property controls inside your user control then they will not know about the page context you are using. You’ll need to explicitly tell them by setting their ‘PageLink’ property to data bind to CurrentPage.PageLink, e.g. <EPiServer:Property runat=”server” PropertyName=”PageName” PageLink=’<%# CurrentPage.PageLink %>’/>
I’ve used this technique successfully on various projects, but I’m always open to other ideas so if anyone can think of a simpler way to approach this or has a better technique, please let me know!
Comments