我有一个 MVC 站点,我们正在使用 MVCSiteMapProvider 4.4.3 和 Autofac。我们使用 XML 和属性的混合构建我们的站点。我们有几百个动态节点,并且启用了安全修整。去年,站点变得越来越大,大约有 120 个控制器。所有控制器都使用因角色而异的授权属性等进行保护。
在我们的布局中,我们称之为@Html.MvcSiteMap().SiteMapPath()
增加了大约 950 毫秒的页面加载时间。如果我们删除该行,我们的页面几乎会立即加载。
我们的菜单过去需要一秒钟才能加载 - 但是我们将它放在 RenderAction 中并简单地缓存了在很大程度上解决了该问题的结果。
这是常见的表现吗?是否有任何明显的方法可以提高 SiteMapPath 的性能或可能导致性能如此糟糕的事情
如果我们重新加载页面,第二次和第一次一样长
仅浏览大约十页并进行分析,但大约 70% 的 CPU 周期似乎已经转到:
MvcSiteMapProvider.Caching.RequestCache.GetValue(String)
MvcSiteMapProvider.RequestCacheableSiteMapNode.GetCacheKey(String)
MvcSiteMapProvider.Collections.Specialized.RouteValueDictionary.GetCacheKey()
MvcSiteMapProvider.RequestCacheableSiteMap.GetCacheKey(String)
MvcSiteMapProvider.Web.Mvc.MvcContextFactory.CreateHttpContext(ISiteMapNode)
MvcSiteMapProvider.RequestCacheableSiteMapNode.get_AreRouteParametersPreserved()
MvcSiteMapProvider.SiteMap.GetChildNodes(ISiteMapNode)
MvcSiteMapProvider.SiteMap.FindSiteMapNodeFromControllerAction(ISiteMapNode, IDictionary[StringObject], RoutMvcSiteMapProvider.Collections.CacheableDictionary`2.ContainsKey(TKey)
eBase)
MvcSiteMapProvider.RequestCacheableSiteMap.IsAccessibleToUser(ISiteMapNode)
MvcSiteMapProvider.Collections.CacheableDictionary`2.get_ReadOperationDictionary()
对 MVCSiteMapProvder 命名空间的总调用次数为 4.34 亿次,而我们自己的代码命名空间为 100 万次。
我们的 Autofac 模块是:
public class MvcSiteMapProviderModule : global::Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
const bool SecurityTrimmingEnabled = false;
const bool EnableLocalization = false;
var absoluteFileName = HostingEnvironment.MapPath("~/Mvc.sitemap");
var absoluteCacheExpiration = TimeSpan.FromMinutes(60);
var includeAssembliesForScan = new[] { "OnboardWeb" };
var currentAssembly = this.GetType().Assembly;
var siteMapProviderAssembly = typeof(SiteMaps).Assembly;
var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly };
var excludeTypes = new Type[]
{
typeof(SiteMapNodeVisibilityProviderStrategy),
typeof(SiteMapXmlReservedAttributeNameProvider),
typeof(SiteMapBuilderSetStrategy)
};
var multipleImplementationTypes = new Type[]
{
typeof(ISiteMapNodeUrlResolver),
typeof(ISiteMapNodeVisibilityProvider),
typeof(IDynamicNodeProvider)
};
// Single implementations of interface with matching name (minus the "I").
CommonConventions.RegisterDefaultConventions(
(interfaceType, implementationType) => builder.RegisterType(implementationType).As(interfaceType).SingleInstance(),
new Assembly[] { siteMapProviderAssembly },
allAssemblies,
excludeTypes,
string.Empty);
// Multiple implementations of strategy based extension points
CommonConventions.RegisterAllImplementationsOfInterface(
(interfaceType, implementationType) => builder.RegisterType(implementationType).As(interfaceType).SingleInstance(),
multipleImplementationTypes,
allAssemblies,
excludeTypes,
"^Composite");
// Registration of internal controllers
CommonConventions.RegisterAllImplementationsOfInterface(
(interfaceType, implementationType) => builder.RegisterType(implementationType).As(interfaceType).AsSelf().InstancePerDependency(),
new Type[] { typeof(IController) },
new Assembly[] { siteMapProviderAssembly },
new Type[0],
string.Empty);
// Visibility Providers
builder.RegisterType<SiteMapNodeVisibilityProviderStrategy>()
.As<ISiteMapNodeVisibilityProviderStrategy>()
.WithParameter("defaultProviderName", string.Empty);
//.WithParameter("defaultProviderName", "MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider, MvcSiteMapProvider");
//builder.RegisterType<BreadCrumbOnlyVisibilityProvider>()
// .As<ISiteMapNodeVisibilityProvider>().InstancePerLifetimeScope();
// Pass in the global controllerBuilder reference
builder.RegisterInstance(ControllerBuilder.Current)
.As<ControllerBuilder>();
builder.RegisterType<BuildManagerAdaptor>()
.As<IBuildManager>();
builder.RegisterType<ControllerBuilderAdaptor>()
.As<IControllerBuilder>();
builder.RegisterType<ControllerTypeResolverFactory>()
.As<IControllerTypeResolverFactory>()
.WithParameter("areaNamespacesToIgnore", new string[0]);
// Configure Security
builder.RegisterType<AuthorizeAttributeAclModule>()
.Named<IAclModule>("authorizeAttributeAclModule");
builder.RegisterType<XmlRolesAclModule>()
.Named<IAclModule>("xmlRolesAclModule");
builder.RegisterType<CompositeAclModule>()
.As<IAclModule>()
.WithParameter(
(p, c) => p.Name == "aclModules",
(p, c) => new[]
{
c.ResolveNamed<IAclModule>("authorizeAttributeAclModule"),
c.ResolveNamed<IAclModule>("xmlRolesAclModule")
});
builder.RegisterInstance(System.Runtime.Caching.MemoryCache.Default)
.As<System.Runtime.Caching.ObjectCache>();
builder.RegisterGeneric(typeof(RuntimeCacheProvider<>))
.As(typeof(ICacheProvider<>));
builder.RegisterType<RuntimeFileCacheDependency>()
.Named<ICacheDependency>("cacheDependency1")
.WithParameter("fileName", absoluteFileName);
builder.RegisterType<CacheDetails>()
.Named<ICacheDetails>("cacheDetails1")
.WithParameter("absoluteCacheExpiration", absoluteCacheExpiration)
.WithParameter("slidingCacheExpiration", TimeSpan.MinValue)
.WithParameter(
(p, c) => p.Name == "cacheDependency",
(p, c) => c.ResolveNamed<ICacheDependency>("cacheDependency1"));
// Configure the visitors
builder.RegisterType<UrlResolvingSiteMapNodeVisitor>()
.As<ISiteMapNodeVisitor>();
// Prepare for our node providers
builder.RegisterType<FileXmlSource>()
.Named<IXmlSource>("xmlSource1")
.WithParameter("fileName", absoluteFileName);
builder.RegisterType<SiteMapXmlReservedAttributeNameProvider>()
.As<ISiteMapXmlReservedAttributeNameProvider>()
.WithParameter("attributesToIgnore", new string[0]);
// Register the sitemap node providers
builder.RegisterType<XmlSiteMapNodeProvider>()
.Named<ISiteMapNodeProvider>("xmlSiteMapNodeProvider1")
.WithParameter("includeRootNode", true)
.WithParameter("useNestedDynamicNodeRecursion", false)
.WithParameter(
(p, c) => p.Name == "xmlSource",
(p, c) => c.ResolveNamed<IXmlSource>("xmlSource1"));
builder.RegisterType<ReflectionSiteMapNodeProvider>()
.Named<ISiteMapNodeProvider>("reflectionSiteMapNodeProvider1")
.WithParameter("includeAssemblies", includeAssembliesForScan)
.WithParameter("excludeAssemblies", new string[0]);
builder.RegisterType<CompositeSiteMapNodeProvider>()
.Named<ISiteMapNodeProvider>("siteMapNodeProvider1")
.WithParameter(
(p, c) => p.Name == "siteMapNodeProviders",
(p, c) => new[]
{
c.ResolveNamed<ISiteMapNodeProvider>("xmlSiteMapNodeProvider1"),
c.ResolveNamed<ISiteMapNodeProvider>("reflectionSiteMapNodeProvider1")
});
// Register the sitemap builders
builder.RegisterType<SiteMapBuilder>()
.Named<ISiteMapBuilder>("siteMapBuilder1")
.WithParameter(
(p, c) => p.Name == "siteMapNodeProvider",
(p, c) => c.ResolveNamed<ISiteMapNodeProvider>("siteMapNodeProvider1"));
// Configure the builder sets
builder.RegisterType<SiteMapBuilderSet>()
.Named<ISiteMapBuilderSet>("builderSet1")
.WithParameter("instanceName", "default")
.WithParameter("securityTrimmingEnabled", SecurityTrimmingEnabled)
.WithParameter("enableLocalization", EnableLocalization)
.WithParameter(
(p, c) => p.Name == "siteMapBuilder",
(p, c) => c.ResolveNamed<ISiteMapBuilder>("siteMapBuilder1"))
.WithParameter(
(p, c) => p.Name == "cacheDetails",
(p, c) => c.ResolveNamed<ICacheDetails>("cacheDetails1"));
builder.RegisterType<SiteMapBuilderSetStrategy>()
.As<ISiteMapBuilderSetStrategy>()
.WithParameter(
(p, c) => p.Name == "siteMapBuilderSets",
(p, c) => c.ResolveNamed<IEnumerable<ISiteMapBuilderSet>>("builderSet1"));
}
}
}
我们有一个动态节点提供者添加了几百个节点(如果我们关闭它会更快但不是很明显)
public class LocationsDynamicNodeProvider : DynamicNodeProviderBase
{
private List<Country> countries;
/// <summary>
/// Lazy loading of countries. Only create the graph when we actually need it.
/// Previously it was in the constructor, but for lightweight object composition we must
/// not do any work in the constructor.
/// </summary>
/// <returns></returns>
private List<Country> GetCountries()
{
if (countries == null)
{
var countryRepository = DependencyResolver.Current.GetService<ICountryRepository>();
countries = countryRepository.AllWithLocations().ToList();
}
return countries;
}
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
countries = GetCountries();
foreach (var country in countries)
{
var countrynode = new DynamicNode
{
Title = country.Name,
Controller = "Assets",
Action = "Index",
Area = "OnboardAsset",
RouteValues = new RouteValueDictionary
{
{ "countryname", country.Name },
{ "locationname", "" },
{ "sitename", "" }
},
ParentKey = "All Assets",
Key = "countrynode_" + country.CountryId
};
yield return countrynode;
foreach (var site in country.Sites)
{
var sitenode = new DynamicNode
{
Title = site.Name,
Controller = "Assets",
Action = "Index",
Area = "OnboardAsset",
RouteValues =
new RouteValueDictionary()
{
{ "countryname", country.Name },
{ "sitename", site.Name },
{ "locationname", "" }
},
ParentKey = "countrynode_" + country.CountryId,
Key = "sitenode_" + site.SiteId
};
yield return sitenode;
foreach (var location in site.Locations)
{
var locationNode = new DynamicNode
{
Title = location.Name,
Controller = "Assets",
Action = "Index",
Area = "OnboardAsset",
RouteValues =
new RouteValueDictionary
{
{ "countryname", country.Name },
{ "sitename", site.Name },
{ "locationname", location.Name }
},
ParentKey = "sitenode_" + site.SiteId
};
yield return locationNode;
}
}
}
}
}
}
站点地图配置:
<mvcSiteMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0" xsi:schemaLocation="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0 MvcSiteMapSchema.xsd">
<mvcSiteMapNode title="Home" controller="HomePage" action="Index" key="Home">
<mvcSiteMapNode title="People" key="PeopleTop" controller="People" action="Index" area="OnboardTeam" >
<mvcSiteMapNode title="All People" key="PeopleIndex" controller="People" action="Index" area="OnboardTeam" visibility="hideChildren" />
</mvcSiteMapNode>
<mvcSiteMapNode title="Assets" key="Assets" controller="Home" action="Index" area="OnboardAsset">
<mvcSiteMapNode title="All Assets" key="All Assets" controller="Assets" action="Index" route="AllAssets">
<mvcSiteMapNode title="LocationNodes" dynamicNodeProvider="Onboard.Web.Infrastructure.Menu.LocationsDynamicNodeProvider, OnboardWeb" />
</mvcSiteMapNode>
</mvcSiteMapNode>
<mvcSiteMapNode title="Jobs" controller="Jobs" action="Index" area="Core" key="Jobs" visibility="hideChildren" />
<mvcSiteMapNode title="Reports" key="Report" clickable="false">
<mvcSiteMapNode title="Certifications" key="Report_Certifications" clickable="false" />
</mvcSiteMapNode>
<mvcSiteMapNode title="CRM" controller= "CRM" area="CRM" key="CRM" action="Index">
</mvcSiteMapNode>
<mvcSiteMapNode title="PO" key="PO" action="GeneralList" controller= "PurchaseOrders">
<mvcSiteMapNode title="Purchase Orders" action="GeneralList" controller= "PurchaseOrders" area="PO" key="PO_List" />
</mvcSiteMapNode>
<mvcSiteMapNode title="Training" key="OnboardTraining" controller="PersonTrainingBookings" action="Index" />
<mvcSiteMapNode title="Document Store" key="Documents" area="Documents" controller="DocumentStore" action="Browse" />
<mvcSiteMapNode title="Admin" key="Admin" roles="Administrator" clickable="false">
<mvcSiteMapNode title="Competence" key="Competences" area="OnboardTeam" controller="Competences" action="Index" />
<mvcSiteMapNode title="Certification" key="Certifications" area="OnboardTeam" controller="Certification" action="Index" />
<mvcSiteMapNode title="Supporting Entities" key="LookupTable" clickable="false" />
<mvcSiteMapNode title="Entity Types" key="LookupTypes" clickable="false" />
<mvcSiteMapNode title="Users and Teams" key="UsersAndTeams" area="Core" controller="UserManagement" action="Index" clickable="false" />
<mvcSiteMapNode title="Companies" key="Organisations" area="Core" controller="Companies" action="Index" clickable="false" />
<mvcSiteMapNode title="Geographic Data" key="Geographic" area="Core" controller="Countries" action="Index" clickable="false" />
</mvcSiteMapNode>
</mvcSiteMapNode>
</mvcSiteMap>
其余节点正在使用控制器操作上的属性添加
我们在发布模式下运行