可以在第一级使用 MVC SiteMapProvider dynamicNode 吗?

Can MVC SiteMapProvider dynamicNode be used on 1st Level?

我使用 MVC SiteMapProvider 已经有一段时间了,我很喜欢它。我正在建立一个电子商务网站,到目前为止,它在我的开发过程中运行良好。

我似乎无法解决的一个问题是如何让 dynamicNode 在第一层工作。

像这样:

www.mysite.com/{type}/{category}/{filter}

只有 3 种类型,所以现在我只有 3 个以类型命名的控制器,它们都使用相同的逻辑和 viewModels,这不是一个理想的可维护性设置。我的 routeConfig 包括 3 条这样的路线。

routes.MapRoute(
            name: "Hardscape",
            url: "hardscape-products/{category}/{filter}",
            defaults: new { controller = "Products", action = "Index", category = UrlParameter.Optional, filter = UrlParameter.Optional},
            namespaces: new[] { "MyApp.Web.Controllers" }
        );

routes.MapRoute(
            name: "Masonry",
            url: "masonry-products/{category}/{filter}",
            defaults: new { controller = "Products", action = "Index", category = UrlParameter.Optional, filter = UrlParameter.Optional},
            namespaces: new[] { "MyApp.Web.Controllers" }
        );

routes.MapRoute(
            name: "Landscape",
            url: "landscape-products/{category}/{filter}",
            defaults: new { controller = "Products", action = "Index", category = UrlParameter.Optional, filter = UrlParameter.Optional},
            namespaces: new[] { "MyApp.Web.Controllers" }
        );

我试过类似的东西,但它 returns 404.

   routes.MapRoute(
            name: "Products",
            url: "{productType}/{category}/{filter}",
            defaults: new { controller = "Products", action = "Index", productType = UrlParameter.Optional,  category = UrlParameter.Optional, filter = UrlParameter.Optional},
            namespaces: new[] { "MyApp.Web.Controllers" }
        );

我已经能够使用 dynamicNode 作为我的类别和过滤器参数在站点地图和菜单中生成我的节点。当我没有静态命名第一级时,第一级有问题

masonry-products/ vs. {productType}/

如果您有解决方案,请告诉我。希望 NightOwl 可以插话。

.NET 的路由框架非常灵活。

对于这种情况,您可以只对类型使用约束。有两种方式:

  1. Use a RegEx.
  2. Implement a custom class.

如果您不希望有太多变化,第一个选项不会太糟糕:

routes.MapRoute(
    name: "Products",
    url: "{productType}/{category}/{filter}",
    defaults: new { controller = "Products", action = "Index", category = UrlParameter.Optional, filter = UrlParameter.Optional},
    constraints: new { productType = @"hardscape-products|masonry-products|landscape-products" },
    namespaces: new[] { "MyApp.Web.Controllers" }
);

第二个选项更动态:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;

public class ProductTypeConstraint : IRouteConstraint
{
    private object synclock = new object();

    public bool Match
        (
            HttpContextBase httpContext,
            Route route,
            string parameterName,
            RouteValueDictionary values,
            RouteDirection routeDirection
        )
    {
        return GetProductTypes(httpContext).Contains(values[parameterName]);
    }

    private IEnumerable<string> GetProductTypes(HttpContextBase httpContext)
    {
        string key = "ProductTypeConstraint_GetProductTypes";
        var productTypes = httpContext.Cache[key];
        if (productTypes == null)
        {
            lock (synclock)
            {
                productTypes = httpContext.Cache[key];
                if (productTypes == null)
                {
                    // TODO: Retrieve the list of Product types from the 
                    // database or configuration file here.
                    productTypes = new List<string>()
                    {
                        "hardscape-products",
                        "masonry-products",
                        "landscape-products"
                    };

                    httpContext.Cache.Insert(
                        key: key,
                        value: productTypes,
                        dependencies: null,
                        absoluteExpiration: System.Web.Caching.Cache.NoAbsoluteExpiration,
                        slidingExpiration: TimeSpan.FromMinutes(15),
                        priority: System.Web.Caching.CacheItemPriority.NotRemovable,
                        onRemoveCallback: null);
                }
            }
        }

        return (IEnumerable<string>)productTypes;
    }
}

这里需要缓存,因为每个请求都会遇到约束。

routes.MapRoute(
    name: "Products",
    url: "{productType}/{category}/{filter}",
    defaults: new { controller = "Products", action = "Index", category = UrlParameter.Optional, filter = UrlParameter.Optional},
    constraints: new { productType = new ProductTypeConstraint() },
    namespaces: new[] { "MyApp.Web.Controllers" }
);

当然,这不是唯一的动态选项。如果您真的需要选择任何 URL,就像在 CMS 中一样,您可以 并从数据库中驱动所有 URL。

不过不确定这个问题与动态节点提供程序有什么关系。我也不明白 "first level" 是什么意思。

您真正需要使用动态节点提供程序做的唯一一件事是匹配您在路由中拥有的相同路由值并提供键-父键关系。必须在 XML 中定义父键或作为 .NET 属性来附加来自提供程序的顶级节点。

路由

dynamicNode.Controller = "Product";
dynamicNode.Action = "Index";
dynamicNode.RouteValues.Add("productType", "hardscape-products");
dynamicNode.RouteValues.Add("category", "some-category");
dynamicNode.RouteValues.Add("filter", "some-filter");

dynamicNode.Controller = "Product";
dynamicNode.Action = "Index";
dynamicNode.PreservedRouteParameters = new string[] { "productType", "category", "filter" };

对您的应用程序有意义的路由值和保留的路由参数的某种组合。

有关这些选项的解释,请阅读 How to Make MvcSiteMapProvider Remember a User's Position

键匹配

// This assumes you have explicitly set a key to "Home"
// in a node outside of the dynamic node provider.
dynamicNode.ParentKey = "Home";
dynamicNode.Key = "Product1";

// This node has the node declared above
// as its parent.
dynamicNode.ParentKey = "Product1";
dynamicNode.Key = "Product1Details";

OP 的解决方案。

非常感谢 NightOwl888 的详细解答,帮助我解决了这个问题。我之前遵循了 MSDN 教程 here,我认为它让我对约束的使用感到困惑。

总而言之,我没有正确定义我的约束,这导致了 404 以及我与 MVCSiteMapProvider 的所有其他问题。这是工作解决方案的示例。

路线

routes.MapRoute(
         name: "Products",
         url: "{productType}/{category}/{filter}/{filterAction}/{filterId}",
         defaults: new { controller = "Products", action = "Index", productType = UrlParameter.Optional, category = UrlParameter.Optional, filter = UrlParameter.Optional, filterAction = UrlParameter.Optional, filterId = UrlParameter.Optional },
         constraints: new { productType = @"building-products|installation-materials|tools" },
        namespaces: new[] { "MyApp.Web.Controllers" }
        );

XML

<mvcSiteMapNode title="Product Type" dynamicNodeProvider="MyApp.Web.SiteMapProviders.ProductTypeSiteMapProvider, MyApp.Web">
  <mvcSiteMapNode title="Category" dynamicNodeProvider="MyApp.Web.SiteMapProviders.CategorySiteMapProvider, MyApp.Web">
    <mvcSiteMapNode title="Option" dynamicNodeProvider="MyApp.Web.SiteMapProviders.OptionSiteMapProvider, MyApp.Web" />
    <mvcSiteMapNode title="Association" dynamicNodeProvider="MyApp.Web.SiteMapProviders.AssociationSiteMapProvider, MyApp.Web" />
  </mvcSiteMapNode>
</mvcSiteMapNode>

4 个动态节点中的前 2 个给你一个想法

public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
    {
        using (var db = new ProductContext())
        {              
            foreach (var productType in db.ProductTypes.ToList())
            {
                DynamicNode dynamicNode = new DynamicNode();
                dynamicNode.Key = productType.Name.ToLower().Replace(" ", "-");
                dynamicNode.Title = productType.Name;
                dynamicNode.Clickable = false;
                yield return dynamicNode;
            }
        }
    }

public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
    {
        using (var db = new ProductContext())
        {              
            foreach (var category in db.Categories.ToList())
            {
                DynamicNode dynamicNode = new DynamicNode();
                dynamicNode.Key = category.Name.Replace(" ", "");
                dynamicNode.Title = category.Name;
                dynamicNode.Controller = "Products";
                dynamicNode.Action = "Index";
                dynamicNode.ParentKey = category.ProductType.Name.ToLower().Replace(" ", "-");
                dynamicNode.RouteValues.Add("productType", category.ProductType.Name.ToLower().Replace(" ", "-"));
                dynamicNode.RouteValues.Add("category", category.Name.ToLower().Replace(" ", "-"));
                dynamicNode.ImageUrl = category.CategoryImage();
                yield return dynamicNode;
            }
        }
    }