Разработка кастомного пакета Umbraco
Пакет Umbraco — это NuGet-пакет, который расширяет CMS: добавляет новые типы свойств, дашборды, секции backoffice, Composer-ы, миграции, API-эндпоинты. Распространяется через NuGet или Our.Umbraco.Org. Архитектурно это обычная библиотека .NET с регистрацией через Composer-паттерн.
Структура пакета
MyPackage/
├── MyPackage.Core/
│ ├── Composers/
│ │ └── MyPackageComposer.cs
│ ├── Services/
│ │ ├── IMyService.cs
│ │ └── MyService.cs
│ ├── Models/
│ ├── Migrations/
│ │ └── AddMyTableMigration.cs
│ ├── NotificationHandlers/
│ └── MyPackage.Core.csproj
├── MyPackage.StaticAssets/
│ ├── App_Plugins/
│ │ └── MyPackage/
│ │ ├── my-property-editor.js
│ │ ├── my-dashboard.js
│ │ └── package.manifest
│ └── MyPackage.StaticAssets.csproj
└── MyPackage.sln
Composer — точка входа
// Composers/MyPackageComposer.cs
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Migrations;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
[assembly: ComposeAfter(typeof(ICoreComposer))]
public class MyPackageComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
// регистрация сервисов
builder.Services.AddSingleton<IMyService, MyService>();
// регистрация обработчиков событий
builder.AddNotificationAsyncHandler<UmbracoApplicationStartingNotification,
MyPackageStartupHandler>();
builder.AddNotificationHandler<ContentPublishedNotification,
ContentPublishedHandler>();
// регистрация миграций
builder.AddNotificationAsyncHandler<UmbracoApplicationStartingNotification,
RunPackageMigrationsHandler>();
// регистрация типа свойства
builder.PropertyEditors()
.Add<MyCustomPropertyEditor>();
}
}
Миграция базы данных
// Migrations/AddMyTableMigration.cs
using Umbraco.Cms.Infrastructure.Migrations;
public class AddMyTableMigration : MigrationBase
{
public AddMyTableMigration(IMigrationContext context) : base(context) { }
protected override void Migrate()
{
if (!TableExists("MyPackageData"))
{
Create.Table<MyPackageDataDto>().Do();
}
else
{
// идемпотентное добавление колонки
if (!ColumnExists("MyPackageData", "ExtraField"))
{
Alter.Table("MyPackageData")
.AddColumn("ExtraField")
.AsString(512)
.Nullable()
.Do();
}
}
}
}
[TableName("MyPackageData")]
[PrimaryKey("Id", AutoIncrement = true)]
public class MyPackageDataDto
{
[Column("Id")]
public int Id { get; set; }
[Column("ContentId")]
public int ContentId { get; set; }
[Column("Data")]
[NullSetting(NullSetting = NullSettings.Null)]
public string? Data { get; set; }
[Column("CreatedAt")]
public DateTime CreatedAt { get; set; }
}
// Запуск миграций при старте
public class RunPackageMigrationsHandler
: INotificationAsyncHandler<UmbracoApplicationStartingNotification>
{
private readonly IMigrationPlanExecutor _migrationPlanExecutor;
private readonly ICoreScopeProvider _scopeProvider;
private readonly IKeyValueService _keyValueService;
private readonly IRuntimeState _runtimeState;
// ... constructor
public async Task HandleAsync(
UmbracoApplicationStartingNotification notification,
CancellationToken ct)
{
if (_runtimeState.Level < RuntimeLevel.Run) return;
var plan = new MigrationPlan("MyPackage")
.From(string.Empty)
.To<AddMyTableMigration>("v1.0.0");
using var scope = _scopeProvider.CreateCoreScope();
var upgrader = new Upgrader(plan);
upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService);
scope.Complete();
await Task.CompletedTask;
}
}
Кастомный тип свойства
// Property Editor — серверная часть
[DataEditor(
alias: "MyPackage.ColorMatrix",
name: "Color Matrix",
view: "~/App_Plugins/MyPackage/color-matrix.html",
Group = "Common",
Icon = "icon-color")]
public class ColorMatrixPropertyEditor : DataEditor
{
public ColorMatrixPropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
EditorType type = EditorType.PropertyValue)
: base(dataValueEditorFactory, type) { }
protected override IConfigurationEditor CreateConfigurationEditor()
=> new ColorMatrixConfigurationEditor();
}
public class ColorMatrixConfigurationEditor : ConfigurationEditor<ColorMatrixConfiguration>
{
public ColorMatrixConfigurationEditor()
{
Fields.Add(new ConfigurationField
{
Key = "columns",
Name = "Columns",
View = "number",
});
Fields.Add(new ConfigurationField
{
Key = "rows",
Name = "Rows",
View = "number",
});
}
}
// App_Plugins/MyPackage/color-matrix.html + controller
angular.module('umbraco').controller('MyPackage.ColorMatrixController',
function ($scope) {
if (!$scope.model.value) {
$scope.model.value = { colors: [] };
}
$scope.addColor = function(hex) {
$scope.model.value.colors.push(hex);
};
$scope.removeColor = function(index) {
$scope.model.value.colors.splice(index, 1);
};
});
Кастомный дашборд
// App_Plugins/MyPackage/dashboard.js
angular.module('umbraco').controller('MyPackage.DashboardController',
function ($scope, $http) {
$scope.stats = null;
$http.get('/umbraco/backoffice/MyPackage/Dashboard/GetStats')
.then(function(response) {
$scope.stats = response.data;
});
});
// Controllers/DashboardController.cs
[Area(AreaNames.BackOffice)]
public class DashboardController : UmbracoAuthorizedApiController
{
private readonly IMyService _service;
public DashboardController(IMyService service) => _service = service;
[HttpGet]
public IActionResult GetStats()
{
var stats = _service.GetStatistics();
return Ok(stats);
}
}
Регистрация в package.manifest:
{
"dashboards": [
{
"alias": "myPackageDashboard",
"view": "/App_Plugins/MyPackage/dashboard.html",
"sections": ["content"],
"weight": -10,
"access": [{ "deny": "isOwner" }]
}
],
"propertyEditors": [
{
"alias": "MyPackage.ColorMatrix",
"name": "Color Matrix",
"editor": {
"view": "/App_Plugins/MyPackage/color-matrix.html"
}
}
]
}
NuGet-пакет
<!-- MyPackage.Core.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PackageId>MyCompany.UmbracoMyPackage</PackageId>
<Version>1.0.0</Version>
<Authors>MyCompany</Authors>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<Description>Custom package for Umbraco 13+</Description>
<PackageTags>umbraco;cms;plugin</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Umbraco.Cms.Core" Version="13.*" />
</ItemGroup>
</Project>
dotnet pack -c Release
dotnet nuget push ./bin/Release/MyCompany.UmbracoMyPackage.1.0.0.nupkg \
--source https://api.nuget.org/v3/index.json \
--api-key $NUGET_API_KEY
Сроки
Простой пакет (сервис + Composer + миграция): 3–5 дней. Пакет с кастомным типом свойства, дашбордом и API-контроллером: 1–2 недели. С тестами, документацией и поддержкой нескольких версий Umbraco: 3–4 недели.







