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…)

Open an EPiServer page

Here are a few ways to retrieve data for a given page (expressed in a PageReference instance) in EPiServer:

PageReference pageref = ...;
PageData pagedata;

// In a page (which is a sub-class of PageBase, e.g. TemplatePage, EditPage, SimplePage)
pagedata = this.Getpage(pageref);

// In a user control (which is a sub-class of UserControlBase)
pagedata = this.Getpage(pageref);

// Anywhere
pagedata = Global.EPDataFactory.GetPage(pageref);

Global.EPDataFactory also has some other useful functions:

// Open a page from a given FriendlyUrl rather than a PageReference
// (which requires knowing the Page ID)
pagedata = Global.EPDataFactory.GetPageByURL("/Bokningsbara-kurser/");

BTW, it’s also possible to initialize a PageReference with a Url (although NOT a Friendly Url):

pageref = PageReference.ParseUrl("http://localhost:8889/shop/Puff.aspx?id=130");

UPDATE (2007-06-04):

If you have a so called “simple address” such as “/foo”, then none of the above will work. In that case you have to use code such as this:

PropertyCriteriaCollection critcol = new PropertyCriteriaCollection();

PropertyCriteria crit = new PropertyCriteria();
crit.Condition = EPiServer.Filters.CompareCondition.Equal;
crit.Name = "PageExternalURL";
crit.Type = PropertyDataType.String;
crit.Value = simple_address.Trim(new char[] { '/' }); // Remove initial "/"
critcol.Add(crit);

PageDataCollection pagecol = Global.EPDataFactory.FindPagesWithCriteria(Global.EPConfig.RootPage, critcol);
if (pagecol.Count > 0)
{
   return pagecol[0].LinkURL;
}

Get URL from an EPiServer PageReference

Retrieving the URL to an EPiServer page seems like a basic thing to do, but it’s not quite obvious…

Referring to a page is normally represented by an instance of the PageReference class, so obviously that contains a public property called Url or something, right?

Wrong!

Instead, this is how to do it (as a bonus I also show how to retrieve the PageReference from a page property):

string url = "";
PropertyData pd = CurrentPage.Property["Shop_CartPage"];
if (pd != null && pd.IsNull == false)
{
	PageReference pageref = (PageReference)pd.Value;
	url = GetPage(pageref).LinkURL;
}

If anyone knows better or more efficient ways to do this, please add a comment to this post 🙂

Pages without page types in EPiServer

When creating “normal” ASPX pages (i.e. pages that are not EPiServer page types) to use in EPiServer sites it can be very useful to have them inherit from TemplatePage or SimplePage (both are EPiServer types) so that dynamic properties can be read, page methods called, etc. Unfortunately this will give rise to an error message similar to this:

The current template "/shop/Varukorg.aspx" does not match the specified page type file "/default.aspx"

This is because TemplatePage and SimplePage assumes that they are part of an EPiServer page type. This problem can be fixed by overriding ValidatePageTemplate() with an empty implementation:

public override void ValidatePageTemplate()
{
}

Voilá! No more error message.

BTW, try finding this information in the EPiServer docs. Not so easy…

User Controls in EPiServer

This is my first post about EPiServer, which basically is a framework for building web sites. It’s very capable and extensible and is in wide use in Scandinavia. For more information, look here: http://www.episerver.com.

Web User Controls in EPiServer solutions should always inherit from EPiServer.UserControlBase rather than the usual System.Web.UI.UserControl. This gives access to useful functions and properties such as

  • PageData GetPage(PageReference pageLink)
  • PageData CurrentPage
  • string Translate(string key)

Note that those functions work even if the current page is not a “proper” page type in EPiServer so it’s missing from the page hierarchy. Very useful!