PageRenderTime 244ms CodeModel.GetById 145ms RepoModel.GetById 1ms app.codeStats 1ms

/Libs/MvcSiteMap.Core/MvcSiteMapProvider.cs

https://bitbucket.org/hodzanassredin/ukonsvalki
C# | 1096 lines | 701 code | 132 blank | 263 comment | 204 complexity | 4b0578c5e6d86e840367c171bf70abd4 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.Xml.Linq;
  6. using System.Web.Routing;
  7. using System.Web.Mvc;
  8. using System.Web.Caching;
  9. using System.Collections.Specialized;
  10. using System.Reflection;
  11. using System.Collections;
  12. using System.Security.Principal;
  13. using System.Web.UI.WebControls;
  14. using System.Web.UI;
  15. using System.Globalization;
  16. namespace MvcSiteMap.Core
  17. {
  18. /// <summary>
  19. /// Provides an XML based SiteMap provider for the ASP.NET MVC framework.
  20. /// </summary>
  21. public class MvcSiteMapProvider : System.Web.StaticSiteMapProvider
  22. {
  23. #region Private fields
  24. /// <summary>
  25. /// Root SiteMap node
  26. /// </summary>
  27. private SiteMapNode rootNode = null;
  28. /// <summary>
  29. /// MvcSiteMap Namespace
  30. /// </summary>
  31. private readonly XNamespace ns_mvc = @"http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-1.0";
  32. /// <summary>
  33. /// SiteMap Namespace
  34. /// </summary>
  35. private readonly XNamespace ns_aspnet = @"http://schemas.microsoft.com/AspNet/SiteMap-File-1.0";
  36. /// <summary>
  37. /// SiteMap root XML element name
  38. /// </summary>
  39. private const string rootName = "siteMap";
  40. /// <summary>
  41. /// SiteMap node XML element name
  42. /// </summary>
  43. private const string nodeName = "siteMapNode";
  44. /// <summary>
  45. /// MVC SiteMap node XML element name
  46. /// </summary>
  47. private const string mvcNodeName = "mvcSiteMapNode";
  48. /// <summary>
  49. /// Cache duration
  50. /// </summary>
  51. private int cacheDuration = 1;
  52. /// <summary>
  53. /// SiteMap file
  54. /// </summary>
  55. private string SiteMapFile = string.Empty;
  56. /// <summary>
  57. /// Cache key
  58. /// </summary>
  59. private string cacheKey = "A33EF2B1-F0A4-4507-B011-94669840F79C";
  60. /// <summary>
  61. /// Scan assemblies for IMvcSiteMapNodeAttribute?
  62. /// </summary>
  63. private bool scanAssembliesForSiteMapNodes = false;
  64. /// <summary>
  65. /// Treat attributes in sitemap XML as route values?
  66. /// </summary>
  67. private bool treatAttributesAsRouteValues = true;
  68. /// <summary>
  69. /// MvcSiteMap ACL module
  70. /// </summary>
  71. private IMvcSiteMapAclModule aclModule = null;
  72. /// <summary>
  73. /// Synchronization lock
  74. /// </summary>
  75. private readonly object syncLock = new object();
  76. /// <summary>
  77. /// Controller builder cache
  78. /// </summary>
  79. private readonly Hashtable controllerCache = new Hashtable();
  80. /// <summary>
  81. /// Child provider cache
  82. /// </summary>
  83. private readonly List<SiteMapProvider> providerCache = new List<SiteMapProvider>();
  84. /// <summary>
  85. /// Node cache
  86. /// </summary>
  87. private readonly Dictionary<string, SiteMapNode> nodeCache = new Dictionary<string, SiteMapNode>();
  88. #endregion
  89. #region Properties
  90. /// <summary>
  91. /// Gets the root System.Web.SiteMapNode object of the site map data that the current provider represents.
  92. /// </summary>
  93. public override SiteMapNode RootNode
  94. {
  95. get
  96. {
  97. if (rootNode != null)
  98. return rootNode;
  99. return base.RootNode;
  100. }
  101. }
  102. #endregion
  103. #region Public methods
  104. /// <summary>
  105. /// Initializes the SiteMap provider and gets the attributes that are set in the config
  106. /// that enable us to customise the behaviour of this provider.
  107. /// </summary>
  108. /// <param name="name">Name of the provider</param>
  109. /// <param name="attributes">Provider attributes</param>
  110. public override void Initialize(string name, NameValueCollection attributes)
  111. {
  112. // Verify parameters
  113. if (attributes == null) throw new ArgumentNullException("attributes");
  114. if (String.IsNullOrEmpty(name)) name = "MvcSiteMapProvider";
  115. // Add a default "description" attribute to config if the
  116. // attribute doesn't exist or is empty
  117. if (string.IsNullOrEmpty(attributes["description"]))
  118. {
  119. attributes.Remove("description");
  120. attributes.Add("description", "ASP.NET MVC SiteMap provider");
  121. }
  122. // Initialize base class
  123. base.Initialize(name, attributes);
  124. // Get the SiteMapFile from the attributes.
  125. this.SiteMapFile = attributes["siteMapFile"];
  126. // If a cacheDuration was passed, set it. Otherwise default to 1 minute.
  127. if (!string.IsNullOrEmpty(attributes["cacheDuration"]))
  128. {
  129. this.cacheDuration = int.Parse(attributes["cacheDuration"]);
  130. }
  131. // If a cache key was set in config, set it.
  132. // Otherwise it will use the default which is a GUID.
  133. if (!string.IsNullOrEmpty(attributes["cacheKey"]))
  134. {
  135. this.cacheKey = attributes["cacheKey"];
  136. }
  137. // Scan assemblies for IMvcSiteMapNodeAttribute?
  138. if (!string.IsNullOrEmpty(attributes["scanAssembliesForSiteMapNodes"]))
  139. {
  140. this.scanAssembliesForSiteMapNodes = Boolean.Parse(attributes["scanAssembliesForSiteMapNodes"]);
  141. }
  142. // Treat attributes in sitemap XML as route values?
  143. if (!string.IsNullOrEmpty(attributes["treatAttributesAsRouteValues"]))
  144. {
  145. this.treatAttributesAsRouteValues = Boolean.Parse(attributes["treatAttributesAsRouteValues"]);
  146. }
  147. // ACL module
  148. if (!string.IsNullOrEmpty(attributes["aclModule"]))
  149. {
  150. aclModule = (IMvcSiteMapAclModule)Activator.CreateInstance(
  151. Type.GetType(attributes["aclModule"]));
  152. }
  153. else
  154. {
  155. aclModule = new DefaultMvcSiteMapAclModule();
  156. }
  157. // Enable Localization
  158. if (!string.IsNullOrEmpty(attributes["enableLocalization"]))
  159. {
  160. EnableLocalization = Boolean.Parse(attributes["enableLocalization"]);
  161. }
  162. // Resource key
  163. if (!string.IsNullOrEmpty(attributes["resourceKey"]))
  164. {
  165. ResourceKey = attributes["resourceKey"];
  166. }
  167. }
  168. /// <summary>
  169. /// Builds the SiteMap, firstly reads in the XML file, and grabs the outer rootNode element and
  170. /// maps this to become our main out rootNode SiteMap node.
  171. /// </summary>
  172. /// <returns>The rootNode SiteMapNode.</returns>
  173. public override SiteMapNode BuildSiteMap()
  174. {
  175. if (rootNode != null && HttpContext.Current.Cache[this.cacheKey] != null)
  176. {
  177. lock (syncLock)
  178. {
  179. if (rootNode != null && HttpContext.Current.Cache[this.cacheKey] != null)
  180. {
  181. // If SiteMap already loaded and our cache key is still set,
  182. // return current root node.
  183. // Checking a cache item enables us to invalidate the SiteMap
  184. // after a given time period.
  185. return rootNode;
  186. }
  187. }
  188. }
  189. lock (this.syncLock)
  190. {
  191. // SiteMap XML
  192. XDocument SiteMapXML = null;
  193. // Clear the current SiteMap.
  194. Clear();
  195. try
  196. {
  197. // Load the XML document.
  198. SiteMapXML = XDocument.Load(HttpContext.Current.Server.MapPath(this.SiteMapFile));
  199. // Enable localization?
  200. var enableLocalization = SiteMapXML.Root.Attribute("enableLocalization");
  201. if (enableLocalization != null && enableLocalization.Value.ToLowerInvariant() == "true")
  202. {
  203. this.EnableLocalization = true;
  204. }
  205. // Get the rootNode SiteMapNode element
  206. XElement rootElement = SiteMapXML.Root.Elements().First();
  207. if (rootElement.Name == ns_aspnet + nodeName || rootElement.Name == nodeName)
  208. {
  209. rootNode = GetSiteMapNodeFromXMLElement(rootElement);
  210. }
  211. else if (rootElement.Name == ns_mvc + mvcNodeName || rootElement.Name == mvcNodeName)
  212. {
  213. rootNode = GetMvcSiteMapNodeFromXMLElement(null, rootElement);
  214. }
  215. else
  216. {
  217. throw new MvcSiteMapException("SiteMap XML root element has an unknown namespace.");
  218. }
  219. // Process our XML file, passing in the main rootNode SiteMap node and xml element.
  220. ProcessXMLNodes(rootNode, rootElement);
  221. // Add our main rootNode node.
  222. AddNode(rootNode);
  223. // Process nodes in the assemblies of the current AppDomain?
  224. if (this.scanAssembliesForSiteMapNodes)
  225. {
  226. var assemblies = AppDomain.CurrentDomain.GetAssemblies()
  227. .Where(a => !a.FullName.StartsWith("mscorlib")
  228. && !a.FullName.StartsWith("System")
  229. && !a.FullName.StartsWith("Microsoft")
  230. && !a.FullName.StartsWith("WebDev")
  231. && !a.FullName.StartsWith("SMDiagnostics")
  232. && !a.FullName.StartsWith("Anonymously")
  233. && !a.FullName.StartsWith("App_"));
  234. foreach (Assembly assembly in assemblies)
  235. {
  236. ProcessNodesInAssembly(assembly);
  237. }
  238. }
  239. // Create a cache item, this is used for the sole purpose of being able to invalidate our SiteMap
  240. // after a given time period, it also adds a dependancy on the SiteMap file
  241. // so that once changed it will refresh your SiteMap, unfortunately at this stage
  242. // there is no dependancy for dynamic data, this could be implemented by clearing the cache item,
  243. // by setting a custom cacheKey, then use this in your administration console for example to
  244. // clear the cache item when the structure requires refreshing.
  245. HttpContext.Current.Cache.Insert(this.cacheKey,
  246. "",
  247. new CacheDependency(HttpContext.Current.Server.MapPath(this.SiteMapFile)),
  248. DateTime.Now.AddMinutes(this.cacheDuration),
  249. Cache.NoSlidingExpiration
  250. );
  251. }
  252. catch (Exception ex)
  253. {
  254. // If there was ANY error loading or parsing the SiteMap XML file, throw an exception.
  255. throw new MvcSiteMapException("An error occured while parsing the SiteMap XML. Check the inner exception for more details.", ex);
  256. }
  257. finally
  258. {
  259. SiteMapXML = null;
  260. }
  261. }
  262. // Finally return our rootNode SiteMapNode.
  263. return rootNode;
  264. }
  265. /// <summary>
  266. /// Clears the current SiteMap provider.
  267. /// </summary>
  268. protected override void Clear()
  269. {
  270. rootNode = null;
  271. controllerCache.Clear();
  272. providerCache.Clear();
  273. nodeCache.Clear();
  274. base.Clear();
  275. }
  276. /// <summary>
  277. /// Adds a System.Web.SiteMapNode object to the node collection that is maintained by the site map provider.
  278. /// </summary>
  279. /// <param name="node">The System.Web.SiteMapNode to add to the node collection maintained by the provider.</param>
  280. protected override void AddNode(SiteMapNode node)
  281. {
  282. lock (nodeCache)
  283. {
  284. if (!nodeCache.ContainsKey(node.Key))
  285. nodeCache.Add(node.Key, node);
  286. }
  287. base.AddNode(node);
  288. }
  289. /// <summary>
  290. /// Adds a System.Web.SiteMapNode object to the node collection that is maintained
  291. /// by the site map provider and specifies the parent System.Web.SiteMapNode
  292. /// object.
  293. /// </summary>
  294. /// <param name="node">The System.Web.SiteMapNode to add to the node collection maintained by the provider.</param>
  295. /// <param name="parentNode">The System.Web.SiteMapNode that is the parent of node.</param>
  296. protected override void AddNode(SiteMapNode node, SiteMapNode parentNode)
  297. {
  298. lock (nodeCache)
  299. {
  300. if (!nodeCache.ContainsKey(node.Key))
  301. nodeCache.Add(node.Key, node);
  302. node.ParentNode = parentNode;
  303. }
  304. base.AddNode(node, parentNode);
  305. }
  306. /// <summary>
  307. /// Calls the BuildSiteMap method.
  308. /// </summary>
  309. /// <returns>Root node of the site map</returns>
  310. protected override SiteMapNode GetRootNodeCore()
  311. {
  312. return this.BuildSiteMap();
  313. }
  314. /// <summary>
  315. /// Retrieves the child site map nodes of a specific SiteMapNode object.
  316. /// </summary>
  317. /// <param name="node">The SiteMapNode for which to retrieve all child site map nodes. </param>
  318. /// <returns>A read-only SiteMapNodeCollection that contains the child site map nodes of node. If security trimming is enabled, the collection contains only site map nodes that the user is permitted to see.</returns>
  319. public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
  320. {
  321. lock (nodeCache)
  322. {
  323. if (nodeCache.ContainsKey(node.Key))
  324. {
  325. SiteMapNodeCollection returnValue = new SiteMapNodeCollection();
  326. returnValue.AddRange(nodeCache.Values.Where(v => v.ParentNode == node).ToArray());
  327. return returnValue;
  328. }
  329. }
  330. return base.GetChildNodes(node);
  331. }
  332. #endregion
  333. #region Helper functions
  334. /// <summary>
  335. /// Recursively processes our XML document, parsing our SiteMapNodes and dynamicNode(s).
  336. /// </summary>
  337. /// <param name="rootNode">The main rootNode SiteMap node.</param>
  338. /// <param name="rootElement">The main rootNode XML element.</param>
  339. protected void ProcessXMLNodes(SiteMapNode rootNode, XElement rootElement)
  340. {
  341. SiteMapNode childNode = rootNode;
  342. // Loop through each element below the current rootNode element.
  343. foreach (XElement node in rootElement.Elements())
  344. {
  345. if (node.Name == nodeName || node.Name == ns_aspnet + nodeName)
  346. {
  347. var providerName = GetAttributeValue(node.Attribute("provider"));
  348. if (!string.IsNullOrEmpty(providerName))
  349. {
  350. // If this is SiteMapNode referencing another provider, add the
  351. // nodes from the other provider.
  352. SiteMapProvider provider = new SiteMapPath { SiteMapProvider = providerName }.Provider;
  353. if (!providerCache.Contains(provider))
  354. {
  355. providerCache.Add(provider);
  356. }
  357. AddNode(provider.RootNode, rootNode);
  358. }
  359. else
  360. {
  361. // If this is a normal SiteMapNode then map the xml element
  362. // to a SiteMapNode, and add the node to the current rootNode.
  363. childNode = GetSiteMapNodeFromXMLElement(node);
  364. AddNode(childNode, rootNode);
  365. }
  366. }
  367. else if (node.Name == mvcNodeName || node.Name == ns_mvc + mvcNodeName)
  368. {
  369. // If this is an MvcSiteMapNode then map the xml element
  370. // to a MvcSiteMapNode, and add the node to the current rootNode.
  371. childNode = this.GetMvcSiteMapNodeFromXMLElement(rootNode, node);
  372. AddNode(childNode, rootNode);
  373. }
  374. else
  375. {
  376. // If the current node is not one of the known node types throw and exception
  377. throw new MvcSiteMapException("An invalid element was found in the SiteMap XML.");
  378. }
  379. // Continue recursively processing the XML file.
  380. ProcessXMLNodes(childNode, node);
  381. }
  382. }
  383. /// <summary>
  384. /// Process nodes in assembly
  385. /// </summary>
  386. /// <param name="assembly">Assembly</param>
  387. protected void ProcessNodesInAssembly(Assembly assembly)
  388. {
  389. var types = new List<KeyValuePair<Type, IMvcSiteMapNodeAttribute>>(); // type and its attribute
  390. // load all types and their attributes
  391. foreach (Type type in assembly.GetTypes())
  392. {
  393. types.Add(new KeyValuePair<Type, IMvcSiteMapNodeAttribute>(type, type.GetCustomAttributes(typeof(IMvcSiteMapNodeAttribute), true).FirstOrDefault() as IMvcSiteMapNodeAttribute));
  394. }
  395. // sort them
  396. types.Sort((p1, p2) => (p1.Value == null ? 0 : p1.Value.Order) - (p2.Value == null ? 0 : p2.Value.Order));
  397. // use them
  398. foreach (var type in types)
  399. {
  400. SiteMapNode controllerNode = null;
  401. if (type.Value != null) // attribute on type-level specified
  402. {
  403. controllerNode = GetMvcSiteMapNodeFromMvcSiteMapNodeAttribute(type.Value, type.Key, null);
  404. SiteMapNode parentNode = (string.IsNullOrEmpty(type.Value.ParentKey) ? null : FindSiteMapNodeFromKey(type.Value.ParentKey)) ?? this.rootNode;
  405. if (controllerNode != null && parentNode != null)
  406. {
  407. AddNode(controllerNode, parentNode);
  408. }
  409. }
  410. foreach (MethodInfo method in type.Key.GetMethods(BindingFlags.Public | BindingFlags.Instance))
  411. {
  412. foreach (MvcSiteMapNodeAttribute attribute in (MvcSiteMapNodeAttribute[])method.GetCustomAttributes(typeof(MvcSiteMapNodeAttribute), true))
  413. {
  414. SiteMapNode node = GetMvcSiteMapNodeFromMvcSiteMapNodeAttribute(attribute, type.Key, method);
  415. SiteMapNode parentNode = (controllerNode != null && string.IsNullOrEmpty(attribute.ParentKey)) ?
  416. controllerNode :
  417. (string.IsNullOrEmpty(attribute.ParentKey) ? this.rootNode : FindSiteMapNodeFromKey(attribute.ParentKey));
  418. parentNode = parentNode ?? this.rootNode;
  419. if (node != null && parentNode != null)
  420. {
  421. AddNode(node, parentNode);
  422. }
  423. }
  424. }
  425. }
  426. }
  427. /// <summary>
  428. /// Determine if a node is accessible for a user
  429. /// </summary>
  430. /// <param name="context">Current HttpContext</param>
  431. /// <param name="node">SiteMap node</param>
  432. /// <returns>True/false if the node is accessible</returns>
  433. public override bool IsAccessibleToUser(HttpContext context, SiteMapNode node)
  434. {
  435. // Delegate to aclModule
  436. if (aclModule != null)
  437. {
  438. return aclModule.IsAccessibleToUser(this, context, node);
  439. }
  440. else
  441. {
  442. throw new MvcSiteMapException("MvcSiteMapProvider ACL module is not configured.");
  443. }
  444. }
  445. /// <summary>
  446. /// Finds the current SiteMap node based on raw url
  447. /// </summary>
  448. /// <param name="rawUrl">Raw URL</param>
  449. /// <returns>Current SiteMap node</returns>
  450. public override SiteMapNode FindSiteMapNode(string rawUrl)
  451. {
  452. return base.FindSiteMapNode(rawUrl);
  453. }
  454. /// <summary>
  455. /// Finds the current SiteMap node based on key
  456. /// </summary>
  457. /// <param name="key">Key</param>
  458. /// <returns>Current SiteMap node</returns>
  459. public override SiteMapNode FindSiteMapNodeFromKey(string key)
  460. {
  461. lock (nodeCache)
  462. {
  463. if (nodeCache.ContainsKey(key))
  464. {
  465. return nodeCache[key];
  466. }
  467. }
  468. return null;
  469. //return base.FindSiteMapNodeFromKey(key);
  470. }
  471. /// <summary>
  472. /// Finds the current SiteMap node based on context
  473. /// </summary>
  474. /// <param name="context">Current HttpContext</param>
  475. /// <returns>Current SiteMap node</returns>
  476. public override SiteMapNode FindSiteMapNode(HttpContext context)
  477. {
  478. // Node
  479. SiteMapNode node = null;
  480. // Search child providers
  481. foreach (var provider in providerCache)
  482. {
  483. node = provider.FindSiteMapNode(context);
  484. if (node != null)
  485. return node;
  486. }
  487. // Fetch route data
  488. HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
  489. RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
  490. if (routeData != null)
  491. {
  492. IDictionary<string, object> routeValues = routeData.Values;
  493. string controller = (string)routeValues["controller"];
  494. string action = (string)routeValues["action"];
  495. node = FindControllerActionNode(this.RootNode, controller, action, routeData.Values)
  496. ?? FindControllerActionNode(this.RootNode, controller, "Index", routeData.Values)
  497. ?? FindControllerActionNode(this.RootNode, "Home", "Index", routeData.Values);
  498. }
  499. // Try base class
  500. if (node == null)
  501. {
  502. node = base.FindSiteMapNode(context);
  503. }
  504. return node;
  505. }
  506. /// <summary>
  507. /// Maps a controller + action from the XML file to a SiteMapNode.
  508. /// </summary>
  509. /// <param name="rootNode">Root node</param>
  510. /// <param name="controller">Controller</param>
  511. /// <param name="action">Action</param>
  512. /// <param name="values">Values</param>
  513. /// <returns>A SiteMapNode which represents the controller + action.</returns>
  514. private SiteMapNode FindControllerActionNode(SiteMapNode rootNode, string controller, string action, IDictionary<string, object> values)
  515. {
  516. if (rootNode != null)
  517. {
  518. // Search current level
  519. foreach (SiteMapNode node in this.GetChildNodes(rootNode))
  520. {
  521. // Check if it is an MvcSiteMapNode
  522. MvcSiteMapNode mvcNode = node as MvcSiteMapNode;
  523. if (mvcNode != null)
  524. {
  525. // Valid node?
  526. if (NodeMatchesRoute(mvcNode, controller, action, values))
  527. {
  528. return mvcNode;
  529. }
  530. }
  531. }
  532. // Search one deeper level
  533. foreach (SiteMapNode node in this.GetChildNodes(rootNode))
  534. {
  535. SiteMapNode SiteMapNode = FindControllerActionNode(node, controller, action, values);
  536. if (SiteMapNode != null)
  537. {
  538. return SiteMapNode;
  539. }
  540. }
  541. }
  542. return null;
  543. }
  544. /// <summary>
  545. /// Node matches route?
  546. /// </summary>
  547. /// <param name="mvcNode">Current node</param>
  548. /// <param name="controller">Controller</param>
  549. /// <param name="action">Action</param>
  550. /// <param name="values">Values</param>
  551. /// <returns>A SiteMapNode which represents the controller + action.</returns>
  552. private bool NodeMatchesRoute(MvcSiteMapNode mvcNode, string controller, string action, IDictionary<string, object> values)
  553. {
  554. bool nodeValid = true;
  555. if (mvcNode != null && mvcNode.Controller.ToLowerInvariant() == controller.ToLowerInvariant() && mvcNode.Action.ToLowerInvariant() == action.ToLowerInvariant())
  556. {
  557. // Verify other route values
  558. if (values.Count > 0)
  559. {
  560. foreach (var pair in values)
  561. {
  562. if (mvcNode[pair.Key] == null || mvcNode[pair.Key].ToString().ToLowerInvariant() != pair.Value.ToString().ToLowerInvariant())
  563. {
  564. if (!mvcNode.DynamicParameters.Contains("*") && (!mvcNode.IsDynamic || !mvcNode.DynamicParameters.Contains(pair.Key)))
  565. {
  566. nodeValid = false;
  567. }
  568. }
  569. }
  570. }
  571. }
  572. else
  573. {
  574. nodeValid = false;
  575. }
  576. return nodeValid;
  577. }
  578. /// <summary>
  579. /// Maps an XElement from the XML file to a SiteMapNode.
  580. /// </summary>
  581. /// <param name="node">The element to map.</param>
  582. /// <returns>A SiteMapNode which represents the XElement.</returns>
  583. protected SiteMapNode GetSiteMapNodeFromXMLElement(XElement node)
  584. {
  585. // Is the node to be handled by the current provider?
  586. var provider = GetAttributeValue(node.Attribute("provider"));
  587. if (!string.IsNullOrEmpty(provider))
  588. return GetSiteMapNodesFromProvider(provider);
  589. // Get the URL attribute, need this so we can get the key.
  590. string url = GetAttributeValue(node.Attribute("url")) ?? "";
  591. // Create a new SiteMap node, setting the key and url
  592. var siteMapNode = new SiteMapNode(this, url)
  593. {
  594. Url = url
  595. };
  596. // Add each attribute to our attributes collection on SiteMapNode.
  597. foreach (XAttribute attribute in node.Attributes())
  598. {
  599. siteMapNode[attribute.Name.ToString()] = attribute.Value;
  600. }
  601. // Set the other properties on SiteMapNode.
  602. // These were added as attributes but can be mapped to properties.
  603. siteMapNode.Title = siteMapNode["title"];
  604. siteMapNode.Description = siteMapNode["description"];
  605. siteMapNode.ResourceKey = siteMapNode["resourceKey"] ?? "";
  606. // Set roles
  607. var roles = siteMapNode["roles"];
  608. if (!string.IsNullOrEmpty(roles))
  609. {
  610. siteMapNode.Roles = roles.Split(',', ';');
  611. }
  612. return siteMapNode;
  613. }
  614. /// <summary>
  615. /// Maps an XElement from the XML file to a SiteMapNode.
  616. /// </summary>
  617. /// <param name="parentNode">Parent SiteMapNode (can be null)</param>
  618. /// <param name="node">The element to map.</param>
  619. /// <returns>A SiteMapNode which represents the XElement.</returns>
  620. protected SiteMapNode GetMvcSiteMapNodeFromXMLElement(SiteMapNode parentNode, XElement node)
  621. {
  622. // Is the node to be handled by the current provider?
  623. var provider = GetAttributeValue(node.Attribute("provider"));
  624. if (!string.IsNullOrEmpty(provider))
  625. return GetSiteMapNodesFromProvider(provider);
  626. // Generate key for node
  627. var key = GetAttributeValue(node.Attribute("key")) ??
  628. (GetAttributeValue(node.Attribute("area")) ?? "") + "_" + (GetAttributeValue(node.Attribute("controller")) ?? "") + "_" + (GetAttributeValue(node.Attribute("action")) ?? "") + "_" + (GetAttributeValue(node.Attribute("title")) ?? "");
  629. // Handle title and description globalization
  630. NameValueCollection explicitResourceKeys = new NameValueCollection();
  631. string title = GetAttributeValue(node.Attribute("title"));
  632. string description = GetAttributeValue(node.Attribute("title"));
  633. HandleResourceAttribute("title", ref title, ref explicitResourceKeys);
  634. HandleResourceAttribute("description", ref description, ref explicitResourceKeys);
  635. // Create a new SiteMap node, setting the key and url
  636. var siteMapNode = new MvcSiteMapNode(this, key, explicitResourceKeys);
  637. // Create a route data dictionary
  638. IDictionary<string, object> routeValues = new Dictionary<string, object>();
  639. // Add each attribute to our attributes collection on the SiteMapNode
  640. // and to a route data dictionary.
  641. foreach (XAttribute attribute in node.Attributes())
  642. {
  643. string attributeName = attribute.Name.ToString();
  644. string attributeValue = attribute.Value;
  645. if (attributeName != "title"
  646. && attributeName != "description")
  647. {
  648. siteMapNode[attributeName] = attributeValue;
  649. }
  650. // Treat attributes in sitemap XML as route values?
  651. if (treatAttributesAsRouteValues)
  652. {
  653. if (attributeName != "title"
  654. && attributeName != "description"
  655. && attributeName != "resourceKey"
  656. && attributeName != "key"
  657. && attributeName != "isDynamic"
  658. && attributeName != "dynamicParameters"
  659. && attributeName != "visibility"
  660. && attributeName != "roles")
  661. {
  662. routeValues.Add(attributeName, attributeValue);
  663. }
  664. }
  665. if (attributeName == "roles")
  666. {
  667. siteMapNode.Roles = attribute.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
  668. }
  669. }
  670. // Set the other properties on SiteMapNode.
  671. // These were added as attributes but can be mapped to properties.
  672. siteMapNode["title"] = title;
  673. siteMapNode.Title = siteMapNode["title"];
  674. siteMapNode["description"] = description;
  675. siteMapNode.Description = siteMapNode["description"];
  676. siteMapNode.ResourceKey = siteMapNode["resourceKey"] ?? "";
  677. if (siteMapNode["area"] != null)
  678. {
  679. siteMapNode.Area = siteMapNode["area"];
  680. }
  681. if (siteMapNode["controller"] == null)
  682. {
  683. siteMapNode["controller"] = "Home";
  684. }
  685. siteMapNode.Controller = siteMapNode["controller"];
  686. if (siteMapNode["action"] == null)
  687. {
  688. siteMapNode["action"] = "Index";
  689. }
  690. siteMapNode.Action = siteMapNode["action"];
  691. siteMapNode.IsDynamic = Convert.ToBoolean(siteMapNode["isDynamic"] ?? "false");
  692. if (!string.IsNullOrEmpty(siteMapNode["dynamicParameters"]))
  693. {
  694. siteMapNode.DynamicParameters.AddRange(siteMapNode["dynamicParameters"].Split(';'));
  695. }
  696. if (!string.IsNullOrEmpty(siteMapNode["visibility"]))
  697. {
  698. siteMapNode.Visibility = (MvcSiteMapNodeVisibility)Enum.Parse(typeof(MvcSiteMapNodeVisibility), siteMapNode["visibility"], true);
  699. }
  700. // Verify route values
  701. if (!routeValues.ContainsKey("area"))
  702. {
  703. // Try getting parent area name
  704. if (parentNode != null && parentNode is MvcSiteMapNode)
  705. {
  706. string parentAreaName = (parentNode as MvcSiteMapNode).Area;
  707. if (!string.IsNullOrEmpty(parentAreaName))
  708. {
  709. siteMapNode.Area = parentAreaName;
  710. routeValues.Add("area", parentAreaName);
  711. }
  712. }
  713. }
  714. if (!routeValues.ContainsKey("controller"))
  715. {
  716. // Try getting parent controller name
  717. if (parentNode != null && parentNode is MvcSiteMapNode)
  718. {
  719. string parentControllerName = (parentNode as MvcSiteMapNode).Controller;
  720. if (!string.IsNullOrEmpty(parentControllerName))
  721. {
  722. siteMapNode.Controller = parentControllerName;
  723. routeValues.Add("controller", parentControllerName);
  724. }
  725. }
  726. }
  727. if (!routeValues.ContainsKey("controller")) routeValues.Add("controller", "Home");
  728. if (!routeValues.ContainsKey("action")) routeValues.Add("action", "Index");
  729. // Add route values to sitemap node
  730. siteMapNode.RouteValues = routeValues;
  731. routeValues = new Dictionary<string, object>();
  732. // Build URL
  733. HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
  734. RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
  735. if (routeData != null)
  736. {
  737. // Set URL based on route
  738. UrlHelper helper = new UrlHelper(new RequestContext(httpContext, routeData));
  739. siteMapNode.Url = helper.RouteUrl(new RouteValueDictionary(routeValues));
  740. // Specify defaults from route on siteMapNode
  741. Route route = routeData.Route as Route;
  742. if (route != null && route.Defaults != null)
  743. {
  744. foreach (var defaultValue in route.Defaults)
  745. {
  746. if (siteMapNode[defaultValue.Key] == null)
  747. {
  748. siteMapNode[defaultValue.Key] = defaultValue.Value.ToString();
  749. }
  750. }
  751. }
  752. }
  753. return siteMapNode;
  754. }
  755. /// Maps an IMvcSiteMapNodeAttribute to a SiteMapNode.
  756. /// </summary>
  757. /// <param name="attribute">IMvcSiteMapNodeAttribute to map</param>
  758. /// <param name="type">Type.</param>
  759. /// <param name="methodInfo">MethodInfo on which the IMvcSiteMapNodeAttribute is applied</param>
  760. /// <returns>A SiteMapNode which represents the IMvcSiteMapNodeAttribute.</returns>
  761. protected SiteMapNode GetMvcSiteMapNodeFromMvcSiteMapNodeAttribute(IMvcSiteMapNodeAttribute attribute, Type type, MethodInfo methodInfo)
  762. {
  763. if (attribute == null)
  764. {
  765. throw new ArgumentNullException("attribute");
  766. }
  767. if (type == null)
  768. {
  769. throw new ArgumentNullException("type");
  770. }
  771. if (methodInfo == null) // try to find Index action
  772. {
  773. var ms = type.FindMembers(MemberTypes.Method, BindingFlags.Instance | BindingFlags.Public, (mi, o) => mi != null && string.Equals(mi.Name, "Index"), null);
  774. if (ms != null)
  775. {
  776. foreach (var m in ms.OfType<MethodInfo>())
  777. {
  778. var pars = m.GetParameters();
  779. if (pars == null || pars.Length == 0)
  780. {
  781. methodInfo = m;
  782. break;
  783. }
  784. }
  785. }
  786. }
  787. string area = attribute.Area;
  788. if (string.IsNullOrEmpty(area))
  789. {
  790. string[] parts = type.Namespace.Split('.');
  791. area = parts[parts.Length - 2];
  792. string[] assemblyParts = type.Assembly.FullName.Split(',');
  793. if (type.Namespace == assemblyParts[0] + ".Controllers" || type.Namespace.StartsWith(area))
  794. {
  795. // Is in default area...
  796. area = "";
  797. }
  798. }
  799. string controller = type.Name.Replace("Controller", "") ?? "Home";
  800. string action = (methodInfo != null ? methodInfo.Name : null) ?? "Index";
  801. if (methodInfo != null) // handle custom action name
  802. {
  803. var actionNameAttribute = methodInfo.GetCustomAttributes(typeof(ActionNameAttribute), true).FirstOrDefault() as ActionNameAttribute;
  804. if (actionNameAttribute != null)
  805. {
  806. action = actionNameAttribute.Name;
  807. }
  808. }
  809. // Generate key for node
  810. var key = !string.IsNullOrEmpty(attribute.Key) ? attribute.Key :
  811. methodInfo != null ? area + "_" + controller + "_" + action + "_" + attribute.Title : Guid.NewGuid().ToString();
  812. // Handle title and description globalization
  813. NameValueCollection explicitResourceKeys = new NameValueCollection();
  814. string title = attribute.Title;
  815. string description = attribute.Description;
  816. HandleResourceAttribute("title", ref title, ref explicitResourceKeys);
  817. HandleResourceAttribute("description", ref description, ref explicitResourceKeys);
  818. // Create a new SiteMap node, setting the key and url
  819. var siteMapNode = new MvcSiteMapNode(this, key, explicitResourceKeys);
  820. // Set the properties on SiteMapNode.
  821. siteMapNode.Title = title;
  822. siteMapNode.Description = description;
  823. siteMapNode.Area = area;
  824. siteMapNode.Controller = controller;
  825. siteMapNode.Action = action;
  826. siteMapNode.Visibility = attribute.Visibility;
  827. siteMapNode.ResourceKey = attribute.ResourceKey;
  828. if (attribute.Visibility == MvcSiteMapNodeVisibility.Full && siteMapNode.IsDynamic)
  829. {
  830. siteMapNode.Visibility = MvcSiteMapNodeVisibility.InSiteMapPathOnly;
  831. }
  832. if (attribute.DetectIsDynamic && methodInfo != null)
  833. {
  834. var pars = methodInfo.GetParameters();
  835. siteMapNode.IsDynamic = pars != null && pars.Length > 0;
  836. if (siteMapNode.IsDynamic)
  837. {
  838. foreach (var parameter in pars)
  839. {
  840. siteMapNode.DynamicParameters.Add(parameter.Name);
  841. }
  842. }
  843. }
  844. else
  845. {
  846. siteMapNode.IsDynamic = attribute.IsDynamic;
  847. if (siteMapNode.IsDynamic && !string.IsNullOrEmpty(attribute.DynamicParameters))
  848. {
  849. siteMapNode.DynamicParameters.AddRange(attribute.DynamicParameters.Split(';', ','));
  850. }
  851. }
  852. // Set node properties
  853. siteMapNode["controller"] = siteMapNode.Controller;
  854. siteMapNode["action"] = siteMapNode.Action;
  855. // Create a route data dictionary
  856. IDictionary<string, object> routeValues = new Dictionary<string, object>();
  857. if (!string.IsNullOrEmpty(siteMapNode.Area))
  858. {
  859. routeValues.Add("area", siteMapNode.Area);
  860. }
  861. routeValues.Add("controller", siteMapNode.Controller);
  862. routeValues.Add("action", siteMapNode.Action);
  863. // Add route values to sitemap node
  864. siteMapNode.RouteValues = routeValues;
  865. routeValues = new Dictionary<string, object>();
  866. // Build URL
  867. HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
  868. RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
  869. if (routeData != null)
  870. {
  871. UrlHelper helper = new UrlHelper(new RequestContext(httpContext, routeData));
  872. siteMapNode.Url = helper.RouteUrl(new RouteValueDictionary(routeValues));
  873. }
  874. return siteMapNode;
  875. }
  876. #endregion
  877. #region Helpers
  878. /// <summary>
  879. /// Given an XAttribute, will either return an empty string if its value is
  880. /// null or the actual value.
  881. /// </summary>
  882. /// <param name="attribute">The attribe to get the value for.</param>
  883. /// <returns></returns>
  884. public string GetAttributeValue(XAttribute attribute)
  885. {
  886. return attribute != null ? attribute.Value : null;
  887. }
  888. /// <summary>
  889. /// Get controller from controller cache
  890. /// </summary>
  891. /// <param name="context">Request context</param>
  892. /// <param name="controllerName">Controller name</param>
  893. /// <returns>Controller instance</returns>
  894. public IController GetController(RequestContext context, string controllerName)
  895. {
  896. if (!controllerCache.ContainsKey(controllerName))
  897. {
  898. lock (controllerCache)
  899. {
  900. if (!controllerCache.ContainsKey(controllerName))
  901. {
  902. IController controller = ControllerBuilder.Current.GetControllerFactory().CreateController(context, controllerName);
  903. controllerCache.Add(controllerName, controller);
  904. return controller;
  905. }
  906. }
  907. }
  908. return (IController)controllerCache[controllerName];
  909. }
  910. /// <summary>
  911. /// Get SiteMap nodes from other provider
  912. /// </summary>
  913. /// <param name="provider">Provider name</param>
  914. /// <returnsRoot node from other provider</returns>
  915. private SiteMapNode GetSiteMapNodesFromProvider(string provider)
  916. {
  917. var ds = new SiteMapDataSource { ShowStartingNode = true };
  918. ds.SiteMapProvider = provider;
  919. var view = (SiteMapDataSourceView)ds.GetView(string.Empty);
  920. var nodes = (SiteMapNodeCollection)view.Select(DataSourceSelectArguments.Empty);
  921. return nodes[0];
  922. }
  923. /// <summary>
  924. /// Handle resource attribute
  925. /// </summary>
  926. /// <param name="attributeName">Attribute name</param>
  927. /// <param name="text">Text</param>
  928. /// <param name="collection">NameValueCollection to be used for localization</param>
  929. private void HandleResourceAttribute(string attributeName, ref string text, ref NameValueCollection collection)
  930. {
  931. if (!string.IsNullOrEmpty(text))
  932. {
  933. string tempStr1 = null;
  934. string tempStr2 = text.TrimStart(new char[] { ' ' });
  935. if (((tempStr2 != null) && (tempStr2.Length > 10)) && tempStr2.ToLower(CultureInfo.InvariantCulture).StartsWith("$resources:", StringComparison.Ordinal))
  936. {
  937. tempStr1 = tempStr2.Substring(11);
  938. string tempStr3 = null;
  939. string tempStr4 = null;
  940. int index = tempStr1.IndexOf(',');
  941. tempStr3 = tempStr1.Substring(0, index);
  942. tempStr4 = tempStr1.Substring(index + 1);
  943. int length = tempStr4.IndexOf(',');
  944. if (length != -1)
  945. {
  946. text = tempStr4.Substring(length + 1);
  947. tempStr4 = tempStr4.Substring(0, length);
  948. }
  949. else
  950. {
  951. text = null;
  952. }
  953. if (collection == null)
  954. {
  955. collection = new NameValueCollection();
  956. }
  957. collection.Add(attributeName, tempStr3.Trim());
  958. collection.Add(attributeName, tempStr4.Trim());
  959. }
  960. }
  961. }
  962. #endregion
  963. }
  964. }