Archive for tag: EPiServer

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. :)

Notes from the EPiServer Meetup in Stockholm.

Yesterday I was at the EPiServer Meetup, which was kindly hosted by KnowIT. It started off with a VERY interesting talk on XSS/CSRF. After a short break with thai food, there was a presentation on developing EPiServer templates that validate and are SEO friendly.

I'm not going to cover the validation/SEO friendlyness here, not because I believe that what they said was bullcrap, but rather because I believe that their approach doesn't give the client the freedom of choice I think goes hand-in-hand with using a large scale CMS like EPiServer. When a client uses EPiServer, and tires of their development partner, they should be able to switch over-night. Furthermore, development time is, and this is only my personal oppinion, increased, since developers have to build their own post-back mechanisms. But enough on that, what they said mostly made sense :).

XSS, or Cross-Site-Scripting, is when someone injects HTML or a script, into a site. There are two different kinds, Non-persistent/Reflected or Persisted/Stored.

The Reflected approach is when someone exploits a XSS vulnerability, for example, modifies a querystring parameter so that html and/or javascript is rendered directly onto the page. They then spread a link to their modified URL, and anyone logging on there might be subjected to some form of fraud or some other malignant attempt at their data.

The Stored approach is when someone submits, for instance to a forum or blog comment, html and/or javascript that is then stored on the page for other visitors to be subjected to. Like as if a blog comment here would contain html and javascript that would render a login form on every page, but when fileld out, would send that data to a third party. In short, it would suck. Big time.

Now, how to protect yourself?

In short: Don't allow HTML being posted to your forms. Oh, and check for scripts too.

OK. But that won't always do. I know, so here's a few more lengthy pointers.

  1. Enable validateRequest in web.config in system.web/pages - if this isn't an option for you, i.e. EPiServer's editor won't work, consider using it in combination with your <location> segments, only disabling it where absolutely needed.
  2. Clean your output. HTML-Encode any input sent from the user, or use RegEx to clean those tags/attributes that can cause harm. For me anyway, RegEx is awesome, but I can NEVER seem to learn them.
  3. Implement The Microsoft Anti-Cross Site Scripting Library
  4. Evaluate your content, and look for vulnerabilites. Repeatedly.

Here are some useful links for those that want to know more.

 

So, what then is that other abbreviation, CSRF? CSRF, or Cross-Site Request Forgery, is when a malignant website uses valid data from your browser, like hi-jacking a validation cookie, and sends a request with that valid data, in your name. This was a wake-up call for me.

A possible scenario is, when you're out in your sunday best, couch-surfing. You check your mail at Gmail in one tab, and doing some "grown-up's surfing" in another tab. Your browser then has an authorization cookie for Gmail, certifying that you're logged in, and everything is A-OK. The grown-up-site you're watching has some malicious code on it, that tries to send data to Gmail behind your back, and since your browser is authorized, Gmail accepts that incoming request, forged as it may be.

Working around this can be a hassle, but it isn't impossible, here are a few tips.

  1. Implement an ANTI-CSRF HttpModule.
  2. Check the referrer header, and make sure it's your site making the request. It can be found in HttpContext.Request.UrlReferrer.
  3. Make sure that any crossdomain.xml doesn't allow everything. This is a Flash thing, so if you don't have any Flash on your site, you don't need to bother.
  4. Limit the lifetime of authentication cookies.
  5. If you're using ASP.NET MVC, use the Html.AntiForgeryToken() helper in conjuction with the [ValidateAntiForgeryToken] attribute on your post method.

Here are some useful links for those that want to know more.

 

I'll be testing Umbraco in general, and the Blog4Umbraco package specifically, for XSS vulnerabilites in the next few days, I'll keep you posted.

Finally, thanks to Sergio Molero at Concrete IT for an excellent presentation.