Archive for tag: EPiServer

An EPiServer developer's introduction to the Umbraco Document API

When working with EPiServer we're used to having a single class to work with (if not using PageTypeBuilder), the PageData class. At first glance it's equivalent in Umbraco is the Document class, but after looking closer you'll see that there is one enormously important difference, caching.

In EPiServer, we get all PageData objects from the DataFactory, since EPiServer implements a factory pattern. It's also implemented as a singleton, making it very simple to interact with. We get the page by passing a PageReference to the factory, getting the page we want. By default the DataFactory returns the currently published version of a page. In Umbraco, we instead call the constructor on the Document object, by passing it an ID of the page we want. A big difference here is that the Document API by default returns the latest version of that page, regardless if it's published or not, if a certain version is required, specify it by passing the version Guid.

Another difference between the PageData and Document objects, is caching. The PageData object you get from the DataFactory is always cached (except when getting pages with FindPagesWithCriteria, but more on that in another post), whereas the Document object is never cached. For this reason, we only work with the Document API when we want to programatically change data. For a cached page in Umbraco, we use the Node API.

In EPiServer, the PageData object is read-only, when we want to change a page, we call CreateWritableClone() on the PageData object, getting a writeable copy of our object, that we can change all we want. When we're done, we pass it back to the DataFactory for saving and/or publishing. In Umbraco, this is not as straight-forward. The Document is always open for changes, and changing a property will persist that change to the database immediately. Properties for an EPiServer PageData object is accessed throught its index property, by property name, like MyPageData["MyProperty"]. Getting a property on the Document object is done with a call to MyDocument.getProperties("MyProperty"), however, this performs a call to the database as soon as you access the Property.Value property. What this essentially means, is that each proeprty is saved as soon as you set its value, calling Document.Save() fires the BeforeSave and AfterSave events, updates the UpdateDate property, and the preview XML. This is important to know. Calling Document.Publish() performs different task related to publishing, but it doesn't push your data to the content cache, for this you need to call umbraco.library.UpdateDocumentCache(int) passing it the ID of the Document. This also ensures that any other servers, if your site uses load balancing, also performs the cache update.

There are a number of methods to get cached content in Umbraco, the old-school way is to use the Node API, most commonly used via XSLT in Macros, but it's also available from code. Another way is to use Linq2Umbraco, that gets cached, typed, instances of your content. It has excellent performance, and if you're more comfortable with using C#/VB for your coding, this is probably the way for you. As I write there is also support for using Razor in Umbraco, and I dare say that it may make XSLT in Umbraco obsolete. It's the new black, and it's friggin' awesome :)

There's a great set of wiki-articles called The Umbraco API Cheat-sheet. Amongst others, it clearly describes the difference between a Node and a Document.

As usual, all and any comments, suggestions and additions are more than welcome.

An EPiServer developer's introduction to the Umbraco UI

Both CMS's, of course, comes with an administration interface. I'll assume that you are familliar with the EPiServer OnlineCenter Dashboard, and the EPiServer CMS Edit- and Admin-modes. However, if you're an Umbracian, I suppose you can imagine, and get the general idea of how EPiServer is laid out. Again, this comparison is not intended to establish which CMS is better, but to serve as a translation of sorts, between the two systems, to get you, as an EPiServer developer, started with Umbraco. This part is intended to give a brief orientation about the tool, and I'll go into greater detail about the various concepts as I get there throughout the series.

The default sections of Umbraco are Content, Media, Users, Members, Settings, and Developer. When you first log in to Umbraco, what you see is normally the "Content" "section", this roughly translates to the EPiServer CMS Edit-mode, and it features the functionality needed to edit, save, publish and work with permissions for content. WHen you have a clean install of Umbraco, there won't be anything here, since, just as in EPiServer, you need DocumentTypes (PageTypes) in order to have Documents (Pages).

The Media section is an ordered structure for Media, i.e. files and folders, that can be used , and re-used, from Documents. It's the equivalent of the file browser in EPiServer, although, not implemented in the same way.

The Users section is where Users for the Umbraco UI are managed, and their respective access rights.

The Members section is where Users for the website are managed. In Umbraco, this is the MembershipProvider that you configure. The Members section is used to manage Members, and it can be used to edit both the User, called a Member, and the Profile for that User.

The Settings section is where you edit the base for your website, such as Document Types, Media Types, Languages and Globalization and Templates. It can also be used to manage Stylesheets and Scripts for your website.

The Developer section is used to manage the most technical parts of the website, such as Data Types and Macros.

The rationale behind the decision to divide the building blocks for your site in Settings and Developer, can be discussed, in fact I can imagine it has been on several occasions. One could argue that Macros are on par with Templates in technicality, but I suppose there had to be a line drawn somewhere, and here it is.

There is also a dashboard, that roughly corresponds to the EPiServer OnlineCenter Dashboard, where you can load custom controls and functionality for each section, however, it doesn't have the same granular security features that EPiServer has, nor does it rely or ASP.NET MVC to build these "gadgets", they are simple (or, in some cases, not so simple) UserControls, but they can be used for really powerful stuff. Your imagination sets the limits here.

To sum it all up, coming from EPiServer, the Content- and Media sections corresponds to the EPiServer CMS Edit-mode, and Users, Members, Settings and Developer correspond to the Admin-mode. Also note, that some tasks that are handled in EPiServer configuration, are done from the UI in Umbraco, and vice-versa. I'll try to amek sure to point these out as I get there.

An introduction to Umbraco for EPiServer-developers. Or vice-versa.

This is the first part in a series introducing Umbraco to EPiServer developers, however, it can also be seen as an introduction to EPiServer for Umbraco developers. It all depends on your perspective. Please keep in mind that this is not an attempt at comparing the two for the sake of which is better, and I do not make the argument for either platform. Both have their pro's and con's, but I leave it up to you to select the platform that best suits your needs and requirements.

In my experience Umbraco is seen as a competent OSS alternative to EPiServer. In my opinion, EPiServer is a very competent product, although, the license costs often excludes it from consideration by many organizations. Umbraco, too, is very competent. They employ similar patterns for some of their functionality, and completely different approaches for some.

I've planned the disposition for the series according to the list below, but it may change as I write. I'll update this post as the series progresses.

Part 1. The administration user interfaces.

Part 2. Document vs. PageData

Part 3. DocumentType vs. PageType

Part 4. DataType vs. PropertyType

Part 5. Property vs. PropertyData

Part 6. Media vs. the VPP

Part 7. Members vs. Users

Part 8. Event hooking

Part 9. Scheduled tasks

Part 10. Building a Custom DataType, vs. Building a Custom PropertyType

Part 11. Extending and customizing the UI.

Part 12. Template vs. PageTemplate

Part 13. Macros vs. DynamicContent

Part 14. Macros (XSLT/Razor/UserControl/Python) vs. WebControls/UserControls

Part 15. Searching with Examine vs. FindPagesWithCriteria

I have been working with both platforms for many years, and I think both offer excellent performance and functionality. However, regardless of that experience, I may have gotten some things wrong, and I hereby invite anyone to correct me, add information, or otherwise express their opinions.

Upgrading From Intergen.MultiPage to EPiServer.LinkCollection

Oh, it's been a while, summer vacation and parenting, along with quite unbloggable (uninteresting) work has kept me from updating, but here's a little tidbit of information that I Googled, but didn't find anything about. Or, almost anything anyway.

Before EPiServer CMS 5 R2, whenever you wanted to have a property with multiple links, you used the MultiPage property by Intergen. It was excellent, it still is, that's why EPiServer chose to include it in the product, but I like to keep EPiServer standard properties as much as possible. Then main reason for this is upgradeability, and I like to do minimal amount of work to keep my clients happy. It costs less for them, and I get to spend more time doing stuff that I find interesting.

I ran into this issue when upgrading a EPiServer CMS 5 site to EPiServer CMS 6, mainly because the MultiPage property looks like crap in EPiServer CMS 6. I found out, using the EPiServer World Forums, and after checking myself, that both property types use the same data structure for their data. This is great. But as always there's a snag.

If you simply switch the property type in the Admin UI, all stored data is wiped. I don't know why, but I can imagine it is to ensure data integrity, so that you can't mess the property data up by accident.

I chose to get at the problem using a bit of T-SQL. I'm no DBA, so someone can probably write to code shorter, faster and prettier. But in most scenarios this is quickly run anyway.

As usual, make backups BEFORE, and I give no warranty. The code is provided as-is.

BEGIN TRANSACTION MultiPageToLinkCollection;
SET NOCOUNT ON;
DECLARE @linkcol int;
DECLARE @linkcolcount int;
DECLARE @multipage int;
DECLARE @multipagecount int;
SET @linkcol = (SELECT TOP 1 [pkID] FROM [tblPageDefinitionType] WHERE Name = 'LinkCollection');
SET @multipage = (SELECT TOP 1 [pkID] FROM [tblPageDefinitionType] WHERE Name = 'MultiPage');
IF @linkcol <> ''
BEGIN
    SET @linkcolcount = (SELECT COUNT(1) FROM [tblPageDefinition] WHERE [fkPageDefinitionTypeID] = @linkcol);
    SET @multipagecount = (SELECT COUNT(1) FROM [tblPageDefinition] WHERE [fkPageDefinitionTypeID] = @multipage);
    IF (@multipagecount > 0)
        BEGIN
        PRINT 'Beginning upgrade...';
        PRINT 'Properties using LinkCollection: ' + CAST(@linkcolcount AS varchar(5));
        PRINT 'Properties using MultiPage: ' + CAST(@multipagecount AS varchar(5));
        UPDATE [tblPageDefinition] SET [fkPageDefinitionTypeID] = @linkcol WHERE [fkPageDefinitionTypeID] = @multipage;
        SET @linkcolcount = (SELECT COUNT(1) FROM [tblPageDefinition] WHERE [fkPageDefinitionTypeID] = @linkcol);
        SET @multipagecount = (SELECT COUNT(1) FROM [tblPageDefinition] WHERE [fkPageDefinitionTypeID] = @multipage);
        PRINT 'Finished.';
        PRINT 'Properties using LinkCollection: ' + CAST(@linkcolcount AS varchar(5));
        PRINT 'Properties using MultiPage: ' + CAST(@multipagecount AS varchar(5));
        END
    ELSE
        BEGIN
        PRINT 'No properties using MultiPage were found.';
        END
END
ELSE
BEGIN
    PRINT 'Unable to find LinkCollection property type. Please make sure your database is for EPiServer CMS 5 R2 (5.2.375.7)';
END
COMMIT TRANSACTION MultiPageToLinkCollection;

Edit: Obviously, you'll need to look in your code and make sure it doesn't do anything funky to get the data from the property. The code in my project did, and it was awful.

Adding icons to the PageTree in EPiServer CMS 6.

A feature I use heavily in Umbraco, and one I've missed in EPiServer CMS, is the use of icons for different page types in the tree. Although this functionality has been around for a while using PageTreeIcons, when using PageTypeBuilder it can be quite cumbersome to maintain a configuration with PageTypeId's, when we want to move away from that.

Heavily inspired (and with some borrowing), I've converted the PageTreeIcons project to a collection of attributes that you can use to alter the icons for page types and properties. It's still an early draft, but it works.

This way, it's really easy to keep track of the icons for your properties, and there's no need to keep an extra section in your web.config. Some features have been removed, since EPiServer CMS 6 already contains some of the functionality (i.e the PageLinkTypeHandler).

It's applied like this, on page types

[PageTypeIcon("~/UI/Images/PageTreeIcons/page.png", typeof(ContentPage))]
[PageType(AvailablePageTypes = new[] { typeof(ContentPage) },
   Filename = "~/UI/Pages/ContentPage.aspx",
   SortOrder = 20,
   Name = "[Public] ContentPage",
   DefaultSortIndex = 100,
   Description = "An ordinary content page.")]
public class ContentPage : PageTypeBuilder.TypedPageData { ... }

and like this, on a property

[PropertyEmptyIcon("~/UI/Images/PageTreeIcons/warning.png", 
   "/pagetreeicons/property/missingkeywords", 
   "MetaKeywords", 
   new [] { typeof(BaseModule), typeof(ModuleContainer), typeof(Contact) })]
[PageTypeProperty(EditCaption = "Keywords",
   HelpText = "A comma-separated string containing keywords used for this page.",
   Searchable = true,
   UniqueValuePerLanguage = true,
   Type = typeof(PropertyString),
   SortOrder = 10)]
public virtual string MetaKeywords {
   get;
   set;
}

Again, kudos to Henrik Nyström, http://thisisnothing.wordpress.com/, for writing this in the first place.

Please let me know if you have any comments, improvements, or questions.

Download the code here.

Don't have any icons? Try FamFamFam Silk!

Simple validation of property values, redux.

Stefan Forsberg wrote an excellent post on validating page data with Data Annotations, his solution is by far a more elegant way of validating data on a property than my ugly hack. So I rewrote my PageType validation hack to fit into his model instead. I converted my very specific code into a more generic ValidationAttribute. Works like a charm, and makes sure that the PageReference actually points to a page of the type you want, or nothing at all.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using EPiServer.Core;
using PageTypeBuilder;

namespace Development {

    ///
    /// Specifies the allowed type of pages selected in the property.
    ///
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
    public sealed class AllowedPageTypeAttribute : ValidationAttribute {

        ///
        /// The allowed types of pages.
        ///
        public Type[] AllowedPageTypes {
            get;
            set;
        }

        public AllowedPageTypeAttribute(Type[] allowedPageTypes)
            : base() {
            this.AllowedPageTypes = allowedPageTypes;
        }

        public override bool IsValid(object value) {
            var reference = value as PageReference;
            // Check if the property ReferenceContainer has a value
            if (PageReference.IsNullOrEmpty(reference))
                return true;

            var page = reference.GetPage();
            if (page == null)
                return false;

            var pageType = page.GetType();

            foreach (Type t in AllowedPageTypes) {
                if (pageType.IsAssignableFrom(t) || t.IsAssignableFrom(pageType) )
                    return true;
            }
            return false;
        }
    }
}

And it's simply implemented on the Page property thus:

///
/// Name: Office
/// Description: The office associated with this container.
///
[AllowedPageType(new[] { typeof(OfficePage) }, ErrorMessage = "/errors/OfficePagePageType")]
[PageTypeProperty(
EditCaption = "Office",
HelpText = "The office associated with this container.",
Searchable = false,
UniqueValuePerLanguage = true,
Type = typeof(PropertyPageReference),
SortOrder = 15,
Required=true)]
public virtual PageReference OfficePage {
        get {
                var value = this.GetPropertyValue(page => page.OfficePage);
                return value ?? PageReference.EmptyReference;
            }
        }
}

It's also very easy to do e-mail validation etc. on properties. Like this for example.

[RegularExpression(@"^\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b$", ErrorMessage = "/errors/EmailFormat")]

It's so simple it almost hurts.

SEO lowercase urls, in EPiServer CMS 6, that actually work.

Last week I wrote a short blog post about how to create consistent, lowercase, URLs for your EPiServer CMS 6 site. While there has been some discussion wether or not the casing of the URLs affect indexing, this post is more of a correction on the code in the last post.

I did what I've been telling people not to do. I copied code, and I expected it to work as advertised. Needless to say, it didn't. The general idea was to hook into the UrlSegment.CreatedUrlSegment event, and make sure that the URL was lowercase. It works fine every time you edit a page, and alter the URLSegment property. However, if you go into admin mode, and decide to rebuild all URLs, you're in for a treat. The Rebuilding of URLs doesn't fire the UrlSegment.CreatedUrlSegment event, since it just alters the property on the PageData object, and creates a new version of the page. This circumvents the event model for URLSegements, I'd call it a bug, others might call it a feature, but I leave it up to more intelligent people than me to decide what to do. In the meantime, I've moved my logic to the DataFactory.Instance.SavingPage event, since that is fired every time the page is saved, no matter where you alter the page from. It's in the DataFactory, it's "unfuckwithable".

I copied code, I'm sorry. I will never do it again. Never. At least not without testing the results throughly first :)

Simple validation of propertyvalues in EPiServer CMS 6

Last week I blogged about SEO lowercase URLs, a concept known to many, and I blatantly stole the idea for the post from someone else. Today I'm gonna try to build on the other post, actually providing some input of my own, and show how one can easily do custom property validation, without having to write your own custom property. Having custom properties can be a tedious way to work, when all you essentially have is a string property, or a PageReference. The other post contains the code for hooking into events, so I suggest you get it from there if you have no idea where to put this.

First, we hook into the event DataFactory.Instance.SavingPage.


void Instance_SavingPage(object sender, PageEventArgs e) {

    // Check if the page is a ReferenceContainer
    if (e.Page is ReferenceContainer) {
        var refContainer = e.Page as ReferenceContainer;
        // Check if the property OfficePage has a value
        if (!PageReference.IsNullOrEmpty(refContainer.OfficePage)) {
            var office = refContainer.OfficePage.GetPage();
            // Check if the page selected in the OfficePage property is of the correct type.
            if (!(office is OfficePage)) {
                e.CancelAction = true;
                e.CancelReason = LanguageManager.Instance.Translate("/errors/validation/properties/OfficePage");
            }
        }
    }

    // Check if the page is an OfficePage
    if (e.Page is OfficePage) {
        var officePage = e.Page as OfficePage;
        // Check if the property ReferenceContainer has a value
        if (!PageReference.IsNullOrEmpty(officePage.ReferenceContainer)) {
            var refContainer = officePage.ReferenceContainer.GetPage();
            // Check if the page selected in the ReferenceContainer property is of the correct type.
            if (!(refContainer is ReferenceContainer)) {
                e.CancelAction = true;
                e.CancelReason = LanguageManager.Instance.Translate("/errors/validation/properties/ReferenceContainer");
            }
        }
    }
}

Basically, we try to get the page from the PageReference property, if it's set, and check if it's of the correct type using functionality in PageTypeBuilder. PageTypeBuilder, I love you.

As always, comments are welcome, maybe I've tried to solve this whole issue the wrong way. Please let me know if I did :)

SEO lowercase urls, in EPiServer CMS 6

Yesterday I read a blog post by Lars Timenes, describing how one could easily generate a bit more friendly URL Segments in EPiServer. (Just an observation, but the EPiServer site hosting the blog post, doesn't seem to use his code.) His example uses the CMS 5 style of hooking into events, so I thought I'd give an example of how to do it in CMS 6 and the new Initialization engine. The back story here, is that sites like Google, sees all lower case URLs, and mixed case URLs as different URLs. Although this can be fixed by adding a <link rel="canonical" /> tag to your markup, I like to have more than one solution to things.

In order to get this to work, you'll need to add references to the EPiServer.Framework.dll and the System.ComponentModel.Composition.dll, both installed with CMS 6 and located in the "C:\Program Files (x86)\EPiServer\Framework\6.0.x.x\bin" directory.

Oh, and as a side note, Yes, I do write all those tedious documentation comments in my production code. It's how I do things. Sorry for being a goody two-shoes.

Edit: After reading Magnus's comment, I added the simple code to detach the event.

Edit: Karl, case doesn't matter to the server, but search engines may or may not (it's entirely up to them, not me), index URLs with different casing as different pages, severly affecting the PageRank of the URL in question. That's why I want my URLs to display consistently, and if I always render them in lowercase, I doubt someone will try to link to my site using a mixed-case URL of their own device.

using System;
using EPiServer;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.Web;

namespace Development {

/// <summary>
/// An <see cref="EPiServer.Framework.IInitializableModule" /> enabling hooking into EPiServer events.
/// </summary>
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class ModuleInitializer : IInitializableModule {


#region IInitializableModule Members
/// <summary>
/// <para>Initializes the Module and attaches custom eventhandlers.</para>
/// <para>Attaches a <see cref="System.EventHandler" /> for the EPiServer.Web.UrlSegment.
/// CreatedUrlSegment event.</para>
/// </summary>
/// <param name="context">The current 
/// <see cref="EPiServer.Framework.Initialization.InitializationEngine"/></param>
public void Initialize(InitializationEngine context) {

UrlSegment.CreatedUrlSegment += 
new EventHandler<UrlSegmentEventArgs>(UrlSegment_CreatedUrlSegment);
}

/// <summary>
/// Makes sure that all URL Segments are always lowercase, for increased SEO performance.
/// </summary>
void UrlSegment_CreatedUrlSegment(object sender, UrlSegmentEventArgs e) {

e.PageData.URLSegment = e.PageData.URLSegment.ToLowerInvariant();
}

/// <summary>
/// Perform pre-loading tasks.
/// </summary>
public void Preload(string[] parameters) {

}

/// <summary>
/// <para>Uninitializes the Module.</para>
/// <para>Detaches the <see cref="System.EventHandler" /> for the EPiServer.Web.UrlSegment.
/// CreatedUrlSegment event added during Initialization.</para>
/// </summary>
/// <param name="context">The current 
/// <see cref="EPiServer.Framework.Initialization.InitializationEngine"/></param>
public void Uninitialize(InitializationEngine context) {
UrlSegment.CreatedUrlSegment -= 
new EventHandler<UrlSegmentEventArgs>(UrlSegment_CreatedUrlSegment);
}
#endregion
}
}

Available for hire from April 1st

Today it became finalized that I will be available for hire from April 1st and on, since Milagro chose not to extend my contract. It has nothing to do with my skills, but is rather an economic issue.

Sad as it may be, it leaves me available for hire, to do Umbraco or EPiServer work, wherever I may be needed. I have several years experience, and am certified, on both platforms. I work well in teams, and solo, and you're really missing out if you don't hire me. If needed, I'll travel for start up meetings etc. So don't hesitate just because I'm Swedish. I'm that good.

There's an online resume-ish site here. And I'll even send you a PDF with more info, if you still have doubts.

I know, cheap trick, but I have a family to support. :)