Розробка сайту на 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 моделі
│ ├── 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 тижнів.







