From 440447dcf8e7f9351bad4d0f6d51bc74a09108a3 Mon Sep 17 00:00:00 2001 From: Paul Knopf Date: Thu, 26 Dec 2013 14:54:14 -0500 Subject: [PATCH 1/3] Added classes/interfaces for building SiteMapNodes fluently. --- .../MvcSiteMapProviderContainerInitializer.cs | 6 +- .../Builder/FluentFactory.cs | 36 ++ .../Builder/FluentSiteMapNodeBuilder.cs | 407 ++++++++++++++++++ .../Builder/FluentSiteMapNodeFactory.cs | 45 ++ .../Builder/FluentSiteMapNodeProvider.cs | 53 +++ .../Builder/IFluentFactory.cs | 28 ++ .../Builder/IFluentSiteMapNodeBuilder.cs | 76 ++++ .../Builder/IFluentSiteMapNodeFactory.cs | 20 + .../MvcSiteMapProvider.csproj | 7 + 9 files changed, 677 insertions(+), 1 deletion(-) create mode 100644 src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentFactory.cs create mode 100644 src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeBuilder.cs create mode 100644 src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeFactory.cs create mode 100644 src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeProvider.cs create mode 100644 src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentFactory.cs create mode 100644 src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeBuilder.cs create mode 100644 src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeFactory.cs diff --git a/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs b/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs index 7b0443af..efa124fd 100644 --- a/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs +++ b/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs @@ -63,7 +63,11 @@ public static void SetUp(Container container) // Added 2013-11-11 by NightOwl888 for SimpleInjector.Verify method: typeof(SiteMapNodeCreator), - typeof(DynamicSiteMapNodeBuilder) + typeof(DynamicSiteMapNodeBuilder), + + // Added 2013-12-26 by theonlylawislove for fluent sitemapnode building + typeof(FluentSiteMapNodeBuilder), + typeof(FluentSiteMapNodeFactory) }; var multipleImplementationTypes = new Type[] { diff --git a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentFactory.cs b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentFactory.cs new file mode 100644 index 00000000..dc47c4bd --- /dev/null +++ b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentFactory.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MvcSiteMapProvider.Linq; + +namespace MvcSiteMapProvider.Builder +{ + /// + /// The default implementation of IFluentFactory + /// + public class FluentFactory + : IFluentFactory + { + /// + /// Create a IFluentSiteMapNodeFactory. + /// + /// This should an existing collection. All nodes created using Add() will be added to this collection. + /// The ISiteMapNodeHelper that will internally create the ISiteMapNodes + /// + public IFluentSiteMapNodeFactory CreateSiteMapNodeFactory(IList result, ISiteMapNodeHelper siteMapNodeHelper) + { + return new FluentSiteMapNodeFactory(this, result, siteMapNodeHelper); + } + + /// + /// Create a IFluentSiteMapNodeBuilder. + /// + /// The site map node helper. + /// + public IFluentSiteMapNodeBuilder CreateSiteMapNodeBuilder(ISiteMapNodeHelper siteMapNodeHelper) + { + return new FluentSiteMapNodeBuilder(this, siteMapNodeHelper); + } + } +} diff --git a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeBuilder.cs b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeBuilder.cs new file mode 100644 index 00000000..864b0154 --- /dev/null +++ b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeBuilder.cs @@ -0,0 +1,407 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web.Mvc; + +namespace MvcSiteMapProvider.Builder +{ + public class FluentSiteMapNodeBuilder + : IFluentSiteMapNodeBuilder + { + private readonly IFluentFactory _fluentFactory; + private readonly ISiteMapNodeHelper _siteMapNodeHelper; + private readonly IList _children = new List(); + private string _area; + private string _controller; + private string _action; + private string _httpMethod; + private string _title; + private string _description; + private string _key; + private string _url; + private bool? _clickable; + private string[] _roles; + private string _resourceKey; + private string _visibilityProvider; + private string _dynamicNodeProvider; + private string _imageUrl; + private string _targetFrame; + private bool? _cacheResolvedUrl; + private string _canonicalUrl; + private string _canonicalKey; + private string[] _metaRobotsValues; + private ChangeFrequency? _changeFrequency; + private UpdatePriority? _updatePriority; + private DateTime? _lastModifiedDate; + private int? _order; + private string _route; + private object _routeValues; + private string[] _preservedRouteParameters; + private string _urlResolver; + private string[] _inheritedRouteParameters; + + public FluentSiteMapNodeBuilder(IFluentFactory fluentFactory, ISiteMapNodeHelper siteMapNodeHelper) + { + if(_siteMapNodeHelper == null) + throw new ArgumentNullException("siteMapNodeHelper"); + if (fluentFactory == null) + throw new ArgumentNullException("fluentFactory"); + + _siteMapNodeHelper = siteMapNodeHelper; + _fluentFactory = fluentFactory; + } + + public IFluentSiteMapNodeBuilder Items(Action children) + { + if (children == null) + return this; + + var factory = _fluentFactory.CreateSiteMapNodeFactory(_children, _siteMapNodeHelper); + children(factory); + + return this; + } + + public IList Children { get { return _children; } } + + public IFluentSiteMapNodeBuilder Area(string value) + { + _area = value; + return this; + } + + public IFluentSiteMapNodeBuilder Controller(string value) + { + _controller = value; + return this; + } + + public IFluentSiteMapNodeBuilder Action(string value) + { + _action = value; + return this; + } + + public IFluentSiteMapNodeBuilder HttpMethod(HttpVerbs? method) + { + _httpMethod = method.HasValue ? method.ToString().ToUpperInvariant() : null; + return this; + } + + public IFluentSiteMapNodeBuilder Title(string value) + { + _title = value; + return this; + } + + public IFluentSiteMapNodeBuilder Description(string value) + { + _description = value; + return this; + } + + public IFluentSiteMapNodeBuilder Key(string value) + { + _key = value; + return this; + } + + public IFluentSiteMapNodeBuilder Url(string value) + { + _url = value; + return this; + } + + public IFluentSiteMapNodeBuilder Clickable(bool? clickable) + { + _clickable = clickable; + return this; + } + + public IFluentSiteMapNodeBuilder Roles(string[] values) + { + _roles = values != null ? values.Where(x => !string.IsNullOrEmpty(x)).ToArray() : null; + return this; + } + + public IFluentSiteMapNodeBuilder ResourceKey(string value) + { + _resourceKey = value; + return this; + } + + public IFluentSiteMapNodeBuilder VisibilityProvider(string value) + { + _visibilityProvider = value; + return this; + } + + public IFluentSiteMapNodeBuilder DynamicNodeProvider(string value) + { + _dynamicNodeProvider = value; + return this; + } + + public IFluentSiteMapNodeBuilder ImageUrl(string value) + { + _imageUrl = value; + return this; + } + + public IFluentSiteMapNodeBuilder TargetFrame(string value) + { + _targetFrame = value; + return this; + } + + public IFluentSiteMapNodeBuilder CachedResolvedUrl(bool? cacheResolvedUrl) + { + _cacheResolvedUrl = cacheResolvedUrl; + return this; + } + + public IFluentSiteMapNodeBuilder CanonicalUrl(string value) + { + _canonicalUrl = value; + return this; + } + + public IFluentSiteMapNodeBuilder CanonicalKey(string value) + { + _canonicalKey = value; + return this; + } + + public IFluentSiteMapNodeBuilder MetaRobotsValues(string[] values) + { + _metaRobotsValues = values != null ? values.Where(x => !string.IsNullOrEmpty(x)).ToArray() : null; + return this; + } + + public IFluentSiteMapNodeBuilder ChangeFrequency(ChangeFrequency? value) + { + _changeFrequency = value; + return this; + } + + public IFluentSiteMapNodeBuilder UpdatePriority(UpdatePriority? value) + { + _updatePriority = value; + return this; + } + + public IFluentSiteMapNodeBuilder LastModifiedDate(DateTime? value) + { + _lastModifiedDate = value; + return this; + } + + public IFluentSiteMapNodeBuilder Order(int order) + { + _order = order; + return this; + } + + public IFluentSiteMapNodeBuilder Route(string value) + { + _route = value; + return this; + } + + public IFluentSiteMapNodeBuilder RouteValues(object routeValues) + { + _routeValues = routeValues; + return this; + } + public IFluentSiteMapNodeBuilder PreservedRouteValues(string[] values) + { + _preservedRouteParameters = values != null ? values.Where(x => !string.IsNullOrEmpty(x)).ToArray() : null; + return this; + } + + public IFluentSiteMapNodeBuilder UrlResolver(string value) + { + _urlResolver = value; + return this; + } + + public IFluentSiteMapNodeBuilder InheritedRouteParameters(string[] values) + { + _inheritedRouteParameters = values != null ? values.Where(x => !string.IsNullOrEmpty(x)).ToArray() : null; + return this; + } + + public string CreateNodeKey(ISiteMapNodeHelper helper, string parentKey) + { + return helper.CreateNodeKey( + parentKey, + _key ?? string.Empty, + _url ?? string.Empty, + _title ?? string.Empty, + _area ?? string.Empty, + _controller ?? string.Empty, + _action ?? string.Empty, + _httpMethod ?? "GET", + !_clickable.HasValue || _clickable.Value); + } + + public ISiteMapNodeToParentRelation CreateNode(ISiteMapNodeHelper helper, ISiteMapNode parentNode) + { + var parentKey = parentNode == null ? "" : parentNode.Key; + var key = CreateNodeKey(helper, parentKey); + + var nodeParentMap = helper.CreateNode(key, parentKey, "Fluent", _resourceKey ?? string.Empty); + var siteMapNode = nodeParentMap.Node; + + AddAttributesToNode(siteMapNode); + if (_routeValues != null) + foreach (var routeValue in new System.Web.Routing.RouteValueDictionary(_routeValues)) + siteMapNode.RouteValues.Add(routeValue); + if (_preservedRouteParameters != null) + foreach (var preservedRouteParameter in _preservedRouteParameters) + siteMapNode.PreservedRouteParameters.Add(preservedRouteParameter); + if (_metaRobotsValues != null) + foreach (var metaRobotValue in _metaRobotsValues) + siteMapNode.MetaRobotsValues.Add(metaRobotValue); + if (_roles != null) + foreach (var role in _roles) + siteMapNode.Roles.Add(role); + + siteMapNode.Title = _title ?? string.Empty; + siteMapNode.Description = string.IsNullOrEmpty(_description) ? siteMapNode.Title : _description; + + siteMapNode.Route = _route ?? string.Empty; + if (!string.IsNullOrEmpty(_area)) siteMapNode.RouteValues.Add("area", _area); + if (!string.IsNullOrEmpty(_controller)) siteMapNode.RouteValues.Add("controller", _controller); + if (!string.IsNullOrEmpty(_action)) siteMapNode.RouteValues.Add("action", _action); + + siteMapNode.Clickable = !_clickable.HasValue || _clickable.Value; + siteMapNode.VisibilityProvider = _visibilityProvider ?? string.Empty; + siteMapNode.DynamicNodeProvider = _dynamicNodeProvider ?? string.Empty; + siteMapNode.ImageUrl = _imageUrl ?? string.Empty; + siteMapNode.TargetFrame = _targetFrame ?? string.Empty; + siteMapNode.HttpMethod = _httpMethod ?? "GET"; + siteMapNode.Url = _url ?? string.Empty; + siteMapNode.CacheResolvedUrl = !_cacheResolvedUrl.HasValue || _cacheResolvedUrl.Value; + siteMapNode.CanonicalUrl = _canonicalUrl ?? string.Empty; + siteMapNode.CanonicalKey = _canonicalKey ?? string.Empty; + siteMapNode.ChangeFrequency = _changeFrequency.HasValue ? _changeFrequency.Value : MvcSiteMapProvider.ChangeFrequency.Undefined; + siteMapNode.UpdatePriority = _updatePriority.HasValue ? _updatePriority.Value : MvcSiteMapProvider.UpdatePriority.Undefined; + siteMapNode.LastModifiedDate = _lastModifiedDate.HasValue ? _lastModifiedDate.Value : DateTime.MinValue; + siteMapNode.Order = _order.HasValue ? _order.Value : 0; + siteMapNode.UrlResolver = _urlResolver ?? string.Empty; + + if (_inheritedRouteParameters != null && parentNode != null) + { + foreach (var inheritedRouteParameter in _inheritedRouteParameters) + { + var item = inheritedRouteParameter.Trim(); + if (siteMapNode.RouteValues.ContainsKey(item)) + throw new MvcSiteMapException(String.Format(Resources.Messages.SiteMapNodeSameKeyInRouteValueAndInheritedRouteParameter, key, _title ?? string.Empty, item)); + if (parentNode.RouteValues.ContainsKey(item)) + siteMapNode.RouteValues.Add(item, parentNode.RouteValues[item]); + } + } + + if (parentNode != null) + { + if (string.IsNullOrEmpty(_area) && !siteMapNode.RouteValues.ContainsKey("area")) + { + siteMapNode.Area = parentNode.Area; + } + if (string.IsNullOrEmpty(_controller) && !siteMapNode.RouteValues.ContainsKey("controller")) + { + siteMapNode.Controller = parentNode.Controller; + } + } + if (!siteMapNode.RouteValues.ContainsKey("area")) + { + siteMapNode.RouteValues.Add("area", ""); + } + + return nodeParentMap; + } + + private void AddAttributesToNode(ISiteMapNode node) + { + if (_area != null) + node.Attributes.Add(new KeyValuePair("area", _area)); + + if (_controller != null) + node.Attributes.Add(new KeyValuePair("controller", _controller)); + + if (_action != null) + node.Attributes.Add(new KeyValuePair("action", _action)); + + if (_httpMethod != null) + node.Attributes.Add(new KeyValuePair("httpMethod", _httpMethod)); + + if (_key != null) + node.Attributes.Add(new KeyValuePair("key", _key)); + + if (_url != null) + node.Attributes.Add(new KeyValuePair("url", _url)); + + if (_clickable.HasValue) + node.Attributes.Add(new KeyValuePair("clickable", _clickable.Value.ToString().ToLowerInvariant())); + + if (_roles != null) + node.Attributes.Add(new KeyValuePair("roles", string.Join(",", _roles))); + + if (_resourceKey != null) + node.Attributes.Add(new KeyValuePair("resourceKey", _resourceKey)); + + if (_visibilityProvider != null) + node.Attributes.Add(new KeyValuePair("visibilityProvider", _visibilityProvider)); + + if (_dynamicNodeProvider != null) + node.Attributes.Add(new KeyValuePair("dynamicNodeProvider", _dynamicNodeProvider)); + + if (_imageUrl != null) + node.Attributes.Add(new KeyValuePair("imageUrl", _imageUrl)); + + if (_targetFrame != null) + node.Attributes.Add(new KeyValuePair("targetFrame", _targetFrame)); + + if (_cacheResolvedUrl.HasValue) + node.Attributes.Add(new KeyValuePair("cacheResolvedUrl", _cacheResolvedUrl.Value.ToString().ToLowerInvariant())); + + if (_canonicalUrl != null) + node.Attributes.Add(new KeyValuePair("canonicalUrl", _canonicalUrl)); + + if (_canonicalKey != null) + node.Attributes.Add(new KeyValuePair("canonicalKey", _canonicalKey)); + + if (_metaRobotsValues != null) + node.Attributes.Add(new KeyValuePair("metaRobotsValues", string.Join(" ", _metaRobotsValues))); + + if (_changeFrequency != null) + node.Attributes.Add(new KeyValuePair("changeFrequency", _changeFrequency)); + + if (_updatePriority.HasValue) + node.Attributes.Add(new KeyValuePair("updatePriority", _updatePriority.Value.ToString())); + + if (_lastModifiedDate.HasValue) + node.Attributes.Add(new KeyValuePair("lastModifiedDate", _lastModifiedDate.Value.ToString())); + + if (_order.HasValue) + node.Attributes.Add(new KeyValuePair("order", _order.Value.ToString())); + + if (_route != null) + node.Attributes.Add(new KeyValuePair("route", _route)); + + if (_lastModifiedDate.HasValue) + node.Attributes.Add(new KeyValuePair("lastModifiedDate", _lastModifiedDate.Value.ToString())); + + if (_preservedRouteParameters != null) + node.Attributes.Add(new KeyValuePair("preservedRouteParameters", string.Join(",", _preservedRouteParameters))); + + if (_urlResolver != null) + node.Attributes.Add(new KeyValuePair("urlResolver", _urlResolver)); + + if (_inheritedRouteParameters != null) + node.Attributes.Add(new KeyValuePair("inheritedRouteParameters", string.Join(",", _inheritedRouteParameters))); + } + } +} diff --git a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeFactory.cs b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeFactory.cs new file mode 100644 index 00000000..8d74bab1 --- /dev/null +++ b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeFactory.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MvcSiteMapProvider.Linq; + +namespace MvcSiteMapProvider.Builder +{ + public class FluentSiteMapNodeFactory + : IFluentSiteMapNodeFactory + { + private readonly IFluentFactory _fluentFactory; + private readonly IList _result; + private readonly ISiteMapNodeHelper _siteMapNodeHelper; + + public FluentSiteMapNodeFactory(IFluentFactory fluentFactory, IList result, ISiteMapNodeHelper siteMapNodeHelper) + { + if(fluentFactory == null) + throw new ArgumentNullException("fluentFactory"); + if(result == null) + throw new ArgumentNullException("result"); + if(siteMapNodeHelper == null) + throw new ArgumentNullException("siteMapNodeHelper"); + + _fluentFactory = fluentFactory; + _result = result; + _siteMapNodeHelper = siteMapNodeHelper; + } + + #region IFluentSiteMapNodeFactory Members + + /// + /// Start the build of a fluent SiteMapNode + /// + /// + public IFluentSiteMapNodeBuilder Add() + { + var builder = _fluentFactory.CreateSiteMapNodeBuilder(_siteMapNodeHelper); + _result.Add(builder); + return builder; + } + + #endregion + } +} diff --git a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeProvider.cs b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeProvider.cs new file mode 100644 index 00000000..5485d5ef --- /dev/null +++ b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeProvider.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace MvcSiteMapProvider.Builder +{ + /// + /// FluentSiteMapNodeProvider class is a ISiteMapNodeProvider that builds nodes in a fluent fashion. + /// + public abstract class FluentSiteMapNodeProvider + : ISiteMapNodeProvider + { + private readonly IFluentFactory _fluentFactory; + + public FluentSiteMapNodeProvider(IFluentFactory fluentFactory) + { + if(fluentFactory == null) + throw new ArgumentNullException("fluentFactory"); + + _fluentFactory = fluentFactory; + } + + public abstract void BuildSitemapNodes(IFluentSiteMapNodeFactory fluentSiteMapNodeFactory); + + #region ISiteMapNodeProvider Members + + public IEnumerable GetSiteMapNodes(ISiteMapNodeHelper helper) + { + var builders = new List(); + var menuItemFactory = _fluentFactory.CreateSiteMapNodeFactory(builders, helper); + + BuildSitemapNodes(menuItemFactory); + + var nodes = new List(); + foreach (var builder in builders) + RecursivelyBuildNodes(helper, null, builder, nodes); + return nodes; + } + + #endregion + + private void RecursivelyBuildNodes(ISiteMapNodeHelper helper, ISiteMapNodeToParentRelation parent, IFluentSiteMapNodeBuilder builder, List nodes) + { + var node = builder.CreateNode(helper, parent != null ? parent.Node : null); + nodes.Add(node); + if (builder.Children != null) + foreach (var child in builder.Children) + RecursivelyBuildNodes(helper, node, child, nodes); + } + } +} diff --git a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentFactory.cs b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentFactory.cs new file mode 100644 index 00000000..612a01a3 --- /dev/null +++ b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentFactory.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MvcSiteMapProvider.Builder +{ + /// + /// Contract for newing up some items that are used for building fluent SiteMapNodes. + /// + public interface IFluentFactory + { + /// + /// Create a IFluentSiteMapNodeFactory. + /// + /// This should an existing collection. All nodes created using Add() will be added to this collection. + /// The ISiteMapNodeHelper that will internally create the ISiteMapNodes + /// + IFluentSiteMapNodeFactory CreateSiteMapNodeFactory(IList result, ISiteMapNodeHelper siteMapNodeHelper); + + /// + /// Create a IFluentSiteMapNodeBuilder. + /// + /// The ISiteMapNodeHelper that will internally create the ISiteMapNodes + /// + IFluentSiteMapNodeBuilder CreateSiteMapNodeBuilder(ISiteMapNodeHelper siteMapNodeHelper); + } +} diff --git a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeBuilder.cs b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeBuilder.cs new file mode 100644 index 00000000..c9f6a34c --- /dev/null +++ b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeBuilder.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web.Mvc; + +namespace MvcSiteMapProvider.Builder +{ + /// + /// Contract for the fluent interface that builds a SiteMapNode fluently + /// + public interface IFluentSiteMapNodeBuilder + { + IFluentSiteMapNodeBuilder Items(Action children); + + IList Children { get; } + + IFluentSiteMapNodeBuilder Area(string value); + + IFluentSiteMapNodeBuilder Controller(string value); + + IFluentSiteMapNodeBuilder Action(string value); + + IFluentSiteMapNodeBuilder HttpMethod(HttpVerbs? method); + + IFluentSiteMapNodeBuilder Title(string value); + + IFluentSiteMapNodeBuilder Description(string value); + + IFluentSiteMapNodeBuilder Key(string value); + + IFluentSiteMapNodeBuilder Url(string value); + + IFluentSiteMapNodeBuilder Clickable(bool? clickable); + + IFluentSiteMapNodeBuilder Roles(string[] values); + + IFluentSiteMapNodeBuilder ResourceKey(string value); + + IFluentSiteMapNodeBuilder VisibilityProvider(string value); + + IFluentSiteMapNodeBuilder DynamicNodeProvider(string value); + + IFluentSiteMapNodeBuilder ImageUrl(string value); + + IFluentSiteMapNodeBuilder TargetFrame(string value); + + IFluentSiteMapNodeBuilder CachedResolvedUrl(bool? cacheResolvedUrl); + + IFluentSiteMapNodeBuilder CanonicalUrl(string value); + + IFluentSiteMapNodeBuilder CanonicalKey(string value); + + IFluentSiteMapNodeBuilder MetaRobotsValues(string[] values); + + IFluentSiteMapNodeBuilder ChangeFrequency(ChangeFrequency? value); + + IFluentSiteMapNodeBuilder UpdatePriority(UpdatePriority? value); + + IFluentSiteMapNodeBuilder LastModifiedDate(DateTime? value); + + IFluentSiteMapNodeBuilder Order(int order); + + IFluentSiteMapNodeBuilder Route(string value); + + IFluentSiteMapNodeBuilder RouteValues(object routeValues); + + IFluentSiteMapNodeBuilder PreservedRouteValues(string[] values); + + IFluentSiteMapNodeBuilder UrlResolver(string value); + + IFluentSiteMapNodeBuilder InheritedRouteParameters(string[] values); + + ISiteMapNodeToParentRelation CreateNode(ISiteMapNodeHelper helper, ISiteMapNode parentNode); + } +} \ No newline at end of file diff --git a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeFactory.cs b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeFactory.cs new file mode 100644 index 00000000..fed278c5 --- /dev/null +++ b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeFactory.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MvcSiteMapProvider.Builder +{ + /// + /// Contract for the fluent SiteMapNode factories. + /// Calling Add() will initiate a fluent SiteMapNode underneath its current context (parent). + /// + public interface IFluentSiteMapNodeFactory + { + /// + /// Start the build of a fluent SiteMapNode + /// + /// + IFluentSiteMapNodeBuilder Add(); + } +} diff --git a/src/MvcSiteMapProvider/MvcSiteMapProvider/MvcSiteMapProvider.csproj b/src/MvcSiteMapProvider/MvcSiteMapProvider/MvcSiteMapProvider.csproj index 4fc6f9b6..0fe4fd85 100644 --- a/src/MvcSiteMapProvider/MvcSiteMapProvider/MvcSiteMapProvider.csproj +++ b/src/MvcSiteMapProvider/MvcSiteMapProvider/MvcSiteMapProvider.csproj @@ -124,9 +124,16 @@ + + + + + + + From 4ebc0613ab30748632d088e12649dc57120db66c Mon Sep 17 00:00:00 2001 From: Paul Knopf Date: Thu, 26 Dec 2013 17:32:46 -0500 Subject: [PATCH 2/3] Added support for dynamic nodes using the fluent builder. The functionality should be exactly the same as the xml node provider. Added a sample implementation in the MvcMusicStore. --- .../MvcSiteMapProviderContainerInitializer.cs | 13 +- .../Code/StoreFluentSiteMapProvider.cs | 114 ++++++++++++++++++ .../MvcMusicStore/MvcMusicStore.csproj | 1 + .../Builder/FluentSiteMapNodeBuilder.cs | 22 +++- .../Builder/FluentSiteMapNodeProvider.cs | 90 ++++++++++++-- .../Builder/IFluentSiteMapNodeBuilder.cs | 2 + 6 files changed, 225 insertions(+), 17 deletions(-) create mode 100644 src/MvcSiteMapProvider/MvcMusicStore/Code/StoreFluentSiteMapProvider.cs diff --git a/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs b/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs index efa124fd..d10975ea 100644 --- a/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs +++ b/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs @@ -1,4 +1,5 @@ -using System; +#define FluentDemo +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -6,6 +7,7 @@ using System.Text; using System.Web.Hosting; using System.Web.Mvc; +using MvcMusicStore.Code; using MvcSiteMapProvider; using MvcSiteMapProvider.Builder; using MvcSiteMapProvider.Caching; @@ -143,14 +145,23 @@ public static void SetUp(Container container) // Register the sitemap node providers +#if !FluentDemo container.RegisterSingle(() => container.GetInstance() .Create(container.GetInstance())); +#else + container.RegisterSingle(); +#endif container.RegisterSingle(() => container.GetInstance() .Create(includeAssembliesForScan)); // Register the sitemap builders +#if !FluentDemo container.RegisterSingle(() => container.GetInstance() .Create(new CompositeSiteMapNodeProvider(container.GetInstance(), container.GetInstance()))); +#else + container.RegisterSingle(() => container.GetInstance() + .Create(new CompositeSiteMapNodeProvider(container.GetInstance(), container.GetInstance()))); +#endif container.RegisterAll(ResolveISiteMapBuilderSets(container, securityTrimmingEnabled, enableLocalization)); container.RegisterSingle(() => new SiteMapBuilderSetStrategy(container.GetAllInstances().ToArray())); diff --git a/src/MvcSiteMapProvider/MvcMusicStore/Code/StoreFluentSiteMapProvider.cs b/src/MvcSiteMapProvider/MvcMusicStore/Code/StoreFluentSiteMapProvider.cs new file mode 100644 index 00000000..8323b813 --- /dev/null +++ b/src/MvcSiteMapProvider/MvcMusicStore/Code/StoreFluentSiteMapProvider.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using MvcSiteMapProvider; +using MvcSiteMapProvider.Builder; + +namespace MvcMusicStore.Code +{ + /// + /// This is an example FluentSiteMapNodeProvider that is a mirror of the ~/Mvc.sitemap. + /// + public class StoreFluentSiteMapProvider : FluentSiteMapNodeProvider + { + public StoreFluentSiteMapProvider(IFluentFactory fluentFactory) + : base(fluentFactory) + { + + } + + public override void BuildSitemapNodes(IFluentSiteMapNodeFactory fluentSiteMapNodeFactory) + { + fluentSiteMapNodeFactory.Add() + .Title("$resources:SiteMapLocalizations,HomeTitle") + .Description("This is the home page") + .Controller("Home") + .Action("Index") + .ChangeFrequency(ChangeFrequency.Always) + .UpdatePriority(UpdatePriority.Normal) + .LastModifiedDate(DateTime.Parse("2002-05-30T09:00:00")) + .Items(homeChildren => + { + homeChildren.Add() + .Title("$resources:SiteMapLocalizations,BrowseGenresTitle") + .Controller("Store") + .Action("Index") + .Items(genresChildren => genresChildren.Add() + .Title("Browse") + .Action("Browse") + .DynamicNodeProvider("MvcMusicStore.Code.StoreBrowseDynamicNodeProvider, Mvc Music Store") + .PreservedRouteValues(new[] { "browse" }) + .Items(browseChildren => browseChildren.Add() + .Title("Details") + .Action("Details") + .DynamicNodeProvider("MvcMusicStore.Code.StoreDetailsDynamicNodeProvider, Mvc Music Store"))); + homeChildren.Add() + .Title("$resources:SiteMapLocalizations,ReviewCartTitle") + .Controller("ShoppingCart") + .Action("Index"); + homeChildren.Add() + .Key("Checkout") + .Title("$resources:SiteMapLocalizations,CheckoutTitle") + .Controller("Checkout") + .Clickable(false); + homeChildren.Add() + .Title("$resources:SiteMapLocalizations,AccountTitle") + .Controller("Account") + .Clickable(false) + .Items(accountChildren => + { + accountChildren.Add() + .Title("$resources:SiteMapLocalizations,LogOnTitle") + .Action("LogOn") + .VisibilityProvider("MvcMusicStore.Code.NonAuthenticatedVisibilityProvider, Mvc Music Store"); + accountChildren.Add() + .Title("$resources:SiteMapLocalizations,LogOffTitle") + .Action("LogOff") + .VisibilityProvider("MvcMusicStore.Code.AuthenticatedVisibilityProvider, Mvc Music Store"); + accountChildren.Add() + .Title("$resources:SiteMapLocalizations,RegisterTitle") + .Action("Register") + .VisibilityProvider("MvcMusicStore.Code.NonAuthenticatedVisibilityProvider, Mvc Music Store"); + accountChildren.Add() + .Title("$resources:SiteMapLocalizations,ChangePasswordTitle") + .Action("ChangePassword") + .VisibilityProvider("MvcMusicStore.Code.AuthenticatedVisibilityProvider, Mvc Music Store"); + }); + homeChildren.Add() + .Title("$resources:SiteMapLocalizations,AdministrationTitle") + .Area("Admin") + .Controller("Home") + .Action("Index") + .Attribute("visibility", "SiteMapPathHelper,!*") + .VisibilityProvider("MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider, MvcSiteMapProvider") + .Items(administrationChildren => administrationChildren.Add() + .Title("$resources:SiteMapLocalizations,StoreManagerTitle") + .Area("Admin") + .Controller("StoreManager") + .Action("Index") + .Items(storeManagerChildren => + { + storeManagerChildren.Add() + .Title("$resources:SiteMapLocalizations,CreateAlbumTitle") + .Action("Create"); + storeManagerChildren.Add() + .Title("$resources:SiteMapLocalizations,EditAlbumTitle") + .Action("Edit"); + storeManagerChildren.Add() + .Title("$resources:SiteMapLocalizations,DeleteAlbumTitle") + .Action("Delete"); + })); + homeChildren.Add() + .Title("$resources:SiteMapLocalizations,SitemapTitle") + .Action("SiteMap") + .UrlResolver("MvcMusicStore.Code.UpperCaseSiteMapNodeUrlResolver, Mvc Music Store"); + }); + } + + public override bool UseNestedDynamicNodeRecursion + { + get { return true; } + } + } +} \ No newline at end of file diff --git a/src/MvcSiteMapProvider/MvcMusicStore/MvcMusicStore.csproj b/src/MvcSiteMapProvider/MvcMusicStore/MvcMusicStore.csproj index 06f0e603..b14889f7 100644 --- a/src/MvcSiteMapProvider/MvcMusicStore/MvcMusicStore.csproj +++ b/src/MvcSiteMapProvider/MvcMusicStore/MvcMusicStore.csproj @@ -284,6 +284,7 @@ configured in the compilation constants --> + diff --git a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeBuilder.cs b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeBuilder.cs index 864b0154..75e0e12e 100644 --- a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeBuilder.cs +++ b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeBuilder.cs @@ -12,6 +12,7 @@ public class FluentSiteMapNodeBuilder private readonly IFluentFactory _fluentFactory; private readonly ISiteMapNodeHelper _siteMapNodeHelper; private readonly IList _children = new List(); + private readonly IDictionary _additionalAttributes = new Dictionary(); private string _area; private string _controller; private string _action; @@ -43,7 +44,7 @@ public class FluentSiteMapNodeBuilder public FluentSiteMapNodeBuilder(IFluentFactory fluentFactory, ISiteMapNodeHelper siteMapNodeHelper) { - if(_siteMapNodeHelper == null) + if (siteMapNodeHelper == null) throw new ArgumentNullException("siteMapNodeHelper"); if (fluentFactory == null) throw new ArgumentNullException("fluentFactory"); @@ -65,6 +66,15 @@ public IFluentSiteMapNodeBuilder Items(Action childre public IList Children { get { return _children; } } + public IFluentSiteMapNodeBuilder Attribute(string key, object value) + { + if (_additionalAttributes.ContainsKey(key)) + _additionalAttributes[key] = value; + else + _additionalAttributes.Add(key, value); + return this; + } + public IFluentSiteMapNodeBuilder Area(string value) { _area = value; @@ -391,9 +401,6 @@ private void AddAttributesToNode(ISiteMapNode node) if (_route != null) node.Attributes.Add(new KeyValuePair("route", _route)); - if (_lastModifiedDate.HasValue) - node.Attributes.Add(new KeyValuePair("lastModifiedDate", _lastModifiedDate.Value.ToString())); - if (_preservedRouteParameters != null) node.Attributes.Add(new KeyValuePair("preservedRouteParameters", string.Join(",", _preservedRouteParameters))); @@ -402,6 +409,13 @@ private void AddAttributesToNode(ISiteMapNode node) if (_inheritedRouteParameters != null) node.Attributes.Add(new KeyValuePair("inheritedRouteParameters", string.Join(",", _inheritedRouteParameters))); + + foreach (var additionalAttribute in _additionalAttributes) + { + if(node.Attributes.ContainsKey(additionalAttribute.Key)) + throw new InvalidOperationException(string.Format("An attempt was made to set attribute {0} manually, but it is already defined internally.", additionalAttribute.Key)); + node.Attributes.Add(additionalAttribute); + } } } } diff --git a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeProvider.cs b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeProvider.cs index 5485d5ef..7e7ac74e 100644 --- a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeProvider.cs +++ b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/FluentSiteMapNodeProvider.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; namespace MvcSiteMapProvider.Builder { @@ -22,8 +19,18 @@ public FluentSiteMapNodeProvider(IFluentFactory fluentFactory) _fluentFactory = fluentFactory; } + [Flags] + protected enum NodesToProcess + { + StandardNodes = 1, + DynamicNodes = 2, + All = StandardNodes | DynamicNodes + } + public abstract void BuildSitemapNodes(IFluentSiteMapNodeFactory fluentSiteMapNodeFactory); + public abstract bool UseNestedDynamicNodeRecursion { get; } + #region ISiteMapNodeProvider Members public IEnumerable GetSiteMapNodes(ISiteMapNodeHelper helper) @@ -33,21 +40,80 @@ public IEnumerable GetSiteMapNodes(ISiteMapNodeHel BuildSitemapNodes(menuItemFactory); - var nodes = new List(); + var result = new List(); foreach (var builder in builders) - RecursivelyBuildNodes(helper, null, builder, nodes); - return nodes; + { + var rootNode = builder.CreateNode(helper, null); + result.Add(rootNode); + result.AddRange(ProcessBuilders(rootNode.Node, builder.Children, NodesToProcess.All, helper)); + } + return result; } #endregion - private void RecursivelyBuildNodes(ISiteMapNodeHelper helper, ISiteMapNodeToParentRelation parent, IFluentSiteMapNodeBuilder builder, List nodes) + /// + /// Recursively processes our XML document, parsing our siteMapNodes and dynamicNode(s). + /// + /// The parent node to process. + /// The correspoding parent XML element. + /// Flags to indicate which nodes to process. + /// The node helper. + protected virtual IList ProcessBuilders(ISiteMapNode parentNode, IList childrenBuilders, NodesToProcess processFlags, ISiteMapNodeHelper helper) { - var node = builder.CreateNode(helper, parent != null ? parent.Node : null); - nodes.Add(node); - if (builder.Children != null) - foreach (var child in builder.Children) - RecursivelyBuildNodes(helper, node, child, nodes); + var result = new List(); + bool processStandardNodes = (processFlags & NodesToProcess.StandardNodes) == NodesToProcess.StandardNodes; + bool processDynamicNodes = (processFlags & NodesToProcess.DynamicNodes) == NodesToProcess.DynamicNodes; + + foreach (var node in childrenBuilders) + { + var child = node.CreateNode(helper, parentNode); + + if (processStandardNodes && !child.Node.HasDynamicNodeProvider) + { + result.Add(child); + + // Continue recursively processing the XML file. + result.AddRange(ProcessBuilders(child.Node, node.Children, processFlags, helper)); + } + else if (processDynamicNodes && child.Node.HasDynamicNodeProvider) + { + // We pass in the parent node key as the default parent because the dynamic node (child) is never added to the sitemap. + var dynamicNodes = helper.CreateDynamicNodes(child, parentNode.Key); + + foreach (var dynamicNode in dynamicNodes) + { + result.Add(dynamicNode); + + if (!this.UseNestedDynamicNodeRecursion) + { + // Recursively add non-dynamic childs for every dynamic node + result.AddRange(ProcessBuilders(dynamicNode.Node, node.Children, NodesToProcess.StandardNodes, helper)); + } + else + { + // Recursively process both dynamic nodes and static nodes. + // This is to allow V3 recursion behavior for those who depended on it - it is not a feature. + result.AddRange(ProcessBuilders(dynamicNode.Node, node.Children, NodesToProcess.All, helper)); + } + } + + if (!this.UseNestedDynamicNodeRecursion) + { + // Process the next nested dynamic node provider. We pass in the parent node as the default + // parent because the dynamic node definition node (child) is never added to the sitemap. + result.AddRange(ProcessBuilders(parentNode, node.Children, NodesToProcess.DynamicNodes, helper)); + } + else + { + // Continue recursively processing the XML file. + // Can't figure out why this is here, but this is the way it worked in V3 and if + // anyone depends on the broken recursive behavior, they probably also depend on this. + result.AddRange(ProcessBuilders(child.Node, node.Children, processFlags, helper)); + } + } + } + return result; } } } diff --git a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeBuilder.cs b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeBuilder.cs index c9f6a34c..9370d00a 100644 --- a/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeBuilder.cs +++ b/src/MvcSiteMapProvider/MvcSiteMapProvider/Builder/IFluentSiteMapNodeBuilder.cs @@ -15,6 +15,8 @@ public interface IFluentSiteMapNodeBuilder IList Children { get; } + IFluentSiteMapNodeBuilder Attribute(string key, object value); + IFluentSiteMapNodeBuilder Area(string value); IFluentSiteMapNodeBuilder Controller(string value); From 64c9c35eb9e0ec0378dea50db56539b6edf265c2 Mon Sep 17 00:00:00 2001 From: Paul Knopf Date: Thu, 26 Dec 2013 17:34:16 -0500 Subject: [PATCH 3/3] Don't use the fluent api demo by default in the MvcMusicStore. Use the xml sitemap node provider. --- .../SimpleInjector/MvcSiteMapProviderContainerInitializer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs b/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs index d10975ea..733e7895 100644 --- a/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs +++ b/src/MvcSiteMapProvider/CodeAsConfiguration/SimpleInjector/DI/SimpleInjector/MvcSiteMapProviderContainerInitializer.cs @@ -1,4 +1,5 @@ -#define FluentDemo +// uncomment this to demonstrace the fluent api demo using StoreFluentSiteMapProvider +//#define FluentDemo using System; using System.Collections.Generic; using System.Linq;