EPiServer CMS

You are currently browsing articles tagged EPiServer CMS.

image I recently got the question how to fix the incorrect alphabetic sorting of child pages in EPiServer. It is usually Scandinavian users complaining that Å, Ä and Ö are sorted together with A and O instead of being at the end of the list.

Sort order: Alphabetical

When you specify the sort order of child pages your selection is stored in the database (tblPage.PeerOrderRule) and is shared for all language branches.

When you use GetChildren() it calls a stored procedure called netPageLinkList that returns the children ordered differently depending on the selection. Sorting is done in the database.

Easy Solution To Child Page Sort Order in EPiServer

1) Easiest way to fix sorting is to change the Collation order of the Name column in the tblPageLanguage table.

image 

different sort order depending on the current culture

The easy solution only allows one common sort order for the entire database and that might not be good enough for multi lingual sites.

2) You can solve this for the whole site by hooking the event DataFactory.FinishedLoadingChildren and resorting the pages. Remember that this event is called every time you call GetChildren() and the result is not cached.

3) Another way is to solve it locally is to use the FilterSort class on you PageDataCollection or setting the SortOrder property on you PageList.

Tags: , , , , , , , ,

EPiServer CMS is using the standard methods in ASP.NET to handle authentication and authorization. On top of this they have added a few providers to handle authentication and access control to EPiServer assets like pages and files uploaded by editors.

Make sure that you read up on how the authentication, location and authorization-tags works in web.config before you try to understand security in EPiServer CMS.

Check list for ASP.NET security

  • MachineKey – Always add a machinekey-tag to your web.config-file. Use this online tool to generate the MachineKey.
  • Authentication – I almost always use ASP.NET Forms authentication because it gives you the most flexibility. This is all you need and I always set timeout high to get the “Remember Me”-checkbox to work as expected. Sometimes I also use the defaultUrl-attribute to control what happens after login.
    <authentication mode="Forms">
      <forms loginUrl="Util/login.aspx" defaultUrl="/"
        timeout="129600" />
    </authentication>
  • Membership and RoleProvider – Configures how a username and password is validated and how to retrieve what groups a user is a member of.
  • Authorization – EPiServer uses authorization tags together with location tags to control access to physical folders like the EPiServer user interface.

Select Security PROVIDERS

Select the right providers for your site:

  • SqlMembershipProvider and SqlRoleProvider from Microsoft stores username, password and group membership in a SQL database. EPiServer is preconfigured with the needed tables so you can just start using them by changing defaultProvider-attribute.
  • WindowsMembershipProvider and WindowsRoleProvider from EPiServer enabled forms login but with windows credentials. This is the default provider for a new installation. A big limitation if an editor needs to work with Access Control is that Window Users and Groups are only synchronized when a user log on. So it is not possible to add rights to a user before it has logged in at least one time and the security admin tool shows cached data for who is a member of a group.
  • ActiveDirectoryMembershipProvider from Microsoft and ActiveDirectoryRoleProvider from EPiServer. Tries to workaround the problem with cached Users and Groups by talking directly to the Active Directory. I have personally have had a lot of issues with exceptions from the Active Directory providers and trouble to get it to work in a DMZ. They are also sensitive to interruptions if the LDAP server is not available. I recommend to use WindowsMembershipProvider if possible since it also uses the local machine as a cache.
  • MultiplexingMembershipProvider and MultiplexingRoleProvider from EPiServer forwards the requests to a list of providers and this enables you to combine both SQL and Windows accounts.

TIPS – IF you Can not login with your Windows Account

EPiServer WindowsMembershipProvider does not work if you try to login with a domain account but do your machine does not have a working connection to your Domain Controler.

Workaround by creating a local account that is a member of the local administrators group.

Tips – Use a local group to optimize and handle Groups in groups

EPiServer WindowsRoleProvider uses queries that only retrieve a list of groups you are a member of directly. It does not discover if you indirectly are a member of group through another group.

This is quite annoying and and AD-admin get something sad in their eyes if you suggest that you should not use groups in groups.

Workaround this by creating a local group on the web server and add the AD group that is using groups in groups. WindowsRoleProvider will see that you are a direct member of the local group.

This technique can also be useful if a lot of different AD-groups should have the same access in EPiServer. The reason is that EPiServer stores one row in a table for each access control entry for each EPiServer page and directory in VPP.

It could simplify your web.config if you do not have to maintain a long list group names for edit and admin mode access. (EPiServer 6 has a new feature that takes care of this.)

Break in to an EPiServer site

Forgot the password? Only got ftp-access? Do not worry, as long as you have the right to change web.config you can always break in!

You need to comment out all “<deny users="*" />” in web.config and then it is possible to access edit and admin mode without authentication.

I suggest that you reset your password or create a new account in admin mode and turn on security as fast as possible!

Notice that you must login to be able to edit pages.

Access Rights for pages and uploaded files

image EPiServer has the following Access Rights that can be set per page. You can also set access right for files in the File Manager.

  • Read – Let’s you see the page or download the file.
  • Create – Allows you to create child pages that will inherit the same ACL as the parent, upload new files or create directories.
  • Change – Allows you to save a new version of a page and mark it ready to publish. You can also check in a new version of an existing files.
  • Delete – Allows you to move a page to the wastebasket or delete it permanently. You can delete files and directories.
  • Publish – Allows you to change and publish pages. Not applicable on files.
  • Administer – Allows you to change the ACL for this page or directory as an editor and change dynamic properties on this page (and indirectly all children)

Users with access to admin mode can always change access control lists and do not need Administer right.

imageNotice that you can only set Access Rights per directory and not on individual files.

Page Files are special. Each page can have its own page folder and files uploaded to this directory have the same availability as the page. So if the page is not publish – no one can access the files in the page folder except editors. Very convenient since scheduled publishing also affects the files!

The same happens when the page is moved to the waste basket. No one can access the files in the page folder than editors. This is a common cause for broken images and links to document when editors copy and paste pages. If you delete the original page, no one can access the images its page folder!

Tips for Access Right configuration

If you follow these guidelines it will be much easier to administer access.

  • Avoid giving users access rights directly. Always add roles (groups) and make users members of these instead.
  • Never give WebEditors group any access rights (except on small sites where you are not going to use Access Rights at all). This roles is intended as a master switch if you have access to edit mode or not.
  • Give the virtual role “Creator” the right to change and delete pages if you setup a site with writers and an editors-in-chief that publish pages. It will save a lot of maintenance work when writers makes mistakes.
  • Using role names with both location and role will simplify when you administer who is a member of what, i.e. pressrelease_writer, pressrelease_publisher, startpage_editor, article_writer, blog_admin

Tags: ,

You get a warning when you try to delete a file or EPiServer page if it is referenced form another page.

EPiServer uses EPiServer.DataAbstraction.SoftLink for this feature and all links to files, other EPiServer pages and external urls are stored here every time you publish a page.

It is also worth to notice that EPiServer built-in keyword table that is used for text searching pages also is updated during publish.

Corrupt data = Broken Links

The index can be corrupt or out of sync and then it is possible to delete a file or a page without getting a warning that it has incoming links. Text search can also fail or return wrong data.

I have seen this very often after a migration from EPiServer CMS 4. EPiServer CMS 4 databases tends to have really poor data for soft links.

The index is updated when you publish a page with a delay controled by indexingDelayAfterPublish setting. If the delay it is very large and the application restarts before the page is indexed, neither soft links or keywords will be updated.

How to repair EPiServer CMS soft link and keyword index

I have created an admin tool that we always run after migration from EPiServer 4 and also if we want to repair the index if the settings has been strange.

You sould set indexingDelayAfterPublish to a very large delay during the time you run the tool because if you try to use LaxyIndexer.IndexPage() in more than one thread at the same time you are guaranteed to get deadlocks or get other strange SQL exceptions sooner or later.

System.Data.SqlClient.SqlException:Violation of PRIMARY KEY constraint ‘PK_tblPageKeyword’. Cannot insert duplicate key in object ‘dbo.tblPageKeyword’.

Example code

[GuiPlugIn(DisplayName = "Reindex pages", Description = "Tool to index keywords and soft links", Area = PlugInArea.AdminMenu, Url = "~/Common/FixIndexing.aspx")]
public partial class FixIndexing : System.Web.UI.Page
{
    private static readonly ILog log;
    private static long QueueLength;
    private static long IndexedPages;
    private static DateTime Started = DateTime.MinValue;
    private static readonly Queue<PageData> Queue = new Queue<PageData>();
    private static object LockRoot = new object();

    static FixIndexing()
    {
        log = LogManager.GetLogger(typeof(FixIndexing));
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        UpdateStatus();
    }

    private void UpdateStatus()
    {
        long q = Interlocked.Read(ref QueueLength);
        long n = Interlocked.Read(ref IndexedPages);
        if (Started != DateTime.MinValue)
        {
            StatusLabel.Text = string.Format("Started: {0}  Indexed pages: {1}  Queue: {2}", Started, n, q);
        }
        FixPageSoftLinkButton.Enabled = q <= 0;
    }

    protected void FixPageSoftLinkButton_Click(object sender, EventArgs e)
    {
        log.Info("Start LazyIndex of all pages!");
        Started = DateTime.Now;
        ThreadPool.QueueUserWorkItem(ThreadProc);
        UpdateStatus();
    }

    // This thread procedure performs the task.
    static void ThreadProc(Object stateInfo)
    {
        if (!Monitor.TryEnter(LockRoot)) return; //Job was already running
        try
        {
            IndexedPages = 0;
            PageData page;
            Queue.Enqueue(DataFactory.Instance.GetPage(PageReference.RootPage, LanguageSelector.MasterLanguage()));
            Interlocked.Increment(ref QueueLength);
            while ((page = Queue.Dequeue()) != null)
            {
                Interlocked.Decrement(ref QueueLength);
                try
                {
                    log.Debug(string.Format("Indexing page {0}:{1}...", page.PageLink.ID, page.PageName));
                    LazyIndexer.IndexPage(page.PageLink.ID);
                    Interlocked.Increment(ref IndexedPages);
                }
                catch (Exception ex)
                {
                    int id = (stateInfo as PageData) != null ? ((PageData)stateInfo).PageLink.ID : -1;
                    log.Error(string.Format("Error while indexing {0}", id), ex);
                }
                try
                {
                    PageDataCollection pages = DataFactory.Instance.GetChildren(page.PageLink, LanguageSelector.MasterLanguage());
                    log.Debug(string.Format("Done indexing page {0}. Adding {1} children to Queue...", page.PageLink.ID, pages.Count));
                    foreach (PageData data in pages)
                    {
                        Queue.Enqueue(data);
                        Interlocked.Increment(ref QueueLength);
                    }
                }
                catch (Exception ex)
                {
                    int id = (stateInfo as PageData) != null ? ((PageData)stateInfo).PageLink.ID : -1;
                    log.Error(string.Format("Error while adding children for {0}", id), ex);
                }

            }
        }
        finally
        {
            Monitor.Exit(LockRoot);
        }

    }

}

Index as Scheduled task

Another way to use the code above is to just add an Execute() method that calls ThreadProc and register it as a Scheduled Job plug-in.

Tags: , ,

I got a question in the mail today how to get started with EPiServer CMS as a developer. Here are my tips:

Tags: ,

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: , , , ,

« Older entries