Разработка кастомного шаблона 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);
    }
}

Grid Layout и блочный редактор

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>

Macro (устаревшее) → View Components

В Umbraco 9+ макросы заменены View Components:

[ViewComponent(Name = "BreadcrumbsWidget")]
public class BreadcrumbsViewComponent : ViewComponent
{
    private readonly IUmbracoContextAccessor _umbracoContextAccessor;

    public BreadcrumbsViewComponent(IUmbracoContextAccessor accessor)
        => _umbracoContextAccessor = accessor;

    public IViewComponentResult Invoke()
    {
        var current = _umbracoContextAccessor
            .GetRequiredUmbracoContext()
            .PublishedRequest?
            .PublishedContent;

        var breadcrumbs = current?.Ancestors()
            .Reverse()
            .Select(n => new BreadcrumbItem(n.Name, n.Url()))
            .Append(new BreadcrumbItem(current?.Name ?? "", null))
            .ToList() ?? [];

        return View("~/Views/Components/Breadcrumbs.cshtml", breadcrumbs);
    }
}

В Layout:

@await Component.InvokeAsync("BreadcrumbsWidget")

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 дней.