Custom modules

Overview

Sitefinity CMS comes with a set of built-in modules, which can be directly used in the pages you create. You can also create your own custom modules and use them side by side with the default ones.

NOTE: Depending on your use case scenario, the officially recommended approach whether to proceed by creating a custom module is as follows:
  • For modules that extend the out-of-the-box functionality of Sitefinity CMS, such as adding integration, widgets, logic, and so on, use custom modules.
  • If you want to define new content types in Sitefinity CMS, use the Module Builder.
    For more information, see Dynamic modules and the Module Builder.

Create a custom module

Each Sitefinity CMS module must inherit from the ModuleBase class and override the Install method. In it, add the logic for the actions the module performs when getting added to a Sitefinity CMS site. For example, registering some configuration, creating backend pages, adding widgets and so on.

You can leverage the Sitefinity VSIX and CLI tools to get started with building custom modules. Both Sitefinity VSIX and Sitefinity CLI enable you to add a new custom module project with the necessary scaffolded module classes, containing inline guidance.

EXAMPLE: To create a general purpose module in Sitefinity CMS, with minimum implementation effort, refer to the sample Database diagnostic module. The module class contains inline documentation for each method used and can guide you into the purpose of each of them.

Install the module

To facilitate modules decoupling, Sitefinity CMS provides an easy mechanism for installing modules automatically. This way, you can create custom modules, which are installed automatically upon Sitefinity CMS startup. For your module to be discovered and auto-installed at startup, you need to mark it with  the SitefinityModuleAttribute assembly attribute.

While the website is initializing, Sitefinity CMS looks for assemblies in the /bin folder, containing modules marked with this attribute and installs them. The SitefinityModuleAttribute requires you to specify your module name, type, title, description, startup type, module ID, and resource class ID if applicable. 

Implement custom modules

In addition to the full implementation of the Database diagnostic module, discussed above, below you can find below an example of a dummy Sitefinity module that you can use as a scaffolding for your custom implementations if you prefer to start from scratch:

using DocumentationSamples.ModuleSamples;
using System;
using System.Collections.Specialized;
using Telerik.OpenAccess.Metadata;
using Telerik.Sitefinity;
using Telerik.Sitefinity.Abstractions;
using Telerik.Sitefinity.Configuration;
using Telerik.Sitefinity.Data;
using Telerik.Sitefinity.Fluent.Definitions;
using Telerik.Sitefinity.Fluent.Modules;
using Telerik.Sitefinity.Localization;
using Telerik.Sitefinity.Model;
using Telerik.Sitefinity.Modules.GenericContent.Configuration;
using Telerik.Sitefinity.Security;
using Telerik.Sitefinity.Security.Configuration;
using Telerik.Sitefinity.Services;
using Telerik.Sitefinity.Web.UI.ContentUI;
using Telerik.Sitefinity.Web.UI.ContentUI.Config;
using Telerik.Sitefinity.Workflow.Model.Tracking;
[assembly: SitefinityModule(DummyModule.moduleName, typeof(DummyModule), "DummyModuleTitle", "DummyModuleDescription", StartupType.OnApplicationStart)]
namespace DocumentationSamples.ModuleSamples
{
public class DummyModule : ModuleBase
{
public override Guid LandingPageId
{
get { return homePageId; }
}
public override void Initialize(ModuleSettings settings)
{
base.Initialize(settings);
App.WorkWith().Module(Name)
.Initialize()
.Configuration<DummyModuleConfig>()
.Localization<DummyModuleResources>();
}
public override void Install(SiteInitializer initializer)
{
initializer.Installer
.CreateModuleGroupPage(groupPageId, "Dummy")
.PlaceUnder(CommonNode.TypesOfContent)
.LocalizeUsing<DummyModuleResources>()
.SetTitleLocalized("PageGroupNodeTitle")
.SetUrlName("dummy")
.SetDescriptionLocalized("PageGroupNodeDescription")
.AddChildPage(DummyModule.homePageId, "DummyHome")
.LocalizeUsing<DummyModuleResources>()
.SetTitleLocalized("DummyTitle")
.SetHtmlTitleLocalized("DummyTitle")
.SetUrlName("dummyhome")
.SetDescriptionLocalized("DummyDescription")
.AddContentView(DummyModuleDefinitions.BackendDefinitionName, "DummyBackendView")
.Done()
.Done()
.PageToolbox()
.ContentSection()
.LoadOrAddWidget<DummyModuleView>("DummyView")
.SetTitle("DummyViewTitle")
.SetDescription("DummyViewDescription")
.LocalizeUsing<DummyModuleResources>()
.SetCssClass("sfDummyViewIcn")
.Done()
.Done()
.Done()
.AddWorkflowType<DummyModuleItem>()
.SetTitle("DummyTitle")
.SetServiceUrl("~/Workflows/DummyWorkflow.xamlx")
.LocalizeUsing<DummyModuleResources>()
.Done();
}
public override void Upgrade(SiteInitializer initializer, Version upgradeFrom)
{
}
protected override ConfigSection GetModuleConfig()
{
return Config.Get<DummyModuleConfig>();
}
public override Type[] Managers
{
get { return new Type[] { typeof(DummyModuleManager) }; }
}
internal static Guid groupPageId = new Guid("AB45CA65-B763-4C8A-84F1-442F701D3971");
internal static Guid homePageId = new Guid("AB45CA65-B763-4C8A-84F1-442F701D3972");
internal const string moduleName = "DummyModule";
}
internal class DummyModuleConfig : ContentModuleConfigBase
{
protected override void InitializeDefaultViews(ConfigElementDictionary<string, ContentViewControlElement> contentViewControls)
{
contentViewControls.Add(DummyModuleDefinitions.BackendDefinitionName,
DummyModuleDefinitions.DefineDummyBackendContentView(contentViewControls));
}
protected override void InitializeDefaultProviders(ConfigElementDictionary<string, DataProviderSettings> providers)
{
providers.Add(new DataProviderSettings(providers)
{
Name = "OpenAccessDataProvider",
Description = "A provider that stores dummy module data in database using OpenAccess ORM.",
ProviderType = typeof(OpenAccessDummyModuleProvider),
Parameters = new NameValueCollection() { { "applicationName", "/Dummy" } }
});
}
protected override void OnPropertiesInitialized()
{
base.OnPropertiesInitialized();
InitializeDefaultPermissions();
}
protected virtual void InitializeDefaultPermissions()
{
var permission = new Permission(Permissions)
{
Name = "Dummy",
Title = "Dummy",
};
Permissions.Add(permission);
permission.Actions.Add(new SecurityAction(permission.Actions)
{
Name = "View",
Type = SecurityActionTypes.View,
Title = "ViewDummy",
});
var customPermissionsDisplaySettings = CustomPermissionsDisplaySettings;
var generalCustomSet = new CustomPermissionsDisplaySettingsConfig(customPermissionsDisplaySettings)
{
SetName = SecurityConstants.Sets.General.SetName
};
customPermissionsDisplaySettings.Add(generalCustomSet);
var newsCustomActions = new SecuredObjectCustomPermissionSet(generalCustomSet.SecuredObjectCustomPermissionSets)
{
TypeName = typeof(DummyModuleItem).FullName
};
generalCustomSet.SecuredObjectCustomPermissionSets.Add(newsCustomActions);
var newsCreateAction = new CustomSecurityAction(newsCustomActions.CustomSecurityActions)
{
Name = SecurityConstants.Sets.General.Create,
ShowActionInList = false
};
newsCustomActions.CustomSecurityActions.Add(newsCreateAction);
var newsModifyAction = new CustomSecurityAction(newsCustomActions.CustomSecurityActions)
{
Name = SecurityConstants.Sets.General.Modify,
ShowActionInList = true,
Title = "ModifyThisItem",
ResourceClassId = typeof(SecurityResources).Name
};
newsCustomActions.CustomSecurityActions.Add(newsModifyAction);
var newsViewAction = new CustomSecurityAction(newsCustomActions.CustomSecurityActions)
{
Name = SecurityConstants.Sets.General.View,
ShowActionInList = true,
Title = "ViewThisItem",
ResourceClassId = typeof(SecurityResources).Name
};
newsCustomActions.CustomSecurityActions.Add(newsViewAction);
var newsDeleteAction = new CustomSecurityAction(newsCustomActions.CustomSecurityActions)
{
Name = SecurityConstants.Sets.General.Delete,
ShowActionInList = true,
Title = "DeleteThisItem",
ResourceClassId = typeof(SecurityResources).Name
};
newsCustomActions.CustomSecurityActions.Add(newsDeleteAction);
var newsChangeOwnerAction = new CustomSecurityAction(newsCustomActions.CustomSecurityActions)
{
Name = SecurityConstants.Sets.General.ChangeOwner,
ShowActionInList = true,
Title = "ChangeOwnerOfThisItem",
ResourceClassId = typeof(SecurityResources).Name
};
newsCustomActions.CustomSecurityActions.Add(newsChangeOwnerAction);
}
}
public class DummyModuleManager : ManagerBase<DummyModuleDataProvider>
{
public DummyModuleManager()
: this(null)
{
}
public DummyModuleManager(string providerName)
: base(providerName)
{
}
public DummyModuleManager(string providerName, string transactionName)
: base(providerName, transactionName)
{
}
protected override GetDefaultProvider DefaultProviderDelegate
{
get { return () => Config.Get<DummyModuleConfig>().DefaultProvider; }
}
public override string ModuleName
{
get { return DummyModule.moduleName; }
}
protected override ConfigElementDictionary<string, DataProviderSettings> ProvidersSettings
{
get { return Config.Get<DummyModuleConfig>().Providers; }
}
public static DummyModuleManager GetManager()
{
return ManagerBase<DummyModuleDataProvider>.GetManager<DummyModuleManager>();
}
public static DummyModuleManager GetManager(string providerName, string transactionName)
{
return ManagerBase<DummyModuleDataProvider>.GetManager<DummyModuleManager>(providerName, transactionName);
}
public static DummyModuleManager GetManager(string providerName)
{
return ManagerBase<DummyModuleDataProvider>.GetManager<DummyModuleManager>(providerName);
}
}
public class DummyModuleDataProvider : DataProviderBase
{
public override Type[] GetKnownTypes()
{
return new Type[] { typeof(DummyModuleItem) };
}
public override string RootKey
{
get { return "DummyModule"; }
}
}
public class OpenAccessDummyModuleProvider : DummyModuleDataProvider, IOpenAccessDataProvider
{
public OpenAccessProviderContext Context
{
get;
set;
}
public MetadataSource GetMetaDataSource(IDatabaseMappingContext context)
{
return null;
}
}
internal class DummyModuleItem : IWorkflowItem
{
public Guid Id
{
get { throw new NotImplementedException(); }
}
public ApprovalTrackingRecordMap ApprovalTrackingRecordMap
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
}
internal class DummyModuleResources : Resource
{
[ResourceEntry(
"PageGroupNodeTitle",
Value = "Dummy Group",
Description = "",
LastModified = "2012/04/19")
]
public string PageGroupNodeTitle
{
get { return this["PageGroupNodeTitle"]; }
}
[ResourceEntry(
"PageGroupNodeDescription",
Value = "Dummy Group Description",
Description = "",
LastModified = "2012/04/19")
]
public string PageGroupNodeDescription
{
get { return this["PageGroupNodeDescription"]; }
}
[ResourceEntry(
"DummyTitle",
Value = "Dummy",
Description = "",
LastModified = "2012/04/19")
]
public string DummyTitle
{
get { return this["DummyTitle"]; }
}
[ResourceEntry(
"DummyDescription",
Value = "Dummy Description",
Description = "",
LastModified = "2012/04/19")
]
public string DummyDescription
{
get { return this["DummyDescription"]; }
}
[ResourceEntry(
"DummyViewTitle",
Value = "Dummy View",
Description = "",
LastModified = "2012/04/19")
]
public string DummyViewTitle
{
get { return this["DummyViewTitle"]; }
}
[ResourceEntry(
"DummyViewDescription",
Value = "Dummy View Frontend Control",
Description = "",
LastModified = "2012/04/19")
]
public string DummyViewDescription
{
get { return this["DummyViewDescription"]; }
}
}
internal class DummyModuleView : ContentView
{
}
internal static class DummyModuleDefinitions
{
internal static ContentViewControlElement DefineDummyBackendContentView(ConfigElement parent)
{
// define content view control
var fluentContentView = App.WorkWith().Module(DummyModule.moduleName).DefineContainer(parent, BackendDefinitionName, typeof(DummyModuleItem));
var backendContentView = fluentContentView.Get();
backendContentView.ViewsConfig.AddLazy(DummyModuleDefinitions.BackendListViewName, () => DefineNewsBackendListView(fluentContentView));
return backendContentView;
}
private static ConfigElement DefineNewsBackendListView(ContentViewControlDefinitionFacade fluentContentView)
{
var fluentMasterView = fluentContentView
.AddMasterView(DummyModuleDefinitions.BackendListViewName)
.LocalizeUsing<DummyModuleResources>()
.SetTitle("DummyTitle")
.SetServiceBaseUrl("~/Sitefinity/Services/Content/DummyItemService.svc/");
var newsGridView = fluentMasterView.Get();
return newsGridView;
}
public const string BackendDefinitionName = "DummModuleBackend";
public const string BackendListViewName = "DummModuleBackendList";
}
}
view raw DummyModule.cs hosted with ❤ by GitHub

Want to learn more?

Increase your Sitefinity skills by signing up for our free trainings. Get Sitefinity-certified at Progress Education Community to boost your credentials.

Get started with Integration Hub | Sitefinity Cloud | Sitefinity SaaS

This free lesson teaches administrators, marketers, and other business professionals how to use the Integration hub service to create automated workflows between Sitefinity and other business systems.

Web Security for Sitefinity Administrators

This free lesson teaches administrators the basics about protecting yor Sitefinity instance and its sites from external threats. Configure HTTPS, SSL, allow lists for trusted sites, and cookie security, among others.

Foundations of Sitefinity ASP.NET Core Development

The free on-demand video course teaches developers how to use Sitefinity .NET Core and leverage its decoupled architecture and new way of coding against the platform.

Was this article helpful?