I have an MVC site and we are using MVCSiteMapProvider 4.4.3 with Autofac. We construct our site using a mixture of XML and Attributes. We have a few hundred dynamic nodes and we have security trimming enabled. Site has been getting larger over the last year with approximately 120 controllers. All controllers are secured using authorize attributes that vary per role etc.
In our layout we call @Html.MvcSiteMap().SiteMapPath()
this adds approximately 950ms onto page load time. If we remove the line our pages load almost instantly.
Our menu used to take a second to load - however we put it in a RenderAction and simply cached the result which largely fixed that issue.
Is this common performance? Is there any obvious way to enhance performance for SiteMapPath or things that may cause the performance to be so poor
If we reload the page it takes as long the second time as the first
Just browsing about ten pages and profiling but about 70% of CPU cyles seem to have gone to:
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()
The total calls to the MVCSiteMapProvder Namespace were 434 million vs 1 million for all of our own code Namespaces.
Our Autofac module is:
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"));
}
}
}
We have one dynamic node provider adding a few hundred nodes (if we turn it off it is faster but not significantly so)
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;
}
}
}
}
}
}
SiteMap config:
<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>
Rest of nodes are adding using attributes on controller actions
We are running in release mode