Розробка кастомного шаблону Umbraco

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка кастомного шаблону Umbraco
Середня
~5 робочих днів
Часті питання

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

Етапи розробки

Останні роботи

  • 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 — це Razor View (.cshtml). Кожному типу контенту (Document Type) відповідає вьюха з такою ж назвою alias. Контроллер можна не писати — Umbraco створює Render Controller за замовчуванням — або написати користувацький для додавання логіки.

Базовий Layout

@* Views/Shared/_Layout.cshtml *@
@using Umbraco.Cms.Web.Common.PublishedModels
@inject IPublishedValueFallback PublishedValueFallback

<!DOCTYPE html>
<html lang="@System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>@ViewData["Title"] – @ViewBag.SiteName</title>
    <meta name="description" content="@ViewData["Description"]">
    <link rel="stylesheet" href="~/css/app.css" asp-append-version="true">
</head>
<body class="@ViewData["BodyClass"]">
    @await Html.PartialAsync("_Navigation")
    <main>@RenderBody()</main>
    @await Html.PartialAsync("_Footer")
    <script src="~/js/app.js" asp-append-version="true" defer></script>
    @RenderSection("scripts", required: false)
</body>
</html>

Strongly-typed шаблон статті

@* Views/Article.cshtml *@
@using Umbraco.Cms.Web.Common.PublishedModels
@model ArticleViewModel
@{
    Layout = "_Layout.cshtml";
    ViewData["Title"] = Model.SeoTitle ?? Model.Title;
    ViewData["Description"] = Model.SeoDescription;
    ViewData["BodyClass"] = "page-article";
}

<article class="container article">
    <header class="article__header">
        @if (Model.CoverImage != null)
        {
            <figure class="article__cover">
                <img
                    src="@Model.CoverImage.GetCropUrl(width: 1200, height: 630)"
                    srcset="@Model.CoverImage.GetCropUrl(600) 600w,
                            @Model.CoverImage.GetCropUrl(1200) 1200w"
                    sizes="(max-width: 768px) 100vw, 1200px"
                    alt="@(Model.CoverImage.Name)"
                    loading="eager"
                >
            </figure>
        }
        <div class="article__meta">
            @if (Model.Category != null)
            {
                <a href="@Model.Category.Url" class="badge">
                    @Model.Category.Name
                </a>
            }
            <h1 class="article__title">@Model.Title</h1>
            <time datetime="@Model.PublishDate?.ToString("yyyy-MM-dd")">
                @Model.PublishDate?.ToString("dd MMMM yyyy")
            </time>
        </div>
    </header>

    <div class="article__body prose">
        @Html.Raw(Model.BodyHtml)
    </div>

    @if (Model.RelatedArticles.Any())
    {
        <aside class="related">
            <h2>Похожие статьи</h2>
            <div class="related__grid">
                @foreach (var related in Model.RelatedArticles)
                {
                    @await Html.PartialAsync("_ArticleCard", related)
                }
            </div>
        </aside>
    }
</article>

Контроллер з даними

// Controllers/ArticleController.cs
public class ArticleController : RenderController
{
    private readonly IUmbracoMapper _mapper;
    private readonly IRelatedContentService _relatedService;

    public ArticleController(
        ILogger<ArticleController> logger,
        ICompositeViewEngine viewEngine,
        IUmbracoContextAccessor umbracoContextAccessor,
        IUmbracoMapper mapper,
        IRelatedContentService relatedService)
        : base(logger, viewEngine, umbracoContextAccessor)
    {
        _mapper    = mapper;
        _relatedService = relatedService;
    }

    public override IActionResult Index()
    {
        if (CurrentPage is not Article article)
            return NotFound();

        var model = new ArticleViewModel
        {
            Title       = article.Title,
            BodyHtml    = article.Body?.ToString(),
            PublishDate = article.PublishDate,
            SeoTitle    = article.SeoTitle,
            SeoDescription = article.SeoDescription,
            CoverImage  = article.CoverImage?.First() as IPublishedContent,
            Category    = article.ArticleCategory?.First() as IPublishedContent,
            RelatedArticles = _relatedService.GetRelated(article, 3),
        };

        return CurrentTemplate(model);
    }
}

Block List та Block Grid

Umbraco 9+ використовує Block List та Block Grid замість Grid Editor:

@* Рендеринг Block List *@
@foreach (var block in Model.Content.Value<BlockListModel>("pageBlocks") ?? [])
{
    var alias = block.Content.ContentType.Alias;
    @await Html.PartialAsync($"Blocks/_{alias}", block)
}

Partial для блоку heroBlock:

@* Views/Partials/Blocks/_heroBlock.cshtml *@
@model Umbraco.Cms.Core.Models.Blocks.BlockListItem

@{
    var content  = (HeroBlock)model.Content;
    var settings = model.Settings as HeroBlockSettings;
}

<section class="hero hero--@settings?.Theme">
    <div class="hero__inner">
        <h1>@content.Heading</h1>
        @if (!string.IsNullOrEmpty(content.Subheading))
        {
            <p class="hero__sub">@content.Subheading</p>
        }
        @if (content.CtaLink != null)
        {
            <a href="@content.CtaLink.Url"
               target="@content.CtaLink.Target"
               class="btn btn--primary">
                @content.CtaLink.Name
            </a>
        }
    </div>
    @if (content.BackgroundImage?.First() is IPublishedContent img)
    {
        <img class="hero__bg" src="@img.GetCropUrl(1920)" alt="" role="presentation">
    }
</section>

Partial Views та Child Actions

@* Views/Partials/_Navigation.cshtml *@
@using Umbraco.Cms.Web.Common.UmbracoContext
@inject IUmbracoContextAccessor UmbracoContextAccessor

@{
    var umbracoContext = UmbracoContextAccessor.GetRequiredUmbracoContext();
    var root = umbracoContext.Content?.GetAtRoot().FirstOrDefault();
    var nav  = root?.Children.Where(n => n.Value<bool>("showInNav"));
}

<nav class="main-nav">
    @foreach (var item in nav ?? [])
    {
        <a href="@item.Url()"
           class="@(item.IsAncestorOrSelf(umbracoContext.PublishedRequest?.PublishedContent) ? "active" : "")">
            @item.Name
        </a>
    }
</nav>

Image Cropper

Umbraco використовує ImageSharp. Кроп конфігурується в типі медіа:

@* З іменованим кропом *@
<img src="@image.GetCropUrl("listCard")" alt="@image.Name">

@* З параметрами *@
<img src="@image.GetCropUrl(width: 400, height: 300, imageCropMode: ImageCropMode.Crop)" alt="">

@* WebP з fallback *@
<picture>
    <source srcset="@image.GetCropUrl(800, imageFormat: ImageFormat.WebP)" type="image/webp">
    <img src="@image.GetCropUrl(800)" alt="@image.Name">
</picture>

Терміни розробки

Комплект шаблонів для корпоративного сайту (Layout, навігація, 6–8 типів сторінок, блочний редактор): 2–3 тижні. Окремий шаблон з користувацьким контролером та View Components: 3–5 днів.