Databinding with a Repeater

This is simple stuff, but I wanted to store these code snippets somewhere for easy re-use…

This is how to do databinding with a Repeater control in ASP.NET.

In the ASPX file:

<asp :Repeater runat="server" ID="rptPuffar">
   <itemtemplate>
          <h1>< %# GetTitle(Container) %></h1>
          <p>< %# Eval("XYZ") %></p>
   </itemtemplate>
</asp>

In the code-behind file:

protected override void Page_Load(object sender, EventArgs e)
{
   if (!IsPostBack)
   {
      PageDataCollection puffar = GetChildren(this.PuffContainer);
      rptPuffar.DataSource = puffar;
      rptPuffar.DataBind();
   }
}



protected string GetTitle(RepeaterItem item)
{
   PageData page = (PageData)item.DataItem;
   return page.PageName;
}

In Page_Load(), we connect the repeater to a datasource (in this case a collection of pages from EPiServer).

GetTitle() is a function that is called by the ItemTemplate in the repeater for extracting the string to include in the page (in this case the name of the page). Eval() can also be used directly in the ASPX file to display the value of a property.

As I said, easy stuff, but now I don’t have to write the code again the next time I need it…

How to detect if an EPiServer page is presented in DOPE mode

In some cases it may be useful to detect when an EPiServer page is displayed in DOPE (Direct On-Page Editing) mode rather than the “normal” View mode. This is how to do it:

string dopemode = Request.Params["DopeMode"];
if (!String.IsNullOrEmpty(dopemode) && dopemode.ToLower() == "enable")
{
   // DOPE mode detected!
}

This is the only way I have found to do this, but it feels like there should be a DOPE flag somewhere in the EPiServer API. If anyone has found it, please leave a comment πŸ™‚

Resizing the Action Window panel in an EPiServer plugin

Have you ever written en EPiServer plugin for the Action Window? If so, you have probably noticed how irritatingly narrow it is. Luckily, the width can easily be adjusted by your plugin using this code:

<script>
  if (window.parent!=null && window.parent.parent!=null &&
    window.parent.parent.document.all['epCustomDIV']!=null)
  {
    window.parent.parent.document.all['epCustomDIV'].parentElement.style.pixelWidth = 500;
  }
</script>

This is the same code that the built-in EPiServer File Manager uses, so I think we can regard it as well tested. It has worked well for me, hope it helps you as well.

How to refer to an EPiServer page

Often you have to refer to a given page in a site. For example, on each page you may want to have a footer with a link to a page that describes the site’s usage of cookies. Note that this is different than linking to pages in the navigation menus since those links are created by traversing all non-hidden pages automatically. It’s also different from links created by the editors in edit mode, since they use the link tool to select the individual pages they want to link to.

The problem we have is instead how to identity a given page in code that we want to create a link to, and the question is what the best way to do that is. As always, the answer depends on the requirements. There are several alternatives, which I will now describe.

Hard-coding the page Url
The easiest way is of course to just hard-code the page’s address (we’re using EPiServer’s friendly Url function here):

<a href="/AboutCookies/">About cookies</a>

That works well, as long as we don’t move the page from the site’s root. If we move it, we will have to adjust all links to that page:

<a href="/InfoPages/AboutCookies/">About cookies</a>

That is obviously not all that great, especially since the page can be moved by a web site editor without thought of the consequences. You can also get into similar problems if you rename the page.

If you don’t use friendly Url’s you won’t have these problems since the page Url in that case will identify the page by its Id. In this case, this solution may be enough for you.

Referring to pages using page properties
The next alternative, and perhaps the most common, is to use page properties to refer to the page in question. In our example we could create a page property called “CookiePage” of the “Page” property type so that the web site editor could select the page which stores information about the cookie policies of the site.

On the page we then use code similar to this:

<episerver:property runat="server" PropertyName="CookiePage"></episerver:property>

Now we can move or rename the page without breaking the link. As an added bonus, EPiServer will now keep track of the reference to the page so if we try to delete it, EPiServer will warn us that the page is referenced.

Referring to pages using dynamic properties
Nothing is perfect though, and one problem with the above approach is that if the page is to be referenced from more than one page (remember, we want the link on all pages) we would need to put the page property on all pages, which of course would be ridiculous. To get around that we use EPiServer’s “dynamic property” functionality. A dynamic property works just like a page property, except that it’s not associated with a page type, but can be set on any page.

So instead of creating a page property called “CookiePage” we create a dynamic property “CookiePage” and set it’s value on the start page of our site. We can then use the same code as before in the ASPX page:

<episerver:property runat="server" PropertyName="CookiePage"></episerver:property>

Now, this works well but we risk having to create a lot of dynamic properties for different settings (there are probably more pages we need to reference than the “cookie page”). We therefore risk having a lot of dynamic properties which can be bewildering for web site editors. It can also be unclear which page these properties should be set on. Furthermore, the values of dynamic properties can normally only be edited by administrators, not by editors. If that is not wanted, we have to choose another solution.

This leads us to the next alternative…

Referring to pages using a settings page
Instead of using a lot of dynamic properties in our web site, we can create a special settings page (with its own page type). On this page we collect all our site-wide settings, such as a reference to our cookie page (i.e. we create a page property called “CookiePage” on the settings page). We then create a dynamic property called “SettingsPage” and set it’s value on the start page for our site so that it points to our settings page.

Finally, we modify our ASPX page so that it now reads

<episerver:property runat="server" PropertyName="CookiePage" PageLinkProperty="SettingsPage"></episerver:property>

This will still read the value of the property “CookiePage”, but now the value will come from the page referenced by the dynamic property “SettingsPage”. This can be seen as a form of property redirection. It’s a minimal code modification and we have gained

  • a clean separation of site settings from normal properties (page and dynamic ones)
  • web site editors can now modifiy the values (this can of course be denied using EPiServer security settings)

In many cases, this is probably the best solution as it’s scalable and gives clean page code. However, code in the code-behind files becomes a little more complicated if you want to retrieve settings from the settings page. Essentially you first have to retrieve the value of the “SettingsPage” property, then open the page it references and then finally read the property value from the loaded page.

If the settings stored in the settings page are mainly page references that are farily static (such as our cookie page) there is one more alternative.

Simple adresses
Each page in EPiServer has, in addition to the “normal” page address (friendly Url or normal), a shortcut address. This is editable on the “Advanced information” tab when editing a page:

cookiepage_property.gif

If you set this property on the cookie page to for example “cookiepage”, this page is accessible as “http://host/cookiepage”. This fact can be used in our page footer by creating a link like this:

<a href="/cookiepage">About cookies</a>

This looks a lot like our first, simple example, but now we can move or rename the page without problems. And in code-behind files we now don’t even have to read property values, we know beforehand what the page address is:

Response.Redirect("/cookiepage");

Very simple and very efficient code. Just what I like πŸ™‚

This alternative is also useful if you have several pages that should be visited in sequence, for example when the user is ordering an item in a web shop. If you set the simple address on each form page, you always know what the address to the next page in the sequence is. If you select one of the other alternatives, it will be a lot of administration before you get all page properties right.

This post got longer than I thought when I started it, the subject may be more complicated than first expected. Maybe you guys out there have opinions on this matter? Please add comment to this post if that is the case πŸ™‚

Cheers,

Emil

Using Web User Controls for EPiServer custom page property types

All content in EPiServer pages are stored in page properties which can be of different types such as short string, XHTML string, date, etc. It is also possible tocreate custom property data types, which can be very useful in many circumstances such as when integrating with other systems or creating content which is not easily representable only in page types.

A custom page property type is created by inheriting from an existing property type such as EPiServer.Core.PropertyLongString and overriding the CreateChildControls() function, and a skeleton looks like this:

[EPiServer.PlugIn.PageDefinitionTypePlugIn(DisplayName = "StrΓ€nglista")]
public class StringList : EPiServer.Core.PropertyLongString
{
    public override void CreateChildControls(string RenderType, System.Web.UI.Control Container)
    {
        switch (RenderType.ToLower()) 
        {
            case "edit":  // Edit mode, display controls to let the user edit the value
                ...
            case "dope":  // Direct on-page editing
                ...
            case "default":  // Show the content in view mode
                ...
            default:
                base.CreateChildControls(RenderType, Container);
                break;
        }
    }
}

So far, all of the above is documented in the book Developing Solutions with EPiServer together with a simple example of creating a BgColour type for choosing a color. However, for more complex properties, the techniques described there (creating control instances and calling Container.Controls.Add()) quickly becomes complicated. It’s then better to create a Web User Control and load that in the CreateChildControls() function. Here’s how to do that:

StringListEdit edit = (StringListEdit)Container.Page.LoadControl(
    "~/templates/PropertyDataTypes/StringListEdit.ascx");
edit.ID = Name;

// Initialize the user control with watever value it takes. In this case we happen
// to use a list of strings stored in this.Strings
edit.Strings = this.Strings;

CopyWebAttributes(Container, edit);
Container.Controls.Add(edit);

In the user control we don’t have to know anything about loading or saving the property value. Initializing is done explicitly in the code above, but how is saving accomplished?

If we only use the code above, the user control will be correctly initialized but no change will ever be saved. What’s missing is a code snippet something like this, just after the code above:

edit.Controls.Add(CreateParseValidator(edit.ValueText));

The ValueText property is a hidden TextBox property of our user control that always holds the string representation of the edited value (we’re using a string to store the value since we’re inheriting from PropertyLongString). The CreateParseValidator() method creates a custom validator that takes care of the saving for us.

NOTE: The validator must be created inside the user control, not inside the container!

This is actually the only way I have found to update the property value, and it’s not exacly apparent from the API or the documentation that we need to create an extra control in the user control with the complete value representation and connect a validator to it. πŸ˜‰

The solution outlined here has worked well for me, but if anyone has other solutions for how to best use user controls with custom property types, please feel free to leave a comment!

/Emil

Forcing ASP.NET to generate valid XHTML code

Ever seen error like these when validating your ASP.NET web site using validator.w3.org?

there is no attribute "name".
<form name="aspnetForm" method="post" action="../shop/StartPage.aspx?id=122" i

document type does not allow element "input" here; missing one of "p", "h1",
"h2", "h3", "h4", "h5", "h6", "div", "pre", "address", "fieldset", "ins", "del" start-tag.
...dkZGQDrhr9OheQO96crtnJaz+8HiO6ew==" />

This will happen if you have the following doctype, indicating that the page content should be XHTML Strict:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

However, using that doctype is not enough for ASP.NET to generate valid XHTML Strict code. To make it do that, make sure that the following is set in the system.web section of Web.config (the default value is “Legacy”):

<xhtmlConformance mode="Strict"/>

Without that setting, ASP.NET will insert invalid attributes on the form tag and place the hidden input fields in invalid contexts. AFAIK, there’s no dialog where you can change this setting, you have to know it’s there. And now you do! πŸ™‚

UPDATE:
The above solution only works in ASP.NET 2.0. Under 1.0 there is no known solution (at least not to me).

Why do I get “Registry access not allowed” error message?

Struggling with this error that occurs the first time the site is viewed after an IIS restart?

epi_reg_error.gif

This problem can be caused by the EPiServer Scheduler service not being installed.

Try this:

C:\Inetpub\wwwroot\bin>C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\InstallUtil.exe EPiServer.SchedulerSvc.exe

(From the EPiServer FAQ: http://www.episerver.com/en/EPiServer_Knowledge_Center/Support/FAQ_EPiServer_4/905/923/)

Retrieve the Html code for a Web User Control

Here’s how to extract the Html code for a web control:

public static string GetHtmlForControl(Control control)
{
    StringWriter textwriter = new StringWriter();
    HtmlTextWriter writer = new HtmlTextWriter(textwriter);
			
    control.DataBind();
    control.RenderControl(writer);
    string html = textwriter.GetStringBuilder().ToString();

    return html;
}

Tip: If you’re having problems with the generated Html being incomplete, then maybe you’re calling the function above in the wrong time? I’ve been having some problems with databound EPiServer controls until we discovered that we were doing this too early. When we started doing it in the PreRenderComplete event of the page, then it started working:

protected void Page_PreRenderComplete(object sender, EventArgs e)
{
    string html = FormHelper.GetHtmlForControl(Butikshuvud);
}

More useful regular expression examples

Here are some more Regex examples that may come in handy:

// Remove Html comments
//html = Regex.Replace(html, @"\<!--&#91;^>&#93;*-->", "");
html = Regex.Replace(html, @"\<!--.*?-->", ""); // new version!

// Validate an email address (with named groups)
Regex regex = new Regex(@"^(?<user>[^@]+)@(?<host>.+)\..+$", RegexOptions.None);
Match m = regex.Match(epost);
/* Check m.Success to decide if valid or not */

// Validate a Swedish zip code ("postnummer")
regex = new Regex(@"^(\d\d\d \d\d|\d\d\d\d\d)$", RegexOptions.None);
m = regex.Match(postnummer);

In the first example, note .*? sequence. The question mark makes the match lazy, as opposed to the default greedy matching strategy. This makes a difference when there’s more than one comment in the html string. In my first try above I used [^>] to avoid matching the end comment marker, with the unfortunate side effect of missing comments with tags as content.

I have also found a useful online Regex testing service here:

http://www.regexlib.com/RETester.aspx

(Update 2009-04-28: Changed service, the previous one is now unavailable…)