9/2/2020 Admin

Creating a Custom Distribution of Blazor Oqtane Using Site Templates


Blazor Oqtane allows you to create a Custom Distribution that installs your custom modules and custom content. This allows you to distribute Oqtane in a way, that when an end-user installs it, the site has your custom module configured and ready to work.

Oqtane

Oqtane is an application that is built using Microsoft’s Blazor technology. It allows you to deploy and run modules written in Blazor. When Oqtane is deployed and running, it provides a dynamic web experience that can be run as client side Blazor or as server side Blazor.

You can find out more about Oqtane at this link: www.oqtane.org

Custom Site Templates

The functionality is implemented using custom Site Templates.

Shaun Walker, the creator of Oqtane, and the author of the feature, describes it this way:

“The focus of Oqtane is to allow developers to build web applications more productively. The requirement is that a developer may want to create a custom theme, some custom modules, and a custom site template. They would package these with Oqtane as a "distribution". When a user installs the system it would automatically install all of the modules, theme, and site template to create a custom application for a specific business purpose.”

“What I am envisioning here is a simple interface ( ie. ISiteTemplate ) which has a Name property and a generic method for CreateSite(Site site). Within this method a developer would have full control over the site creation process. They could create any entities they desire ( ie. users, roles, pages, modules, folders, files, jobs, etc.... ).”

“…Inside the CreateSite() method, the developer would have the ability to call any Repository method it wants - ie, AddPage(), AddRole(), etc...”

Examine the Default Template

image

The Site Template, used when installing Oqtane, is defined in the SiteTemplate property in the appsettings.json file.

If you don't define a Site Template the Default Template is used.

image

A Site Template inherits from ISiteTemplate and implements a CreateSite method, that takes a Site object as a parameter, and returns a collection of PageTemplate.

A PageTemplate can contain a collection of PageTemplateModule that is used to define any modules that are to be loaded on the page.

The full definition is below:

 public class SiteTemplate
    {
        public string Name { get; set; }
        public string TypeName { get; set; }
    }
    public class PageTemplate
    {
        public string Name { get; set; }
        public string Parent { get; set; }
        public string Path { get; set; }
        public string Icon { get; set; }
        public bool IsNavigation { get; set; }
        public bool IsPersonalizable { get; set; }
        public string PagePermissions { get; set; }
        public List<PageTemplateModule> PageTemplateModules { get; set; }
        [Obsolete("This property is obsolete", false)]
        public bool EditMode { get; set; }
    }
    public class PageTemplateModule
    {
        public string ModuleDefinitionName { get; set; }
        public string Title { get; set; }
        public string Pane { get; set; }
        public string ModulePermissions { get; set; }
        public string Content { get; set; }
    }

 

image

Oqtane includes two Site Templates, DefaultSiteTemplate and EmptySiteTemplate.

Below is an example of code from the DefaultSiteTemplate (with the content edited for brevity).

Notice that it defines a PageTemplate, ads a HtmlText module, and sets the content of the HtmlText module:

 

   public class DefaultSiteTemplate : ISiteTemplate
    {
        private readonly IWebHostEnvironment _environment;
        private readonly ISiteRepository _siteRepository;
        private readonly IFolderRepository _folderRepository;
        private readonly IFileRepository _fileRepository;
        public DefaultSiteTemplate(IWebHostEnvironment environment, 
            ISiteRepository siteRepository, 
            IFolderRepository folderRepository, 
            IFileRepository fileRepository)
        {
            _environment = environment;
            _siteRepository = siteRepository;
            _folderRepository = folderRepository;
            _fileRepository = fileRepository;
        }
        public string Name
        {
            get { return "Default Site Template"; }
        }
        public List<PageTemplate> CreateSite(Site site)
        {
            List<PageTemplate> _pageTemplates = new List<PageTemplate>();
            _pageTemplates.Add(new PageTemplate
            {
                Name = "Home",
                Parent = "",
                Path = "",
                Icon = "home",
                IsNavigation = true,
                IsPersonalizable = false,
                PagePermissions = new List<Permission> {
                    new Permission(PermissionNames.View, Constants.AllUsersRole, true),
                    new Permission(PermissionNames.View, Constants.AdminRole, true),
                    new Permission(PermissionNames.Edit, Constants.AdminRole, true)
                }.EncodePermissions(),
                PageTemplateModules = new List<PageTemplateModule> {
                    new PageTemplateModule {
                        ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client",
                        Title = "Welcome To Oqtane...", Pane = "Content",
                        ModulePermissions = new List<Permission> {
                            new Permission(PermissionNames.View, Constants.AllUsersRole, true),
                            new Permission(PermissionNames.View, Constants.AdminRole, true),
                            new Permission(PermissionNames.Edit, Constants.AdminRole, true)
                        }.EncodePermissions(),
                        Content = "<p>Hello World!</p>"
                    }
                }
            });
            return _pageTemplates;
        }
    }

 

Templates can also perform any other logic, that can be executed using C# code.


The following example shows how the Default Template copies the file for the Oqtane logo, to the proper location, and adds a reference to it in the database:

 

    if (System.IO.File.Exists(
        Path.Combine(_environment.WebRootPath, "images", "logo-white.png")))
    {
        string folderpath = Utilities.PathCombine(
            _environment.ContentRootPath, "Content", "Tenants", site.TenantId.ToString(), 
            "Sites", site.SiteId.ToString(), Path.DirectorySeparatorChar.ToString());
        System.IO.Directory.CreateDirectory(folderpath);
        if (!System.IO.File.Exists(Path.Combine(folderpath, "logo-white.png")))
        {
            System.IO.File.Copy(Path.Combine(_environment.WebRootPath, 
                "images", "logo-white.png"), Path.Combine(folderpath, "logo-white.png"));
        }
        Folder folder = _folderRepository.GetFolder(site.SiteId, "");
        Oqtane.Models.File file = _fileRepository.AddFile(
            new Oqtane.Models.File { 
                FolderId = folder.FolderId, 
                Name = "logo-white.png", 
                Extension = "png", Size = 8192, 
                ImageHeight = 80, ImageWidth = 250 
            });
        site.LogoFileId = file.FileId;
        _siteRepository.UpdateSite(site);
    }

 

Creating A Custom Template

image

To create our own custom Site Template, we can add a class to:

oqtane.framework\Oqtane.Server\Infrastructure\SiteTemplates\CustomSiteTemplate.cs

Note: You do not have to alter the Oqtane source code to create a new template, you can simply create a separate project (that inherits from ISiteTemplate) and include your assembly (.dll) in the distribution. You would need to also include the code/assemblys for any custom modules you want to load in the template.

We use the following code:

 

using Oqtane.Models;
using Oqtane.Infrastructure;
using System.Collections.Generic;
using Oqtane.Repository;
using Microsoft.AspNetCore.Hosting;
using Oqtane.Extensions;
using Oqtane.Shared;
using System.IO;
namespace Oqtane.SiteTemplates
{
    public class CustomSiteTemplate : ISiteTemplate
    {
        private readonly IWebHostEnvironment _environment;
        private readonly ISiteRepository _siteRepository;
        private readonly IFolderRepository _folderRepository;
        private readonly IFileRepository _fileRepository;
        public CustomSiteTemplate(
            IWebHostEnvironment environment, 
            ISiteRepository siteRepository, 
            IFolderRepository folderRepository, 
            IFileRepository fileRepository)
        {
            _environment = environment;
            _siteRepository = siteRepository;
            _folderRepository = folderRepository;
            _fileRepository = fileRepository;
        }
        public string Name
        {
            get { return "Custom Site Template"; }
        }
        public List<PageTemplate> CreateSite(Site site)
        {
            List<PageTemplate> _pageTemplates = 
                new List<PageTemplate>();
            _pageTemplates.Add(new PageTemplate
            {
                Name = "Home",
                Parent = "",
                Path = "",
                Icon = "home",
                IsNavigation = true,
                IsPersonalizable = false,
                PagePermissions = new List<Permission> {
                    new Permission(PermissionNames.View, 
                    Constants.AllUsersRole, true),
                    new Permission(PermissionNames.View, 
                    Constants.AdminRole, true),
                    new Permission(PermissionNames.Edit, 
                    Constants.AdminRole, true)
                }.EncodePermissions(),
                PageTemplateModules = new List<PageTemplateModule> {
                    new PageTemplateModule { 
                        ModuleDefinitionName = 
                        "Oqtane.Modules.HtmlText, Oqtane.Client", 
                        Title = "Welcome To Oqtane...", 
                        Pane = "Content",
                        ModulePermissions = new List<Permission> {
                            new Permission(PermissionNames.View, 
                            Constants.AllUsersRole, true),
                            new Permission(PermissionNames.View, 
                            Constants.AdminRole, true),
                            new Permission(PermissionNames.Edit, 
                            Constants.AdminRole, true)
                        }.EncodePermissions(),
                        Content = "<h4>This is my Custom Site Template</h4>"
                    }
                }
            });
            if (System.IO.File.Exists(Path.Combine(
                _environment.WebRootPath, 
                "images", "logo-white.png")))
            {
                string folderpath = Utilities.PathCombine(
                    _environment.ContentRootPath, 
                    "Content", 
                    "Tenants", 
                    site.TenantId.ToString(), 
                    "Sites", site.SiteId.ToString(), 
                    Path.DirectorySeparatorChar.ToString());
                System.IO.Directory.CreateDirectory(folderpath);
                if (!System.IO.File.Exists(Path.Combine(folderpath, 
                    "logo-white.png")))
                {
                    System.IO.File.Copy(Path.Combine(_environment.WebRootPath, 
                        "images", "logo-white.png"), Path.Combine(
                            folderpath, 
                            "logo-white.png"));
                }
                Folder folder = _folderRepository.GetFolder(site.SiteId, "");
                Oqtane.Models.File file = _fileRepository.AddFile(
                    new Oqtane.Models.File { 
                        FolderId = folder.FolderId, Name = "logo-white.png", 
                        Extension = "png", 
                        Size = 8192, 
                        ImageHeight = 80, 
                        ImageWidth = 250 });
                site.LogoFileId = file.FileId;
                _siteRepository.UpdateSite(site);
            }
            return _pageTemplates;
        }
    }
}

 

We set the template to load, when Oqtane is installed, by setting the SiteTemplate property in the appsettings.json file:

 

{
  "ConnectionStrings": {
    "DefaultConnection": ""
  },
  "Runtime": "Server",
  "Installation": {
    "DefaultAlias": "",
    "HostPassword": "",
    "HostEmail": "",
    "SiteTemplate": "Oqtane.SiteTemplates.CustomSiteTemplate, Oqtane.Server",
    "DefaultTheme": "",
    "DefaultLayout": "",
    "DefaultContainer": ""
  }
}

 

image

When we install Oqtane, the custom Site Template is used.

 

Using Deploy To Azure

image

If we want to use the Deploy to Azure button (from our own GitHub repository), we can do that with the following additional steps.

image

In this example the code will reside in its own GitHub project called AdefWebserver/oqtane.framework, in its own GitHub branch,  called custom-template.

We update the azuredeploy.json file, in the root of the project, to point to the repository and the branch:

 

image

 

Next, we update the README.md file, in the root of the project…

image

We alter the url of the Deploy to Azure button to point to the azuredeploy.json file in the root of the project (note that it is URL encoded):

 

https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fadefwebserver%2Foqtane.framework%2Fcustom-template%2Fazuredeploy.json

 

When the Deploy to Azure button is clicked, a wizard will display and allow the user to install Oqtane using the custom Site Template.

For more information see: Oqtane Deploy to Azure.

 

Links

What is Blazor Oqtane?

ISiteTemplate ideas and extensions #296

Sites should be created based on ISiteTemplate #281

Oqtane Deploy to Azure

An unhandled error has occurred. Reload 🗙