怎样才能更好的优化这个EpiServer面包屑导航代码呢?

How can we better optimize this EpiServer breadcrumb navigation code?

我正在努力提高 MVC EpiServer 网站的性能,并且正在寻找有关面包屑控件代码优化的一些指导。我们正在尝试减少此站点运行时在 IIS 中的内存占用量。

目前这段代码在一个视图中,我打算通过将它放入一个帮助程序来修改它 class。但我的核心问题围绕着这里使用的实际对象。这些是矫枉过正还是不必要的?

PageData sp = DataFactory.Instance.GetPage(PageReference.StartPage);
PageRouteHelper pageRouteHelper = EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance<PageRouteHelper>();
PageData currentPage = pageRouteHelper.Page;
PageData pp = DataFactory.Instance.GetPage(currentPage.ParentLink);

List<PageData> navList = new List<PageData>();
while (pp.ContentLink != sp.ContentLink)
{
    if (pp.HasTemplate() && pp.VisibleInMenu && pp.IsVisibleOnSite())
    {
        navList.Add(pp);
    }
    pp = DataFactory.Instance.GetPage(pp.ParentLink);
}
navList.Add(pp);
navList.Reverse();

sp = null;
pageRouteHelper = null;
currentPage = null;
pp = null;

以上是视图中的代码块。然后一个简单的循环遍历 navList,像这样:

@foreach (PageData page in navList)
{
    <li>@Html.PageLink(page)</li>
}

我不喜欢代码使用了两次 DataFactory.Instance.GetPage,并且实例化了 3 个 PageData 对象(加上一个 PageData 列表)来执行此任务。拥有 50 多个属性和大约 40 个方法,PageData 对象看起来很重。

这对我来说有点矫枉过正,尤其是当我们想要从每个 PageData 对象获得的是它的可见性、锚 URL 和锚文本时。我真的很想避免代码膨胀,尤其是在这个 EpiServer 网站的大部分页面上呈现的 Breadcrumb 控件上。

感谢您的帮助。

对于您对 PageData 的担忧:

PageData 对象(以及您在自己的系统中创建的所有派生对象("ArticlePage"、"ListPage" 或其他)都缓存在内存中。与 "FooBar-style" 教程示例相比class是的,它是巨大的,但在现实生活中,您可以轻松地处理数百个它们用于现代服务器上的单个页面加载。添加一些输出缓存或其他缓存解决方案,它就不是问题了练习。为每个请求做一堆 Get<T>(...)GetChildren<T>(...) 是完全没问题的。

它确实具有大量属性,但对于给定的页面对象具有 "one source of truth" 可能比对整个站点中的每一次使用都具有自定义查询更好。突然,您会想要一些您定义的文本 属性。或者访问访问控制列表。或者检查它的内容类型。或者编辑它并保存它。

例如:在一个简单的菜单中,您可能都需要它的名称,可能是某种形式的 "Intro"、"Excerpt" 或 "MainIntro" 属性,它是ContentLink(为 <a href=...> 创建实际值)此外,您通常会处理特定类型的项目,而不是 PageData 基础 class,它打开了您自己定义的更多属性。

所以没有,既没有必要也没有矫枉过正。这是常见的做法,并且有效。如果您只是不想在编辑时看到所有属性,您可以简单地将它们处理为 IContent。但是请注意,它背后的实现仍然是 PageData 对象(或者更确切地说,运行时生成的代理 class 是您正在检索的实际页面类型)。

除此之外:

  • 您不应该在视图中放置大量这样的代码。程序逻辑属于控制器或其他 classes。这与 Episerver 无关,更多的是与一般最佳实践有关。
  • 您应该避免访问 DataFactory,而是通过依赖注入(作为构造函数参数,或通过 ServiceLocator.Current.GetInstance<IContentLoader>.
  • 检索 IContentLoader

这是制作面包屑的一种灵活而简单的方法:

创建一个文件,如 HtmlHelperExtensions.cs,其中包含:

public static IHtmlString BreadCrumbs(
    this HtmlHelper helper,
    ContentReference currentPage,
    Func<MenuItemViewModel, HelperResult> itemTemplate = null,
    bool includeCurrentPage = true,
    bool requireVisibleInMenu = true,
    bool requirePageTemplate = true)
{
    itemTemplate = itemTemplate ?? GetDefaultItemTemplate(helper);
    Func<IEnumerable<PageData>, IEnumerable<PageData>> filter = GetFilter(requireVisibleInMenu, requirePageTemplate);
    var menuItems = new List<MenuItemViewModel>();
    var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();

    var currentPageData = contentLoader.Get<PageData>(currentPage);
    ContentReference parentLink = currentPageData.ParentLink;

    if (includeCurrentPage)
        menuItems.Add(CreateBreadCrumb(currentPageData, currentPage, contentLoader, filter));

    var pages = new List<PageData>();

    do
    {
        var page = contentLoader.Get<PageData>(parentLink);
        pages.Add(page);
            parentLink = page.ParentLink;
        } while (!parentLink.Equals(ContentReference.RootPage));

    menuItems.AddRange(
            pages.FilterForDisplay(requirePageTemplate, requireVisibleInMenu)
                .Select(page => CreateBreadCrumb(page, currentPage, contentLoader, filter)));

    menuItems.Reverse();

    return menuItems.List(itemTemplate);
}

private static MenuItemViewModel CreateBreadCrumb(
    PageData page,
    ContentReference currentContentLink,
    IContentLoader contentLoader,
    Func<IEnumerable<PageData>, IEnumerable<PageData>> filter)
{
    var menuItem = new MenuItemViewModel(page)
    {
        Selected = page.ContentLink.CompareToIgnoreWorkID(currentContentLink),
        HasChildren = new Lazy<bool>(() => filter(contentLoader.GetChildren<PageData>(page.ContentLink)).Any())
    };
    return menuItem;
}

private static Func<IEnumerable<PageData>, IEnumerable<PageData>> GetFilter(bool requireVisibleInMenu, bool requirePageTemplate)
{
    return pages => pages.FilterForDisplay(requirePageTemplate, requireVisibleInMenu);
}

而且,在您看来;

@helper BreadCrumb(MenuItemViewModel breadCrumbItem)
{
    <li>
        @if (breadCrumbItem.Page.HasTemplate() && !breadCrumbItem.Page.ContentLink.CompareToIgnoreWorkID(Model.CurrentPage.ContentLink))
        {
            @Html.PageLink(breadCrumbItem.Page)
        }
        else
        {
            @breadCrumbItem.Page.Name
        }
    </li>
}

<nav>
    <ul>
        @Html.BreadCrumbs(Model.CurrentPage.ContentLink, BreadCrumb, requireVisibleInMenu: false, includeCurrentPage: false)
    </ul>
</nav>