PageRenderTime 54ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/v3.3.3/Source/MvcSiteMapProvider/DefaultSiteMapProvider.cs

https://bitbucket.org/tobrien/mvcsitemapprovider-v3.3.3
C# | 1785 lines | 1236 code | 187 blank | 362 comment | 315 complexity | 0224c641bf2e344e4bbd13ab9932e295 MD5 | raw file
  1. #region Using directives
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Collections.Specialized;
  5. using System.Globalization;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Reflection;
  9. using System.Web;
  10. using System.Web.Caching;
  11. using System.Web.Mvc;
  12. using System.Web.Routing;
  13. using System.Xml.Linq;
  14. using MvcSiteMapProvider.Extensibility;
  15. using MvcSiteMapProvider.External;
  16. #endregion
  17. namespace MvcSiteMapProvider
  18. {
  19. /// <summary>
  20. /// DefaultSiteMapProvider class
  21. /// </summary>
  22. public class DefaultSiteMapProvider
  23. : StaticSiteMapProvider
  24. {
  25. #region Private
  26. protected const string RootName = "mvcSiteMap";
  27. protected const string NodeName = "mvcSiteMapNode";
  28. protected readonly XNamespace ns = "http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-3.0";
  29. protected readonly object synclock = new object();
  30. protected string cacheKey = "__MVCSITEMAP_A33EF2B1-F0A4-4507-B011-94669840F79C";
  31. protected string aclCacheItemKey = "__MVCSITEMAP_7C881C5D-9338-4CEF-AF5F-4BA5B31EB1C0";
  32. protected string currentNodeCacheKey = "__MVCSITEMAP_C7704085-E4F5-42C2-BE74-3DD32D360623";
  33. protected SiteMapNode root;
  34. protected bool isBuildingSiteMap = false;
  35. protected bool scanAssembliesForSiteMapNodes;
  36. protected string siteMapFile = string.Empty;
  37. protected string siteMapFileAbsolute = string.Empty;
  38. protected List<string> excludeAssembliesForScan = new List<string>();
  39. protected List<string> includeAssembliesForScan = new List<string>();
  40. protected List<string> attributesToIgnore = new List<string>();
  41. #endregion
  42. #region Properties
  43. /// <summary>
  44. /// Gets or sets the node key generator.
  45. /// </summary>
  46. /// <value>The node key generator.</value>
  47. public INodeKeyGenerator NodeKeyGenerator { get; set; }
  48. /// <summary>
  49. /// Gets or sets the controller type resolver.
  50. /// </summary>
  51. /// <value>The controller type resolver.</value>
  52. public IControllerTypeResolver ControllerTypeResolver { get; set; }
  53. /// <summary>
  54. /// Gets or sets the action method parameter resolver.
  55. /// </summary>
  56. /// <value>The action method parameter resolver.</value>
  57. public IActionMethodParameterResolver ActionMethodParameterResolver { get; set; }
  58. /// <summary>
  59. /// Gets or sets the acl module.
  60. /// </summary>
  61. /// <value>The acl module.</value>
  62. public IAclModule AclModule { get; set; }
  63. /// <summary>
  64. /// Gets or sets the site map node URL resolver.
  65. /// </summary>
  66. /// <value>The site map node URL resolver.</value>
  67. public ISiteMapNodeUrlResolver SiteMapNodeUrlResolver { get; set; }
  68. /// <summary>
  69. /// Gets or sets the site map node visibility provider.
  70. /// </summary>
  71. /// <value>The site map node visibility provider.</value>
  72. public ISiteMapNodeVisibilityProvider SiteMapNodeVisibilityProvider { get; set; }
  73. /// <summary>
  74. /// Gets or sets the site map provider event handler.
  75. /// </summary>
  76. /// <value>The site map provider event handler.</value>
  77. public ISiteMapProviderEventHandler SiteMapProviderEventHandler { get; set; }
  78. /// <summary>
  79. /// Gets or sets the duration of the cache.
  80. /// </summary>
  81. /// <value>The duration of the cache.</value>
  82. public int CacheDuration { get; private set; }
  83. /// <summary>
  84. /// Get or sets the name of HTTP method to use when checking node accessibility.
  85. /// </summary>
  86. /// <value>
  87. /// The name of HTTP method to use when checking node accessibility or null to use
  88. /// the method of the current request. Defaults to null.
  89. /// </value>
  90. public string RouteMethod { get; private set; }
  91. /// <summary>
  92. /// Gets the RootNode for the current SiteMapProvider.
  93. /// </summary>
  94. public override SiteMapNode RootNode
  95. {
  96. get
  97. {
  98. return GetRootNodeCore();
  99. }
  100. }
  101. /// <summary>
  102. /// Gets the <see cref="T:System.Web.SiteMapNode"/> object that represents the currently requested page.
  103. /// </summary>
  104. /// <returns>A <see cref="T:System.Web.SiteMapNode"/> that represents the currently requested page; otherwise, null, if the <see cref="T:System.Web.SiteMapNode"/> is not found or cannot be returned for the current user.</returns>
  105. public override SiteMapNode CurrentNode
  106. {
  107. get
  108. {
  109. if (HttpContext.Current.Items[currentNodeCacheKey] == null)
  110. {
  111. var currentNode = base.CurrentNode;
  112. HttpContext.Current.Items[currentNodeCacheKey] = currentNode;
  113. return currentNode;
  114. }
  115. return (SiteMapNode)HttpContext.Current.Items[currentNodeCacheKey];
  116. }
  117. }
  118. #endregion
  119. #region Constructor
  120. /// <summary>
  121. /// Initializes a new instance of the <see cref="DefaultSiteMapProvider"/> class.
  122. /// </summary>
  123. public DefaultSiteMapProvider()
  124. {
  125. CacheDuration = 5;
  126. }
  127. #endregion
  128. #region Methods
  129. /// <summary>
  130. /// Returns the current root, otherwise calls the BuildSiteMap method.
  131. /// </summary>
  132. /// <returns></returns>
  133. protected override SiteMapNode GetRootNodeCore()
  134. {
  135. lock (synclock)
  136. {
  137. BuildSiteMap();
  138. return root;
  139. }
  140. }
  141. /// <summary>
  142. /// Retrieves a Boolean value indicating whether the specified <see cref="T:System.Web.SiteMapNode"/> object can be viewed by the user in the specified context.
  143. /// </summary>
  144. /// <param name="context">The <see cref="T:System.Web.HttpContext"/> that contains user information.</param>
  145. /// <param name="node">The <see cref="T:System.Web.SiteMapNode"/> that is requested by the user.</param>
  146. /// <returns>
  147. /// true if security trimming is enabled and <paramref name="node"/> can be viewed by the user or security trimming is not enabled; otherwise, false.
  148. /// </returns>
  149. /// <exception cref="T:System.ArgumentNullException">
  150. /// <paramref name="context"/> is null.
  151. /// - or -
  152. /// <paramref name="node"/> is null.
  153. /// </exception>
  154. public override bool IsAccessibleToUser(HttpContext context, SiteMapNode node)
  155. {
  156. if ((isBuildingSiteMap && CacheDuration > 0) || !SecurityTrimmingEnabled)
  157. {
  158. return true;
  159. }
  160. // Construct call cache?
  161. if (context.Items[aclCacheItemKey] == null)
  162. {
  163. context.Items[aclCacheItemKey] = new Dictionary<SiteMapNode, bool>();
  164. }
  165. Dictionary<SiteMapNode, bool> aclCacheItem
  166. = (Dictionary<SiteMapNode, bool>)context.Items[aclCacheItemKey];
  167. // Is the result of this call cached?
  168. if (!aclCacheItem.ContainsKey(node))
  169. {
  170. aclCacheItem[node] = AclModule.IsAccessibleToUser(ControllerTypeResolver, this, context, node);
  171. }
  172. return aclCacheItem[node];
  173. }
  174. /// <summary>
  175. /// Initializes our custom provider, gets the attributes that are set in the config
  176. /// that enable us to customise the behaiviour of this provider.
  177. /// </summary>
  178. /// <param name="name"></param>
  179. /// <param name="attributes"></param>
  180. public override void Initialize(string name, NameValueCollection attributes)
  181. {
  182. // Initialize base
  183. base.Initialize(name, attributes);
  184. // Get the siteMapFile from the passed in attributes.
  185. siteMapFile = attributes["siteMapFile"];
  186. if (!string.IsNullOrEmpty(siteMapFile))
  187. {
  188. siteMapFileAbsolute = HttpContext.Current.Server.MapPath(siteMapFile);
  189. if (!File.Exists(siteMapFileAbsolute))
  190. {
  191. throw new MvcSiteMapException(Resources.Messages.SiteMapFileNotFound);
  192. }
  193. }
  194. // If a cacheDuration was passed in set it, otherwise
  195. // it will default to 5 minutes.
  196. if (!string.IsNullOrEmpty(attributes["cacheDuration"]))
  197. {
  198. CacheDuration = int.Parse(attributes["cacheDuration"]);
  199. }
  200. // If a cache key was set in config, set it.
  201. // Otherwise it will use the default which is a GUID.
  202. if (!string.IsNullOrEmpty(attributes["cacheKey"]))
  203. {
  204. cacheKey = attributes["cacheKey"];
  205. }
  206. // Enable Localization
  207. if (!string.IsNullOrEmpty(attributes["enableLocalization"]))
  208. {
  209. EnableLocalization = Boolean.Parse(attributes["enableLocalization"]);
  210. }
  211. // Resource key
  212. if (!string.IsNullOrEmpty(attributes["resourceKey"]))
  213. {
  214. ResourceKey = attributes["resourceKey"];
  215. }
  216. else if (!string.IsNullOrEmpty(siteMapFile))
  217. {
  218. ResourceKey = Path.GetFileName(siteMapFile).ToLowerInvariant();
  219. }
  220. // Scan assemblies for IMvcSiteMapNodeAttribute?
  221. if (!string.IsNullOrEmpty(attributes["scanAssembliesForSiteMapNodes"]))
  222. {
  223. scanAssembliesForSiteMapNodes = Boolean.Parse(attributes["scanAssembliesForSiteMapNodes"]);
  224. // TODO Deprecated ---
  225. // Which assemblies should be skipped?
  226. if (!string.IsNullOrEmpty(attributes["skipAssemblyScanOn"]))
  227. {
  228. throw new MvcSiteMapException("The skipAssemblyScanOn attribute in your Web.config settings is deprecated. Please use the excludeAssembliesForScan attribute instead.");
  229. }
  230. // ---
  231. // Is an exclude list given?
  232. if (!string.IsNullOrEmpty(attributes["excludeAssembliesForScan"]))
  233. {
  234. var temp = attributes["excludeAssembliesForScan"];
  235. if (!string.IsNullOrEmpty(temp))
  236. {
  237. excludeAssembliesForScan = temp.Split(';', ',').ToList();
  238. }
  239. }
  240. // Is an include list given?
  241. if (!string.IsNullOrEmpty(attributes["includeAssembliesForScan"]))
  242. {
  243. var temp = attributes["includeAssembliesForScan"];
  244. if (!string.IsNullOrEmpty(temp))
  245. {
  246. includeAssembliesForScan = temp.Split(';', ',').ToList();
  247. }
  248. }
  249. }
  250. // Which attributes in the sitemap XML should be ignored?
  251. if (!string.IsNullOrEmpty(attributes["attributesToIgnore"]))
  252. {
  253. var tempAttributesToIgnore = attributes["attributesToIgnore"];
  254. if (!string.IsNullOrEmpty(tempAttributesToIgnore))
  255. {
  256. attributesToIgnore = tempAttributesToIgnore.Split(';', ',').ToList();
  257. }
  258. }
  259. // Is a node key generator given?
  260. if (!string.IsNullOrEmpty(attributes["nodeKeyGenerator"]))
  261. {
  262. NodeKeyGenerator = Activator.CreateInstance(
  263. Type.GetType(attributes["nodeKeyGenerator"])) as INodeKeyGenerator;
  264. }
  265. if (NodeKeyGenerator == null)
  266. {
  267. NodeKeyGenerator =
  268. #if !NET35
  269. DependencyResolver.Current.GetService<INodeKeyGenerator>() ??
  270. #endif
  271. new DefaultNodeKeyGenerator();
  272. }
  273. // Is a controller type resolver given?
  274. if (!string.IsNullOrEmpty(attributes["controllerTypeResolver"]))
  275. {
  276. ControllerTypeResolver = Activator.CreateInstance(
  277. Type.GetType(attributes["controllerTypeResolver"])) as IControllerTypeResolver;
  278. }
  279. if (ControllerTypeResolver == null)
  280. {
  281. ControllerTypeResolver =
  282. #if !NET35
  283. DependencyResolver.Current.GetService<IControllerTypeResolver>() ??
  284. #endif
  285. new DefaultControllerTypeResolver();
  286. }
  287. // Is an action method parameter resolver given?
  288. if (!string.IsNullOrEmpty(attributes["actionMethodParameterResolver"]))
  289. {
  290. ActionMethodParameterResolver = Activator.CreateInstance(
  291. Type.GetType(attributes["actionMethodParameterResolver"])) as IActionMethodParameterResolver;
  292. }
  293. if (ActionMethodParameterResolver == null)
  294. {
  295. ActionMethodParameterResolver =
  296. #if !NET35
  297. DependencyResolver.Current.GetService<IActionMethodParameterResolver>() ??
  298. #endif
  299. new DefaultActionMethodParameterResolver();
  300. }
  301. // Is an acl module given?
  302. if (!string.IsNullOrEmpty(attributes["aclModule"]))
  303. {
  304. AclModule = Activator.CreateInstance(
  305. Type.GetType(attributes["aclModule"])) as IAclModule;
  306. }
  307. if (AclModule == null)
  308. {
  309. AclModule =
  310. #if !NET35
  311. DependencyResolver.Current.GetService<IAclModule>() ??
  312. #endif
  313. new DefaultAclModule();
  314. }
  315. if (!string.IsNullOrEmpty(attributes["routeMethod"]))
  316. {
  317. RouteMethod = attributes["routeMethod"];
  318. }
  319. // Is a SiteMapNode URL resolver given?
  320. if (!string.IsNullOrEmpty(attributes["siteMapNodeUrlResolver"]))
  321. {
  322. SiteMapNodeUrlResolver = Activator.CreateInstance(
  323. Type.GetType(attributes["siteMapNodeUrlResolver"])) as ISiteMapNodeUrlResolver;
  324. }
  325. if (SiteMapNodeUrlResolver == null)
  326. {
  327. SiteMapNodeUrlResolver =
  328. #if !NET35
  329. DependencyResolver.Current.GetService<ISiteMapNodeUrlResolver>() ??
  330. #endif
  331. new DefaultSiteMapNodeUrlResolver();
  332. }
  333. // Is a SiteMapNode visibility provider given?
  334. if (!string.IsNullOrEmpty(attributes["siteMapNodeVisibilityProvider"]))
  335. {
  336. SiteMapNodeVisibilityProvider = Activator.CreateInstance(
  337. Type.GetType(attributes["siteMapNodeVisibilityProvider"])) as ISiteMapNodeVisibilityProvider;
  338. }
  339. if (SiteMapNodeVisibilityProvider == null)
  340. {
  341. SiteMapNodeVisibilityProvider =
  342. #if !NET35
  343. DependencyResolver.Current.GetService<ISiteMapNodeVisibilityProvider>() ??
  344. #endif
  345. new DefaultSiteMapNodeVisibilityProvider();
  346. }
  347. // Is a SiteMapProvider event handler given?
  348. if (!string.IsNullOrEmpty(attributes["siteMapProviderEventHandler"]))
  349. {
  350. SiteMapProviderEventHandler = Activator.CreateInstance(
  351. Type.GetType(attributes["siteMapProviderEventHandler"])) as ISiteMapProviderEventHandler;
  352. }
  353. if (SiteMapProviderEventHandler == null)
  354. {
  355. SiteMapProviderEventHandler =
  356. #if !NET35
  357. DependencyResolver.Current.GetService<ISiteMapProviderEventHandler>() ??
  358. #endif
  359. new DefaultSiteMapProviderEventHandler();
  360. }
  361. }
  362. /// <summary>
  363. /// Adds a <see cref="T:System.Web.SiteMapNode"/> object to the node collection that is maintained by the site map provider.
  364. /// </summary>
  365. /// <param name="node">The <see cref="T:System.Web.SiteMapNode"/> to add to the node collection maintained by the provider.</param>
  366. protected override void AddNode(SiteMapNode node)
  367. {
  368. if (SiteMapProviderEventHandler.OnAddingSiteMapNode(new SiteMapProviderEventContext(this, node, root)))
  369. {
  370. try
  371. {
  372. // Avoid issue with url table not clearing correctly.
  373. if (base.FindSiteMapNode(node.Url) != null)
  374. {
  375. base.RemoveNode(node);
  376. }
  377. // Allow for external URLs
  378. var encoded = EncodeExternalUrl(node);
  379. // Add the node
  380. base.AddNode(node);
  381. // Restore the external URL
  382. if (encoded)
  383. {
  384. DecodeExternalUrl(node);
  385. }
  386. }
  387. catch (InvalidOperationException)
  388. {
  389. if (!isBuildingSiteMap)
  390. {
  391. throw;
  392. }
  393. }
  394. SiteMapProviderEventHandler.OnAddedSiteMapNode(new SiteMapProviderEventContext(this, node, root));
  395. }
  396. }
  397. /// <summary>
  398. /// Adds a <see cref="T:System.Web.SiteMapNode"/> to the collections that are maintained by the site map provider and establishes a parent/child relationship between the <see cref="T:System.Web.SiteMapNode"/> objects.
  399. /// </summary>
  400. /// <param name="node">The <see cref="T:System.Web.SiteMapNode"/> to add to the site map provider.</param>
  401. /// <param name="parentNode">The <see cref="T:System.Web.SiteMapNode"/> under which to add <paramref name="node"/>.</param>
  402. /// <exception cref="T:System.ArgumentNullException">
  403. /// <paramref name="node"/> is null.
  404. /// </exception>
  405. /// <exception cref="T:System.InvalidOperationException">
  406. /// The <see cref="P:System.Web.SiteMapNode.Url"/> or <see cref="P:System.Web.SiteMapNode.Key"/> is already registered with the <see cref="T:System.Web.StaticSiteMapProvider"/>. A site map node must be made up of pages with unique URLs or keys.
  407. /// </exception>
  408. protected override void AddNode(SiteMapNode node, SiteMapNode parentNode)
  409. {
  410. try
  411. {
  412. // Avoid issue with url table not clearing correctly.
  413. if (base.FindSiteMapNode(node.Url) != null)
  414. {
  415. base.RemoveNode(node);
  416. }
  417. // Allow for external URLs
  418. var encoded = EncodeExternalUrl(node);
  419. // Add the node
  420. try
  421. {
  422. base.AddNode(node, parentNode);
  423. }
  424. catch
  425. {
  426. if (parentNode != null) base.RemoveNode(parentNode);
  427. base.AddNode(node, parentNode);
  428. }
  429. // Restore the external URL
  430. if (encoded)
  431. {
  432. DecodeExternalUrl(node);
  433. }
  434. }
  435. catch (InvalidOperationException)
  436. {
  437. if (!isBuildingSiteMap)
  438. {
  439. throw;
  440. }
  441. }
  442. }
  443. #endregion
  444. #region Node Creation
  445. /// <summary>
  446. /// Creates a site map node with the specified key and resources
  447. /// </summary>
  448. /// <param name="key">Node key</param>
  449. /// <param name="explicitResourceKeys">Explicit resource keys</param>
  450. /// <param name="implicitResourceKey">Implicit resource key.</param>
  451. /// <returns>New Site Map Node</returns>
  452. protected virtual MvcSiteMapNode CreateSiteMapNode(string key, NameValueCollection explicitResourceKeys, string implicitResourceKey)
  453. {
  454. return new MvcSiteMapNode(this, key, explicitResourceKeys, implicitResourceKey);
  455. }
  456. #endregion
  457. #region Namespace Override
  458. /// <summary>
  459. /// Namespace of the site map file
  460. /// </summary>
  461. /// <value>The site map namespace.</value>
  462. protected virtual XNamespace SiteMapNamespace
  463. {
  464. get { return ns; }
  465. }
  466. #endregion
  467. #region Sitemap Building/XML Parsing
  468. /// <summary>
  469. /// Builds the sitemap, firstly reads in the XML file, and grabs the outer root element and
  470. /// maps this to become our main out root SiteMap node.
  471. /// </summary>
  472. /// <exception cref="MvcSiteMapException"></exception>
  473. /// <returns>The root SiteMapNode.</returns>
  474. public override SiteMapNode BuildSiteMap()
  475. {
  476. // Return immediately if this method has been called before
  477. if (root != null) // && (HttpContext.Current.Cache[cacheKey] != null || isBuildingSiteMap)
  478. {
  479. return root;
  480. }
  481. // Build sitemap
  482. SiteMapNode rootNode;
  483. lock (synclock)
  484. {
  485. // Return immediately if this method has been called before
  486. if (root != null) // && (HttpContext.Current.Cache[cacheKey] != null || isBuildingSiteMap)
  487. {
  488. return root;
  489. }
  490. XDocument siteMapXml;
  491. try
  492. {
  493. // Does the SiteMap XML exist?
  494. if (File.Exists(siteMapFileAbsolute))
  495. {
  496. // Load the XML document.
  497. siteMapXml = XDocument.Load(siteMapFileAbsolute);
  498. // If no namespace is present (or the wrong one is present), replace it
  499. foreach (var e in siteMapXml.Descendants())
  500. {
  501. if (string.IsNullOrEmpty(e.Name.Namespace.NamespaceName) || e.Name.Namespace != this.SiteMapNamespace)
  502. {
  503. e.Name = XName.Get(e.Name.LocalName, this.SiteMapNamespace.ToString());
  504. }
  505. }
  506. // Enable Localization?
  507. string enableLocalization =
  508. siteMapXml.Element(this.SiteMapNamespace + RootName).GetAttributeValue("enableLocalization");
  509. if (!string.IsNullOrEmpty(enableLocalization))
  510. {
  511. EnableLocalization = Boolean.Parse(enableLocalization);
  512. }
  513. // Get the root mvcSiteMapNode element, and map this to an MvcSiteMapNode
  514. var rootElement = siteMapXml.Element(this.SiteMapNamespace + RootName).Element(this.SiteMapNamespace + NodeName);
  515. rootNode = GetSiteMapNodeFromXmlElement(rootElement, null);
  516. isBuildingSiteMap = true;
  517. SetRootNode(rootNode);
  518. // Process our XML file, passing in the main root sitemap node and xml element.
  519. ProcessXmlNodes(root, rootElement);
  520. }
  521. // Look for assembly-defined nodes
  522. // Process nodes in the assemblies of the current AppDomain?
  523. if (scanAssembliesForSiteMapNodes)
  524. {
  525. isBuildingSiteMap = true;
  526. // List of assemblies
  527. IEnumerable<Assembly> assemblies;
  528. if (includeAssembliesForScan.Any())
  529. {
  530. // An include list is given
  531. assemblies = AppDomain.CurrentDomain.GetAssemblies()
  532. .Where(a => includeAssembliesForScan.Contains(new AssemblyName(a.FullName).Name));
  533. }
  534. else
  535. {
  536. // An exclude list is given
  537. assemblies = AppDomain.CurrentDomain.GetAssemblies()
  538. .Where(a => !a.FullName.StartsWith("mscorlib")
  539. && !a.FullName.StartsWith("System")
  540. && !a.FullName.StartsWith("Microsoft")
  541. && !a.FullName.StartsWith("WebDev")
  542. && !a.FullName.StartsWith("SMDiagnostics")
  543. && !a.FullName.StartsWith("Anonymously")
  544. && !a.FullName.StartsWith("App_")
  545. && !excludeAssembliesForScan.Contains(new AssemblyName(a.FullName).Name));
  546. }
  547. foreach (Assembly assembly in assemblies)
  548. {
  549. // http://stackoverflow.com/questions/1423733/how-to-tell-if-a-net-assembly-is-dynamic
  550. if (!(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder)
  551. && assembly.ManifestModule.GetType().Namespace != "System.Reflection.Emit")
  552. {
  553. ProcessNodesInAssembly(assembly);
  554. }
  555. }
  556. }
  557. // Do we have a root node?
  558. if (root == null)
  559. {
  560. throw new MvcSiteMapException(Resources.Messages.CouldNotDetermineRootNode);
  561. }
  562. }
  563. catch (MvcSiteMapException)
  564. {
  565. throw;
  566. }
  567. catch (Exception ex)
  568. {
  569. throw new MvcSiteMapException(Resources.Messages.UnknownException, ex);
  570. }
  571. finally
  572. {
  573. // Create a cache item, this is used for the sole purpose of being able to invalidate our sitemap
  574. // after a given time period, it also adds a dependancy on the sitemap file,
  575. // so that once changed it will refresh your sitemap, unfortunately at this stage
  576. // there is no dependancy for dynamic data, this could be implemented by clearing the cache item,
  577. // by setting a custom cacheKey, then use this in your administration console for example to
  578. // clear the cache item when the structure requires refreshing.
  579. CacheDependency cacheDependency = null;
  580. if (File.Exists(siteMapFileAbsolute))
  581. {
  582. cacheDependency = new CacheDependency(siteMapFileAbsolute);
  583. }
  584. HttpContext.Current.Cache.Add(cacheKey,
  585. DateTime.Now,
  586. cacheDependency,
  587. DateTime.Now.AddMinutes(CacheDuration),
  588. Cache.NoSlidingExpiration,
  589. CacheItemPriority.Default,
  590. new CacheItemRemovedCallback(OnSiteMapChanged));
  591. isBuildingSiteMap = false;
  592. siteMapXml = null;
  593. }
  594. }
  595. // Finally return our root SiteMapNode.
  596. return root;
  597. }
  598. /// <summary>
  599. /// Refreshes this instance.
  600. /// </summary>
  601. public void Refresh()
  602. {
  603. if (HttpContext.Current.Cache[cacheKey] != null)
  604. {
  605. HttpContext.Current.Cache.Remove(cacheKey);
  606. }
  607. }
  608. /// <summary>
  609. /// When using caching, this method is being used to refresh the sitemap when the root sitemap node identifier is removed from cache.
  610. /// </summary>
  611. /// <param name="key">Cached item key.</param>
  612. /// <param name="item">Cached item.</param>
  613. /// <param name="reason">Reason the cached item was removed.</param>
  614. private void OnSiteMapChanged(string key, object item, CacheItemRemovedReason reason)
  615. {
  616. lock (synclock)
  617. {
  618. if (item != null) // && ((DateTime)item).AddMinutes(CacheDuration).CompareTo(DateTime.Now) <= 0)
  619. {
  620. // Clear the sitemap
  621. Clear();
  622. // Try refreshing the sitemap
  623. if (HttpContext.Current != null)
  624. {
  625. BuildSiteMap();
  626. }
  627. }
  628. }
  629. }
  630. /// <summary>
  631. /// Sets the root node.
  632. /// </summary>
  633. /// <param name="rootNode">The root node.</param>
  634. protected void SetRootNode(SiteMapNode rootNode)
  635. {
  636. if (this.root != null)
  637. {
  638. throw new MvcSiteMapException(Resources.Messages.DuplicateRootNodeDetected);
  639. }
  640. root = rootNode;
  641. AddNode(root);
  642. }
  643. /// <summary>
  644. /// Recursively processes our XML document, parsing our siteMapNodes and dynamicNode(s).
  645. /// </summary>
  646. /// <param name="rootNode">The main root sitemap node.</param>
  647. /// <param name="rootElement">The main root XML element.</param>
  648. protected void ProcessXmlNodes(SiteMapNode rootNode, XElement rootElement)
  649. {
  650. // Loop through each element below the current root element.
  651. foreach (XElement node in rootElement.Elements())
  652. {
  653. SiteMapNode childNode;
  654. if (node.Name == this.SiteMapNamespace + NodeName)
  655. {
  656. // If this is a normal mvcSiteMapNode then map the xml element
  657. // to an MvcSiteMapNode, and add the node to the current root.
  658. childNode = GetSiteMapNodeFromXmlElement(node, rootNode);
  659. SiteMapNode parentNode = rootNode;
  660. //if (childNode.ParentNode != null && childNode.ParentNode != rootNode)
  661. //{
  662. // parentNode = childNode.ParentNode;
  663. //}
  664. if (!HasDynamicNodes(childNode))
  665. {
  666. AddNode(childNode, parentNode);
  667. }
  668. else
  669. {
  670. var dynamicNodesCreated = AddDynamicNodesFor(childNode, parentNode);
  671. // Add non-dynamic childs for every dynamicnode
  672. foreach (var dynamicNodeCreated in dynamicNodesCreated)
  673. {
  674. ProcessXmlNodes(dynamicNodeCreated, node);
  675. }
  676. }
  677. }
  678. else
  679. {
  680. // If the current node is not one of the known node types throw and exception
  681. throw new Exception(Resources.Messages.InvalidSiteMapElement);
  682. }
  683. // Continue recursively processing the XML file.
  684. ProcessXmlNodes(childNode, node);
  685. }
  686. }
  687. /// <summary>
  688. /// Process nodes in assembly
  689. /// </summary>
  690. /// <param name="assembly">Assembly</param>
  691. protected void ProcessNodesInAssembly(Assembly assembly)
  692. {
  693. // Create a list of all nodes defined in the assembly
  694. List<IMvcSiteMapNodeAttributeDefinition> assemblyNodes
  695. = new List<IMvcSiteMapNodeAttributeDefinition>();
  696. // Retrieve types
  697. Type[] types;
  698. try
  699. {
  700. types = assembly.GetTypes();
  701. }
  702. catch (ReflectionTypeLoadException ex)
  703. {
  704. types = ex.Types;
  705. }
  706. // Add all types
  707. foreach (Type type in types)
  708. {
  709. try
  710. {
  711. var attribute =
  712. type.GetCustomAttributes(typeof(IMvcSiteMapNodeAttribute), true).FirstOrDefault() as
  713. IMvcSiteMapNodeAttribute;
  714. if (attribute != null)
  715. {
  716. assemblyNodes.Add(new MvcSiteMapNodeAttributeDefinitionForController
  717. {
  718. SiteMapNodeAttribute = attribute,
  719. ControllerType = type
  720. });
  721. }
  722. }
  723. catch
  724. {
  725. }
  726. // Add their methods
  727. try
  728. {
  729. var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
  730. .Where(x => x.GetCustomAttributes(typeof(IMvcSiteMapNodeAttribute), true).Any());
  731. foreach (var method in methods)
  732. {
  733. var attributes =
  734. (IMvcSiteMapNodeAttribute[])
  735. method.GetCustomAttributes(typeof(IMvcSiteMapNodeAttribute), false);
  736. foreach (var attribute in attributes)
  737. {
  738. assemblyNodes.Add(new MvcSiteMapNodeAttributeDefinitionForAction
  739. {
  740. SiteMapNodeAttribute = attribute,
  741. ControllerType = type,
  742. ActionMethodInfo = method
  743. });
  744. }
  745. }
  746. }
  747. catch
  748. {
  749. }
  750. }
  751. // Create nodes from MVC site map node attribute definitions
  752. CreateNodesFromMvcSiteMapNodeAttributeDefinitions(assemblyNodes.OrderBy(n => n.SiteMapNodeAttribute.Order));
  753. }
  754. /// <summary>
  755. /// Creates the nodes from MVC site map node attribute definitions.
  756. /// </summary>
  757. /// <param name="definitions">The definitions.</param>
  758. protected void CreateNodesFromMvcSiteMapNodeAttributeDefinitions(IEnumerable<IMvcSiteMapNodeAttributeDefinition> definitions)
  759. {
  760. // A dictionary of nodes to process later (node, parentKey)
  761. Dictionary<SiteMapNode, string> nodesToProcessLater
  762. = new Dictionary<SiteMapNode, string>();
  763. // Search root node prior to searching other nodes
  764. if (definitions.Where(t => String.IsNullOrEmpty(t.SiteMapNodeAttribute.ParentKey)).Count() == 1)
  765. {
  766. SiteMapNode attributedRootNode = null;
  767. var item = definitions.Where(t => String.IsNullOrEmpty(t.SiteMapNodeAttribute.ParentKey)).Single();
  768. var actionNode = item as MvcSiteMapNodeAttributeDefinitionForAction;
  769. if (actionNode != null)
  770. {
  771. // Create node for action
  772. attributedRootNode = GetSiteMapNodeFromMvcSiteMapNodeAttribute(
  773. actionNode.SiteMapNodeAttribute, actionNode.ControllerType, actionNode.ActionMethodInfo);
  774. }
  775. else
  776. {
  777. var controllerNode = item as MvcSiteMapNodeAttributeDefinitionForController;
  778. if (controllerNode != null)
  779. {
  780. // Create node for controller
  781. attributedRootNode = GetSiteMapNodeFromMvcSiteMapNodeAttribute(
  782. controllerNode.SiteMapNodeAttribute, controllerNode.ControllerType, null);
  783. }
  784. }
  785. SetRootNode(attributedRootNode);
  786. }
  787. // Create nodes
  788. foreach (var assemblyNode in definitions.Where(t => !String.IsNullOrEmpty(t.SiteMapNodeAttribute.ParentKey)))
  789. {
  790. SiteMapNode nodeToAdd = null;
  791. // Create node
  792. var actionNode = assemblyNode as MvcSiteMapNodeAttributeDefinitionForAction;
  793. if (actionNode != null)
  794. {
  795. // Create node for action
  796. nodeToAdd = GetSiteMapNodeFromMvcSiteMapNodeAttribute(
  797. actionNode.SiteMapNodeAttribute, actionNode.ControllerType, actionNode.ActionMethodInfo);
  798. }
  799. else
  800. {
  801. var controllerNode = assemblyNode as MvcSiteMapNodeAttributeDefinitionForController;
  802. if (controllerNode != null)
  803. {
  804. // Create node for controller
  805. nodeToAdd = GetSiteMapNodeFromMvcSiteMapNodeAttribute(
  806. controllerNode.SiteMapNodeAttribute, controllerNode.ControllerType, null);
  807. }
  808. }
  809. // Add node
  810. if (nodeToAdd != null)
  811. {
  812. // Root node?
  813. if (string.IsNullOrEmpty(assemblyNode.SiteMapNodeAttribute.ParentKey))
  814. {
  815. SetRootNode(nodeToAdd);
  816. }
  817. else
  818. {
  819. var parentNode = FindSiteMapNodeFromKey(assemblyNode.SiteMapNodeAttribute.ParentKey);
  820. if (parentNode == null)
  821. {
  822. nodesToProcessLater.Add(nodeToAdd, assemblyNode.SiteMapNodeAttribute.ParentKey);
  823. }
  824. else
  825. {
  826. if (!HasDynamicNodes(nodeToAdd))
  827. {
  828. AddNode(nodeToAdd, parentNode);
  829. }
  830. else
  831. {
  832. AddDynamicNodesFor(nodeToAdd, parentNode);
  833. }
  834. }
  835. }
  836. }
  837. }
  838. // Process list of nodes that did not have a parent defined.
  839. // If this does not succeed at this time, parent will default to root node.
  840. foreach (var nodeToAdd in nodesToProcessLater)
  841. {
  842. var parentNode = FindSiteMapNodeFromKey(nodeToAdd.Value);
  843. if (parentNode == null)
  844. {
  845. var temp = nodesToProcessLater.Keys.Where(t => t.Key == nodeToAdd.Value).FirstOrDefault();
  846. if (temp != null)
  847. {
  848. parentNode = temp;
  849. }
  850. }
  851. parentNode = parentNode ?? root;
  852. if (parentNode != null)
  853. {
  854. if (!HasDynamicNodes(nodeToAdd.Key))
  855. {
  856. AddNode(nodeToAdd.Key, parentNode);
  857. }
  858. else
  859. {
  860. AddDynamicNodesFor(nodeToAdd.Key, parentNode);
  861. }
  862. }
  863. }
  864. }
  865. /// <summary>
  866. /// Adds the dynamic nodes for node.
  867. /// </summary>
  868. /// <param name="node">The node.</param>
  869. /// <param name="parentNode">The parent node.</param>
  870. private IEnumerable<SiteMapNode> AddDynamicNodesFor(SiteMapNode node, SiteMapNode parentNode)
  871. {
  872. // List of dynamic nodes that have been created
  873. var createdDynamicNodes = new List<SiteMapNode>();
  874. // Dynamic nodes available?
  875. if (!HasDynamicNodes(node))
  876. {
  877. return createdDynamicNodes;
  878. }
  879. // Build dynamic nodes
  880. var mvcNode = node as MvcSiteMapNode;
  881. foreach (var dynamicNode in mvcNode.DynamicNodeProvider.GetDynamicNodeCollection().ToList())
  882. {
  883. string key = dynamicNode.Key;
  884. if (string.IsNullOrEmpty(key))
  885. {
  886. key = NodeKeyGenerator.GenerateKey(parentNode == null ? "" : parentNode.Key, Guid.NewGuid().ToString(), mvcNode.Url, mvcNode.Title, mvcNode.Area, mvcNode.Controller, mvcNode.Action, mvcNode.Clickable);
  887. }
  888. var clone = mvcNode.Clone(key) as MvcSiteMapNode;
  889. foreach (var kvp in dynamicNode.RouteValues)
  890. {
  891. clone.RouteValues[kvp.Key] = kvp.Value;
  892. }
  893. foreach (var kvp in dynamicNode.Attributes)
  894. {
  895. clone[kvp.Key] = kvp.Value;
  896. }
  897. clone.DynamicNodeProvider = null;
  898. clone.IsDynamic = true;
  899. if (!string.IsNullOrEmpty(dynamicNode.Title))
  900. {
  901. clone.Title = dynamicNode.Title;
  902. }
  903. if (!string.IsNullOrEmpty(dynamicNode.Description))
  904. {
  905. clone.Description = dynamicNode.Description;
  906. }
  907. if (!string.IsNullOrEmpty(dynamicNode.TargetFrame))
  908. {
  909. clone.TargetFrame = dynamicNode.TargetFrame;
  910. }
  911. if (!string.IsNullOrEmpty(dynamicNode.ImageUrl))
  912. {
  913. clone.ImageUrl = dynamicNode.ImageUrl;
  914. }
  915. if (!string.IsNullOrEmpty(dynamicNode.Route))
  916. {
  917. clone.Route = dynamicNode.Route;
  918. }
  919. if (dynamicNode.LastModifiedDate.HasValue)
  920. {
  921. clone.LastModifiedDate = dynamicNode.LastModifiedDate.Value;
  922. }
  923. if (dynamicNode.ChangeFrequency != ChangeFrequency.Undefined)
  924. {
  925. clone.ChangeFrequency = dynamicNode.ChangeFrequency;
  926. }
  927. if (dynamicNode.UpdatePriority != UpdatePriority.Undefined)
  928. {
  929. clone.ChangeFrequency = dynamicNode.ChangeFrequency;
  930. }
  931. if (dynamicNode.PreservedRouteParameters.Any())
  932. {
  933. clone.PreservedRouteParameters = String.Join(";", dynamicNode.PreservedRouteParameters.ToArray());
  934. }
  935. if (dynamicNode.Roles != null && dynamicNode.Roles.Any())
  936. {
  937. clone.Roles = dynamicNode.Roles.ToArray();
  938. }
  939. // Optionally, an area, controller and action can be specified. If so, override the clone.
  940. if (!string.IsNullOrEmpty(dynamicNode.Area))
  941. {
  942. clone.Area = dynamicNode.Area;
  943. }
  944. if (!string.IsNullOrEmpty(dynamicNode.Controller))
  945. {
  946. clone.Controller = dynamicNode.Controller;
  947. }
  948. if (!string.IsNullOrEmpty(dynamicNode.Action))
  949. {
  950. clone.Action = dynamicNode.Action;
  951. }
  952. // If the dynamic node has a parent key set, use that as the parent. Otherwise use the parentNode.
  953. if (dynamicNode.ParentKey != null && !string.IsNullOrEmpty(dynamicNode.ParentKey))
  954. {
  955. var parent = FindSiteMapNodeFromKey(dynamicNode.ParentKey);
  956. if (parent != null)
  957. {
  958. AddNode(clone, parent);
  959. createdDynamicNodes.Add(clone);
  960. }
  961. }
  962. else
  963. {
  964. AddNode(clone, parentNode);
  965. createdDynamicNodes.Add(clone);
  966. }
  967. }
  968. // Insert cache dependency
  969. CacheDescription cacheDescription = mvcNode.DynamicNodeProvider.GetCacheDescription();
  970. if (cacheDescription != null)
  971. {
  972. HttpContext.Current.Cache.Add(
  973. cacheDescription.Key,
  974. "",
  975. cacheDescription.Dependencies,
  976. cacheDescription.AbsoluteExpiration,
  977. cacheDescription.SlidingExpiration,
  978. cacheDescription.Priority,
  979. new CacheItemRemovedCallback(OnSiteMapChanged));
  980. }
  981. // Done!
  982. return createdDynamicNodes;
  983. }
  984. /// <summary>
  985. /// Determines whether the specified node has dynamic nodes.
  986. /// </summary>
  987. /// <param name="node">The node.</param>
  988. /// <returns>
  989. /// <c>true</c> if the specified node has dynamic nodes; otherwise, <c>false</c>.
  990. /// </returns>
  991. protected bool HasDynamicNodes(SiteMapNode node)
  992. {
  993. // Dynamic nodes available?
  994. var mvcNode = node as MvcSiteMapNode;
  995. if (mvcNode == null || mvcNode.DynamicNodeProvider == null)
  996. {
  997. return false;
  998. }
  999. return true;
  1000. }
  1001. /// <summary>
  1002. /// Clears the current sitemap.
  1003. /// </summary>
  1004. protected override void Clear()
  1005. {
  1006. root = null;
  1007. base.Clear();
  1008. }
  1009. /// <summary>
  1010. /// Retrieves a <see cref="T:System.Web.SiteMapNode"/> object that represents the currently requested page using the specified <see cref="T:System.Web.HttpContext"/> object.
  1011. /// </summary>
  1012. /// <param name="context">The <see cref="T:System.Web.HttpContext"/> used to match node information with the URL of the requested page.</param>
  1013. /// <returns>
  1014. /// A <see cref="T:System.Web.SiteMapNode"/> that represents the currently requested page; otherwise, null, if no corresponding <see cref="T:System.Web.SiteMapNode"/> can be found in the <see cref="T:System.Web.SiteMapNode"/> or if the page context is null.
  1015. /// </returns>
  1016. public override SiteMapNode FindSiteMapNode(HttpContext context)
  1017. {
  1018. var httpContext = new HttpContext2(context);
  1019. var routeData = RouteTable.Routes.GetRouteData(httpContext);
  1020. var currentNode = FindSiteMapNode(HttpContext.Current, routeData);
  1021. if (HttpContext.Current.Items[currentNodeCacheKey] == null && currentNode != null)
  1022. {
  1023. HttpContext.Current.Items[currentNodeCacheKey] = currentNode;
  1024. }
  1025. return currentNode;
  1026. }
  1027. /// <summary>
  1028. /// Finds the site map node.
  1029. /// </summary>
  1030. /// <param name="context">The context.</param>
  1031. /// <returns></returns>
  1032. public SiteMapNode FindSiteMapNode(ControllerContext context)
  1033. {
  1034. return FindSiteMapNode(HttpContext.Current, context.RouteData);
  1035. }
  1036. /// <summary>
  1037. /// Finds the site map node.
  1038. /// </summary>
  1039. /// <param name="context">The context.</param>
  1040. /// <param name="routeData">The route data.</param>
  1041. /// <returns></returns>
  1042. private SiteMapNode FindSiteMapNode(HttpContext context, RouteData routeData)
  1043. {
  1044. // Node
  1045. SiteMapNode node = null;
  1046. // Fetch route data
  1047. var httpContext = new HttpContext2(context);
  1048. if (routeData != null)
  1049. {
  1050. RequestContext requestContext = new RequestContext(httpContext, routeData);
  1051. VirtualPathData vpd = routeData.Route.GetVirtualPath(
  1052. requestContext, routeData.Values);
  1053. string appPathPrefix = (requestContext.HttpContext.Request.ApplicationPath
  1054. ?? string.Empty).TrimEnd('/') + "/";
  1055. node = base.FindSiteMapNode(httpContext.Request.Path) as MvcSiteMapNode;
  1056. if (!routeData.Values.ContainsKey("area"))
  1057. {
  1058. if (routeData.DataTokens["area"] != null)
  1059. {
  1060. routeData.Values.Add("area", routeData.DataTokens["area"]);
  1061. }
  1062. else
  1063. {
  1064. routeData.Values.Add("area", "");
  1065. }
  1066. }
  1067. MvcSiteMapNode mvcNode = node as MvcSiteMapNode;
  1068. if (mvcNode == null || routeData.Route != RouteTable.Routes[mvcNode.Route])
  1069. {
  1070. if (NodeMatchesRoute(RootNode as MvcSiteMapNode, routeData.Values))
  1071. {
  1072. node = RootNode;
  1073. }
  1074. }
  1075. if (node == null)
  1076. {
  1077. node = FindControllerActionNode(RootNode, routeData.Values, routeData.Route);
  1078. }
  1079. }
  1080. // Try base class
  1081. if (node == null)
  1082. {
  1083. node = base.FindSiteMapNode(context);
  1084. }
  1085. // Check accessibility
  1086. if (node != null)
  1087. {
  1088. if (node.IsAccessibleToUser(context))
  1089. {
  1090. return node;
  1091. }
  1092. }
  1093. return null;
  1094. }
  1095. /// <summary>
  1096. /// Finds the controller action node.
  1097. /// </summary>
  1098. /// <param name="rootNode">The root node.</param>
  1099. /// <param name="values">The values.</param>
  1100. /// <param name="route">The route.</param>
  1101. /// <returns>
  1102. /// A controller action node represented as a <see cref="SiteMapNode"/> instance
  1103. /// </returns>
  1104. private SiteMapNode FindControllerActionNode(SiteMapNode rootNode, IDictionary<string, object> values, RouteBase route)
  1105. {
  1106. if (rootNode != null)
  1107. {
  1108. // Get all child nodes
  1109. SiteMapNodeCollection childNodes = GetChildNodes(rootNode);
  1110. // Search current level
  1111. foreach (SiteMapNode node in childNodes)
  1112. {
  1113. // Check if it is an MvcSiteMapNode
  1114. var mvcNode = node as MvcSiteMapNode;
  1115. if (mvcNode != null)
  1116. {
  1117. // Look at the route property
  1118. if (!string.IsNullOrEmpty(mvcNode.Route))
  1119. {
  1120. if (RouteTable.Routes[mvcNode.Route] == route)
  1121. {
  1122. // This looks a bit weird, but if i set up a node to a general route ie /Controller/Action/ID
  1123. // I need to check that the values are the same so that it doesn't swallow all of the nodes that also use that same general route
  1124. if (NodeMatchesRoute(mvcNode, values))
  1125. {
  1126. return mvcNode;
  1127. }
  1128. }
  1129. }
  1130. else if (NodeMatchesRoute(mvcNode, values))
  1131. {
  1132. return mvcNode;
  1133. }
  1134. }
  1135. }
  1136. // Search one deeper level
  1137. foreach (SiteMapNode node in childNodes)
  1138. {
  1139. var siteMapNode = FindControllerActionNode(node, values, route);
  1140. if (siteMapNode != null)
  1141. {
  1142. return siteMapNode;
  1143. }
  1144. }
  1145. }
  1146. return null;
  1147. }
  1148. /// <summary>
  1149. /// Nodes the matches route.
  1150. /// </summary>
  1151. /// <param name="mvcNode">The MVC node.</param>
  1152. /// <param name="values">The values.</param>
  1153. /// <returns>
  1154. /// A matches route represented as a <see cref="bool"/> instance
  1155. /// </returns>
  1156. private bool NodeMatchesRoute(MvcSiteMapNode mvcNode, IDictionary<string, object> values)
  1157. {
  1158. var nodeValid = true;
  1159. if (mvcNode != null)
  1160. {
  1161. // Find action method parameters?
  1162. IEnumerable<string> actionParameters = new List<string>();
  1163. if (mvcNode.DynamicNodeProvider == null && mvcNode.IsDynamic == false)
  1164. {
  1165. actionParameters = ActionMethodParameterResolver.ResolveActionMethodParameters(
  1166. ControllerTypeResolver, mvcNode.Area, mvcNode.Controller, mvcNode.Action);
  1167. }
  1168. // Verify route values
  1169. if (values.Count > 0)
  1170. {
  1171. // Checking for same keys and values.
  1172. if( !CompareMustMatchRouteValues( mvcNode.RouteValues, values ) )
  1173. {
  1174. return false;
  1175. }
  1176. foreach( var pair in values )
  1177. {
  1178. if (!string.IsNullOrEmpty(mvcNode[pair.Key]))
  1179. {
  1180. if (mvcNode[pair.Key].ToLowerInvariant() == pair.Value.ToString().ToLowerInvariant())
  1181. {
  1182. continue;
  1183. }
  1184. else
  1185. {
  1186. // Is the current pair.Key a parameter on the action method?
  1187. if (!actionParameters.Contains(pair.Key, StringComparer.InvariantCultureIgnoreCase))
  1188. {
  1189. return false;
  1190. }
  1191. }
  1192. }
  1193. else
  1194. {
  1195. if (pair.Value == null || string.IsNullOrEmpty(pair.Value.ToString()) || pair.Value == UrlParameter.Optional)
  1196. {
  1197. continue;
  1198. }
  1199. else if (pair.Key == "area")
  1200. {
  1201. return false;
  1202. }
  1203. }
  1204. }
  1205. }
  1206. }
  1207. else
  1208. {
  1209. nodeValid = false;
  1210. }
  1211. return nodeValid;
  1212. }
  1213. /// <summary>
  1214. /// Returns whether the two route value collections have same keys and same values.
  1215. /// </summary>
  1216. /// <param name="mvcNodeRouteValues">The route values of the original node.</param>
  1217. /// <param name="routeValues">The route values to check in the given node.</param>
  1218. /// <returns><c>True</c> if the <paramref name="mvcNodeRouteValues"/> contains all keys and the same values as the given <paramref name="routeValues"/>, otherwise <c>false</c>.</returns>
  1219. private static bool CompareMustMatchRouteValues(IDictionary<string, object> mvcNodeRouteValues, IDictionary<string, object> routeValues)
  1220. {
  1221. var routeKeys = mvcNodeRouteValues.Keys;
  1222. foreach (var pair in routeValues)
  1223. {
  1224. if (routeKeys.Contains(pair.Key) && !mvcNodeRouteValues[pair.Key].ToString().Equals(pair.Value.ToString(), StringComparison.OrdinalIgnoreCase))
  1225. {
  1226. return false;
  1227. }
  1228. }
  1229. return true;
  1230. }
  1231. #endregion
  1232. #region Mappers
  1233. /// <summary>
  1234. /// Maps an XMLElement from the XML file to an MvcSiteMapNode.
  1235. /// </summary>
  1236. /// <param name="node">The element to map.</param>
  1237. /// <param name="parentNode">The parent SiteMapNode</param>
  1238. /// <returns>An MvcSiteMapNode which represents the XMLElement.</returns>
  1239. protected MvcSiteMapNode GetSiteMapNodeFromXmlElement(XElement node, SiteMapNode parentNode)
  1240. {
  1241. // Get area, controller and action from node declaration
  1242. string area = node.GetAttributeValue("area");
  1243. string controller = node.GetAttributeValue("controller");
  1244. // Inherit area and controller from parent
  1245. var parentMvcNode = parentNode as MvcSiteMapNode;
  1246. if (parentMvcNode != null)
  1247. {
  1248. if (string.IsNullOrEmpty(area))
  1249. {
  1250. area = parentMvcNode.Area;
  1251. }
  1252. if (string.IsNullOrEmpty(controller))
  1253. {
  1254. controller = parentMvcNode.Controller;
  1255. }
  1256. }
  1257. // Generate key for node
  1258. string key = NodeKeyGenerator.GenerateKey(
  1259. parentNode == null ? "" : parentNode.Key,
  1260. node.GetAttributeValue("key"),
  1261. node.GetAttributeValue("url"),
  1262. node.GetAttributeValue("title"),
  1263. area,
  1264. controller,
  1265. node.GetAttributeValue("action"),
  1266. !(node.GetAttributeValue("clickable") == "false"));
  1267. // Handle title and description globalization
  1268. var explicitResourceKeys = new NameValueCollection();
  1269. var title = node.GetAttributeValue("title");
  1270. var description = node.GetAttributeValue("description") ?? title;
  1271. HandleResourceAttribute("title", ref title, ref explicitResourceKeys);
  1272. HandleResourceAttribute("description", ref description, ref explicitResourceKeys);
  1273. // Handle implicit resources
  1274. var implicitResourceKey = node.GetAttributeValue("resourceKey");
  1275. if (!string.IsNullOrEmpty(implicitResourceKey))
  1276. {
  1277. title = null;
  1278. description = null;
  1279. }
  1280. // Create a new SiteMap node, setting the key and url
  1281. var siteMapNode = CreateSiteMapNode(key, explicitResourceKeys, implicitResourceKey);
  1282. siteMapNode.Title = title;
  1283. siteMapNode.Description = description;
  1284. siteMapNode.ResourceKey = implicitResourceKey;
  1285. // Create a route data dictionary
  1286. IDictionary<string, object> routeValues = new Dictionary<string, object>();
  1287. AttributesToRouteValues(node, siteMapNode, routeValues);
  1288. // Inherit area and controller from parent
  1289. if (parentMvcNode != null)
  1290. {
  1291. if (siteMapNode["area"] == null)
  1292. {
  1293. siteMapNode["area"] = parentMvcNode.Area;
  1294. routeValues.Add("area", parentMvcNode.Area);
  1295. }
  1296. if (node.GetAttributeValue("controller") == "")
  1297. {
  1298. siteMapNode["controller"] = parentMvcNode.Controller;
  1299. routeValues.Add("controller", parentMvcNode.Controller);
  1300. }
  1301. var action = "action";
  1302. if (node.GetAttributeValue(action) == String.Empty)
  1303. {
  1304. siteMapNode[action] = parentMvcNode.Action;
  1305. routeValues.Add(action, parentMvcNode.Action);
  1306. }
  1307. }
  1308. // Add defaults for area
  1309. if (!routeValues.ContainsKey("area"))
  1310. {
  1311. siteMapNode["area"] = "";
  1312. routeValues.Add("area", "");
  1313. }
  1314. // Add defaults for SiteMapNodeUrlResolver
  1315. if (siteMapNode.UrlResolver == null)
  1316. {
  1317. siteMapNode.UrlResolver = this.SiteMapNodeUrlResolver;
  1318. }
  1319. // Add defaults for SiteMapNodeVisibilityProvider
  1320. if (siteMapNode.VisibilityProvider == null)
  1321. {
  1322. siteMapNode.VisibilityProvider = this.SiteMapNodeVisibilityProvider;
  1323. }
  1324. // Clickable?
  1325. if (!siteMapNode.Clickable)
  1326. {
  1327. siteMapNode.Url = "";
  1328. }
  1329. // Add inherited route values to sitemap node
  1330. foreach (var inheritedRouteParameter in siteMapNode.InheritedRouteParameters.Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries))
  1331. {
  1332. var item = inheritedRouteParameter.Trim();
  1333. if (parentMvcNode != null && parentMvcNode.RouteValues.ContainsKey(item))
  1334. {
  1335. routeValues[item] = parentMvcNode.RouteValues[item];
  1336. }
  1337. }
  1338. // Add route values to sitemap node
  1339. siteMapNode.RouteValues = routeValues;
  1340. // Add node's route defaults
  1341. var httpContext = new HttpContextWrapper(HttpContext.Current);
  1342. RouteData routeData = siteMapNode.GetRouteData(httpContext);
  1343. if (routeData != null)
  1344. {
  1345. // Specify defaults from route on siteMapNode
  1346. var route = routeData.Route as Route;
  1347. if (route != null && route.Defaults != null)
  1348. {
  1349. foreach (var defaultValue in route.Defaults)
  1350. {
  1351. if (siteMapNode[defaultValue.Key] == null && defaultValue.Value != null)
  1352. {
  1353. siteMapNode[defaultValue.Key] = defaultValue.Value.ToString();
  1354. }
  1355. }
  1356. }
  1357. }
  1358. siteMapNode.ParentNode = parentMvcNode;
  1359. return siteMapNode;
  1360. }
  1361. /// <summary>
  1362. /// Add each attribute to our attributes collection on the siteMapNode
  1363. /// and to a route data dictionary.
  1364. /// </summary>
  1365. /// <param name="node">The element to map.</param>
  1366. /// <param name="siteMapNode">The SiteMapNode to map to</param>
  1367. /// <param name="routeValues">The RouteValueDictionary to fill</param>
  1368. protected virtual void AttributesToRouteValues(XElement node, MvcSiteMapNode siteMapNode, IDictionary<string, object> routeValues)
  1369. {
  1370. foreach (XAttribute attribute in node.Attributes())
  1371. {
  1372. var attributeName = attribute.Name.ToString();
  1373. var attributeValue = attribute.Value;
  1374. if (attributeName != "title"
  1375. && attributeName != "description")
  1376. {
  1377. siteMapNode[attributeName] = attributeValue;
  1378. }
  1379. // Process route values
  1380. if (
  1381. attributeName != "title"
  1382. && attributeName != "description"
  1383. && attributeName != "resourceKey"
  1384. && attributeName != "key"
  1385. && attributeName != "roles"
  1386. && attributeName != "url"
  1387. && attributeName != "clickable"
  1388. && attributeName != "dynamicNodeProvider"
  1389. && attributeName != "urlResolver"
  1390. && attributeName != "visibilityProvider"
  1391. && attributeName != "lastModifiedDate"
  1392. && attributeName != "changeFrequency"
  1393. && attributeName != "updatePriority"
  1394. && attributeName != "targetFrame"
  1395. && attributeName != "imageUrl"
  1396. && attributeName != "inheritedRouteParameters"
  1397. && attributeName != "preservedRouteParameters"
  1398. && !attributesToIgnore.Contains(attributeName)
  1399. && !attributeName.StartsWith("data-")
  1400. )
  1401. {
  1402. routeValues.Add(attributeName, attributeValue);
  1403. }
  1404. if (attributeName == "roles")
  1405. {
  1406. siteMapNode.Roles = attribute.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
  1407. }
  1408. }
  1409. }
  1410. /// <summary>
  1411. /// Gets the site map node from MVC site map node attribute.
  1412. /// </summary>
  1413. /// <param name="attribute">IMvcSiteMapNodeAttribute to map</param>
  1414. /// <param name="type">Type.</param>
  1415. /// <param name="methodInfo">MethodInfo on which the IMvcSiteMapNodeAttribute is applied</param>
  1416. /// <returns>
  1417. /// A SiteMapNode which represents the IMvcSiteMapNodeAttribute.
  1418. /// </returns>
  1419. protected SiteMapNode GetSiteMapNodeFromMvcSiteMapNodeAttribute(IMvcSiteMapNodeAttribute attribute, Type type, MethodInfo methodInfo)
  1420. {
  1421. if (attribute == null)
  1422. {
  1423. throw new ArgumentNullException("attribute");
  1424. }
  1425. if (type == null)
  1426. {
  1427. throw new ArgumentNullException("type");
  1428. }
  1429. if (methodInfo == null) // try to find Index action
  1430. {
  1431. var ms = type.FindMembers(MemberTypes.Method, BindingFlags.Instance | BindingFlags.Public,
  1432. (mi, o) => mi != null && string.Equals(mi.Name, "Index"), null);
  1433. foreach (MethodInfo m in ms.OfType<MethodInfo>())
  1434. {
  1435. var pars = m.GetParameters();
  1436. if (pars.Length == 0)
  1437. {
  1438. methodInfo = m;
  1439. break;
  1440. }
  1441. }
  1442. }
  1443. // Determine area (will only work if controller is defined as Assembly.<Area>.Controllers.HomeController)
  1444. string area = "";
  1445. if (!string.IsNullOrEmpty(attribute.AreaName))
  1446. {
  1447. area = attribute.AreaName;
  1448. }
  1449. if (string.IsNullOrEmpty(area))
  1450. {
  1451. var parts = type.Namespace.Split('.');
  1452. area = parts[parts.Length - 2];
  1453. var assemblyParts = type.Assembly.FullName.Split(',');
  1454. if (type.Namespace == assemblyParts[0] + ".Controllers" || type.Namespace.StartsWith(area))
  1455. {
  1456. // Is in default areaName...
  1457. area = "";
  1458. }
  1459. }
  1460. // Determine controller and (index) action
  1461. string controller = type.Name.Substring(0, type.Name.IndexOf("Controller"));
  1462. string action = (methodInfo != null ? methodInfo.Name : null) ?? "Index";
  1463. if (methodInfo != null) // handle custom action name
  1464. {
  1465. var actionNameAttribute = methodInfo.GetCustomAttributes(typeof(ActionNameAttribute), true).FirstOrDefault() as ActionNameAttribute;
  1466. if (actionNameAttribute != null)
  1467. {
  1468. action = actionNameAttribute.Name;
  1469. }
  1470. }
  1471. // Generate key for node
  1472. string key = NodeKeyGenerator.GenerateKey(
  1473. null,
  1474. attribute.Key,
  1475. attribute.Url,
  1476. attribute.Title,
  1477. area,
  1478. controller, action,
  1479. attribute.Clickable);
  1480. // Handle title and description globalization
  1481. var explicitResourceKeys = new NameValueCollection();
  1482. var title = attribute.Title;
  1483. var description = attribute.Description;
  1484. HandleResourceAttribute("title", ref title, ref explicitResourceKeys);
  1485. HandleResourceAttribute("description", ref description, ref explicitResourceKeys);
  1486. // Create a new SiteMap node, setting the key and url
  1487. var siteMapNode = CreateSiteMapNode(key, explicitResourceKeys, null);
  1488. // Set the properties on siteMapNode.
  1489. siteMapNode.Title = title;
  1490. siteMapNode.Description = description;
  1491. siteMapNode.Roles = attribute.Roles;
  1492. if (!string.IsNullOrEmpty(attribute.Route))
  1493. {
  1494. siteMapNode["route"] = attribute.Route;
  1495. }
  1496. siteMapNode["area"] = area;
  1497. siteMapNode["controller"] = controller;
  1498. siteMapNode["action"] = action;
  1499. siteMapNode["dynamicNodeProvider"] = attribute.DynamicNodeProvider;
  1500. siteMapNode["urlResolver"] = attribute.UrlResolver;
  1501. siteMapNode["visibilityProvider"] = attribute.VisibilityProvider;
  1502. siteMapNode.LastModifiedDate = attribute.LastModifiedDate;
  1503. siteMapNode.ChangeFrequency = attribute.ChangeFrequency;
  1504. siteMapNode.UpdatePriority = attribute.UpdatePriority;
  1505. siteMapNode.TargetFrame = attribute.TargetFrame;
  1506. siteMapNode.ImageUrl = attribute.ImageUrl;
  1507. siteMapNode.PreservedRouteParameters = attribute.PreservedRouteParameters;
  1508. if (!string.IsNullOrEmpty(attribute.Url))
  1509. siteMapNode.Url = attribute.Url;
  1510. siteMapNode.ResourceKey = attribute.ResourceKey;
  1511. // Create a route data dictionary
  1512. IDictionary<string, object> routeValues = new Dictionary<string, object>();
  1513. routeValues.Add("area", area);
  1514. routeValues.Add("controller", controller);
  1515. routeValues.Add("action", action);
  1516. // Add route values to sitemap node
  1517. siteMapNode.RouteValues = routeValues;
  1518. // Add defaults for SiteMapNodeUrlResolver
  1519. if (siteMapNode.UrlResolver == null)
  1520. {
  1521. siteMapNode.UrlResolver = this.SiteMapNodeUrlResolver;
  1522. }
  1523. // Add defaults for SiteMapNodeVisibilityProvider
  1524. if (siteMapNode.VisibilityProvider == null)
  1525. {
  1526. siteMapNode.VisibilityProvider = this.SiteMapNodeVisibilityProvider;
  1527. }
  1528. // Clickable?
  1529. siteMapNode.Clickable = attribute.Clickable;
  1530. return siteMapNode;
  1531. }
  1532. #endregion
  1533. #region Helpers
  1534. /// <summary>
  1535. /// Handle resource attribute
  1536. /// </summary>
  1537. /// <param name="attributeName">Attribute name</param>
  1538. /// <param name="text">Text</param>
  1539. /// <param name="collection">NameValueCollection to be used for localization</param>
  1540. private static void HandleResourceAttribute(string attributeName, ref string text, ref NameValueCollection collection)
  1541. {
  1542. if (!string.IsNullOrEmpty(text))
  1543. {
  1544. string tempStr1;
  1545. var tempStr2 = text.TrimStart(new[] { ' ' });
  1546. if (((tempStr2.Length > 10)) && tempStr2.ToLower(CultureInfo.InvariantCulture).StartsWith("$resources:", StringComparison.Ordinal))
  1547. {
  1548. tempStr1 = tempStr2.Substring(11);
  1549. string tempStr3;
  1550. string tempStr4;
  1551. var index = tempStr1.IndexOf(',');
  1552. tempStr3 = tempStr1.Substring(0, index);
  1553. tempStr4 = tempStr1.Substring(index + 1);
  1554. var length = tempStr4.IndexOf(',');
  1555. if (length != -1)
  1556. {
  1557. text = tempStr4.Substring(length + 1);
  1558. tempStr4 = tempStr4.Substring(0, length);
  1559. }
  1560. else
  1561. {
  1562. text = null;
  1563. }
  1564. if (collection == null)
  1565. {
  1566. collection = new NameValueCollection();
  1567. }
  1568. collection.Add(attributeName, tempStr3.Trim());
  1569. collection.Add(attributeName, tempStr4.Trim());
  1570. }
  1571. }
  1572. }
  1573. /// <summary>
  1574. /// Encodes the external URL.
  1575. /// </summary>
  1576. /// <param name="node">The node.</param>
  1577. /// <returns></returns>
  1578. protected bool EncodeExternalUrl(SiteMapNode node)
  1579. {
  1580. var url = node.Url;
  1581. if (url.Contains("http") || url.Contains("ftp"))
  1582. {
  1583. node.Url = HttpContext.Current.Server.UrlEncode(url);
  1584. return true;
  1585. }
  1586. return false;
  1587. }
  1588. /// <summary>
  1589. /// Decodes the external URL.
  1590. /// </summary>
  1591. /// <param name="node">The node.</param>
  1592. protected void DecodeExternalUrl(SiteMapNode node)
  1593. {
  1594. node.Url = HttpContext.Current.Server.UrlDecode(node.Url);
  1595. }
  1596. #endregion
  1597. }
  1598. }