frontend-razor

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Frontend Razor Patterns

前端Razor视图模式

Overview

概述

This skill covers Razor view patterns for Optimizely CMS including page templates, block rendering, and content area handling.
本Skill涵盖了Optimizely CMS项目中的Razor视图模式,包括页面模板、块渲染和内容区域处理。

Page Templates

页面模板

Basic Page View

基础页面视图

cshtml
@model ArticlePage

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
    ViewData["Title"] = Model.MetaTitle ?? Model.Heading;
}

<article class="article">
    <header class="article-header">
        <h1>@Model.Heading</h1>
        @if (Model.PublishedDate.HasValue)
        {
            <time datetime="@Model.PublishedDate.Value.ToString("yyyy-MM-dd")">
                @Model.PublishedDate.Value.ToString("MMMM d, yyyy")
            </time>
        }
    </header>

    @if (Model.HeroImage != null)
    {
        <figure class="article-hero">
            <img src="@Url.ContentUrl(Model.HeroImage)" alt="@Model.Heading" />
        </figure>
    }

    <div class="article-content">
        @Html.PropertyFor(m => m.MainBody)
    </div>

    @Html.PropertyFor(m => m.RelatedContentArea)
</article>
cshtml
@model ArticlePage

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
    ViewData["Title"] = Model.MetaTitle ?? Model.Heading;
}

<article class="article">
    <header class="article-header">
        <h1>@Model.Heading</h1>
        @if (Model.PublishedDate.HasValue)
        {
            <time datetime="@Model.PublishedDate.Value.ToString("yyyy-MM-dd")">
                @Model.PublishedDate.Value.ToString("MMMM d, yyyy")
            </time>
        }
    </header>

    @if (Model.HeroImage != null)
    {
        <figure class="article-hero">
            <img src="@Url.ContentUrl(Model.HeroImage)" alt="@Model.Heading" />
        </figure>
    }

    <div class="article-content">
        @Html.PropertyFor(m => m.MainBody)
    </div>

    @Html.PropertyFor(m => m.RelatedContentArea)
</article>

View with ViewModel

带ViewModel的视图

cshtml
@model ArticleViewModel

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<article class="article">
    <h1>@Model.Heading</h1>

    @Html.Raw(Model.Body)

    <section class="related-articles">
        <h2>Related Articles</h2>
        <ul>
            @foreach (var related in Model.RelatedArticles)
            {
                <li>
                    <a href="@related.Url">@related.Heading</a>
                </li>
            }
        </ul>
    </section>
</article>
cshtml
@model ArticleViewModel

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<article class="article">
    <h1>@Model.Heading</h1>

    @Html.Raw(Model.Body)

    <section class="related-articles">
        <h2>Related Articles</h2>
        <ul>
            @foreach (var related in Model.RelatedArticles)
            {
                <li>
                    <a href="@related.Url">@related.Heading</a>
                </li>
            }
        </ul>
    </section>
</article>

Block Templates

块模板

Block View

块视图

cshtml
@model HeroBlock

<section class="hero" style="background-image: url('@Url.ContentUrl(Model.BackgroundImage)')">
    <div class="hero-content">
        <h1>@Model.Heading</h1>
        @if (!string.IsNullOrEmpty(Model.Subheading))
        {
            <p class="hero-subheading">@Model.Subheading</p>
        }
        @if (Model.CallToActionUrl != null)
        {
            <a href="@Model.CallToActionUrl" class="btn btn-primary">
                @(Model.CallToActionText ?? "Learn More")
            </a>
        }
    </div>
</section>
cshtml
@model HeroBlock

<section class="hero" style="background-image: url('@Url.ContentUrl(Model.BackgroundImage)')">
    <div class="hero-content">
        <h1>@Model.Heading</h1>
        @if (!string.IsNullOrEmpty(Model.Subheading))
        {
            <p class="hero-subheading">@Model.Subheading</p>
        }
        @if (Model.CallToActionUrl != null)
        {
            <a href="@Model.CallToActionUrl" class="btn btn-primary">
                @(Model.CallToActionText ?? "Learn More")
            </a>
        }
    </div>
</section>

Block with Edit Mode Support

支持编辑模式的块视图

cshtml
@using EPiServer.Web.Mvc.Html
@model TeaserBlock

<div class="teaser">
    @Html.PropertyFor(m => m.Heading, new { Tag = "h3", CssClass = "teaser-heading" })

    @Html.PropertyFor(m => m.Text, new { CssClass = "teaser-text" })

    @if (Model.Link != null)
    {
        <a href="@Url.ContentUrl(Model.Link)" class="teaser-link">
            @Html.PropertyFor(m => m.LinkText)
        </a>
    }
</div>
cshtml
@using EPiServer.Web.Mvc.Html
@model TeaserBlock

<div class="teaser">
    @Html.PropertyFor(m => m.Heading, new { Tag = "h3", CssClass = "teaser-heading" })

    @Html.PropertyFor(m => m.Text, new { CssClass = "teaser-text" })

    @if (Model.Link != null)
    {
        <a href="@Url.ContentUrl(Model.Link)" class="teaser-link">
            @Html.PropertyFor(m => m.LinkText)
        </a>
    }
</div>

Content Areas

内容区域

Rendering Content Areas

渲染内容区域

cshtml
@using EPiServer.Web.Mvc.Html
@model PageData

<main class="page-content">
    @* Default rendering *@
    @Html.PropertyFor(m => m.MainContentArea)

    @* With custom tag and CSS class *@
    @Html.PropertyFor(m => m.SidebarArea, new {
        Tag = "aside",
        CssClass = "sidebar",
        ChildrenTag = "div",
        ChildrenCssClass = "sidebar-block"
    })

    @* With custom item wrapper *@
    @Html.PropertyFor(m => m.FooterArea, new {
        CustomTag = "section",
        CssClass = "footer-blocks"
    })
</main>
cshtml
@using EPiServer.Web.Mvc.Html
@model PageData

<main class="page-content">
    @* Default rendering *@
    @Html.PropertyFor(m => m.MainContentArea)

    @* With custom tag and CSS class *@
    @Html.PropertyFor(m => m.SidebarArea, new {
        Tag = "aside",
        CssClass = "sidebar",
        ChildrenTag = "div",
        ChildrenCssClass = "sidebar-block"
    })

    @* With custom item wrapper *@
    @Html.PropertyFor(m => m.FooterArea, new {
        CustomTag = "section",
        CssClass = "footer-blocks"
    })
</main>

Content Area with Custom Rendering

自定义渲染的内容区域

cshtml
@using EPiServer.Core
@model ContentArea

@if (Model != null && Model.FilteredItems.Any())
{
    <div class="content-blocks">
        @foreach (var item in Model.FilteredItems)
        {
            var content = item.GetContent();
            <div class="content-block @GetBlockClass(content)">
                @Html.DisplayFor(m => content)
            </div>
        }
    </div>
}

@functions {
    private string GetBlockClass(IContent content)
    {
        return content.GetOriginalType().Name.ToLowerInvariant().Replace("block", "-block");
    }
}
cshtml
@using EPiServer.Core
@model ContentArea

@if (Model != null && Model.FilteredItems.Any())
{
    <div class="content-blocks">
        @foreach (var item in Model.FilteredItems)
        {
            var content = item.GetContent();
            <div class="content-block @GetBlockClass(content)">
                @Html.DisplayFor(m => content)
            </div>
        }
    </div>
}

@functions {
    private string GetBlockClass(IContent content)
    {
        return content.GetOriginalType().Name.ToLowerInvariant().Replace("block", "-block");
    }
}

Tag Helpers

标签助手

Optimizely Tag Helpers

Optimizely标签助手

cshtml
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, EPiServer.Web.Mvc

@* Content link tag helper *@
<a epi-link="@Model.LinkToPage">@Model.LinkText</a>

@* Property editing *@
<div epi-edit="MainBody">
    @Html.Raw(Model.MainBody?.ToHtmlString())
</div>

@* Content URL *@
<img src="@Url.ContentUrl(Model.Image)" alt="@Model.ImageAlt" />
cshtml
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, EPiServer.Web.Mvc

@* Content link tag helper *@
<a epi-link="@Model.LinkToPage">@Model.LinkText</a>

@* Property editing *@
<div epi-edit="MainBody">
    @Html.Raw(Model.MainBody?.ToHtmlString())
</div>

@* Content URL *@
<img src="@Url.ContentUrl(Model.Image)" alt="@Model.ImageAlt" />

Custom Tag Helper

自定义标签助手

csharp
[HtmlTargetElement("optimizely-breadcrumb")]
public class BreadcrumbTagHelper : TagHelper
{
    private readonly IContentLoader _contentLoader;
    private readonly IUrlResolver _urlResolver;

    public ContentReference CurrentPage { get; set; }

    public BreadcrumbTagHelper(
        IContentLoader contentLoader,
        IUrlResolver urlResolver)
    {
        _contentLoader = contentLoader;
        _urlResolver = urlResolver;
    }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "nav";
        output.Attributes.Add("aria-label", "Breadcrumb");

        var breadcrumbs = GetBreadcrumbs(CurrentPage);
        var list = new StringBuilder("<ol class=\"breadcrumb\">");

        foreach (var crumb in breadcrumbs)
        {
            list.Append($"<li><a href=\"{crumb.Url}\">{crumb.Name}</a></li>");
        }

        list.Append("</ol>");
        output.Content.SetHtmlContent(list.ToString());
    }
}
csharp
[HtmlTargetElement("optimizely-breadcrumb")]
public class BreadcrumbTagHelper : TagHelper
{
    private readonly IContentLoader _contentLoader;
    private readonly IUrlResolver _urlResolver;

    public ContentReference CurrentPage { get; set; }

    public BreadcrumbTagHelper(
        IContentLoader contentLoader,
        IUrlResolver urlResolver)
    {
        _contentLoader = contentLoader;
        _urlResolver = urlResolver;
    }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "nav";
        output.Attributes.Add("aria-label", "Breadcrumb");

        var breadcrumbs = GetBreadcrumbs(CurrentPage);
        var list = new StringBuilder("<ol class=\"breadcrumb\">");

        foreach (var crumb in breadcrumbs)
        {
            list.Append($"<li><a href=\"{crumb.Url}\">{crumb.Name}</a></li>");
        }

        list.Append("</ol>");
        output.Content.SetHtmlContent(list.ToString());
    }
}

Partial Views

局部视图

Shared Partial

共享局部视图

cshtml
@* Views/Shared/_ArticleCard.cshtml *@
@model ArticleViewModel

<article class="article-card">
    @if (!string.IsNullOrEmpty(Model.ThumbnailUrl))
    {
        <img src="@Model.ThumbnailUrl" alt="" class="article-card-image" />
    }
    <div class="article-card-content">
        <h3 class="article-card-title">
            <a href="@Model.Url">@Model.Heading</a>
        </h3>
        <p class="article-card-excerpt">@Model.Excerpt</p>
        <time datetime="@Model.PublishedDate?.ToString("yyyy-MM-dd")">
            @Model.PublishedDate?.ToString("MMM d, yyyy")
        </time>
    </div>
</article>
cshtml
@* Views/Shared/_ArticleCard.cshtml *@
@model ArticleViewModel

<article class="article-card">
    @if (!string.IsNullOrEmpty(Model.ThumbnailUrl))
    {
        <img src="@Model.ThumbnailUrl" alt="" class="article-card-image" />
    }
    <div class="article-card-content">
        <h3 class="article-card-title">
            <a href="@Model.Url">@Model.Heading</a>
        </h3>
        <p class="article-card-excerpt">@Model.Excerpt</p>
        <time datetime="@Model.PublishedDate?.ToString("yyyy-MM-dd")">
            @Model.PublishedDate?.ToString("MMM d, yyyy")
        </time>
    </div>
</article>

Using Partial

使用局部视图

cshtml
@model ArticleListPage

<div class="article-list">
    @foreach (var article in Model.Articles)
    {
        @await Html.PartialAsync("_ArticleCard", article)
    }
</div>
cshtml
@model ArticleListPage

<div class="article-list">
    @foreach (var article in Model.Articles)
    {
        @await Html.PartialAsync("_ArticleCard", article)
    }
</div>

Edit Mode Support

编辑模式支持

Edit Mode Detection

编辑模式检测

cshtml
@using EPiServer.Web
@model PageData

@if (PageEditing.PageIsInEditMode)
{
    <div class="edit-mode-notice">
        You are in edit mode
    </div>
}

<div class="content" data-epi-edit="@Html.EditAttributes(m => m.MainBody)">
    @Html.PropertyFor(m => m.MainBody)
</div>
cshtml
@using EPiServer.Web
@model PageData

@if (PageEditing.PageIsInEditMode)
{
    <div class="edit-mode-notice">
        You are in edit mode
    </div>
}

<div class="content" data-epi-edit="@Html.EditAttributes(m => m.MainBody)">
    @Html.PropertyFor(m => m.MainBody)
</div>

Best Practices

最佳实践

  1. Use PropertyFor for editable properties
  2. Support edit mode with proper attributes
  3. Use partials for reusable components
  4. Separate views and viewmodels for complex pages
  5. Use tag helpers for clean markup
  6. Handle null content gracefully
  1. 使用PropertyFor处理可编辑属性
  2. 支持编辑模式并添加适当的属性
  3. 使用局部视图实现可复用组件
  4. 分离视图与ViewModel处理复杂页面
  5. 使用标签助手实现简洁的标记
  6. 优雅处理空内容