EPiServer CMS

You are currently browsing articles tagged EPiServer CMS.

The Event Management System in EPiServer CMS 5 is used to propagate information that a page has changed to other web servers connected to the same database.

This is very important to setup if you have more than one web server or an enterprise site with several start pages because if it does not invalidate cache, the other web sites can potentially return old versions of a page!

Pitfall – Default configuration is using Multicast UDP!

If you read Configuring EPiServer CMS 5 R2 SP2 Enterprise you will learn that all you have to do is to set enableRemoteEvents="true" in web.config.

It is only mentioned briefly that it is using UDP and you may have to change your firewall settings. Too be more specific the default configuration is using multicast UDP broadcast! Tell this to your network guys and watch their reaction…

If you look around you will also find a tech note about the Event Management System Specification and a FAQ about setting up a server to use TCP protocol but not all clues needed are found there – you have to dig with the reflector to find out the rest.

Scenario: 4 start pages, 8 Front-end servers IN DMZ and 2 in LAN

You can change how the Event Management System communicates by changing WCF settings in web.config. It is possible to use TCP instead of UDP and also specify ports to use.

First, the current release (5 R2 SP2) does not support WCF Port Sharing so we need one port opened in the firewall for each start page (that is when you have several siteSettings-tags in web.config).

In our case we need to get four ports (for example port 13000-13003) opened in the firewall for TCP traffic between servers in DMZ and LAN.

Use the sample configuration below as a template to get it to work. In production I suggest using configSource-attribute in web.config to keep the WCF-settings in separate files.

Web.Config

<system.serviceModel>
  <services configSource="system.serviceModel.services.config" />
  <client configSource="system.serviceModel.client.config" />
  <behaviors>
    <serviceBehaviors>
      <behavior name="DebugServiceBehaviour">
        <!--TODO: the option should be only in test environment true in the production should be false-->
        <serviceDebug includeExceptionDetailInFaults="true" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <bindings>
    <netTcpBinding>
      <binding name="RemoteEventsBinding">
        <security mode="None" />
      </binding>
    </netTcpBinding>
  </bindings>
</system.serviceModel>

system.serviceModel.services.config

Use this to configure WCF Services (listens to incoming messages). Setup one service for each EPiServer site-tag.

  • Name should be unique.
  • Name must be the value of episerver/sites[siteid] plus "/EPiServer.Events.Remote.EventReplication", i.e. if siteId is “sss.se” then name should be “sss.se/EPiServer.Events.Remote.EventReplication”.
  • Each site must use a diffrent port in services/service/endpoint[address].
  • Use "localhost" in the address attribute to bind to all NIC on the machine.
<services>
  <service name="sss.se/EPiServer.Events.Remote.EventReplication" behaviorConfiguration="DebugServiceBehaviour">
    <endpoint name="RemoteEventServiceEndPoint" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://localhost:13000/RemoteEventService" binding="netTcpBinding" />
  </service>
  <service name="sss.no/EPiServer.Events.Remote.EventReplication" behaviorConfiguration="DebugServiceBehaviour">
    <endpoint name="RemoteEventServiceEndPoint" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://localhost:13001/RemoteEventService" binding="netTcpBinding" />
  </service>
  <service name="sss.dk/EPiServer.Events.Remote.EventReplication" behaviorConfiguration="DebugServiceBehaviour">
    <endpoint name="RemoteEventServiceEndPoint" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://localhost:13002/RemoteEventService" binding="netTcpBinding" />
  </service>
  <service name="sss.com/EPiServer.Events.Remote.EventReplication" behaviorConfiguration="DebugServiceBehaviour">
    <endpoint name="RemoteEventServiceEndPoint" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://localhost:13003/RemoteEventService" binding="netTcpBinding" />
  </service>
</services>

system.serviceModel.client.config

And each server that changes the content must also be aware of where to send notifications so we must list all servers and websites.

  • Name should be unique.
  • It is better if address contains IP-addresses than hostnames.
  • There should be one endpoint for each website on a server where port number matches the ports used in the services/service/endpoint[address].
<client>
  <!-- SERVER1 -->
  <endpoint name="sss.se-SERVER1" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://SERVER1:13000/RemoteEventService" binding="netTcpBinding" />
  <endpoint name="sss.no-SERVER1" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://SERVER1:13001/RemoteEventService" binding="netTcpBinding" />
  <endpoint name="sss.dk-SERVER1" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://SERVER1:13002/RemoteEventService" binding="netTcpBinding" />
  <endpoint name="sss.com-SERVER1" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://SERVER1:13003/RemoteEventService" binding="netTcpBinding" />
  <!-- SERVER2 -->
  <endpoint name="sss.se-SERVER2" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://SERVER2:13000/RemoteEventService" binding="netTcpBinding" />
  <endpoint name="sss.no-SERVER2" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://SERVER2:13001/RemoteEventService" binding="netTcpBinding" />
  <endpoint name="sss.dk-SERVER2" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://SERVER2:13002/RemoteEventService" binding="netTcpBinding" />
  <endpoint name="sss.com-SERVER2" contract="EPiServer.Events.ServiceModel.IEventReplication" bindingConfiguration="RemoteEventsBinding" address="net.tcp://SERVER2:13003/RemoteEventService" binding="netTcpBinding" />
  <!-- Repeat pattern above for all other servers and sites -->
</client>

Good luck! And let me know if this helps you or if you get stuck…

Credits to Petter Klang at EPiServer Support and Shahram Shahinzadeh on the backend dev team that has been helping us out.

Tags: , , , ,

I got a support question on this Exception today. First thought is that the ip-address was misstyped or the wrong license.config file was used but ipconfig /all and the IPRestiriction tag in license.config matched!

How does EPiServer validate the ip-address?

The code to check is quite simple. It is using the DNS service to lookup the name of the computer and then lookup all ip-addresses for that computer name.

foreach (IPAddress address in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{
    if (!IPAddress.IsLoopback(address) && this._ip.Equals(address))
    {
        this._isValid = true;
    }
}

Troubleshooting License Exception  

A quick test is to add a small debug.aspx file to find out what values are returned.

<%@ Page Language="C#" AutoEventWireup="true"  %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
 ">
<script runat="server">

    protected void Button1_Click(object sender, EventArgs e)
    {
        foreach (System.Net.IPAddress address in System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList)
        {
            ListBox1.Items.Add(address.ToString()+ "-" + System.Net.IPAddress.IsLoopback(address).ToString());
        }
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml
 " >
<head runat="server"><title></title></head>
<body>
    <form id="form1" runat="server">
    <h1>Check IP-addresses</h1>
    <p>Current host name: <%= System.Net.Dns.GetHostName() %></p>
    <div>
        <asp:ListBox ID="ListBox1" runat="server"></asp:ListBox>
        <asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="Test" />
    </div>
    </form>
</body>
</html>

Resolution to LicenseException in this case

Running the test aboved showed us that the IP-address returned was the loopback address 127.0.0.1 instead of the computers ip-address.

Since this setup is an enterprise site with a lot of diffrent host names pointing to diffrent start pages the local hosts-file (at C:\Windows\System32\drivers\etc\hosts) was edited and all known host names was added pointing to 127.0.0.1 to make it possible to test. One entry was accidentally added with the machine name and when we removed that line everything started to work!

Tags: , , , ,

Mari Jørgensen wrote about Breaking change in GetChildren() and I would like to share some of my findings when working with PageData from code when you want to use the built-in flow for publishing.

As you might know a Page Version can have VersionStatus Not Ready (CheckedOut), Ready To Publish (CheckedOut), Published and Previously Published. Since you in almost all cases are only interested in the published version most methods in DataFactory class only returns PageData objects with the published version.

Get unpublished pages and pages not in current language branch

GetPage() and GetChildren() returns page(s) published in the current language. You always have to use in a ILanguageSelector if you want to get PageData for another Language Branch than the current language branch.

PageDataCollection pages =
  DataFactory.Instance.GetChildren(
    CurrentPage.PageLink,  LanguageSelector.AutoDetect(true));

This will retrieve all children the same way as the Page Tree in the Structure Tab in Edit Mode. If there is no published version in the Current Content Language it will return PageData for the Master Langauge Branch, regardless of Publish Status.

Saving a page without publishing it

It is easy to create a new page and not publish it. This can be used for moderation where an Editor uses the publish button in Edit mode to approve.

PageData page = DataFactory.Instance.GetDefaultPageData(
                  rootpage.PageLink, "My Page Type");
page.PageName = "New Page";
DataFactory.Instance.Save(page, SaveAction.CheckIn,
                          AccessLevel.NoAccess);

SaveAction.CheckIn will make you page Ready to Publish.

Access Rights

Even if the current your is not an Editor you may give them Edit access rights to their pages.  It is very easy to add access rights for the current user after the page is saved. Note that all existing access right on the parent page will be inherited as usual.

PageAccessControlList acl = new PageAccessControlList(page.PageLink);
acl.Add(new AccessControlEntry(
          Membership.GetUser().UserName,
          AccessLevel.Read | AccessLevel.Edit | AccessLevel.Create | AccessLevel.Delete,
          SecurityEntityType.User));
acl.Save();

It is also easy to filter a collection and remove pages you should not be able to change.

new FilterAccess(AccessLevel.Edit).Filter(pages);

Another approach is to show the page but maybe disable the edit button.

bool canChange = page.QueryDistinctAccess(AccessLevel.Edit);

Page Versions and Unpublished Pages

Property Values for the Published Version of a Page is stored in different tables in the database than all other versions of the page. You need something called WorkID in your PageReference to load other versions of a page than the published version.

WARNING! Last time I checked GetPage() and GetPages() returned skeleton PageData objects, where all user defined properties are null, for unpublished pages if you did not have a WorkID.

This is an example of how you have to use PageVersion class to retrieve a list of all versions of a page. Each PageVersion has a PageReference with both PageID and WorkID

public static PageData GetLastVersion(PageReference pageRef)
{
    PageVersionCollection pageVersions = PageVersion.List(pageRef);
    PageReference lastVersion = pageVersions[0].ID;
    foreach (PageVersion pageVersion in pageVersions)
    {
        if (pageVersion.IsMasterLanguageBranch)
        {
            lastVersion = pageVersion.ID;
        }
    }
    return DataFactory.Instance.GetPage(lastVersion,
             LanguageSelector.AutoDetect(true));
}

When you have a PageReference with WorkID you can use it with GetPage() to retrieve other versions of a Page. Using and a LanguageSelector with fallback to Master Language is required to get around the filter.

Update a page without creating a new version

Sometimes you want to change a PageData object without creating a new version. In the example below UpdatePageFromForm copies values from text boxes to the page. If a value has changed it will be saved.

page = GetLastVersion(pageRef).CreateWritableClone();
UpdatePageFromForm(page);
if (page.IsModified)
{
    SaveAction saveAction = SaveAction.CheckIn;
    if (page.Status != VersionStatus.Published)
    {
        // Update existing version if it is not published
        saveAction = saveAction | SaveAction.ForceCurrentVersion;
    }
    DataFactory.Instance.Save(page, saveAction);
}

That’s all for now folks!

Please, leave a comment if you learned something. It is good for my blogging morale to know that someone got helped…

Tags: , , , , , , , , , ,

How do you inject dynamic content into your web page? Following is a list of the most common methods I find when doing quality and code reviews with some comments and dangerous pitfalls.

#1: Inline Expressions with Code Render Blocks

Using inline expressions is a shortcut for calling the Write method.

Example – DONT:

<h2><%= CurrentPage.PageName %></h2>
<img src="<%= CurrentPage["ImageUrl"] %>" alt="<%= CurrentPage["MainIntro"] %>" />
<div class="mainarea">

     <%= CurrentPage["MainBody"] %>
</div>

Since xhtml is just text, writing some more in-between existing static blocks is fast and simple. Probably the easiest way to inject dynamic content but it is also dangerous!

Example – DO always use HtmlEncode:

<h2><%= HttpUtility.HtmlEncode(CurrentPage.PageName) %></h2>

What happens if our PageName property contains a “<” or “&” character in the example above? Your page will not validate and you risk that it breaks down. It can also be exposed to script injection if the content is user generated.

To be on the safe side you should always call HttpUtility.HtmlEncode on all strings that you inject with inline expressions.

Example – DO always use a fallback for src-attributes

<img src="<%= CurrentPage["ImageUrl"] ?? "/Missing.png" %>" />

If your ImageUrl property is empty you would get a img-tag with an empty src-attribute. This can lead to the page being rendered twice. FireFox interpret a null src-attribute a relative url and evaluate it using the page url as base returning the same url.

#2: With EPiServer:Property web control

Using the EPiServer Property Web Controls instead of inline expressions has several benefits. It could be used for rendering the PageName and MainBody in the example above.

Example – DO use EPiServer:Property web control

<h2><EPiServer:Property PropertyName="PageName" runat="server" /></h2>
<EPiServer:Property CssClass="mainarea" PropertyName="MainBody" runat="server" />

1) When rendering EPiServer’s ToWebString() is used. This does almost the same job as always calling HtmlEncode yourself.

public virtual void CreateDefaultControls()
{
    Label target = new Label();
    target.Text = PropertyData.ToWebString();
    CopyWebAttributes(target);
    Controls.Add(target);
}

2) It enables the Simple Edit feature for the editors without any extra work. (If you do not know what “Simple Edit” is, you should read the Editors Manual for EPiServer before doing any more development.)

3) Different types have get different rendering automatically. Commonly used for PageReferences and Url’s that are rendered as a Hyperlink.

Example – DO use EPiServer:Property for rendering links if possible

<EPiServer:PageList PageLinkProperty="NewsArchivePage" MaxCount="5" runat="server" >
  <HeaderTemplate>
    <h3><EPiServer:Property PropertyName="PageLink" runat="server" /></h3>
    <ul>
  </HeaderTemplate>
  <ItemTemplate>
    <li><EPiServer:Property PropertyName="PageLink" runat="server" /></li>
  </ItemTemplate>
  <FooterTemplate>
    </ul>
  </FooterTemplate>
</EPiServer:PageList>

4) Use PageLinkProperty to follow a Page-property to another page and render a value. Takes care of all hassle and calls GetPage() and check for nulls for you.

Example – DO use EPiServer:Property to render values from another page

<h3><EPiServer:Property PageLinkProperty="FeatureArticlePage" PropertyName="PageLink" runat="server" /></h3>
<div><EPiServer:Property PageLinkProperty="FeatureArticlePage" PropertyName="MainIntro" runat="server" /></div>

Tags: , , , , ,

I would like to congratulate Mikael Lundin to becoming a Certified EPiServer Developer. He writes about this experience in his blog with some tips about what you should study. I would like to complement what he has written with some of the tips I give to my students who are going to take the certification.

About the EPiServer Certification

You have two hours to complete around 70 multiple choice questions. You can skip a question during exam and go back to it later but you can not change answered questions.  To pass, you need answer correct on 60% of the questions.

My tips for passing the test

Preparing yourself is a must because the the goal of the test is to separate those who have worked with EPiServer from those who have not. But working with EPiServer is not enough, you must also have a good understanding of all features availible in EPiServer, even those you have not used…

  • Do develop with EPiServer for a while before you take the test. I do not think many have passed the certification directly after completing the basic developer training.
  • Do check EPiServer’s list of Knowledge Areas you are going to be tested on.
  • Do read the Editors and Administrators manual. As a developer you are supposed to know how to use the product from an end users perspective.
  • EPiServer Developers knows to little about Deployment in general. Make sure that you undrestand how to configure EPiServer. Do you know how Mirroring works or Enterprise features like multi-homed sites and load balancing is configured?
  • Do read all the Tech Notes for the latest EPiServer CMS release. You need to have a basic understanding about all features in EPiServer.
  • Do read through the pages in the Developers Guide part of EPiServer SDK, too. Personally I do not understand why they put some pages in the SDK and some as Tech Notes. It is high and low in both places and you must at least browse through it all…
  • Do not try to read through the whole Framework Reference. You could look on a few key classes like: PageBase, UserControlBase, TemplatePage, IPageSource, PageData, PageReference, DataFactory, PropertyData and PropertyDataControl. And also make sure you know your EPiServer Web Controls.

Many questions is on a very detailed level and I must say that I have heard a lot of complaints that the test is too hard. You have to make qualified guesses. I had to do a lot of qualified guessing myself , and I’m an EPiServer teacher and have turned EPiServer inside out with Roeders Reflector!

Remeber, you do not have to get 100% to pass and if you follow the tips above I’m sure you too will be a Certified EPiServer Developer…

Tags: ,

Newer entries »