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!  🙂

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 info@nicolaayan.com

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 info@nicolaayan.com

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

Solution
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.

References
Dan Matthews blog: http://world.episerver.com/blogs/Dan-Matthews/Dates/2014/10/Database-failures-when-creating-a-site-in-Visual-Studio/

 

 

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.

 [GroupDefinitions]
 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.

 [CultureSpecific]
 [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.

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

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

[GuiPlugIn(
 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);
      }
      else
      {
         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))
   {
      blob.Write(fs);
   }
   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!

 

Embedding a Youtube Video on EPiServer

Embedding youtube video

Several pages in our website require embedded Youtube videos. So I created a Youtube block that accepts the following Content properties:

  • Title
  • Description
  • Embed link

While it’s Settings properties are:

  • Width
  • Height
  • Allow Full Screen
  • Auto Play

Here’s my code for the model:

 public class YoutubeBlock : BlockData  
 {  
      [Display(  
           Name = "Title",  
           GroupName = SystemTabNames.Content,  
           Order = 10)]  
      public virtual string VideoTitle { get; set; }  
      [Display(  
           Name = "Description",  
           GroupName = SystemTabNames.Content,  
           Order = 20)]  
      public virtual string Description { get; set; }  
      [Display(  
           Name = "Url to Youtube Video",  
           GroupName = SystemTabNames.Content,  
           Order = 30)]  
      [Required]  
      public virtual Url YoutubeLink { get; set; }  
      [Display(  
           Name = "Width",  
           GroupName = SystemTabNames.Settings,  
           Order = 10)]  
      public virtual int Width { get; set; }  
      [Display(  
           Name = "Height",  
           GroupName = SystemTabNames.Settings,  
           Order = 20)]  
      public virtual int Height { get; set; }  
      [Display(  
           Name = "Allow Full Screen?",  
           GroupName = SystemTabNames.Settings,  
           Order = 30)]  
      public virtual bool AllowFullScreen { get; set; }  
      [Display(  
           Name = "Auto Play?",  
           GroupName = SystemTabNames.Settings,  
           Order = 40)]  
      public virtual bool AutoPlay { get; set; }  
      public override void SetDefaultValues(ContentType contentType)  
      {  
           this.AllowFullScreen = true;  
           this.AutoPlay = false;  
           this.Width = 520;  
           this.Height = 292;  
      }  
 }  

Nothing special with my controller:

 public class YoutubeBlockController : BlockController<YoutubeBlock>  
 {  
      public override ActionResult Index(YoutubeBlock currentBlock)  
      {  
           return PartialView(currentBlock);  
      }  
 }  

And then my View:

 @model YoutubeBlock  
 <div class="youtubediv">  
   @if (String.IsNullOrEmpty(Model.VideoTitle))  
   {  
     <h3>@Html.PropertyFor(y => y.VideoTitle)</h3>  
   }  
   @if (String.IsNullOrEmpty(Model.Description))  
   {  
     <p>@Html.PropertyFor(y => y.Description)</p>  
   }  
   @{  
     var youtubeLink = Model.YoutubeLink.OriginalString;  
     if (Model.AutoPlay)  
     {  
       youtubeLink += ((youtubeLink.Contains("?")) ? "&" : "?") + "autoplay?=1";  
     }  
     var allowFullScreen = Model.AllowFullScreen ? "allowfullscreen" : "";  
   }  
   <iframe width="@Model.Width" height="@Model.Height" src="@youtubeLink" @allowFullScreen style="border: none;"></iframe>  
 </div>  

The recommended way of embedding video is to use the iframe. However, you can also use the elements Object and Embed as per W3schools.

Meanwhile on the EPiServer CMS Edit mode…

 

 

How to Create an RSS Feed in EPiServer (ASP.NET MVC)

Thanks to Ted and a guy from CodeInside for your blogs which helped come up with this solution.

Problem: Create an RSS feed for ASP.NET MVC Website with EPiServer

Solution: We need three things

1. RssResult class
2. RssPage page type
3. RssPageController which creates the xml rss feed

So let’s begin with the RssResult class. This class needs to inherit FileResult (System.Web.Mvc) passing rss+xml as content type.

 public class RssResult : FileResult  
{
private readonly SyndicationFeed _feed;
public RssResult(SyndicationFeed feed)
: base("application/rss+xml")
{
_feed = feed;
}
public RssResult(string title, List<SyndicationItem> feedItems)
: base("application/rss+xml")
{
_feed = new SyndicationFeed(title, title, HttpContext.Current.Request.Url) { Items = feedItems };
}
protected override void WriteFile(HttpResponseBase response)
{
using (XmlWriter writer = XmlWriter.Create(response.OutputStream))
{
_feed.GetRss20Formatter().WriteTo(writer);
}
}
}

Then, we create a model page type which I’ve named RssPage.cs. It basically only has one property which holds the root page of the RSS

 [ContentType(  
DisplayName = "Rss Page",
GUID = "2ba86356-847f-439f-bf1c-36f7cf70158e",
Description = "Used to create an RSS Feed")]
public class RssPage : SitePageData
{
[Display(
Name = "RSS Root Page",
GroupName = SystemTabNames.Content,
Order = 10)]
[Required]
public virtual PageReference RssPageLink { get; set; }
public PageDataCollection GetPages()
{
var pages = DataFactory.Instance.GetChildren(RssPageLink);
return FilterForVisitor.Filter(pages);
}
}

Now we create the controller for this page type.

 public class RssPageController : PageControllerBase<RssPage>  
{
public ActionResult Index(RssPage currentPage)
{
return Feed(currentPage);
}
public virtual ActionResult Feed(RssPage currentPage)
{
var items = new List<SyndicationItem>();
var newsPages = DataFactory.Instance.GetChildren<NewsPage>(currentPage.RssPageLink);
var pageUri = currentPage.RssPageLink.GetUri();
foreach(var newsPage in newsPages)
{
var feedPackageItem = new SyndicationItem(newsPage.PageTitle, newsPage.Description, pageUri);
feedPackageItem.PublishDate = newsPage.StartPublish;
items.Add(feedPackageItem);
}
return new RssResult("My News Feed", items);
}
}

Now we create an instance of this page on EPiServer.

Navigating to this page now shows an xml rss page

Any questions, please comment!

EPiServer 7.x vs EPiServer 8

I was tasked to find out whether our current active license for CMS 7 will still work with EPiServer 8. So I did some research and thought of sharing it here as well.

Summary Version:

  • Good news – Our 7.x license will continue to work for EPiServer 8.0
  • Current EPiServer version available in NuGet is 8.3.0.
  • EPiServer 8 has breaking changes but mostly needing just code recompilation
Long Version:
Here’s the forum that confirms our active subscription will continue to work for this new major version.

If the customer has an active subscription the license that has been used for 7.x will continue to work. When it comes to how certifications are handled for new major versions we will soon give you an update on that. But, rest assured, your EPiServer 7 certification will continue to be valid.

EPiServer has started a continuous release process since about a year ago. Checking their last releases, here’s what is recent…

Updating to CMS 8 is no different from updating to the feature updates delivered via NuGet. They have however, named it to a major version and this is because it has some breaking changes. List of breaking changes here: http://world.episerver.com/documentation/Items/Upgrading/EPiServer-CMS/8/Breaking-changes/
Some of the new features:

  • Multi-publish and preview – there’s a new concept called ‘Projects’ in the Editor
  • Performance Improvements
  • Updated browser support – IE9 is no longer supported!
  • Support for canonical URLs
  • Better ways to organize content types and properties
  • Improved control to ensure design consistency
Other links I thought are useful:

Hope this helps anyone!

Int64 / Long / Bigint Property is not supported in EPiServer 8

Yes, you read it right. You can’t have a model property of type Int64 / long.

I came across an issue where in I have a model that requires to store an Id (from the database) as one of it’s properties. It so happens that most tables we have at work go by an Id of type Bigint (SQL Server).

So I had to create something like:

 public class MyModel {  
[Editable(false)]
[BackingType(typeof(PropertyNumber))]
public virtual long SomeObjectId { get; set;}
// and some other properties below...
}

However, when I run the app, I get the following error:

Type ‘System.Int64’ could not be mapped to a PropertyDefinitionType

Yes, PropertyNumber only supports Int32, etc.

So I fixed this by changing the data type to string so it stores the Int64 as string, but parse it back when I need it as Int64.

 public class MyModel {  
[Editable(false)]
[BackingType(typeof(
PropertyString))]
public virtual string SomeObjectId { get; set;}
// and some other properties below...
}

Notice that I also updated the backing type.

I’m not sure if this is the best way of playing with Int64’s, but it solved my problem!

PS. Looks like this has been the case ever since

Using XForms in EPiServer 8 MVC

Ha, another milestone for me – I was able to create an XForm in EPiServer and hook it up to a block!

First step I took:  Create a Form block, which consists of the following properties:

 public class FormBlock  
{
public virtual string Heading { get; set; }
public virtual XForm Form { get; set; }
[Ignore]
public virtual string ActionUri { get; set; }
}

And then create a controller that populates the ActionUri property. Here is what it looks like:

 public class FormBlockController : BlockController<FormBlock>  
{
private readonly PageRouteHelper pageRouteHelper;
public FormBlockController(PageRouteHelper pageRouteHelper)
{
this.pageRouteHelper = pageRouteHelper;
}
public override ActionResult Index(FormBlock currentBlock)
{
// Create postback url
if (currentBlock.Form != null && this.pageRouteHelper.Page != null)
{
var actionUri = "XFormPost/";
actionUri = UriSupport.AddQueryString(actionUri, "failedAction", "Failed");
actionUri = UriSupport.AddQueryString(actionUri, "successAction", "Success");
currentBlock.ActionUri = actionUri;
}
return PartialView(currentBlock);
}
}

The view is short and simple:

 @model FormBlock  
<h2 @Html.EditAttributes(m => m.Heading)>@Model.Heading</h2>
@using (Html.BeginXForm(Model.Form, new { Action = Model.ActionUri}))
{
Html.RenderXForm(Model.Form);
}

2nd step: Create a Page Type where one of it’s properties is the newly created Form block

 public class FormPage  
{
public virtual FormBlock FormBlock { get; set; }
}

And then create the Controller. Since the ActionUri in the Form block has been set to “XFormPost/”, we create that Action method as you can see below. I have also added Success and Failed actions that need to be further implemented. But for now, we leave it as is.

 public class FormPageController : PageControllerBase<FormPage>  
{
private readonly XFormPageUnknownActionHandler _xformHandler;
private string _contentId;
public FormPageController()
{
_xformHandler = new XFormPageUnknownActionHandler();
_contentId = string.Empty;
}
public ActionResult Index(FormPage currentPage)
{
var model = new PageViewModel<FormPage>(currentPage);
return View(model);
}
[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult XFormPost(XFormPostedData xFormpostedData, string contentId)
{
_contentId = contentId;
return _xformHandler.HandleAction(this);
}
#region Success and failed actions
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Success(FormPage currentPage, XFormPostedData xFormPostedData)
{
var model = new PageViewModel<FormPage>(currentPage);
return View("Success", currentPage);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Failed(FormPage currentPage, XFormPostedData xFormPostedData)
{
var model = new PageViewModel<FormPage>(currentPage);
return View("Failed", currentPage);
}
#endregion
}

And finally the View:

 @model PageViewModel<FormPage>  
@{
Layout = "~/Views/Shared/Layouts/_SomeLayout.cshtml";
}
<div class="row">
@Html.PropertyFor(p => p.CurrentPage.FormBlock)
</div>
<div class="row">
@Html.PropertyFor(p => p.CurrentPage.NonTextContent)
</div>

3rd step: Run and create an instance of the Form Page. On the Form block, you can choose which XForm to use in the block.

Or you can also create a new one if needed.