Generate a sitemap.xml for your Episerver website

In this post, I will cover how you can generate a sitemap.xml for your Episerver website. All credits to Geta SEO Sitemaps, you will now be able to generate one in under 5 minutes!

  1. Download and install the Nuget package “Geta.SEO.Sitemaps” to your Episerver application Geta.SEO.Sitemaps Nuget package
  2. Build and run your application
  3. Navigate to Admin > Tools and you will find the “Search engine sitemap settings”. Fill in the correct details here. You can even add multiple sitemaps if your Episerver instance has multiple sites / languages. Geta SEO Sitemap settings
  4. Enable the scheduled job by going to Admin > Scheduled Jobs. Apply your settings and manually start the job to test and see your sitemap.xml

Easy peasy!  🙂

Uploading a database to Azure using SQL Server Management Studio

Took me a while to find out, but deploying a SQL Server database to Azure is actually very easy. Make sure you have your Azure connection details ready!

The below instructions / screenshots are from SSMS 2016. Should work for older versions too, although you might see different wordings.

  1. Open SQL Server Management Studio (SSMS) and connect to the SQL Server where your local database is hosted
  2. Right Click on the database > Deploy Database to Microsoft Azure SQL Database Deploy Database to Microsoft Azure SQL Database
  3. Specify connection details by clicking on “Connect”. 
  4. After entering details, click on “Options” below, to specify the timeout to be 60 seconds. (Had to do this in my case cos my database is hosted in South Central US and I’m connecting from Australia it kept timing out). I ticked the “Encrypt connection” true too. 
  5. Back to the Deployment settings window > Specify your Microsoft Azure SQL Database settings. Make sure you choose what you need as this affects payment / subscription.
  6. When everything looks good > click next and it will upload your database to Azure for you!


Episerver CMS Content Migration

Content Migration

I will cover how to automate content migration into an Epi website from a non-Epi CMS. Yes, you read that right. You can automate migration of content into Epi from another CMS and save yourselves the time and money required for a manual data-entry.

Even if your content is currently stored in a Java-based CMS, PHP-based CMS, it can be done. As long as you (or your devs) have access to your current non-Epi CMS database and can perform queries to, then it is doable.

If you are a non-technical person who has a technical resource willing to build it for you, then you can stop here and forward this blog entry to them. You’re welcome.

If you are a non-technical person who doesn’t have a technical resource to do it, then PM me on

If you are a technical person, then the below post is for you. High level, here are the steps you need to take:

  1. Prepare the  assets in your file system in the structure you want them to be uploaded in Epi.
  2. Develop a tool that, when given a file path, will read the contents and upload the files programmatically into the Episerver Assets. Tool discussed here.
  3. Create a JSON/XML export file from your current non-Epi CMS
  4. Create an Epi plugin that can understand the JSON/XML export file and use the Episerver API to programmatically create your pages/blocks

The assets migration has to happen first so when you’re ready to migrate content, your pages/blocks can properly refer to the correct existing assets (i.e. using ContentReference properties)

The sample code below uses JSON. The file on step 3 should look something like this:

CMS Content source

What the fourth tool needs to do is recursively parse the JSON file and based on the “Type”, it will create the relevant Episerver page type. Thanks to Episerver API.

The heart of this tool is this recursive method below.

private ContentReference GetOrCreatePage<T>
(JObject jsonPage, ContentReference parentPageReference) 
where T : PageData
   // check if page exists...
   var existingPage = ContentRepository.Service.GetChildren<T>(parentPageReference)
      .FirstOrDefault(c => c.Name == (string)jsonPage["Name"]);

   ContentReference pageReference;
   if (existingPage == null) {
      var newPage = ContentRepository.Service.GetDefault<T>(parentPageReference);
      pageReference = ContentRepository.Service.Save(newPage, SaveAction.SkipValidation);
   } else {
      pageReference = existingPage.ContentLink;

   // Populate content
   PopulateContent(pageReference, jsonPage);

   // Iterate through child pages and recursively get or create
   foreach (var childPage in (JArray)jsonPage["Children"]) {
      GetOrCreatePage((JObject)childPage, pageReference);

   return pageReference;

You will also need helper methods such as getting content reference based on url:

private ContentReference GetContentReferenceForUrl(string url) {
   return UrlResolver.Service.Route(new UrlBuilder(url))?.ContentLink;

I also have a method that takes care of updating/creating a block for a page:

private void UpdateOrCreateBlockForPage<T>(string blockName, JObject jsonBlock, ContentReference parentLink, ContentArea contentArea) 
where T : BlockData {
   // check if block exists on page
   var assetFolder = ContentAssetHelper.Service.GetOrCreateAssetFolder(parentLink);
   var existingBlock = ContentRepository.Service.GetChildren<T>(assetFolderForPage.ContentLink)
      .FirstOrDefault(b => (b as IContent).Name == blockName);

   ContentReference blockReference;
   if (existingBlock == null) {
      var newBlock = ContentRepository.Service.GetDefault<T>(assetFolderForPage.ContentLink);
      (newBlock as IContent).Name = blockName;
      blockReference = ContentRepository.Service.Save(newBlock as IContent, SaveAction.SkipValidation);
      if (contentArea == null) contentArea = new ContentArea();
      contentArea.Items.Add(new ContentAreaItem {
         ContentLink = (existingBlock as IContent).ContentLink
    } else {
      blockReference = existingBlock.ContentLink;

    // update content
    PopulateContentForBlock(blockReference, jsonBlock);


That’s pretty much it. Hope you get the flow!

If you get stuck/have questions, post a comment below or email me on

Appreciate a comment if this has helped you in anyway!




Episerver – Could not find stored procedure ‘netCategoryListAll’

My scenario
I tried creating a new database schema for my Episerver website that uses the createDatabaseSchema=”true” on the episerver.framework config node. This creates a new database schema if it detects there’s none for the database specified in the config. However, when I first ran my Episerver website which was configured to an empty database, I got the following error:

server error could not find netCategoryListAll

Run the following scripts:

  • %systemroot%\Microsoft.NET\Framework64\v4.0.30319\SQL\en\SqlPersistenceService_Schema.sql
  • %systemroot%\Microsoft.NET\Framework64\v4.0.30319\SQL\en\SqlPersistenceService_Logic.sql
  • [your site installation folder]\packages\EPiServer.CMS.Core.7.7.1\tools\EPiServer.Cms.Core.sql

Please note the third script above can be taken from the NuGet package installed on your website’s packages. If you don’t have this, you can create an empty Episerver website in Visual Studio using the Episerver templates which will create a packages folder that contains this script.

Dan Matthews blog:



How to order tabs of properties for a page type

If you have quite a complicated page/block type where you end up having more than 5 tabs of properties, then you start thinking of sorting these tabs too.

To achieve this, make sure to use the attribute [GroupDefinition] and specify the Order.

 public static class MyGroupNames

    [Display(Order = 50)]
    public const string Banner = "Banner";

    [Display(Order = 101)]
    public const string Tab1 = "Tab 1";

    [Display(Order = 102)]
    public const string Tab2 = "Tab 2";

    [Display(Order = 103)]
    public const string Tab3 = "Tab 3";

    [Display(Order = 104)]
    public const string Tab4 = "Tab 4";

    [Display(Order = 105)]
    public const string Tab5 = "Tab 5";

    [Display(Order = 300)]
    public const string Sidebar = "Sidebar";

Then use these TabNames on your property.

 [Display(GroupName = LocalGroupNames.Tab1, Name = "Tab 1 Title", Order = 200)]
 public virtual String Tab1Title { get; set; }

Then voila you have a nicely grouped set of tabs in Episerver!

sorted tabs in episerver

Episerver: How to upload media assets in bulk

Today I created a small tool in Episerver that reads through my local file system and programmatically uploads all media files and folders in the same structure as they are in the file system. As of this writing, Episerver does not currently support uploading of folders and subdirectories.

I have developed the tool to show within the Episerver > CMS > Admin > Tools section and looks like this

Import media assets form

The destination field lets users choose to upload to either the GlobalAssetFolder or to a SiteAssetsFolder.

When it finishes the upload, the file system that looks like this:

import media assets directory

Will then be transferred including all subdirectories and files to the media asset pane:

import media asset pane

So how did I achieve this? It’s pretty simple!

First of all, update your web.config to let your application know that you have a publicmodule.

   <publicModules rootPath="~/modules/" autoDiscovery="Modules">
      <add name="ImportMediaAssets" />

Then I created the following controller under inside the modules folder.

 Area = PlugInArea.AdminMenu,
 Url = "/ImportMediaAssets",
 DisplayName = "Import media assets")]
 public class ImportMediaAssetsController : Controller
    // Insert your logic here...

Notes regarding the above code:

  • GuiPlugin – attribute that allows you to extend the Episerver user-interface.
  • GuiPlugin Area – tells the Episerver user-interface where to put your plugin
  • GuiPlugin Url – If you’re using MVC, then this will be the controller name + action name (empty if Index)

Then, in my controller, I have two action methods. The Index() gets called on click of the “Import media assets” link in the Tools menu. While the BeginUpload() gets called on submit of the form, just like hooking up any other MVC post.

public ActionResult Index()
   var model = new ImportMediaAssetModel();
   return View(model);

public ActionResult BeginUpload(ImportMediaAssetModel postedModel)
   var directory = new DirectoryInfo(postedModel.SourcePath);

   if (postedModel.Destination == ImportMediaAssetModel.GlobalAssetFolder)
      CreateAndUploadFilesAndFolders(directory, ContentReference.SiteBlockFolder, postedModel.OverrideExistingItems);
   return View(directory);

The important function here is the CreateUploadFilesAndFolders() method.

  • Iterate through each subdirectory and programmatically create in Episerver
  • Iterate through each file and call the UploadFile() method which we will look at later
private void CreateAndUploadFilesAndFolders(DirectoryInfo dir, ContentReference parentLink, bool overrideFiles)
   foreach (var childFile in dir.GetFiles())
      if (fileCount >= 1) break;
      UploadFile(childFile, parentLink, overrideFiles);
   foreach (var childDir in dir.GetDirectories())
      var storedFolder = _contentRepository.Service.GetBySegment(parentLink, childDir.Name,
 LanguageSelector.AutoDetect()) as ContentFolder;

      if (storedFolder != null)
         CreateAndUploadFilesAndFolders(childDir, storedFolder.ContentLink, overrideFiles);
         var contentFolder = _contentRepository.Service.GetDefault<ContentAssetFolder>(parentLink);
         contentFolder.Name = childDir.Name;
         var contentAssetsFolderReference = _contentRepository.Service.Save(contentFolder, SaveAction.Publish);
         CreateAndUploadFilesAndFolders(childDir, contentAssetsFolderReference, overrideFiles);

Finally, here’s my UploadFile() implementation.

private void UploadFile(FileInfo file, ContentReference parentLink, bool overrideFiles)
   //Get a suitable MediaData type from extension
   var extension = GetExtension(file.Name);
   var mediaType = _contentMediaResolver.Service.GetFirstMatching(extension);
   var contentType = _contentTypeRepository.Service.Load(mediaType);

   // Try get existing media
   var storedMedia = _contentRepository.Service.GetBySegment(parentLink, file.Name,
 LanguageSelector.AutoDetect()) as IContentMedia;
   if (!overrideFiles && storedMedia != null) return;

   //Get a new empty file data
   var media = _contentRepository.Service.GetDefault<IContentMedia>(parentLink, contentType.ID);
   var container = media.BinaryDataContainer;
   var blob = _blobFactory.Service.CreateBlob(container, extension);
   using (var fs = new FileStream(file.FullName, FileMode.Open))
   media.BinaryData = blob;
   media.Name = file.Name;
   _contentRepository.Service.Save(media, SaveAction.Publish, AccessLevel.NoAccess);

Of course you have to also implement the razor view, that will be completely up to you. But my model looks like this:

public class ImportMediaAssetModel
   public string Destination { get; set; }
   public string SourcePath { get; set; }
   public bool OverrideExistingItems { get; set; }

Easy right? 🙂

If you have any questions, please let me know!