From 15f2053086b1b041961a5a0ab037e1c89c539e75 Mon Sep 17 00:00:00 2001 From: Gerard Smit Date: Wed, 11 Oct 2023 00:18:10 +0200 Subject: [PATCH] Add ISkinService for Abstractions Project Fixes #5837 --- .../Skins/ISkinPackageInfo.cs | 30 ++++ .../Skins/ISkinService.cs | 129 ++++++++++++++++++ .../Skins/SkinScope.cs | 32 +++++ .../DotNetNuke.Abstractions/Skins/SkinType.cs | 21 +++ DNN Platform/Library/Startup.cs | 4 + .../Library/UI/Skins/SkinController.cs | 122 +++++++++++++++-- .../Library/UI/Skins/SkinPackageInfo.cs | 58 ++++++-- DNN Platform/Library/UI/Skins/SkinScope.cs | 23 ++-- DNN Platform/Library/UI/Skins/SkinType.cs | 22 ++- 9 files changed, 388 insertions(+), 53 deletions(-) create mode 100644 DNN Platform/DotNetNuke.Abstractions/Skins/ISkinPackageInfo.cs create mode 100644 DNN Platform/DotNetNuke.Abstractions/Skins/ISkinService.cs create mode 100644 DNN Platform/DotNetNuke.Abstractions/Skins/SkinScope.cs create mode 100644 DNN Platform/DotNetNuke.Abstractions/Skins/SkinType.cs diff --git a/DNN Platform/DotNetNuke.Abstractions/Skins/ISkinPackageInfo.cs b/DNN Platform/DotNetNuke.Abstractions/Skins/ISkinPackageInfo.cs new file mode 100644 index 00000000000..da147469a63 --- /dev/null +++ b/DNN Platform/DotNetNuke.Abstractions/Skins/ISkinPackageInfo.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information + +namespace DotNetNuke.Abstractions.Skins; + +using System.Collections.Generic; + +/// The skin package info. +public interface ISkinPackageInfo +{ + /// Gets or sets the ID of the package. + int PackageId { get; set; } + + /// Gets or sets the ID of the skin package. + int SkinPackageId { get; set; } + + /// Gets or sets the ID of the portal. + /// If the portal ID is -1, then the skin package is a global skin package. + int PortalId { get; set; } + + /// Gets or sets the name of the skin. + string SkinName { get; set; } + + /// Gets or sets the skins in the skin package. + Dictionary Skins { get; set; } + + /// Gets or sets the type of the skin. + string SkinType { get; set; } +} diff --git a/DNN Platform/DotNetNuke.Abstractions/Skins/ISkinService.cs b/DNN Platform/DotNetNuke.Abstractions/Skins/ISkinService.cs new file mode 100644 index 00000000000..9a2e0fa8323 --- /dev/null +++ b/DNN Platform/DotNetNuke.Abstractions/Skins/ISkinService.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information + +namespace DotNetNuke.Abstractions.Skins; + +using System.Collections.Generic; + +using DotNetNuke.Abstractions.Portals; +using DotNetNuke.UI.Skins; + +/// Handles the Business Control Layer for Skins. +public interface ISkinService +{ + /// Gets the root folder for the skins. + string RootSkin { get; } + + /// Gets the root folder for the containers. + string RootContainer { get; } + + /// Adds a skin to the skin package. + /// The skin package id. + /// The skin source path. + /// The skin id. + int AddSkin(int skinPackageId, string skinSrc); + + /// Adds a skin package. + /// The skin package to add. + /// The skin package id. + int AddSkinPackage(ISkinPackageInfo skinPackage); + + /// Checks if a skin can be deleted. + /// Path to the skin folder. + /// Path to the portal home directory (). + /// True if the skin can be deleted. + bool CanDeleteSkin(string folderPath, string portalHomeDirMapPath); + + /// Deletes a skin. + /// The skin to delete. + void DeleteSkin(int skinId); + + /// Deletes a skin package. + /// The skin package to delete. + void DeleteSkinPackage(ISkinPackageInfo skinPackage); + + /// Gets the global default admin container. + /// + /// This is not the default admin container for the portal. + /// To get the default admin container for the portal use instead. + /// + /// The global default admin container. + string GetDefaultAdminContainer(); + + /// Gets the global default admin skin. + /// + /// This is not the default admin skin for the portal. + /// To get the default admin skin for the portal use instead. + /// + /// The global default admin skin. + string GetDefaultAdminSkin(); + + /// Gets the global default container. + /// + /// This is not the default container for the portal. + /// To get the default container for the portal use instead. + /// + /// The global default container. + string GetDefaultPortalContainer(); + + /// Gets the global default skin. + /// + /// This is not the default skin for the portal. + /// To get the default skin for the portal use instead. + /// + /// The global default skin. + string GetDefaultPortalSkin(); + + /// Gets the skin source path. + /// + /// [G]Skins/Xcillion/Inner.ascx becomes [G]Skins/Xcillion. + /// + /// The input skin source path. + /// The skin source path. + string FormatSkinPath(string skinSrc); + + /// Formats the skin source path. + /// + /// By default the following tokens are replaced:
+ /// [G] - Host path (default: '/Portals/_default/').
+ /// [S] - Home system directory (default: '/Portals/[PortalID]-System/').
+ /// [L] - Home directory (default: '/Portals/[PortalID]/'). + ///
+ /// + /// [G]Skins/Xcillion/Inner.ascx becomes /Portals/_default/Skins/Xcillion/Inner.ascx. + /// + /// The input skin source path. + /// The portal settings containing configuration data. + /// The formatted skin source path. + string FormatSkinSrc(string skinSrc, IPortalSettings portalSettings); + + /// Determines if a given skin is defined as a global skin. + /// This is the app relative path and filename of the skin to be checked. + /// True if the skin is located in the HostPath child directories. + /// This function performs a quick check to detect the type of skin that is + /// passed as a parameter. Using this method abstracts knowledge of the actual location + /// of skins in the file system. + /// + bool IsGlobalSkin(string skinSrc); + + /// Sets the skin for the specified and . + /// The root folder of the skin. Either or . + /// The portal to set the skin for. + /// The type of the skin. + /// The skin source path. + /// Thrown if is not or . + void SetSkin(string skinRoot, int portalId, SkinType skinType, string skinSrc); + + /// Sets the skin source path. + /// The skin to set the source path for. + /// The skin source path. + void UpdateSkin(int skinId, string skinSrc); + + /// Get all skins for the specified within the specified . + /// The portal to get the skins for. + /// The root folder to search for skins. Default: . + /// The scope to search for skins. Default: All. + /// A list of skin names and their source paths. + IEnumerable> GetSkins(IPortalInfo portalInfo, string skinRoot = null, SkinScope scope = SkinScope.All); +} diff --git a/DNN Platform/DotNetNuke.Abstractions/Skins/SkinScope.cs b/DNN Platform/DotNetNuke.Abstractions/Skins/SkinScope.cs new file mode 100644 index 00000000000..d338b966eb1 --- /dev/null +++ b/DNN Platform/DotNetNuke.Abstractions/Skins/SkinScope.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information + +// ReSharper disable once CheckNamespace +namespace DotNetNuke.UI.Skins; + +using DotNetNuke.Abstractions.Skins; + +/// +/// The scope of a skin. +/// +/// +/// This enum is used for . +/// +public enum SkinScope +{ + /// + /// The skin is applied to all portals and the current portal. + /// + All = 0, + + /// + /// The skin is applied to all portals. + /// + Host = 1, + + /// + /// The skin is applied to the current portal only. + /// + Site = 2, +} diff --git a/DNN Platform/DotNetNuke.Abstractions/Skins/SkinType.cs b/DNN Platform/DotNetNuke.Abstractions/Skins/SkinType.cs new file mode 100644 index 00000000000..387183e10d5 --- /dev/null +++ b/DNN Platform/DotNetNuke.Abstractions/Skins/SkinType.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information + +namespace DotNetNuke.UI.Skins; + +/// +/// The type of a skin. +/// +public enum SkinType +{ + /// + /// The skin is a portal skin. + /// + Portal = 0, + + /// + /// The skin is an admin skin. + /// + Admin = 1, +} diff --git a/DNN Platform/Library/Startup.cs b/DNN Platform/Library/Startup.cs index 0e50da81a46..bb82fdc7519 100644 --- a/DNN Platform/Library/Startup.cs +++ b/DNN Platform/Library/Startup.cs @@ -13,6 +13,7 @@ namespace DotNetNuke using DotNetNuke.Abstractions.Portals; using DotNetNuke.Abstractions.Portals.Templates; using DotNetNuke.Abstractions.Prompt; + using DotNetNuke.Abstractions.Skins; using DotNetNuke.Application; using DotNetNuke.Common; using DotNetNuke.Common.Internal; @@ -35,6 +36,7 @@ namespace DotNetNuke using DotNetNuke.Services.Search.Controllers; using DotNetNuke.UI.Modules; using DotNetNuke.UI.Modules.Html5; + using DotNetNuke.UI.Skins; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -58,6 +60,8 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); #pragma warning restore CS0618 + services.AddScoped(); + services.AddTransient(); services.AddTransient(); services.AddScoped(); diff --git a/DNN Platform/Library/UI/Skins/SkinController.cs b/DNN Platform/Library/UI/Skins/SkinController.cs index f9c4d7b7344..02a82aca47c 100644 --- a/DNN Platform/Library/UI/Skins/SkinController.cs +++ b/DNN Platform/Library/UI/Skins/SkinController.cs @@ -10,6 +10,8 @@ namespace DotNetNuke.UI.Skins using System.IO.Compression; using System.Text.RegularExpressions; + using DotNetNuke.Abstractions.Portals; + using DotNetNuke.Abstractions.Skins; using DotNetNuke.Common; using DotNetNuke.Common.Utilities; using DotNetNuke.Data; @@ -25,7 +27,7 @@ namespace DotNetNuke.UI.Skins /// Class : SkinController /// /// Handles the Business Control Layer for Skins. - public class SkinController + public class SkinController : ISkinService { private const string GlobalSkinPrefix = "[G]"; private const string PortalSystemSkinPrefix = "[S]"; @@ -35,6 +37,7 @@ public class SkinController private static readonly Regex SdirRegex = new Regex("\\[s]", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex LdirRegex = new Regex("\\[l]", RegexOptions.IgnoreCase | RegexOptions.Compiled); + /// public static string RootSkin { get @@ -43,6 +46,7 @@ public static string RootSkin } } + /// public static string RootContainer { get @@ -51,17 +55,32 @@ public static string RootContainer } } - public static int AddSkin(int skinPackageID, string skinSrc) + /// + string ISkinService.RootSkin => RootSkin; + + /// + string ISkinService.RootContainer => RootContainer; + + /// + public static int AddSkin(int skinPackageId, string skinSrc) { - return DataProvider.Instance().AddSkin(skinPackageID, skinSrc); + return DataProvider.Instance().AddSkin(skinPackageId, skinSrc); } + /// public static int AddSkinPackage(SkinPackageInfo skinPackage) + { + return AddSkinPackage((ISkinPackageInfo)skinPackage); + } + + /// + public static int AddSkinPackage(ISkinPackageInfo skinPackage) { EventLogController.Instance.AddLog(skinPackage, PortalController.Instance.GetCurrentPortalSettings(), UserController.Instance.GetCurrentUserInfo().UserID, string.Empty, EventLogController.EventLogType.SKINPACKAGE_CREATED); - return DataProvider.Instance().AddSkinPackage(skinPackage.PackageID, skinPackage.PortalID, skinPackage.SkinName, skinPackage.SkinType, UserController.Instance.GetCurrentUserInfo().UserID); + return DataProvider.Instance().AddSkinPackage(skinPackage.PackageId, skinPackage.PortalId, skinPackage.SkinName, skinPackage.SkinType, UserController.Instance.GetCurrentUserInfo().UserID); } + /// public static bool CanDeleteSkin(string folderPath, string portalHomeDirMapPath) { string skinType; @@ -113,14 +132,22 @@ public static bool CanDeleteSkin(string folderPath, string portalHomeDirMapPath) return canDelete; } + /// public static void DeleteSkin(int skinID) { DataProvider.Instance().DeleteSkin(skinID); } + /// public static void DeleteSkinPackage(SkinPackageInfo skinPackage) { - DataProvider.Instance().DeleteSkinPackage(skinPackage.SkinPackageID); + DeleteSkinPackage((ISkinPackageInfo)skinPackage); + } + + /// + public static void DeleteSkinPackage(ISkinPackageInfo skinPackage) + { + DataProvider.Instance().DeleteSkinPackage(skinPackage.SkinPackageId); EventLogController.Instance.AddLog(skinPackage, PortalController.Instance.GetCurrentPortalSettings(), UserController.Instance.GetCurrentUserInfo().UserID, string.Empty, EventLogController.EventLogType.SKINPACKAGE_DELETED); } @@ -151,6 +178,7 @@ public static string FormatMessage(string title, string body, int level, bool is return message + ": " + body + Environment.NewLine; } + /// public static string FormatSkinPath(string skinSrc) { string strSkinSrc = skinSrc; @@ -162,7 +190,14 @@ public static string FormatSkinPath(string skinSrc) return strSkinSrc; } + /// public static string FormatSkinSrc(string skinSrc, PortalSettings portalSettings) + { + return FormatSkinSrc(skinSrc, (IPortalSettings)portalSettings); + } + + /// + public static string FormatSkinSrc(string skinSrc, IPortalSettings portalSettings) { string strSkinSrc = skinSrc; if (!string.IsNullOrEmpty(strSkinSrc)) @@ -184,41 +219,54 @@ public static string FormatSkinSrc(string skinSrc, PortalSettings portalSettings return strSkinSrc; } + /// public static string GetDefaultAdminContainer() { SkinDefaults defaultContainer = SkinDefaults.GetSkinDefaults(SkinDefaultType.ContainerInfo); return "[G]" + RootContainer + defaultContainer.Folder + defaultContainer.AdminDefaultName; } + /// public static string GetDefaultAdminSkin() { SkinDefaults defaultSkin = SkinDefaults.GetSkinDefaults(SkinDefaultType.SkinInfo); return "[G]" + RootSkin + defaultSkin.Folder + defaultSkin.AdminDefaultName; } + /// public static string GetDefaultPortalContainer() { SkinDefaults defaultContainer = SkinDefaults.GetSkinDefaults(SkinDefaultType.ContainerInfo); return "[G]" + RootContainer + defaultContainer.Folder + defaultContainer.DefaultName; } + /// public static string GetDefaultPortalSkin() { SkinDefaults defaultSkin = SkinDefaults.GetSkinDefaults(SkinDefaultType.SkinInfo); return "[G]" + RootSkin + defaultSkin.Folder + defaultSkin.DefaultName; } + /// public static SkinPackageInfo GetSkinByPackageID(int packageID) { return CBO.FillObject(DataProvider.Instance().GetSkinByPackageID(packageID)); } + /// public static SkinPackageInfo GetSkinPackage(int portalId, string skinName, string skinType) { return CBO.FillObject(DataProvider.Instance().GetSkinPackage(portalId, skinName, skinType)); } + /// public static List> GetSkins(PortalInfo portalInfo, string skinRoot, SkinScope scope) + { + return GetSkins((IPortalInfo)portalInfo, skinRoot, scope); + } + + /// + public static List> GetSkins(IPortalInfo portalInfo, string skinRoot, SkinScope scope) { var skins = new List>(); switch (scope) @@ -238,18 +286,13 @@ public static List> GetSkins(PortalInfo portalInfo, return skins; } - /// Determines if a given skin is defined as a global skin. - /// This is the app relative path and filename of the skin to be checked. - /// True if the skin is located in the HostPath child directories. - /// This function performs a quick check to detect the type of skin that is - /// passed as a parameter. Using this method abstracts knowledge of the actual location - /// of skins in the file system. - /// + /// public static bool IsGlobalSkin(string skinSrc) { return skinSrc.Contains(Globals.HostPath); } + /// public static void SetSkin(string skinRoot, int portalId, SkinType skinType, string skinSrc) { var selectedCultureCode = LocaleController.Instance.GetCurrentLocale(portalId).Code; @@ -305,9 +348,12 @@ public static void SetSkin(string skinRoot, int portalId, SkinType skinType, str } break; + default: + throw new ArgumentOutOfRangeException(nameof(skinRoot), skinRoot, "Expected either 'Skins' or 'Containers'"); } } + /// public static void UpdateSkin(int skinID, string skinSrc) { DataProvider.Instance().UpdateSkin(skinID, skinSrc); @@ -455,6 +501,56 @@ public static string UploadLegacySkin(string rootPath, string skinRoot, string s return strMessage; } + /// + int ISkinService.AddSkin(int skinPackageId, string skinSrc) => AddSkin(skinPackageId, skinSrc); + + /// + int ISkinService.AddSkinPackage(ISkinPackageInfo skinPackage) => AddSkinPackage(skinPackage); + + /// + bool ISkinService.CanDeleteSkin(string folderPath, string portalHomeDirMapPath) + => CanDeleteSkin(folderPath, portalHomeDirMapPath); + + /// + void ISkinService.DeleteSkin(int skinId) => DeleteSkin(skinId); + + /// + void ISkinService.DeleteSkinPackage(ISkinPackageInfo skinPackage) => DeleteSkinPackage(skinPackage); + + /// + string ISkinService.GetDefaultAdminContainer() => GetDefaultAdminContainer(); + + /// + string ISkinService.GetDefaultAdminSkin() => GetDefaultAdminSkin(); + + /// + string ISkinService.GetDefaultPortalContainer() => GetDefaultPortalContainer(); + + /// + string ISkinService.GetDefaultPortalSkin() => GetDefaultPortalSkin(); + + /// + string ISkinService.FormatSkinPath(string skinSrc) => FormatSkinPath(skinSrc); + + /// + string ISkinService.FormatSkinSrc(string skinSrc, IPortalSettings portalSettings) + => FormatSkinSrc(skinSrc, portalSettings); + + /// + bool ISkinService.IsGlobalSkin(string skinSrc) => IsGlobalSkin(skinSrc); + + /// + void ISkinService.SetSkin(string skinRoot, int portalId, SkinType skinType, string skinSrc) + => SetSkin(skinRoot, portalId, skinType, skinSrc); + + /// + void ISkinService.UpdateSkin(int skinId, string skinSrc) + => UpdateSkin(skinId, skinSrc); + + /// + IEnumerable> ISkinService.GetSkins(IPortalInfo portalInfo, string skinRoot, SkinScope scope) + => GetSkins(portalInfo, skinRoot ?? RootSkin, scope); + private static void AddSkinFiles(List> skins, string skinRoot, string skinFolder, string skinPrefix) { foreach (string skinFile in Directory.GetFiles(skinFolder, "*.ascx")) @@ -487,7 +583,7 @@ private static List> GetHostSkins(string skinRoot) return skins; } - private static List> GetPortalSkins(PortalInfo portalInfo, string skinRoot) + private static List> GetPortalSkins(IPortalInfo portalInfo, string skinRoot) { var skins = new List>(); diff --git a/DNN Platform/Library/UI/Skins/SkinPackageInfo.cs b/DNN Platform/Library/UI/Skins/SkinPackageInfo.cs index 46e35aaea22..9d6c149811f 100644 --- a/DNN Platform/Library/UI/Skins/SkinPackageInfo.cs +++ b/DNN Platform/Library/UI/Skins/SkinPackageInfo.cs @@ -1,24 +1,25 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information namespace DotNetNuke.UI.Skins { using System; using System.Collections.Generic; using System.Data; using System.Xml.Serialization; - + + using DotNetNuke.Abstractions.Skins; using DotNetNuke.Common.Utilities; using DotNetNuke.Entities; - using DotNetNuke.Entities.Modules; - using Newtonsoft.Json; - + using DotNetNuke.Entities.Modules; + using Newtonsoft.Json; + /// Project : DotNetNuke /// Class : SkinPackageInfo /// /// Handles the Business Object for Skins. [Serializable] - public class SkinPackageInfo : BaseEntityInfo, IHydratable + public class SkinPackageInfo : BaseEntityInfo, IHydratable, ISkinPackageInfo { private int packageID = Null.NullInteger; private int portalID = Null.NullInteger; @@ -27,6 +28,7 @@ public class SkinPackageInfo : BaseEntityInfo, IHydratable private string skinType; private Dictionary skins = new Dictionary(); + /// public int PackageID { get @@ -40,6 +42,7 @@ public int PackageID } } + /// public int SkinPackageID { get @@ -53,6 +56,7 @@ public int SkinPackageID } } + /// public int PortalID { get @@ -66,6 +70,7 @@ public int PortalID } } + /// public string SkinName { get @@ -79,7 +84,8 @@ public string SkinName } } - [XmlIgnore] + /// + [XmlIgnore] [JsonIgnore] public Dictionary Skins { @@ -94,6 +100,7 @@ public Dictionary Skins } } + /// public string SkinType { get @@ -105,9 +112,9 @@ public string SkinType { this.skinType = value; } - } - - /// + } + + /// public int KeyID { get @@ -119,9 +126,30 @@ public int KeyID { this.SkinPackageID = value; } - } - - /// + } + + /// + int ISkinPackageInfo.PackageId + { + get => this.PackageID; + set => this.PackageID = value; + } + + /// + int ISkinPackageInfo.SkinPackageId + { + get => this.SkinPackageID; + set => this.SkinPackageID = value; + } + + /// + int ISkinPackageInfo.PortalId + { + get => this.PortalID; + set => this.PortalID = value; + } + + /// public void Fill(IDataReader dr) { this.SkinPackageID = Null.SetNullInteger(dr["SkinPackageID"]); diff --git a/DNN Platform/Library/UI/Skins/SkinScope.cs b/DNN Platform/Library/UI/Skins/SkinScope.cs index 745128b1116..38c004f59c1 100644 --- a/DNN Platform/Library/UI/Skins/SkinScope.cs +++ b/DNN Platform/Library/UI/Skins/SkinScope.cs @@ -1,13 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information - -namespace DotNetNuke.UI.Skins -{ - public enum SkinScope - { - All = 0, - Host = 1, - Site = 2, - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information + +using System.Runtime.CompilerServices; + +using DotNetNuke.UI.Skins; + +// The enum 'SkinScope' has been moved to DotNetNuke.Abstractions +[assembly: TypeForwardedTo(typeof(SkinScope))] diff --git a/DNN Platform/Library/UI/Skins/SkinType.cs b/DNN Platform/Library/UI/Skins/SkinType.cs index 4e0b4b6a524..3061920a3bc 100644 --- a/DNN Platform/Library/UI/Skins/SkinType.cs +++ b/DNN Platform/Library/UI/Skins/SkinType.cs @@ -1,12 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information - -namespace DotNetNuke.UI.Skins -{ - public enum SkinType - { - Portal = 0, - Admin = 1, - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information + +using System.Runtime.CompilerServices; + +using DotNetNuke.UI.Skins; + +// The enum 'SkinType' has been moved to DotNetNuke.Abstractions +[assembly: TypeForwardedTo(typeof(SkinType))]