Разработка сайта на CMS Umbraco
Umbraco — CMS на ASP.NET Core с открытым исходным кодом. Работает на .NET 8, использует SQL Server или SQLite, поддерживает кластеризацию. Хорошо подходит для корпоративных сайтов, порталов, multisite-инфраструктур — там, где заказчик привязан к Microsoft-стеку или нужен headless CMS с enterprise-возможностями.
Архитектура проекта
MyProject/
├── MyProject.Web/ # основной веб-проект
│ ├── Controllers/ # Surface и Render Controllers
│ ├── Models/ # strongly-typed models
│ ├── Views/ # Razor Views
│ │ ├── Partials/
│ │ └── Shared/
│ ├── Composers/ # внедрение данных в вьюхи
│ ├── NotificationHandlers/ # события Umbraco
│ ├── wwwroot/
│ └── Program.cs
├── MyProject.Core/ # бизнес-логика
│ ├── Services/
│ └── Models/
└── MyProject.Tests/
Настройка хост-приложения
// Program.cs
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.CreateUmbracoBuilder()
.AddBackOffice()
.AddWebsite()
.AddDeliveryApi() // headless API
.AddComposers()
.Build();
WebApplication app = builder.Build();
await app.BootUmbracoAsync();
app.UseUmbraco()
.WithMiddleware(u =>
{
u.UseBackOffice();
u.UseWebsite();
})
.WithEndpoints(u =>
{
u.UseInstallerEndpoints();
u.UseBackOfficeEndpoints();
u.UseWebsiteEndpoints();
});
await app.RunAsync();
Strongly-typed модели
Umbraco генерирует модели через ModelsBuilder. После настройки типов контента в backoffice:
dotnet run -- umbraco-models-builder generate
Использование в контроллере:
using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;
public class ArticleController : RenderController
{
private readonly ILogger<ArticleController> _logger;
public ArticleController(
ILogger<ArticleController> logger,
ICompositeViewEngine viewEngine,
IUmbracoContextAccessor umbracoContextAccessor)
: base(logger, viewEngine, umbracoContextAccessor)
{
_logger = logger;
}
[HttpGet]
public IActionResult Index()
{
if (CurrentPage is not ContentModels.Article article)
{
return NotFound();
}
var viewModel = new ArticleViewModel
{
Title = article.Title,
Body = article.Body,
PublishDate = article.PublishDate,
Author = article.Author?.Name,
Tags = article.Tags?.Split(',').Select(t => t.Trim()).ToArray() ?? [],
};
return CurrentTemplate(viewModel);
}
}
Razor View
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ArticleViewModel>
@{
Layout = "_Layout.cshtml";
}
<article class="article">
<header>
<h1>@Model.Title</h1>
@if (Model.PublishDate.HasValue)
{
<time datetime="@Model.PublishDate.Value.ToString("yyyy-MM-dd")">
@Model.PublishDate.Value.ToString("dd.MM.yyyy")
</time>
}
@if (!string.IsNullOrEmpty(Model.Author))
{
<span class="author">@Model.Author</span>
}
</header>
<div class="article__body">
@Html.Raw(Model.Body)
</div>
@if (Model.Tags?.Length > 0)
{
<footer class="tags">
@foreach (var tag in Model.Tags)
{
<a href="/[email protected](tag)">@tag</a>
}
</footer>
}
</article>
Composers — настройка DI
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
public class SiteComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.Services.AddScoped<IArticleService, ArticleService>();
builder.Services.AddScoped<ISitemapService, SitemapService>();
// регистрация обработчиков событий
builder.AddNotificationHandler<ContentPublishedNotification, SearchIndexHandler>();
builder.AddNotificationHandler<ContentSavedNotification, CacheInvalidationHandler>();
}
}
Headless через Delivery API
Umbraco 12+ включает встроенный Delivery API:
// appsettings.json
{
"Umbraco": {
"CMS": {
"DeliveryApi": {
"Enabled": true,
"PublicAccess": true,
"ApiKey": "your-api-key",
"DisallowedContentTypeAliases": ["settings", "internalPage"],
"RichTextOutputAsJson": false
}
}
}
}
Запросы к API:
GET /umbraco/delivery/api/v2/content?contentType=article&sort=createDate:desc&take=10
Authorization: Api-Key your-api-key
GET /umbraco/delivery/api/v2/content/item/my-article-slug
Ответ типизированный, включает свойства типа контента, медиафайлы, связи.
Surface Controller для форм
public class ContactFormController : SurfaceController
{
private readonly IMailService _mailService;
public ContactFormController(
IUmbracoContextAccessor contextAccessor,
IUmbracoDatabaseFactory databaseFactory,
ServiceContext services,
AppCaches appCaches,
ILogger<ContactFormController> logger,
IProfilingLogger profilingLogger,
IPublishedUrlProvider publishedUrlProvider,
IMailService mailService)
: base(contextAccessor, databaseFactory, services, appCaches,
logger, profilingLogger, publishedUrlProvider)
{
_mailService = mailService;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Submit(ContactFormModel model)
{
if (!ModelState.IsValid)
{
return CurrentUmbracoPage();
}
await _mailService.SendContactEmailAsync(model);
TempData["FormSuccess"] = true;
return RedirectToCurrentUmbracoPage();
}
}
Мультисайт
// один инстанс Umbraco, несколько сайтов
// Настраивается в backoffice: Content → правой кнопкой → "Allow as root"
// Для каждого корневого узла — свой домен в Domains
// Получение текущего сайта в шаблоне:
@inject IUmbracoContextAccessor UmbracoContext
@{
var root = UmbracoContext.GetRequiredUmbracoContext()
.Content?
.GetAtRoot()
.First(n => n.IsAncestorOrSelf(Model.Content!));
var siteName = root?.Value<string>("siteName");
}
Производительность
// appsettings.json — кэш
{
"Umbraco": {
"CMS": {
"Runtime": {
"Mode": "BackofficeDevelopment"
},
"WebRouting": {
"DisableAlternativeTemplates": false,
"DisableFindContentByIdentifierPath": false
},
"NuCache": {
"BTreeBlockSize": 4096
}
}
}
}
Umbraco использует NuCache (in-memory + disk) для хранения опубликованного контента — без обращений к БД при чтении публичных страниц.
Сроки
Корпоративный сайт 10–15 типов контента с кастомными шаблонами: 3–5 недель. С headless API, интеграцией со внешними сервисами, мультисайтом: 6–10 недель. Настройка существующего инстанса под новый дизайн — от 2 недель.







