A blog about understanding and extending Episerver. May contain traces of posts not concerning Episerver.

Post navigation

The current TinyMCE version in Episerver has some flaws which are a bit of a pain for editors. One of them is when using Chrome/Safari and pasting content into the TinyMCE editor. The content is pasted where the cursor is, but the page scrolls to the top. We have customers that frequently updates long articles, and see this as a big problem. This problem is reported as a bug at Episerver, but the bug is located in TinyMCE and won’t be fixed until a new/updated editor arrives. (Rumours says a new/updated editor is heading our way, but time will tell when it’s here.)

Anyway, one of the editors found out that if you click “Paste as plain text” before pasting, TinyMCE does not scroll to the top. Happy days. So, to solve this problem, all we had to do was select this option as default.

Searching in the navigation bar with multiple sites, multiple languages and a lot of content rarely gives you the result you want. Or.. To be honest. Just having the same name on two pages gives you headache. Luckily we have the power to change that!

Two pages with the same name. Tooltip isn’t very helpful.

As you can see, we have two pages with the same name. I know that the page I’m looking for is located somewhere under Campaigns, but which one is it?

Since the search is provider based, we can create our own search provider and tell EPiServer to use that instead. To create your own search provider, all you have to do is implement the interface ISearchProvider and decorate your class with the SearchProvider attribute.

In this case, all we want to do is modify the output of the search, therefore we will just extend the search being used. In this example we will extend the EPiServer Find search provider, but the code will probably work on the EPiServer Search provider as well.

To do this, we create a new class called CustomPageSearchProvider, this class will extend EPiServer.Find.Cms.SearchProviders.EnterprisePageSearchProvider. To get our provider an unique name we need to override Category and give it our name.

Now build your project and navigate to Admin \ Config \ Search Configuration. If everything works as expected you should see your Custom Pages Search there. Enable your newly created provider, and make sure that it’s above the other pages search providers, or disable them.

Here we can decide which search providers to use, and also set in which order they are executed.

Now it’s time to modify the search result before sending it back to the client. Let’s start by overriding Search(Query query). We still want to use the search functionality from EPiServer Finds search, so we execute base.Search(query) into a search result. The result will contain a list with SearchResult. This class contains some properties like Title, Url and also some metadata containing the Id of the hit. We want to modify the Title of the SearchResult to it will contain information of where the page is located in the structure. To do that, we get all ancestors of the content, reverse that list, skip the first two entries (we don’t want the root or start page), and finally we format the title as we want to!

Here are two code snippets to help you make a XhtmlString display external links instead of the internal ones and also render blocks, dynamic content etc. This could be helpful if you don’t have access to a HtmlHelper like in a WebApi.

The first one just converts links. It loops through all fragments and converts internal UrlFragments to external.

We just migrated a system from a custom built CMS into EPiServer. If the editor added a link to an unpublished page in the old CMS, it was automatically unlinked for the visitor until the page was published. We tried to find a built in way of doing in this in EPiServer, but could not find one. So instead we created a custom HtmlHelper that did if for us.

The helper method uses Html Agility pack to parse the html code and find links, then we find the content in EPiServer and check if it is published or not. If it’s unpublished we remove the wrapping a-tag.

Bear in mind that the lookup if a page is published or not costs in performance. We’ve added caching on the lookup so it’s only the first lookup on a url that takes time. Another thing to consider in the code below is that we only check pages, and not other content.

With the old VPP file system you could create a new version of a file by right click the file and select Create new version. With the new file system this function has disappeared. You can however update a file by uploading a new file with the same name. A customer to us needed the old method back because of various reasons.

We wanted a quick solution for this feature, so we decided to add a custom template to our GenericMedia-type (inherited from MediaData) where the editor could upload a new version of the file. To accomplish this we needed to go through three steps.

Create a view

Create a Controller with a TemplateDescriptor

Create a UIDescriptor that tells GenericMedia to use OnPageEditView

1. View

First we create a view. We know that this View will get a model of GenericMedia, and that we need a form where we can upload a file. We also add a way to send feedback from the Controller via ViewData.

<input type="submit"value="Update file"onclick="return confirm('Are you sure you want to update this file?');"/>

</form>

2. Controller with TemplateDescriptor

Now we create a controller to the View. The Controller needs a Index-method where we can pass in a HttpPostedFileBase if this is posted from the View. If this is passed into the method we add some logic to update the file-data of the current content.

We also need to add a TemplateDescriptor-attribute to this controller. In the attribute we define that this controller should be used with RenderingTag.Edit, that it’s a MvcController and where we can find the view.

3. UIDescriptor

Finally we need an UIDescriptor to say that when editing a GenericMedia, we need to see the OnPageEditView. Otherwise we will always see the AllPropertiesView when editing the media. The UIDescriptor is defined by creating a class that inherits from UIDescriptor and with the attribute [UIDescriptorRegistration].

C#

1

2

3

4

5

6

7

8

9

10

[UIDescriptorRegistration]

publicclassGenericMediaUIDescriptor:UIDescriptor<GenericMedia>

{

publicGenericMediaUIDescriptor()

:base("icon-document")

{

DefaultView=CmsViewNames.OnPageEditView;

DisabledViews=newstring[]{};

}

}

At last

If you know a smoother way of doing this, please leave a reply in the comments. 🙂

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.

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.

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.

Some tips if you run into problem

The schedule job fails with thread was being abortedMake 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.

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.

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:

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.

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.