怎样才能更好的优化这个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>
我正在努力提高 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>