Build out site page hierarchy programmatically - getting started

Vote:
 

Hi. It's been a couple years since I've worked much in EpiServer and now we're creating a new site to replace a SharePoint site. The SP site is a training site with lots of subsites representing various programs we have and then each one has a hierarchical page structure within it. We want to bring this same structure over and we already have all the page types and a generic container page (to represent folders) set up. When I say bring it over, the plan is to read a CSV file and create the necessary pages from that. You can just assume I already know what pages need to be created as long as I can read a file, it's just a matter of how to create the pages.

To give you a picture of what I'm talking about, it would be something like this:

  • Start Page
    • About
    • Contact
    • Training 
      • Session 1 
        • Program 1
          • 01
            • Summary
            • Curriculum
            • Stations
            • Media
          • 02
            • Summary
            • Curriculum
            • Stations
            • Media
        • Program 2
          • 01
            • Summary
            • Curriculum
            • Stations
            • Media
      • Session 2

...and so on for about 4000 pages.

Partly because I'm so rusty on EPiServer, I'm just struggling with getting started and what actually has to be done. First of all, would this be easier as an external application like a console app or an admin tool page in EPiServer? We've done the latter previously in another site using the GuiPlugInAttribute. Console app sounds easier to me, but how do I set it up to work with with the EPiServer API? Second, I've looked at Creating a page programmatically, but do I have to create and publish one page at a time or could I do it in some kind of transaction where if the process gets interupted, no changes have been made yet? Since there will be so many pages, efficiency is also a consideration. Is there anything else in addition to the Creating a page programmatically you think might be useful here? Thanks!

#192688
Edited, May 23, 2018 1:01
Vote:
 

You can use a console app if you like to. What you would need to do then is basically have a IConfigurableModule that configures up the connectionString towards DataAccessOptions and then creates an instance of InitializationEngine and calls Initialize on it (the console app could even be written in .Net Core if you prefer that). Another alternative would be to create a scheduled job that does the work. If you decide to go the console app way I could post you some sample code on how to set it up.

There is no bulk Save API so you need to create the pages one by one. Does the existing data have some identifier like a Guid? If so I would recommend that you assign IContent.ContentGuid with that identifier before saving because then if the work is interrupted and you need to rerun it you could have code that queries IPermanentLinkMapper.Find(Guid contentGuid) before creating. If that returns something that it is not null then htat would mean the page is already imported.

#192694
May 23, 2018 9:53
Vote:
 

If I were you I would have created the site first and then extened the interface with a page where you upload a file and do the import.
It is very easy to extend and that gives you so much more control over the visual part and you are in Episerver context so you can use the built in stuff.

As Johan say, if you have GUID already make shore to use them so the process can be repeatable.

Read more here on how you can extend the interface in an easy and flexable way

https://world.episerver.com/blogs/Henrik-Fransas/Dates/2016/11/extending-the-episerver-editor-interface/

#192695
May 23, 2018 10:14
Vote:
 

Thank you both! I think I would rather have it as an admin plugin under the tools section than such a prominent item in the main admin menu because this won't be used often, if ever again. However, your links may be helpful for other purposes in the future. The fact that it may never be used again is also why I was leaning towards a console app. I think we're actually going to be able to create the content faster manually, but I'm actually just curious how to set up a console app now. I sort of, kind of get the jist of what you are saying, Johan, but I'm not sure I could figure it out on my own. Some sample code would be great if you have the time!

#193144
May 24, 2018 0:30
Vote:
 

To run in a console app you would need to take dependencies to packages EPiServer.CMS.Core and EPiServer.ServiceLocation.StructureMap

And here is some code to setup CMS to run in a console app. This sample just list the name of the startpage (for the wildcard site) and its children (you need to change the connection string to the database you want to initialize against) but you could change it to for example create content (you would need to reference the assembly with your content models though):

namespace CmsConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var engine = new InitializationEngine((IEnumerable<IInitializableModule>)null, HostType.WebApplication, EPiServerAssemblies());
            engine.Initialize();

            var contentRepository = engine.Locate.Advanced.GetInstance<IContentRepository>();
            var startPage = contentRepository.Get<IContent>(SiteDefinition.Current.StartPage);
            Console.WriteLine("Site Structure:");
            Console.WriteLine(startPage.Name);
            foreach (var child in contentRepository.GetChildren<IContent>(startPage.ContentLink))
                Console.WriteLine($"--{child.Name}");

            engine.Uninitialize();
        }

        private static IEnumerable<Assembly> EPiServerAssemblies()
        {
            var assemblies = new List<Assembly>();
            assemblies.Add(typeof(Program).Assembly);//this
            assemblies.Add(typeof(EPiServer.Data.ConnectionStringOptions).Assembly);//EPiServer.Data
            assemblies.Add(typeof(EPiServer.Framework.InitializableModuleAttribute).Assembly);//EPiServer.Framework
            assemblies.Add(typeof(EPiServer.Core.IContent).Assembly);//EPiServer
            assemblies.Add(typeof(EPiServer.Enterprise.IDataExporter).Assembly);//EPiServer.Enterprise
            assemblies.Add(typeof(EPiServer.Personalization.VisitorGroups.VisitorGroupCriterion).Assembly);//EPiServer.ApplicationModules
            assemblies.Add(typeof(EPiServer.Events.EventMessage).Assembly);//EPiServer.Events
            assemblies.Add(typeof(EPiServer.ServiceLocation.StructureMapServiceLocator).Assembly);//EPiServer.ServiceLocation.StructureMap
            return assemblies;
        }
    }

    [ModuleDependency(typeof(EPiServer.Data.DataInitialization))]
    public class DataAccessInitialization : IConfigurableModule
    {
        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.Services.Configure<DataAccessOptions>(o => o.SetConnectionString("<your connection string>"));
        }

        public void Initialize(InitializationEngine context)
        {}

        public void Uninitialize(InitializationEngine context)
        {}
    }
}
#193169
May 24, 2018 10:12
Vote:
 

Thanks for that example Johan, I might use that sometime :-)

#193170
May 24, 2018 10:35
Vote:
 

Thank you, Johan. Helpful as always!

#193596
May 31, 2018 22:35
Vote:
 

I've been trying this out now, but I am receiving an ArgumentNullException on the line that gets the start page:

var startPage = contentRepository.Get<IContent>(SiteDefinition.Current.StartPage);

StartPage's ID is 0 and the exception message is "The provided content link does not have a value." I tried changing it from StartPage to RootPage which, instead, gives me a LicenseException complaining that License.config doesn't exist for my console app (which it doesn't, but it also doesn't exist for my web app when I run it out of Visual Studio and that doesn't get any  LicenseExceptions - why would the console app need a license file but not the web app?) I get the same results when connecting to my local .mdf that I use when running the site from Visual Studio as when connecting to the deployed site's SQL Server hosted database. Any ideas?

#193765
Jun 04, 2018 23:27
Vote:
 

what host mappings you do have defined for your site? might be that you are missing wildard (`*`) definition for the site?

#193768
Jun 05, 2018 6:54
Vote:
 

For a web application is ContentReference.StartPage resolved from the http request. In a console application is that not possible of course so in that case will ContentReference.StartPage be the site with the wildcard ('*') host. An alternative is to use ISiteDefinitionRepository and load your preferable site and then assign that site to SiteDefintion.Current.

The reason you do not get a license exception when you run your web site in dev is that no license check is performed when running under IIS Express. But running a site under IIS or as a console application requires a valid license.

#193774
Jun 05, 2018 8:46
Vote:
 

Yeah, I didn't have a wildcard host. I had tried that but I think another problem prevented it from looking fixed. That took care of it, thanks! For the license issue, I have to run the console app on the server using its license file. Very inconvenient not to be able to test it on my own development machine :(. When it came to saving, I had to also include a reference to EPiServer.UI for, what looked like validation, to succeed. Possibly related to a extended SelectOneAttribute (EnumAttribute) that hooks up enum properties to an ISelectionFactory. 

Now I'm just trying to figure out why my property values won't save. Thanks for your help with getting this going!

#193937
Edited, Jun 07, 2018 21:24
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.