Convention for EPiServer Find to ignore large files

EPiServer Find has a limit when indexing files (last I heard this was 50MB), but nothing really happens if you try to index a large files except that you get an error message from the indexing job. But if you have some very large files, you might bump in to performance problems on the server. Therefore it might be a good idea to add a convention that ignores large files.

In this case we will save the files size on the media and then check this property in the convention.

First we add a property to the media type. In this case we have a media type named GenericMedia that inherits from MediaData.

[ContentType(GUID = "EE3BD195-7CB0-4756-AB5F-E5E223CD9820")]
public class GenericMedia : MediaData
{
public virtual int FileSizeInKb { get; set; }
}

 

After that, we create a startup module named ContentInitialization that listens to the CreatingContent event. In the creating content event, we find out how big the file is, and saves it to the property we just created.

[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class ContentInitialization : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
var eventRegistry = ServiceLocator.Current.GetInstance();
eventRegistry.CreatingContent += OnCreatingContent;
}

private static void OnCreatingContent(object sender, ContentEventArgs e)
{
var content = e.Content as GenericMedia;
if (content != null)
{
var path = (content.BinaryData as FileBlob).FilePath;
var length = new FileInfo(path).Length;
content.FileSizeInKb = (int)(length / 1024);
}
}

public void Preload(string[] parameters) {}
public void Uninitialize(InitializationEngine context) {}
}

 

Now the only thing missing is the convention. For this we create another module named FindInitialization and add the convention there. The reason for creating several startup modules is both for code readability, but they might also have different dependencys to other modules. In this case we only let files smaller than 20000kb to be indexed.

[ModuleDependency(typeof(EPiServer.Find.Cms.Module.IndexingModule))]
public class FindInitialization : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
ContentIndexer.Instance.Conventions.ForInstancesOf().ShouldIndex(x => (x.FileSizeInKb < 20000));
}

public void Uninitialize(InitializationEngine context) {}
public void Preload(string[] parameters) {}
}

Some tips if you run into problem

The schedule job fails with thread was being aborted
Make sure that there is no resource limit on the application pool or that the server runs out of memory. When indexing a lot of content, the server will use a lot of CPU and memory.

EPiServer Find tells me that “The remote server returned an error: (413) Request Entity Too Large. Request entity too large.” but I have no file large files.
When the schedule job runs, it sends content in batches, and it seems like a batch cannot be larger than 50MB(?). You can set the size of a batch by

ContentIndexer.Instance.MediaBatchSize = somesize; // Not sure if this is in use anymore though
ContentIndexer.Instance.ContentBatchSize = somesize;

But if you have a decent amount of content, this will fail due to many requests.

Add CultureSpecific to an exisiting block area – keep the data

Recently we built a globalized site in EPiServer 7. When the page was filled with content, the client wished that the block area should be the same for all languages.

Once the page was launched the customer realized that they needed to have different blocks in different languages, which meant that we needed to make the block area CultureSpecific. When making such a change, it means that the master language will keep the value of the block area, while all other languages will have a null value.

To get around this, I wrote a script that loops through all the pages from the master language, then retrieves the other language versions of the same page, and if the block area is null, we set the block area value to the same as the master language.

If you need to do this, make sure that you try this on a development / test environment first.


[ScheduledPlugIn(DisplayName="Populate block area")]
public class PopulateBlockArea
{
private static int changedPages = 0;
private const string masterLang = "en";
public static string Execute()
{
changedPages = 0;
var ls = new LanguageSelector(masterLang);

var start = DataFactory.Instance.GetPage(PageReference.StartPage, ls) as BasePage;

Populate(start);

foreach (var pageRef in DataFactory.Instance.GetDescendents(start.ContentLink.ToPageReference()))
{
var page = DataFactory.Instance.GetPage(pageRef, ls) as BasePage;
if (page != null)
{
Populate(page);
}
}

return string.Format("Changed values of {0} pages.", changedPages);
}

private static void Populate(BasePage page)
{
foreach (var lang in page.ExistingLanguages)
{
if (page.MainContentArea != null && lang.TwoLetterISOLanguageName != page.Language.TwoLetterISOLanguageName)
{
var langPage = DataFactory.Instance.GetPage(page.ContentLink.ToPageReference(), new LanguageSelector(lang.TwoLetterISOLanguageName));
if (langPage != null && langPage is BasePage)
{
var writePage = langPage.CreateWritableClone() as BasePage;
if (writePage.MainContentArea != null)
{
continue;
}
writePage.MainContentArea = page.MainContentArea;

DataFactory.Instance.Save(writePage, EPiServer.DataAccess.SaveAction.Publish, EPiServer.Security.AccessLevel.NoAccess);
changedPages++;
}
}
}
}
}

Find page id from page files folder id

A customer had an URL to a page file, but couldn’t locate the page where the file was stored. And somehow the search in EPiServer didn’t give any hits on the file, so started digging around in the database and found the column ExternalFolderID in the table tblPage.

So a simple query like “SELECT pkID FROM tblPage WHERE ExternalFolderID = 3493” where 3493 was the page folder id gave us the answer.

Edit:

As Anders says in the comment below, you can also get the page by code:

PageData ownerPage = UnifiedDirectory.GetOwnerPageFromVirtualPath(“/PageFiles/3493/”);

Handle children with Action window plugin

Every now and then I bump in to a “problem” where I have to delete children from a node or copy/move children from one node to another. The first time I ran into this problem, I made a gui plugin for the action window to solve this for me. And now, a few years later, I thought that it might be nice to share it. :)

The plugin can also be useful when you have a large recycle bin that can’t be emptied. The you can delete a few pages at a time.

The plugin is really simple. You choose source node, destination node (if needed) and if you are deleting pages, you can choose how many pages you want to keep. If you choose “Count” you will also see how many descendants the page has.

Btw, when deleting, we’re starting with the page at the bottom of the page list.

Download from EPiServer World

How to get a page property value from the start page in EPiServer

When browsing through google analytics, it’s interesting to see what people search for to find your blog. So, I will try to make a few blog posts about the most frequent search terms so it will be easier to find. :)

If you have a property that you need to use all over the site, a global setting, then one way is to add that property to the startpage. (If you have a lot of settings it might be handier to have a special page and page type where the settings are stored.)

To access that property you first need to get the pagedata object for the start page, and then it’s easy to access the property.


var startPage = DataFactory.Instance.GetPage(PageReference.StartPage);

var property = startPage["PropertyName"];

If you are using PageTypeBuilder, you can access the property strongly typed by


var startPage = DataFactory.Instance.GetPage(PageReference.StartPage) as StartPageType;

var property = startPage.PropertyName;

Error in UI-path when upgrading from EPiServer 5 to 6

The other day we were upgrading a site for a customer from EPiServer 5 to EPiServer 6. In the upgrade process an exception was thrown telling us:

At C:\Program Files (x86)\EPiServer\CMS\6.0.530.0\Upgrade\System Scripts\Upgrade Site (SqlServer).ps1:361 char:29

+                  $newUiUrl = $uiUrl.Replace <<<< ($uiPath, $uiPath+”/CMS”)

System.Management.Automation.ParentContainsErrorRecordException: Cannot convert argument “1”, with value: “/CMS”, for “Replace” to type “System.Char”: “Cannot convert value “/CMS” to type “System.Char”. Error: “Strängen måste vara exakt ett tecken lång.””

After some research we realized that the uiUrl in episerver.config was set to https://www.somedomain.com/ui/ and the upgrade script didn’t like it and most likely expected the value to be ~/ui/.

The solution was to change it to ~/ui/ during upgrade and then change it back.

Match EPiServer properties and Page Type Builder properties (Updated)

Updated: 2011-09-21, Lee Crowe has updated this plugin. Please check out his blog post: Introducing Page Type Builder UI

Updated: This can now be found on GitHub: https://github.com/eriknordin/PTB-Helpers

Updated: 2010-11-10 16:23, see more at the end of this post!

Using PageTypeBuilder when creating EPiServer web sites?

Then you’re probably aware of when removing properties in code, they won’t change in EPiServer. Same thing when changing a property type in PTB, it won’t change in EPiServer.

In larger projects with a lot of inheritance between page types this can be really annoying, going through all page types and remove/change properties. So I created a plugin that displays miss matches between EPiServer and PTB-properties. In the plugin you can also delete unwanted properties, but for now you can’t convert them to another property type.

The plugin will appear in admin-mode:

And looks something like this.

Don’t forget to put the plugin in a secured folder (or remove it in production environment).

This plugin is tested with EPiServer CMS 6 and Page Type Builder 1.3.

Download the plugin at: http://world.episerver.com/Code/Erik-Nordin/Match-EPiServer-and-Pgae-Type-Builder-properties/

Update

Fixed a bug reported at the code section and also added a link to the page type, and also added a link to edit the property if there was a mismatch.

Make your PropertyNumber a bit wider

The default style for PropertyNumber in EPiServer is often a bit short if you have number larger than 999 and it also aligns the text to left. An easy way around this is to use EPiServer PropertyControlClassFactory.

Just create a “PropertyNumberWideControl” that inherits PropertyNumberControl and override CreateEditControls to modify the style as you feel like.

namespace Some.Web.SpecializedProperties.Controls
{
public class PropertyNumberWideControl : PropertyNumberControl
{
public override void CreateEditControls()
{
base.CreateEditControls();

base.EditControl.Style.Add("width", "100px");
base.EditControl.Style.Add("text-align", "right");
}
}
}

There are now two ways to make this control override the default PropertyNumberControl.

1.
In Web.Config, find <episerver.baseLibrary><classFactories>. In that node you might find this <add type=”EPiServer.Core.PropertyControlClassFactory, EPiServer” id=”PropertyControlFactory”>, if not add it. Then you have to register your new control and that it should override the old one: <register type=”EPiServer.Core.PropertyNumber, EPiServer” mappedType=”Some.Web.SpecializedProperties.Controls.PropertyNumberWideControl, Some.Web” />.

<episerver.baseLibrary>

<classFactories>
.....
<add type="EPiServer.Core.PropertyControlClassFactory, EPiServer" id="PropertyControlFactory">
<register type="EPiServer.Core.PropertyNumber, EPiServer" mappedType="Some.Web.SpecializedProperties.Controls.PropertyNumberWideControl, Some.Web" />
</add>
</classFactories>
</episerver.baseLibrary>

2. You can also override the control with code. To do that, take a look at this post from Ted Nyberg.

Send mail in development environment

Just realized that in web.config you can specify a directory to “send” mail to instead of using a smtp-server, pretty neat. Just change to the following where you specify the smtp settings and you won’t have any troubles with smtp-servers. Just don’t forget to change this when you really need to send a mail.

<system.net>  
   <mailSettings>  
      <smtp deliveryMethod="SpecifiedPickupDirectory">  
         <specifiedPickupDirectory 
             pickupDirectoryLocation="c:\directory\to\emails\" />  
      </smtp>  
   </mailSettings>  
</system.net>

Quick way to create checkboxlist / dropdownlist properties

In EPiServer there is a easy way to create a checkbox list property by extending some existing classes.

In this example I will fetch the children for the start page and display them. In a future blog post I will show you how to do the same with a dropdown list control, and also show you have to extend this with Settings for properties to make the properties much more dynamic and useful.

Check box list / multiple choice property

1. Create a new class and make that class extend EPiServer.Core.PropertyMultiValue. This class should also have the attribute PageDefinitionPlugIn to be recognized as a property.

[PageDefinitionTypePlugIn(DisplayName="Custom CheckBox list")]
public class PropertyCustomCheckBoxList : PropertyMultipleValue
{
public override EPiServer.Core.IPropertyControl CreatePropertyControl()
{
return new PropertyCustomCheckBoxListControl();
}
}

2. Create the property control and extend EPiServer.Web.PropertyControls.PropertySelectMultipleControlBase

public class PropertyCustomCheckBoxListControl : PropertySelectMultipleControlBase
{
}

3. Now we need to populate the property with some data. In the control class, override the method SetupEditControls and do some magic

protected override void SetupEditControls()
{
// Get children to start page
foreach (PageData page in DataFactory.Instance.GetChildren(PageReference.StartPage))
{
// Create list item
ListItem li = new ListItem(page.PageName, page.PageLink.ID.ToString());
// check if list item is selected
li.Selected = ((PropertyMultipleValue)PropertyData).IsValueActive(li.Value);
// add item to checkbox list
this.EditControl.Items.Add(li);
}
}

4. That’s it. Everything else is handled by the classes you extend. By the way, the value of the property is saved as a comma separated string.