fredag den 19. december 2014

Creating media items from a .NET Stream

From time to time, we need to create media items from .NET Stream objects (mostly when a user has uploaded a file).

In this case, this little helper function makes it really easy.

The parentItem is the parent item where them media item should be created below.
The fileName is the name of the file, including the file type (like "test.txt").
The contentStream is the stream containing the file content.
The fileBased bool defines if the mediaItem should be stored in the database or in the filesystem.

public static MediaItem CreateMediaItemFromStream(Item parentItem, string fileName, Stream contentStream, bool fileBased)
{
    if (parentItem == null)
    {
        throw new ArgumentNullException("parentItem", "parentItem must not be null.");
    }

    if (string.IsNullOrEmpty(fileName))
    {
        throw new ArgumentException("fileName must not be empty", "fileName");
    }

    // Just in case forwardslash is used, we replace it with backslash, before we do the test.
    string newMediaItemName = fileName.Substring(0, fileName.LastIndexOf('.')).Replace('/', '\\');

    if (newMediaItemName.Contains("\\"))
    {
        // We end here, if the file is uploaded using an ASP.NET FileUpload control, and the IE setting
        // "Include local directory path when uploading files to a server" is enabled.
        // Then we have to remove everything up till the last backslash, to get the filename.
        newMediaItemName = newMediaItemName.Substring(newMediaItemName.LastIndexOf('\\') + 1);
    }

    MediaCreatorOptions md = new MediaCreatorOptions
    {
        Database = DatabaseHelper.MasterDatabase,
        Destination = string.Format("{0}/{1}", parentItem.Paths.FullPath, ItemUtil.ProposeValidItemName(newMediaItemName)),
        FileBased = fileBased,
        IncludeExtensionInItemName = false,
        Versioned = false
    };

    using (new SecurityDisabler())
    {
        return MediaManager.Creator.CreateFromStream(contentStream, fileName, md);
    }
}

This also handles files being uploaded from Internet Explore, when it sends it's entire file path instead of just the file name.

torsdag den 18. december 2014

The case with the ASP.NET worker process being terminated from time to time

So, the other day we ran into an interresting issue, where a customers solution was crashing the entire worker proces.

Every time this happened, a few seconds before the Sitecore log file ended - this error showed up:

5784 10:41:07 ERROR Unhandled exception detected. The ASP.NET worker process will be terminated.
Exception: System.UnauthorizedAccessException
Message: The current user does not have write access to this item. User: sitecore/Anonymous, Item: Home ({71B4ACD7-226F-47A6-913D-86A40C0F9EBC})
Source: Sitecore.Kernel
at Sitecore.Data.Items.ItemEditing.BeginEdit()
at Sitecore.Data.Engines.TemplateEngine.<>c__DisplayClass3.b__0()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()

It seem to be related to publishing some item based on a certain template.
After a while, with the help from Sitecore Support, we found the issue - it seemed one of the templates (the one the items being published was based on) was inheriting the same template twice.

This is not normally possible (you cannot select the same item multiple times in a multilist field in Sitecore) - so the field's raw value had to have been manipulated somehow.

After removing the double inheritance, it started working without any issues.
So if you run into this issue, take a look at your template inheritance - the problem might be hidden there :-)

tirsdag den 11. november 2014

Why you shouldn't just hardcode X-UA-Compatible meta tag in your layout

Sometimes, people either hardcode the X-UA-Compatible meta tag in the <head> section of the page, or in the web.config file as a custom header.

I have seen this a few times now, and every time it breaks something (especially with versions of Sitecore before 7.2) .

The problem is, that doing it this way, you prevent Sitecore from changing it when it is needed for it's own tools to work (like the page editor).

Instead, what you should do, is add the following code to the Page_Load function of your layout:

if (Sitecore.Context.PageMode.IsNormal)
{
    Response.AddHeader("X-UA-Compatible", "IE=edge,chrome=1");
}

Now, the X-UA-Compatible indication is still sent to the browser, which knows how to handle it (since it can be told this both using headers and meta tags) - but both Preview and the Page Editor works like they should, since it doesn't get set.

onsdag den 5. november 2014

Change item resolving to only find items that is in the current language

When developing solutions that has multiple languages, it is almost impossible to end up in a situation, where some of the content pages isn't translated to all the languages the website is avaliable in.

This ends up with some weird pages, that might just show stuff like "$name" in certian places (if that is what is written in the Standard Values for that field in that language).

To fix this issue, so the frontend behaves as if the page doesn't even exist in the frontend, if it isn't there in the current language, we create a small HttpRequest processor.

So, first create a new class in Visual Studio, and enter the following code:

public class EnsureItemLanguage : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {
        if (args == null)
        {
            throw new ArgumentNullException("args");
        }

        if (Sitecore.Context.Item == null || Sitecore.Context.Database == null)
        {
            return;
        }

        if (!Sitecore.Context.PageMode.IsNormal)
        {
            return;
        }

        if (Sitecore.Context.Database.Name != "master" && Sitecore.Context.Database.Name != "web")
        {
            return;
        }

        if (!Sitecore.Context.Item.Paths.FullPath.StartsWith("/sitecore/content", StringComparison.InvariantCultureIgnoreCase))
        {
            return;
        }

        if (!DoesItemExistInLanguage(Sitecore.Context.Item, Sitecore.Context.Item.Language))
        {
            Sitecore.Context.Item = null;
        }
    }

    private static bool DoesItemExistInLanguage(Item item, Language language)
    {
        if (item == null)
        {
            throw new ArgumentNullException("item");
        }

        if (language == null)
        {
            throw new ArgumentNullException("language");
        }

        return Array.Exists(item.Versions.GetVersions(true), version => version.Language == language);
    }
}

This resets the Sitecore.Context.Item to null if the current item does not exist in the current language.

To make this work, you must also create an include file, to make this load after the normal ItemResolver.

To do this, create an include config file, and add the following to it:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="Namespace.Classname, Assembly" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"/>
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

Where Namespace, Classname and Assembly has been replaced with your own values.

That is all that is needed to do this :-)

Unable to close the rich text editor in page editor mode after applying Chrome 37 patch from Sitecore

A while ago, Google updated Chrome so it no longer supports the showModalDialog javascript function, which causes it to break Sitecore on Chrome (other browsers are following along soon, so if you aren't using Chrome, you still have to think about this issue).

To see if you are affected, please take a look at this knowledgebase article, and apply it if it is needed for your Sitecore version.

When the patch have been applied however, there is a new issue that is introduced, which is that you can not close the rich text editor in the page editor by clicking the buttons.

You can edit and save just fine, but the window does not close when it should (you can use the cross in the top right corner, but that is not userfriendly to have to force the users to do this).

There is a small fix for this issue:
  1. Open the “[Website]/sitecore/shell/Controls/Rich Text Editor/EditorPage.js” file.
  2. Replace the following line in the “scCloseEditor” method:
window.close();

With:

if (top._scDialogs.length != 0) {
       top.dialogClose();
} else {
       window.close();
}

This fixes the issue, so it once again is possible to have the dialogs closing when they should.

onsdag den 22. oktober 2014

Working around Sitecore ECM 2.1 creating newsletters with identical paths in the content tree

When using the ECM 2.1 module to create newsletters in Sitecore, there seems to be an unfortunate problem.

When the newsletter is created, it is done by creating an item in Sitecore, named after the branch it is created from - and if not changed, it will stay with this name.

This is a problem, if you are inserting controls in the newsletter, that uses the path of the newsletter as it source, since it then will not be able to find the right datasource.

Already, newsletters are placed into folder, that is named after their creating timestamp, so that helps a bit on the problem - if there only is one newsletter in each folder with the same name.

But since ECM doesn't know that it should do that, there is a workaround for this - it's an ugly hack, but it works.

Create an include file, and name it so it gets loaded after Sitecore.EmailCampaign.config (like this: Sitecore.Support.422404.config - named after the support issue I got this workaround in).

Paste this into the config file, and same it:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="MaxMessageFolderSize">
        <patch:attribute name="value">1</patch:attribute>
      </setting>
    </settings>
  </sitecore>
</configuration>

Thats it - then it creates a new folder for each newsletter, which ensures there never is duplicate paths.

mandag den 29. september 2014

Not able to change field values after installing a package with the template having it's fields changed.

Okay, so this is an interresting issue.

First some background - when changing a field on a template in Sitecore to/from "Shared", the value gets moved between the SharedFields table and the UnversionedFields/VersionedFields tables in the database.

This is also why it warns, that changing this might take a while (and also result in data loss, but thats not because of this issue).

If you have items created from a template, where a field is shared, and you install a package that has a new version of the template, where the field is no longer shared, things will break.

In this case, if the field was changed to "Non-shared" using the package installer, you will not be able to change the value on the items based on this template.

This is likely because the value is not moved from the SharedFields table, like it would be if the field was changed from the content editor - resulting in your changes being saved to the right table, but everything being read from the SharedFields table, which is never changed.

You can test this yourself, by following these steps:

  1. Install two identical Sitecore solutions - I'll refer to them as A and B
  2. In A, create a template with a simple text field, make the text field shared.
  3. In A, create a few items based on this template, and fill out the field with some values, and save the items.
  4. In A, make a package with the template and the items.
  5. In B, install the package.
  6. In A, change the template, so the field is no longer shared.
  7. In A, create a package with just the template, and not the items.
  8. In B, install the package, and overwrite the template, template section and template field items.
  9. In B, try and change the value on some of the items, and notice how they will not save when clicking save.
I contacted Sitecore Support with this, and they found sent me a workaround that fixes the issue.

So if you are having this issue, contact Sitecore Support, and ask for support dll #310642 .

Do note, that this has to be installed before the package gets installed, since that is when the fix is applied, that moves the values to the right tables.

onsdag den 3. september 2014

A small processor to make sure page URL's are SEO friendly

If you have been in the business of creating web solutions for a while, you have most likely been asked about what to do to improve the SEO-friendliness of the current project you are working on.

Since SEO is not always clearly defined, I'll not go into details on everything that can be done, but instead pinpoint two things.

These two URL's are not the same, when being indexed by a search engine:

http://www.test.org/Some/Page
http://www.test.org/Some/Page/

Also, there two links are not the same:

http://www.test.org/Some/Page
http://www.test.org/Some/PaGe

Since users are able to type URL's, we cannot ensure that they always type them the way we want them to - but we can force the browser (and search engines) to act like we want.

One way (there are most likely several other ways) to do this, is to create a small processor, that looks at the incoming URL, and changes it a bit if needed.

So here we go - create a new class in Visual Studio, and enter the following code:

public class SeoUrlProcessor : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {
        if (args == null || args.Context == null)
        {
            return;
        }

        if (Sitecore.Context.Item == null)
        {
            return;
        }

        if (!Sitecore.Context.PageMode.IsNormal)
        {
            return;
        }

        // Extend this list with other sites you find will be broken by this.
        if (Sitecore.Context.Site.Name == "shell" || Sitecore.Context.Site.Name == "publishing")
        {
            return;
        }

        bool urlHasBeenChanged = false;
        string incomingUrl = args.Context.Request.RawUrl;

        // In case language embedding is used, this is needed to make sure incomingUrl contains the language.
        // Otherwise, the comparing of the URL's will not match up.
        if (!string.IsNullOrEmpty(args.Context.Request.Url.Query))
        {
            incomingUrl = incomingUrl.Replace(args.Context.Request.Url.Query, string.Empty);
        }

        UriBuilder builder = new UriBuilder(args.Context.Request.Url)
        {
            Path = incomingUrl
        };

        // This works like a one-time flip-bit, once it has been set to true, it stays as true.
        urlHasBeenChanged |= HandleTrailingSlash(builder);
        urlHasBeenChanged |= HandleCasing(builder);

        if (urlHasBeenChanged)
        {
            RedirectToUrl(args, builder.ToString());
        }
    }

    private static bool HandleTrailingSlash(UriBuilder builder)
    {
        if (!builder.Path.EndsWith("/"))
        {
            return false;
        }

        builder.Path = builder.Path.TrimEnd('/');

        return true;
    }

    private static bool HandleCasing(UriBuilder builder)
    {
        string destinationUrl = LinkManager.GetItemUrl(Sitecore.Context.Item);

        if (!builder.Path.Equals(destinationUrl, StringComparison.InvariantCultureIgnoreCase))
        {
            return false;
        }

        if (builder.Path == destinationUrl)
        {
            return false;
        }

        builder.Path = destinationUrl;

        return true;
    }

    private static void RedirectToUrl(HttpRequestArgs args, string url)
    {
        args.Context.Response.Clear();
        args.Context.Response.Headers.Add("Location", url);
        args.Context.Response.Status = "301 - Moved Permanently";
        args.Context.Response.StatusCode = 301;
        args.Context.Response.End();

        args.AbortPipeline();
    }
}

Save the file.

Let me explain the HandleCasing function, since that can be a bit confusing.

First we compare the URL's without looking at casing at all - if they aren't identical, we can be pretty sure the user has hit some kind of aliasing, since they are standing on this item, but the incoming URL is totally different - in that case, we don't wanna redirect the user anywhere.

Then, if they are identical, we compare them again - this time look at if the are completly identical - and if the aren't, that means that the casing is wrong somewhere (like the user typing "products" instead of "Products").

In that case, we replace the Path part of the URL with the one Sitecore's link manager generated, which ensures that every view of the page has the URL typed as it is inside Sitecore.

Finally, you just need to create an include file, for Sitecore to run this.
So create an XML file, and paste the following test into it, replacing Namespace, Classname and Assembly with your own values:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="Namespace.Classname, Assembly" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"/>
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

Now you are done. :)

Try accessing a few pages, where you try adding a tailing slash to the URL, and where you try mixing the casing differently than inside Sitecore.

onsdag den 27. august 2014

Upgraded to Visual Studio 2013, and now your Sitecore project can no longer find namespaces?

It seems like Visual Studio 2013 does things a bit differently.

This is not directly related to Sitecore - but it is related to having a web.config inside your project folder.

The problem seems to be, that Visual Studio 2013 starts to parse web.config, and if it finds any errors, or any mismatches (like a web.config that does not match the version of Sitecore, or some ASP.NET references doesn't match) - Visual Studio will start marking every namespace as invalid, even though intellisense works fine.

If this happens to you - try and renaming web.config to confirm that this is what is causing the problem for you.

If it is, try and use a clean web.config from the version of Sitecore you are using, to see if that works.

If it works - compare the two versions of web.config and see what is different, and work your way from here, untill you find what is causing this.

A hint might be to look at assemblybindings ;-)

onsdag den 20. august 2014

Disable indexing of an entire site using HTTP headers

Sometimes a customer wants to prevent an entire website from being indexed (like when they are creating content before the site goes live).

If the site is hosted on the same Sitecore instance as other websites, using robots.txt is not an option.

So here is another way, that is better, and that isn't an all-or-nothing approach.

First, take the template defining your frontpage item, and add a new checkbox field to this template - call the field "Not Indexable".

Next, create a new class in Visual Studio, and add the following code:

public class NotIndexableProcessor : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {
        if (args == null || args.Context == null)
        {
            return;
        }

        Item homeItem = Sitecore.Context.Database.GetItem(Sitecore.Context.Site.StartPath);

        if (homeItem == null)
        {
            return;
        }

        CheckboxField notIndexableField = homeItem.Fields["Not Indexable"];
        if (notIndexableField == null || !notIndexableField.Checked)
        {
            return;
        }

        args.Context.Response.Headers["X-Robots-Tag"] = "noindex, nofollow";
    }
}

Now you just need to create an include file, for Sitecore to run this.
So do this in your favorite XML editor, and add the following text to it:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="Namespace.Classname, Assembly" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"/>
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

Replace the Namespace, Classname and Assembly, so they match your setup.

Now, if this checkbox is checked, and an item below it is accessed, the X-Robots-Tag header will be added to the response, and search engines will not index anything on it.

Creating a new item with a specific ID instead of an auto-generated one.

Normally, when you create an item from code, you find the parent item, and use the Add method on this item to create the child.

This is the recommended way to create items, however - it misses one crucial thing, which is defining what ID the newly created item has.

The logical question to ask is - why would you want to deside which ID the item has?
The answer is - alot of times when you import data from something where the unique ID already is a GUID.

The reason for this is, that is makes it very fast to lookup if the item already exists, and easy to handle cross-references between items.

Also, if you are migrating items from one Sitecore instance to another by code (as in - creating new items, not copying the old items over) - keeping the ID's might be a good idea, to again make cross-references work.

Enough walk you say? Okay, here is how to do it:

// The name of the item to be created.
string newItemName = "My New Item";

// Some way to get the parent item.
Item parentItem = GetItemParent();

// The ID of the template to create the item from - can be a branch ID.
ID templateID = new ID("{1618FA40-8E57-4018-968A-5996E4952D93}");

// The ID of the item to be created.
ID newItemID = new ID("{9AAFC1F7-F276-4651-ADD2-6C9ED6F1B9E0}");

// Should security for the operation be checked?
SecurityCheck checkSecurity = SecurityCheck.Enable;

// Now we create the item.
Item newItem = ItemManager.CreateItem(newItemName, parentItem, templateID, newItemID, checkSecurity);

Now we have created an item, with the ID defined in newItemID.

Pretty simple, don't you think?

Require HTTP / HTTPS for certain pages with only a single processor, a base template and a few simple items.

From time to time, customers would like to define, that a certain page should be viewed using HTTPS, while the rest of the website remains as HTTP.

There are many ways to do this - however, I find this solution to be the most simple, since it doesn't require alot of custom code, and once implemented, it can be reused without having to think about it.

Some items

The first step is to create a few dummy items, that is used to define the states that can be selected in the dropdown box added to the base template.

To do this, go into the /sitecore/System/Modules folder, and create a new folder here called "Require SSL Modes"

Right click this folder, and select "Insert from Template" - now find the "Standard Template" template, and insert an item from this template - call the item "Inherit".

Duplicate this item three times, and call the three copies "Leave Default", "Force HTTP" and "Force HTTPS".

Now you should have 4 items in the folder.
The reason for using the "Standard Template" instead of our own template is, that we don't need any fields on these, so just creating an extra template just for this seems redundant.

A template

The next thing to do, is to create a base template, that has the field used on pages to define if a page should be using this or not.

So go into the /sitecore/Templates folder, and create a template where it makes sense in your structure.

On this template, add a single field, call it "HTTP Mode" - set it's type to Droplist, and define it's source to "/sitecore/System/Modules/Require SSL Modes" - then save the template.

Now make your page templates inherit from this template, so they have the field.

A simple processor

Now we are getting to the fun part - fire up Visual Studio, and create the following C# class:

public class RequireSslProcessor : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {
        if (Sitecore.Context.Item == null)
        {
            return;
        }

        if (args == null || args.Context == null)
        {
            return;
        }

        // First we set the current item as our starting point.
        Item currentItem = Sitecore.Context.Item;

        // We keep going until we haven't found anything.
        while (currentItem != null)
        {
            // Also, the current page need to inherit our field.
            Field httpModeField = currentItem.Fields["HTTP Mode"];
            if (httpModeField == null)
            {
                return;
            }

            Uri url = args.Context.Request.Url;
            Uri newUrl;

            // If one of the force modes, and the other one is present, we rewrite the url scheme.
            switch (httpModeField.Value)
            {
                case "Force HTTP":
                    if (url.Scheme.Equals("http", StringComparison.InvariantCultureIgnoreCase))
                    {
                        return;
                    }

                    newUrl = RewriteUrlScheme(url, "http");

                    break;
                case "Force HTTPS":
                    if (url.Scheme.Equals("https", StringComparison.InvariantCultureIgnoreCase))
                    {
                        return;
                    }

                    newUrl = RewriteUrlScheme(url, "https");

                    break;
                case "Inherit":
                    currentItem = currentItem.Parent;
                    continue;
                default:
                    return;
            }

            // And then we redirect, and stops the pipeline.
            args.Context.Response.Clear();
            args.Context.Response.Headers.Add("Location", newUrl.ToString());
            args.Context.Response.Status = "301 - Moved Permanently";
            args.Context.Response.StatusCode = 301;
            args.Context.Response.End();

            args.AbortPipeline();
            return;
        }
    }

    private static Uri RewriteUrlScheme(Uri url, string newScheme)
    {
        // First we change the scheme.
        UriBuilder newUriBuilder = new UriBuilder(url)
        {
            Scheme = newScheme
        };

        // Then we change the port, since changing the scheme doesn't change the port for some reason.
        switch (newScheme)
        {
            case "http":
                newUriBuilder.Port = 80;
                break;
            case "https":
                newUriBuilder.Port = 443;
                break;
        }

        return newUriBuilder.Uri;
    }
}

Save the class, compile the project and make sure the DLL file is in the bin folder of the Sitecore installation.

An include file to wrap it up

The last thing to do, is to create an include file, so Sitecore knows that it should use the processor.

Open your favorite XML editor and create a new XML file - call it Modules.RequireSSLModes.config .

Enter this into the file, replacing the Namespace, Classname and Assembly parts so they match your project:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="Namespace.Classname, Assembly" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"/>
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

Then save the config file.

To test this, make sure the website is configured to use both HTTP and HTTPS inside IIS.
Then create a few pages from items that inherits this field, and change their setting to different HTTP Mode values, and navigate around between them.

It is really that simple :)

onsdag den 11. juni 2014

Attaching a Mediaitem to an email from code

This time, we will be looking at how to attach a Mediaitem to an email from code, which can be useful from time to time.

Doing this is really simple - here is how to do it:

public static Attachment CreateMailAttachment(MediaItem mediaItem)
{
    if (mediaItem == null)
    {
        throw new ArgumentNullException("mediaItem", "mediaItem must not be null");
    }

    ContentType contentType = new ContentType(mediaItem.MimeType);
    Attachment attachment = new Attachment(mediaItem.GetMediaStream(), contentType);

    attachment.ContentDisposition.FileName = string.Format("{0}.{1}", mediaItem.Name, mediaItem.Extension);
    return attachment;
}

First we have to figure out the mime type of the attachement, this is made quite easy, since there is a property for this on the Mediaitem itself - so we just create a ContentType object, and use it, together with the stream containing the file data, to create an Attachment.

Then we create the filename, so it has the original filename, and return it.

I know it's not much, but it can be quite helpful when having to generate emails with files attached.

fredag den 30. maj 2014

Fixing a database with too many orphaned blobs

Hello again,

This time, I'm gonna tell you about an interresting problem we ran into this week.

We had a customers solution, that contained around 6GB worth of media items (I know that, because we just loaded them into the solution using serialization).

Things where working fine - we removed them a few time to test our import tool (using the little known dbbrowser gem in Sitecore), and finally we where ready to backup the database and move it to it's final destination.

Entering the folder on the MSSQL server, looking at the .mdf file, showed that the database was taking about 25GB of diskspace.....

So clearly something was wrong - so tried shrinking the database, but it only said around 200MB of free space in the database, so that's not gonna work.

The next thing to try, was running the database cleanup tool inside Sitecore, which ran for a while, and then bugged out with a timeout exception.
This however indicated that whatever it was doing, it seems to be doing it on quite alot of data.

It seems the problem is, that this tool fails if there is too many orphaned blobs in the database - orphaned blobs can happen in a few cases - like removing media items using the dbbrowser it seems. (Overwriting media folder using the package installer might also cause this).

Talked to a co-worker, which had this timeout problem before, and got a SQL script from Sitecore Support, that does what the cleanup tool does, but works better, since it is run from inside SQL Server Management Studio.

Tried running it, and after around 90 minutes, it was done, and the database could be shrinked down to 7GB - weee.

However, the script had one problem, it was just standing there running, not really posting any info on what it was doing, how far it had come etc.

So, I took the SQL script, made a small C# program that does the same thing, but gives a percent indicator for how far it has come, and how long it expects it to take to finish running.

The best part of this is, that I'm allowed to share it with you guys - so here it is, my little tool, that fixes the problem in a friendly way.

You are welcome to disassemble it, if you like me are unsure about just running code downloaded from the internet.

onsdag den 14. maj 2014

Templates and inheritance of layouts... And why you should never do it

Okay, it's been a while since my last blog post, but I've been busy with other stuff.

Anyway, the other day, I ran into an interresting problem on one of the solutions we have made for a customer a while back.

It seems like that if you are inheriting from templates that has a layout (and sublayouts) defined, you might end up with a really weird acting Presentation Details window.

Here is what we had - these four templates:

Article Main Site
Article Sub Sites
News Article Main Site
News Article Sub Sites

All four templates had their presentation details set on their standard values, like they should.
The Main Site templates was using the layout "Main Layout" and the Sub Sites templates was using the "Sub Sites Layout" layout.

Now comes the problem - the customer was wondering how all the sudden, when they created news articles on their subsites, the layout of the news article was using the layout from the main site.

So, what to do? Well ofcouse, you just open the Sitecore backend, navigate to one of the news articles from a subsite, that acts weird, and takes a look at the Presentation Details, to see what is defined there.... To my surprise - it shows the "Sub Sites Layout" as the layout being used on this item.

So now we have an item showing layout A in the backend, but using layout B when rendering... Okay, maybe something hasn't been published correct, so I took a look at the web database - looks the same...

Back to the master database, going to the News Article Sub Sites template, and then to it's standard values - looking at Presentation Details here, everything looks correct... So the item is showing the wrong layout - so back to the news folder, and create a new test news item, so we know that layout hasn't been changed.... That one is also broken!

As you might imagine by now, I was getting pretty puzzled by this, untill somebody suggested that maybe it was a problem with inheritance...

He was right - it turned out, that "News Article Main Site" was inheriting from "Article Main Site", which seemed to work, since they where supposed to have the same presentations, so no problem showing up there.
However, "News Article Sub Sites" was inheriting from "Article Main Site", which meant that it inherited totally different presentations, resulting in this weird behaviour.

The solution was to switch the inheritance to inherit from the right template.

This is (one of) the reason that you should never inherit from a template with defined Presentation Details - it will break badly.

Instead, what you should do, is create a template for each field you want to have on your templates, and then inherit the fields from these templates (so you only have to define them once) - and then copy Presentation Details from template to template - this means that you have maintain them for each template, but thats better than having to debug this problem, and trying to clean up after it.

torsdag den 20. marts 2014

Sitecore 7.2, Media picker, The core database and alot of headaches

Okay, this is an interresting bug.

It seems like the new Media picker in Sitecore 7.x (the SPEAK version) has a bug - you can trigger it this way:

1: Create a template with an image field.
2: Set the source property of the field to a folder inside the media library.
3: Create an item based on this template, and click Browse on the image field.

Notice that no images from the folder is displayed.

To workaround this, the folder structure should be published to the core database (this is the real bug)......

Now, this workaround causes big problems with Sitecore 7.2, since the publishing framework has been changed, which means adding the core database as a publishing target is dangerous.

Here is a simple guide to trigger this problem:

1: Install clean Sitecore 7.2 initial release
2: Create "core" publishing target
3: Click the "Publish Item" button on the Core item
4: Select the /sitecore/layout/Layouts item
5: Click the "Publish Item" button on the Layouts item.
6: Press F5 to reload Sitecore
7: Open the content editor - you will now see "The layout cannot be found".
8: Open /sitecore/admin/dbbrowser.aspx , open the core database and see that the Layouts item no longer has any children.

This means, that if the Layouts (there are other items that is right as dangerous) item gets published to the core database - every layout defined the core database is wiped, and Sitecore is completely broken.

So, if you are using Sitecore 7.x (which you should, since it is great!), here is a better workaround untill the image picker can get fixed.

Create an include file, give it a name, os it gets loaded after Sitecore.Speak.config (Like Support.406666.config) (406666 is the issue number for this issue), and add this to it:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <overrideXmlControls>
      <override xmlControl="Sitecore.Shell.Applications.Media.MediaBrowser" with="/sitecore/client/applications/Dialogs/SelectMediaDialog">
        <patch:delete />
      </override>
    </overrideXmlControls>
  </sitecore>
</configuration>

This changes the media picker, so it reverts to using the old one, that works.

fredag den 14. marts 2014

Fixing Webforms for Marketeers saving dates in different formats

We had an issue a while ago, with a customer where their webforms started saving dates in different formats in the database, which caused the exported data to be mangled.

Sitecore Support found the solution - it seems like the DatePicker control has a bug, where the DatePicker is missing the correct adapter.

The solution is simple - create your own DatePicker class, that extends the builtin DatePicker class, like this:

[Adapter(typeof(DateAdapter))]
public class DatePicker : Sitecore.Form.Web.UI.Controls.DatePicker
{

}

Compile the code, and then go to this item in the master db: "/sitecore/system/Modules/Web Forms for Marketers/Settings/Field Types/Simple Types/Date Picker"

Here you change the Assembly and Class fields to match this class, save the item and publish it.

Done! - really, that is all there is to it - do this, and now your dates show up correct.

mandag den 3. marts 2014

Adding new renderings to an item from code

Starting with Sitecore 6.4 and forward, renderings in Sitecore is saved as deltas (differences between the standard values and the "full" rendering) on the item.

This means, that we need to do some work if we want to read, change and save the renderings.

Here is how to both read the renderings, and how to save the deltas back to the item:

public static IEnumerable<RenderingDefinition> GetRenderings(Item item, DeviceItem device)
{
    string xmlLayout = XmlDeltas.GetFieldValue(item.Fields[FieldIDs.LayoutField], XmlDeltas.WithEmptyValue("<r />"));

    LayoutDefinition layoutDefinition = LayoutDefinition.Parse(xmlLayout);
    DeviceDefinition deviceDefinition = layoutDefinition.GetDevice(device.ID.ToString());

    return deviceDefinition.Renderings.ToArray().Cast<RenderingDefinition>();
}

By using the GetFieldValue on the XmlDeltas class, we get the combined layout from the standard values combined with the delta.

Now, to be able to add a new definition to the item, here is what you have to do:

public static void InsertRendering(Item item, DeviceItem device, int index, RenderingDefinition rendering)
{
    string xmlLayout = XmlDeltas.GetFieldValue(item.Fields[FieldIDs.LayoutField], XmlDeltas.WithEmptyValue("<r />"));

    LayoutDefinition layoutDefinition = LayoutDefinition.Parse(xmlLayout);
    DeviceDefinition deviceDefinition = layoutDefinition.GetDevice(device.ID.ToString());

    deviceDefinition.Insert(index, rendering);

    using (new EditContext(item))
    {
        XmlDeltas.SetFieldValue(item.Fields[FieldIDs.LayoutField], layoutDefinition.ToXml());
    }
}

Here we get the definition for the specified device, and just insert the rendering into the passed index, moving the other renderings down the list.

Finally we edit the item, and tells it to set the value of the layout field on the item to the xml defining the entire layout.

The secret is in how SetFieldValue works, since it, behind the scenes, generates the delta between the standard values and the passed xml, and only saves that to the item.

Using this as the base, will allow you to change the renderings, since you can use the same logic to create a ChangeRendering function.

torsdag den 27. februar 2014

Rendering controls from a placeholder on another item

From time to time, it makes sense to render the content that is defined on another item, as if it was defined on the item the user is viewing.

To do this, we have created an custom placeholder, that can be inserted like any other ASP.NET control, and then configured to get the renderings from somewhere else.

First we need to define a base class for all user controls inserted on a control, to both help getting the datasource, and being able to change it from outside of the control itself.

This can also be done for webcontrols, but I'll leave that to you to figure out how to do that :)

Here is how the base class looks:

public abstract class BaseSublayoutUserControl : UserControl
{
    private Item _datasourceItem;

    private NameValueCollection _renderingParameters;

    protected Item Datasource
    {
        get
        {
            if (_datasourceItem != null)
            {
                return _datasourceItem;
            }

            try
            {
                Sublayout sublayout = (Sublayout)Parent;
                if (!string.IsNullOrEmpty(sublayout.DataSource))
                {
                    _datasourceItem = Sitecore.Context.Database.GetItem(sublayout.DataSource);
                }
            }
            catch (NullReferenceException)
            {
                _datasourceItem = null;    
            }
            catch (InvalidCastException)
            {
                _datasourceItem = null;    
            }
            catch (InvalidOperationException)
            {
                _datasourceItem = null;
            }

            return _datasourceItem;
        }

        set
        {
            _datasourceItem = value;
        }
    }

    protected NameValueCollection RenderingParameters
    {
        get
        {
            if (_renderingParameters != null)
            {
                return _renderingParameters;
            }

            _renderingParameters = string.IsNullOrEmpty(Attributes["sc_parameters"]) ? new NameValueCollection() : HttpUtility.ParseQueryString(Attributes["sc_parameters"]);

            return _renderingParameters;
        }
    }

    internal void ForceDatasourceItem(Item newDatasource)
    {
        _datasourceItem = newDatasource;
    }

    internal void ForceParameters(string parameters)
    {
        Attributes["sc_parameters"] = parameters;
    }
}

Here both the parameters and the datasource is exposed, so it is easy to get them from the control.
Then every usercontrol that is to be inserted using the page editor should just inherit this class instead of the normal UserControl class, and all is fine.

Now that we have the base class done, we need to implement the placeholder - this requires two classes, the placeholder itself, and what is known as a ControlBuilder, which makes it possible to configure how the control behave when inserted.

Here is the code for the control builder class:

public sealed class RemoteLayoutRendererControlBuilder : ControlBuilder
{
    public override bool AllowWhitespaceLiterals()
    {
        return false;
    }
}

Then, we just need to control that does all the work:

[ControlBuilder(typeof(RemoteLayoutRendererControlBuilder))]
[ToolboxData("<{0}:RemoteLayoutRenderer runat=\"server\" />")]
public class RemoteLayoutRenderer : Control
{
    public RemoteLayoutRenderer()
    {
        Device = Sitecore.Context.Device;
    }

    public Item Item { get; set; }

    public string PlaceholderKey { get; set; }

    public DeviceItem Device { get; set; }

    protected override void CreateChildControls()
    {
        base.CreateChildControls();

        if (Item == null || string.IsNullOrEmpty(PlaceholderKey))
        {
            return;
        }

        if (string.IsNullOrEmpty(Item[FieldIDs.LayoutField]))
        {
            return;
        }

        IEnumerable<RenderingReference> renderings = from rendering in Item.Visualization.GetRenderings(Device, false)
                                                        where rendering.Placeholder.EndsWith(PlaceholderKey, StringComparison.InvariantCultureIgnoreCase)
                                                        select rendering;

        int i = 0;

        foreach (RenderingReference rendering in renderings)
        {
            Control control;

            try
            {
                switch (rendering.RenderingItem.InnerItem.TemplateName)
                {
                    case "Sublayout":
                        control = GetSublayout(rendering, i++);
                        break;
                    // Hint - here might be a good place to handle webcontrols ;-)
                    default:
                        continue;
                }
            }
            catch (TargetInvocationException)
            {
                continue;
            }
            catch (InvalidCastException)
            {
                continue;
            }

            if (control != null)
            {
                Controls.Add(control);
            }
        }
    }

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);

        Load += RemoteLayoutRendererLoad;
    }

    private void RemoteLayoutRendererLoad(object sender, EventArgs e)
    {
        EnsureChildControls();
    }

    private BaseSublayoutUserControl GetSublayout(RenderingReference rendering, int controlIndex)
    {
        if (string.IsNullOrEmpty(rendering.RenderingItem.InnerItem["Path"]))
        {
            return null;
        }

        BaseSublayoutUserControl control = Page.LoadControl(rendering.RenderingItem.InnerItem["Path"]) as BaseSublayoutUserControl;

        if (control == null)
        {
            return null;
        }

        if (!string.IsNullOrEmpty(rendering.Settings.DataSource))
        {
            control.ForceDatasourceItem(Sitecore.Context.Database.GetItem(rendering.Settings.DataSource));
        }

        if (!string.IsNullOrEmpty(rendering.Settings.Parameters))
        {
            control.ForceParameters(rendering.Settings.Parameters);
        }

        control.ID = ID + "_dynamic_" + controlIndex;

        return control;
    }
}

Now we can just insert this like any other ASP.NET control, and define which placeholder to load the controls from, and then set the Item property to the item they are defined on.

As long as the user controls inherit from our base class, we can configure their datasource and parameters, so they don't know that they have been pulled in somewhere else.

onsdag den 19. februar 2014

Create a custom Sitecore cache

I have been experimenting with creating a custom Sitecore cache, to be able to cache the result of some calculations that happens all the time.

In this case, we happen to quite often lookup which of the sites defined in Sitecore, that matches a certain criteria (has certain sub items).

In solutions with many sites, this quickly starts to slow the solution down - but there is a simple solution, which is caching the result :)

So first, we have to implement the cache, which requires two classes - the cache (called SiteCache) and a cache manager (called SiteCacheManager).

Here is the code for the SiteCache:

internal class SiteCache : CustomCache
{
    public SiteCache(string name, long maxSize) : base(name, maxSize)
    {
    }

    public bool IsSiteInCache(string siteName)
    {
        object obj = GetObject(siteName);

        return obj != null;
    }

    public void AddSiteToCache(string siteName)
    {
        SetObject(siteName, true, sizeof(bool));
    }
}

And here is the code for the SiteCacheManager

internal static class SiteCacheManager
{
    private static readonly SiteCache Cache = new SiteCache("SiteCache", StringUtil.ParseSizeString("100KB"));

    public static bool IsSiteInCache(string siteName)
    {
        return Cache.IsSiteInCache(siteName);
    }

    public static void AddSiteToCache(string siteName)
    {
        Cache.AddSiteToCache(siteName);
    }

    public static void Clear()
    {
        Cache.Clear();
    }
}

The way it works is pretty simple, since the manager is static, as soon as Sitecore starts, it creates a new instance of the SiteCache cache.
Then, to use this, we create a simple function to test if sites are valid:

public bool IsSiteValid(string siteName)
{
    if (SiteCacheManager.IsSiteInCache(siteName))
    {
        return true;
    }

    bool siteValid = RunSiteTest(siteName);

    if (siteValid)
    {
        SiteCacheManager.AddSiteToCache(siteName);
    }

    return siteValid;
}

This way, we always look in the cache first, and if the site isn't there, we do the normal tests, and if the result is that the site is valid, we add it to the cache, so we can avoid the test the next time.

The reason we do this, is because the cache might have been flushed, so we need to always do the check if the site is missing from the cache.

So this works best, if most of the things you test on is valid - if most of the sites in the solution for this is returning false from the test, the amount of time saved will be minimal.

mandag den 13. januar 2014

Presenting at the Danish Sitecore Developer Group meeting

Hey,

Next wednesday, I'll be presenting how we at the company I work at (DIS/PLAY A/S), have streamlined our Sitecore development process.

So in case you happen to be in Copenhagen, and want to hear about this, please drop by :-)

You can read more about it here:
http://www.meetup.com/Danish-Sitecore-Developer-Group/events/134529942/

This is also why there hasn't been any updates on this blog for a little while, since I've been busy preparing.