Разработка кастомного пакета Umbraco

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомного пакета Umbraco
Сложная
~1-2 недели
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка кастомного пакета 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 недели.