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

时间:2015-10-15 22:22:53

标签: asp.net-mvc asp.net-mvc-5 mvcsitemapprovider

我一直在搞乱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" }
        );

我尝试过类似的东西,但它会返回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可以加入。

2 个答案:

答案 0 :(得分:1)

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

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

  1. Use a RegEx
  2. Implement a custom class
  3. 如果你不期待很多变化,那么第一个选项就不会那么糟糕:

    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" }
    );
    

    当然,这不是唯一的动态选择。如果您确实需要选择任何您选择的网址,例如在CMS中,您可以inherit RouteBase并从数据库中提取所有网址。

    不确定这个问题与动态节点提供程序有什么关系。我也不明白&#34;第一级&#34;的含义。

    您真正需要对动态节点提供程序执行的唯一操作是匹配路由中的相同路由值并提供密钥 - 父密钥关系。必须在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");
    

    OR

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

    OR

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

    有关这些选项的说明,请阅读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";
    

答案 1 :(得分:0)

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;
            }
        }
    }