Создание дочерних узлов для DynamicNode в MvcSiteMapProvider с динамическими параметрами

Я использую MvcSiteMapProvider 2.2.1 (http://mvcsitemap.codeplex.com), и у меня возникла проблема с созданием дочерних элементов под динамическим узлом (с использованием dynamicNodeProvider), когда эти дочерние элементы имеют динамический параметр (id).

Я теряю панировочные сухари по следующему маршруту:

Магазины / 5 / Товары / Редактировать / 23

где шаблон URL:

Магазины / {storeID} / {controller} / {action} / {id}

Он отлично работает, когда ID не указан (т. Е. Действие «Новый»). Но когда идентификатор указан, он не соответствует маршруту, и мои хлебные крошки (с использованием помощника SiteMapPath) остаются пустыми.

Мой файл Sitemap: (сокращенный)

<?xml version="1.0" encoding="utf-8" ?>
<mvcSiteMap xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-2.0">
    <mvcSiteMapNode title="Home" controller="Dashboard" action="Index" changeFrequency="Always" updatePriority="Normal">
        <mvcSiteMapNode title="My Account" controller="Account" action="Index" key="Account" />
        <mvcSiteMapNode title="My Stores" area="Stores" controller="Home" action="Index" visibilityProvider="ControlPanel.Areas.Stores.StoreAreaVisibilityProvider, ControlPanel"  >
            <mvcSiteMapNode title="Store" action="Index" dynamicNodeProvider="ControlPanel.Areas.Stores.StoreAreaNodeProvider, ControlPanel" />
        </mvcSiteMapNode>
    </mvcSiteMapNode>
</mvcSiteMap>

Регистрация по месту:

public override void RegisterArea(AreaRegistrationContext context)
{
        context.MapRoute(
            "Store_Index",
            "Stores",
            new { action = "Index", controller = "Home" },
            new string[] { "ControlPanel.Areas.Stores.Controllers" }
            );

        context.MapRoute(
            "Store_default",
            "Stores/{storeID}/{controller}/{action}/{id}",
            new { action = "Index", controller = "Manage", id = UrlParameter.Optional },
            new { storeID = @"\d+" },
            new string[] { "ControlPanel.Areas.Stores.Controllers" }
        );
    }

Первая попытка:

Первым делом я попытался создать дочерние узлы прямо в xml карты сайта как дочерние элементы динамического узла. Это совсем не сработало, и они оказались детьми «Дома». Я бы поместил туда атрибут ParentKey, за исключением того, что они будут повторяться для каждого магазина, и поэтому будет несколько родительских ключей.

<?xml version="1.0" encoding="utf-8" ?>
<mvcSiteMap xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-2.0">
  <mvcSiteMapNode title="Home" controller="Dashboard" action="Index" changeFrequency="Always" updatePriority="Normal">
    <mvcSiteMapNode title="My Account" controller="Account" action="Index" key="Account" />
    <mvcSiteMapNode title="My Stores" area="Stores" controller="Home" action="Index" visibilityProvider="ControlPanel.Areas.Stores.StoreAreaVisibilityProvider, ControlPanel"  >
      <mvcSiteMapNode title="Store" action="Index" dynamicNodeProvider="ControlPanel.Areas.Stores.StoreAreaNodeProvider, ControlPanel">
        <mvcSiteMapNode title="Products" area="Stores" controller="Products" action="Index">
          <mvcSiteMapNode title="Edit" area="Stores" controller="Products" action="Edit"/>
          <mvcSiteMapNode title="New" area="Stores" controller="Products" action="Edit"/>
        </mvcSiteMapNode>
      </mvcSiteMapNode>
    </mvcSiteMapNode>
  </mvcSiteMapNode>
</mvcSiteMap>

Вторая попытка:

Казалось, что следующий вариант - просто добавить дочерние узлы прямо в мой DynamicNodeProvider. Это работало намного лучше, за исключением узлов с динамическими параметрами.

(ниже изменено для простоты объяснения ...)

public class StoreAreaNodeProvider : IDynamicNodeProvider
{
    public IEnumerable<DynamicNode> GetDynamicNodeCollection()
    {
        var nodes = new List<DynamicNode>();

        foreach (var store in repo.GetStores())
        {
            DynamicNode node = new DynamicNode();
            node.Title = store.Name;
            node.Area = "Stores";
            node.Controller = "Manage";
            node.Action = "Index";
            node.RouteValues.Add("storeID", store.StoreID);
            node.Key = "Store_" + store.StoreID.ToString();

            nodes.Add(node);

            //Child of node
            DynamicNode productsNode = new DynamicNode();
            productsNode.Title = "Products";
            productsNode.Area = "Stores";
            productsNode.Controller = "Products";
            productsNode.Action = "Index";
            productsNode.RouteValues.Add("storeID", store.StoreID);
            productsNode.ParentKey = String.Format("Store_{0}", store.StoreID.ToString());
            productsNode.Key = String.Format("Store_{0}_Products", store.StoreID.ToString());

            nodes.Add(productsNode);

            //child of productsNode
            DynamicNode editNode = new DynamicNode();
            editNode.Title = "Edit";
            editNode.Area = "Stores";
            editNode.Action = "Edit";
            editNode.Controller = "Products";
            editNode.RouteValues.Add("storeID", store.StoreID);
            //I can't add the RouteValue "ID" here because it is dynamic
            //I would have do loop through every product for this store with
            //another dynamic node provider, but that seems terribly inefficient and stupid
            editNode.ParentKey = String.Format("Store_{0}_Products", store.StoreID.ToString());
            editNode.Attributes.Add("visibility", "SiteMapPathHelper,!*");

            nodes.Add(editNode);
        }

        return nodes;
    }
}

В сводке

Работает: Stores / 5 / Products / New
Не работает: Stores / 5 / Products / Edit / 23
Для шаблона URL: Магазины / {storeID} / {controller} / {action} / {id}

Что бы я хотел делать:

editNode.Attributes.Add("isDynamic", "true");
editNode.Attributes.Add("dynamicParameters", "id");

Как я могу имитировать старый атрибут dynamicParameters MvcSiteMapProvider на узле, который является дочерним элементом dynamicNode? В основном мне нужно, чтобы он игнорировал значение маршрута "id" при сопоставлении маршрутов.

Надеюсь, я объяснил это правильно и не завалил вас информацией. Спасибо!


ОБНОВЛЕНИЕ:

Вот решение, которое сработало для меня на основе ответа Якуба.

В MvcSiteMapProvider 2.x вы можете создать собственную реализацию ISiteMapNodeUrlResolver вместо того, чтобы изменять исходный код. Поэтому я в основном добавил возможность иметь атрибут dynamicParameters

Класс:

namespace ControlPanel
{
    public class CustomSiteMapNodeUrlResolver : ISiteMapNodeUrlResolver
    {
        public virtual string ResolveUrl(MvcSiteMapNode mvcSiteMapNode, string area, string controller, string action, IDictionary<string, object> routeValues)
        {
            RequestContext ctx;
            if (HttpContext.Current.Handler is MvcHandler)
                ctx = ((MvcHandler)HttpContext.Current.Handler).RequestContext;
            else
                ctx = new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData());

            //Begin Added Code
            if (mvcSiteMapNode["dynamicParameters"] != null)
            {
                foreach (var item in mvcSiteMapNode["dynamicParameters"].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    var dp = item.Trim();
                    routeValues[da] = ctx.RouteData.Values[dp];
                }
            }
            //End Added Code

            return new UrlHelper(ctx).Action(action, controller, new RouteValueDictionary(routeValues));
        }
    }
}

Web.config:

<siteMap defaultProvider="MvcSiteMapProvider" enabled="true">
  <providers>
    <clear/>
    <add name="MvcSiteMapProvider"
         type="MvcSiteMapProvider.DefaultSiteMapProvider, MvcSiteMapProvider"
         siteMapFile="~/Mvc.Sitemap"
         securityTrimmingEnabled="true"
         attributesToIgnore="visibility,dynamicParameters"
         scanAssembliesForSiteMapNodes="true" 
         siteMapNodeUrlResolver="ControlPanel.CustomSiteMapNodeUrlResolver, ControlPanel"
         siteMapNodeVisibilityProvider="MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider, MvcSiteMapProvider" />
  </providers>
</siteMap>

Поставщик динамических узлов:

DynamicNode node = new DynamicNode();
node.Attributes.Add("dynamicParameters", "id");

person Chris Curtis    schedule 09.11.2010    source источник
comment
Сейчас 2012 год, я использую версию 3.x и все еще имею ту же проблему.   -  person Alper    schedule 03.12.2012
comment
И проблема в том, что ваш собственный метод ResolveUrl запускается сразу после запуска приложения (без перехода на эту параметризованную страницу), поэтому строка, которую вы получаете с параметром текущего маршрута ctx.RouteData.Values ​​[dp], имеет значение null, поэтому это невозможно. для замены параметра маршрута карты сайта текущим параметром маршрута. Любые идеи?   -  person Alper    schedule 03.12.2012
comment
Привет, @AlperOzcetin, я так давно не касался этого кода, понятия не имею. И на самом деле через несколько месяцев после этой публикации этот проект развился настолько, что MvcSiteMapProvider не делал этого за меня, и в итоге мы развернули собственное решение с нуля (на котором сайт работает и сегодня). Удачи...   -  person Chris Curtis    schedule 13.12.2012


Ответы (1)


Я использую версию 1.x. У меня была аналогичная проблема с динамическими параметрами.

Пришлось доработать исходный код - внес изменения в MvcSiteMapNode.cs. Это новая реализация свойства Url.

    public override string Url
    {
        get
        {
            if (!string.IsNullOrEmpty(this.url))
                return this.url;

            RequestContext ctx;
            if (HttpContext.Current.Handler is MvcHandler)
                ctx = ((MvcHandler)HttpContext.Current.Handler).RequestContext;
            else
                ctx = new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData());

            var routeValues = new RouteValueDictionary(RouteValues);

            foreach (var key in DynamicParameters)
                routeValues.Add(key, ctx.RouteData.Values[key]);

            return new UrlHelper(ctx).Action(Action, Controller, routeValues);
        }
        set
        {
            this.url = value;
        }
    }

Обратите внимание, как фактические значения dynamicParameters добавляются в коллекцию routeValues.

Вышеупомянутое изменение позволило мне определить динамические параметры в карте сайта (например, id) и создать хлебные крошки со ссылками, такими как Account / User / Edit / 23.

Я кратко ознакомился с версией 2.x, и вы сможете применить аналогичный патч. Надеюсь, это поможет вам ...

person Jakub Konecki    schedule 09.11.2010
comment
Потрясающе ... Большое вам спасибо. Я принял ваше предложение и изменил его, чтобы он соответствовал стилю 2.x, и опубликовал его как еще один ответ. - person Chris Curtis; 10.11.2010
comment
Также ... что здесь общего? Отмечать ли ваш ответ как принятый (что я хочу сделать) или отмечать свой ответ, поскольку он содержит код, который сработал для меня? - person Chris Curtis; 10.11.2010
comment
@Chris - Как насчет того, чтобы вы отредактировали свой вопрос и разместили там последний фрагмент кода? PS. Я рад, что это сработало для вас. - person Jakub Konecki; 10.11.2010