-
Notifications
You must be signed in to change notification settings - Fork 218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SiteMapPath Issue with passing sourceMetadata parameters #383
Comments
Multiple nodes with exactly the same route values are not supported. When 2 nodes with the exact same route values are configured, the first match always wins - this is normal and expected behavior. However, I can't tell from your comment exactly what you are trying to achieve. You are using named menu instances, but it isn't really necessary if you just want 2 menus with the exact same node structure. Also, I it appears that you intend some conditional logic, but since your visibility providers are showing the exact same structure I don't see where a condition would be useful. Please explain your use case. |
The structure is same but the products are different & this are just a few for an example scenario, have a few more as well. The routeValues are different it will be productType = "product1", "product2", so on. Depending on the condition, the product name in the left hand nav & breadcrumb should change that's what i want to achieve. But seems like since the main productName1 page has a productType parameter while the Product1 doesn't, it is not working as expected. If i pass a productType parameter to Product1, then it works. But ideally it should work without it as well, since the left hand nav is working without the parameter passed. |
There are 2 ways to handle custom route values as explained in the documentation:
Or you can combine both techniques (on different route values on the same node). The first option works without any special hacks, but it doesn't scale to more than about 10,000 - 15,000 nodes. The second option scales much better, but requires that you include the custom route data (that is, any route key that is not "area", "controller", or "action") for parent nodes in each of the child nodes, and the keys must keep a consistent meaning for the same ancestry. But I think the crux of your problem is that a) you are configuring multiple nodes with the same route combination, b) you aren't accounting for all of the route parameters, and c) you are putting a condition on which HTML helper instance to show, which is what visibility providers and/or custom HTML helpers are intended for. The idea is that each route will match a different node, and the values in the menu and breadcrumb change according to which node is the "current" one. Unless you are using preservedRouteParameters, in which case you need to make the title change dynamically. When you use preservedRouteParameters, it means that MvcSiteMapProvider will not automatically build the URLs for the menu. You have to use a table or a list of some kind to generate the hyperlinks, and then the breadcrumb will change according to which pages you visit. That is why you generally need to use a visibility provider to exclude the one node that matches all of the products (and leave its parent node) in the Menu and SiteMap. There is a blog post with a downloadable demo that goes into detail about using the above techniques, although I haven't yet created a demo for a wizard if that is what you are doing. |
Thanks for the explanation, but not able to get much out of it. Also I am not able to get why the left hand nav is working fine & the breadcrumbs are not. By wizard you mean to say the breadcrumbs? |
By wizard, I mean that by your example it looks like you want the user to visit a product page, then visit a customization page for the product, then do two other actions on the product before perhaps submitting the whole build to some other controller (perhaps a shopping cart). Here is your configuration (at least the part that makes sense):
The Product2 nodes have exactly the same route values as the Product1 nodes, so they will never match a request. I have removed them from this list because that is not a valid configuration. But, you really haven't explained the workflow enough for me to give you any more than general advice. I can't tell if you intended your navigation structure to be like this or if you have something else in mind, but I suspect this isn't what you intended. And you haven't described your desired URL/Routing structure either, which is what drives the SiteMap. It is unclear what Action1 and Action2 are - are these additional steps in a wizard? Are you editing different details on a product, and if so, why are they nested below the "Select your type" node instead of siblings to "Select your type"? Why do you have your nodes set up to show "Product1" twice in same the breadcrumb? Why is there no node to select from a list or category of products in your configuration (which is usually the first step whether you are browsing or editing products)? Typically selecting product options occurs on the product page, so what reason do you have to make "Select your type" as a separate page from the product page (if not making a multi-step wizard)? If you can show me a table of URLs and desired breadcrumbs for those URLs, I can show you a configuration that works with that structure, but unless you have an idea of where you are going I can't tell you how to get there. Just so you understand, you are not building menus and breadcrumbs one at a time, you are building a map of nodes (representing URLs) that all of the navigation controls will be driven from. The design is very similar to using the ASP.NET 2.0 SiteMap Providers, except that you typically configure matching routes for MVC instead of matching URLs. The menus in your example show up fine because they do not depend on matching the current node to be visible, but the breadcrumb does. The route values that you enter into the node must match the route values in the request, then you will get a match, and the breadcrumbs will be visible. Understanding how to make the URL match the node is critical to get it working (which is why I sent you to the blog post that describes it in some detail). And the key of the route value parameter is not important - MvcSiteMapProvider treats the key "id" the same as "productType" as in your example. The only condition is that they need to match the routes in the request. Those routes are configured in MVC. Note that I added the home page to your example because it is almost never omitted in a typical navigation scenario (although you might want to use a visibility provider to hide it in a wizard). <mvcSiteMapNode title="Home" controller="Home" action="Index">
<mvcSiteMapNode title="ProductName1" controller="Product" action="ProductDetails" productType="productname" visibility="Home,SomeOtherCondition,Condition1,SiteMapPathHelper,!*">
<mvcSiteMapNode title="Choose your type" controller="Product" action="ChooseType" productType="productName" visibility="Home,SomeOtherCondition,Condition1,SiteMapPathHelper,!*">
<mvcSiteMapNode title="Product1" controller="Product" action="Action1" visibility="Home,Condition1,SiteMapPathHelper,!*">
<mvcSiteMapNode title="Product1Child" controller="Product" action="Acion2" visibility="Home,Condition1, SiteMapPathHelper,!*"/>
</mvcSiteMapNode>
</mvcSiteMapNode>
</mvcSiteMapNode>
</mvcSiteMapNode> Let's single out this node: <mvcSiteMapNode title="ProductName1" controller="Product" action="ProductDetails" productType="productname" visibility="Home,SomeOtherCondition,Condition1,SiteMapPathHelper,!*"> So, taking the above example, you have configured the values
This means that to match that node (for the breadcrumb), your incoming route needs the same keys and values. If you have the default route configured in MVC (and no other routes): public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
} The URL you need to match the node is
Because the route values that MVC generates from that URL based on this route configuration are:
If you wanted to clean up the URL, you could add a new route: public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Product",
url: "Product/{action}/{productType}",
defaults: new { controller = "Product", action = "Index" }
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
} And now to match the same node, you can use the URL If you configure another node to match the case where you have no route value, it will work. <mvcSiteMapNode title="Home" controller="Home" action="Index">
<mvcSiteMapNode title="ProductName1" controller="Product" action="ProductDetails"/>
<mvcSiteMapNode title="ProductName1" controller="Product" action="ProductDetails" productType="productname" visibility="Home,SomeOtherCondition,Condition1,SiteMapPathHelper,!*">
<mvcSiteMapNode title="Choose your type" controller="Product" action="ChooseType" productType="productName" visibility="Home,SomeOtherCondition,Condition1,SiteMapPathHelper,!*">
<mvcSiteMapNode title="Product1" controller="Product" action="Action1" visibility="Home,Condition1,SiteMapPathHelper,!*">
<mvcSiteMapNode title="Product1Child" controller="Product" action="Acion2" visibility="Home,Condition1, SiteMapPathHelper,!*"/>
</mvcSiteMapNode>
</mvcSiteMapNode>
</mvcSiteMapNode>
</mvcSiteMapNode> This is a valid configuration, because you have a different route value combination. However, this doesn't make a lot of sense because there is usually a separate controller action to handle listing all of the products. <mvcSiteMapNode title="Home" controller="Home" action="Index">
<mvcSiteMapNode title="Product List" controller="Product" action="Index"/>
<mvcSiteMapNode title="ProductName1" controller="Product" action="ProductDetails" productType="productname" visibility="Home,SomeOtherCondition,Condition1,SiteMapPathHelper,!*">
<mvcSiteMapNode title="Choose your type" controller="Product" action="ChooseType" productType="productName" visibility="Home,SomeOtherCondition,Condition1,SiteMapPathHelper,!*">
<mvcSiteMapNode title="Product1" controller="Product" action="Action1" visibility="Home,Condition1,SiteMapPathHelper,!*">
<mvcSiteMapNode title="Product1Child" controller="Product" action="Acion2" visibility="Home,Condition1, SiteMapPathHelper,!*"/>
</mvcSiteMapNode>
</mvcSiteMapNode>
</mvcSiteMapNode>
</mvcSiteMapNode> Also, it is normal for the product details to be nested inside of the list node, but I am not sure what you intend to do with your other nodes. You have not told your controller what product you are dealing with in "Action1" or "Action2", so I don't see why they are nested below "Product1" - are they related to Product1 or not? <mvcSiteMapNode title="Home" controller="Home" action="Index">
<mvcSiteMapNode title="Product List" controller="Product" action="Index">
<mvcSiteMapNode title="ProductName1" controller="Product" action="ProductDetails" productType="productname" visibility="Home,SomeOtherCondition,Condition1,SiteMapPathHelper,!*"/>
</mvcSiteMapNode>
</mvcSiteMapNode> |
Hi, below is the sitemap, this resembles same as what I am using:
Below is the RouteConfig.cs: public static void RegisterRoutes(RouteCollection routes)
} What I am doing is: First you select the product i.e either Mens, Womens or Kids when you go to - Homepage/HeadingPage/Products Suppose you select Mens it goes to http://localhost:56267/Product/ProductDetails/Mens Left hand nav should be: Products Breadcrumb should be: Homepage > HeadingPage > Products > Mens At this point we store the id i.e Mens in session & for further use it from session. From that page user selects the product type i.e Clothing, footwear, etc. So user goes to http://localhost:56267/Product/ChooseType In the action method, we get the id i.e Mens from Session & set that value in the Model & in the view based on the Model value Mens/Womens/Kids, pass the SourceMetaData parameter as name = "MensClothing" When user selects Clothing, it redirects to http://localhost:56267/Clothing/MensClothing So based on that the left hand nav should be: Products Breadcrumb should be: Homepage > HeadingPage > Products > Mens > Choose your type > Clothing Now on this page I have search functionality, so user enters search text in a textbox which redirects to Search results page. i.e http://localhost:56267/CLothing/SearchList Same as in above, we get id value from session & pass it to View. So based on that the left hand nav should be: Products Breadcrumb should be: Homepage > HeadingPage > Products > Mens > Choose your type > Clothing > Search results Same is followed in case of Womens & Kids. Hope this information might help you in assisting me. Thanks. |
Thanks, yes this is helpful for understanding your scenario. Just so you understand, the SiteMap is completely stateless. It can be made to read information from session state, but the breadcrumb will break if the user goes directly to the page, such as when going from search engine results page > In those cases, you basically have 2 choices
Either way, this will seriously negatively impact your search engine placement because search engines can't properly index pages that rely on session state variables. In my opinion, if you expect your pages to be search engine indexed, you should not use session state. And there is no reason to for this scenario. If you were to always put the "id" information into the URL, it will fix both the issue with navigating directly to the page and also with search engine indexing. You can also improve your indexing (and navigation) situation by making deep URLs that progressively show the hierarchy of pages. For example:
On the other hand, if your user has to login to see these pages, this doesn't apply to search engines, but users who bookmark URLs without having the corresponding state information will still have broken navigation if you don't put the "state" information into the URL. Either way, it is better to put your "id" information in the URL than in session state - that will ensure the navigation will work 100% of the time instead of breaking in certain cases, and make it possible to use preservedRouteParameters, which will give your SiteMap a much smaller memory footprint on your server. You can use URLs without the state information, but in addition to the SEO and navigation problems, with MvcSiteMapProvider you are limited to about 10,000 URLs for the whole site. With that in mind, I have a few more questions I need answers for:
|
Also the main point is I don't want to pass Id in the URL for all pages. This is because we also have a functionality of basket, so suppose a user has gone to - /Products/Mens/Clothing/Search?query=red So I need an alternative solution. Hope it helps. |
I have created a demo project that shows the configuration that works for your scenario. Note that you will either need to use external DI as shown in the demo or you will need to make a custom fork of MvcSiteMapProvider (and a custom build) with the changes from the I used Ninject in the example, but there is no reason why you can't use another DI container. The way it works is that it copies over the specified session state keys in the Note that the nodes still require unique route value combinations, but this is altering the request in order to match those unique combinations. Even though the URL of the request is not unique, the URL + session state values are. You may still need to do some styling and/or make HTML layout changes for your "left nav". You can make HTML changes by editing or creating new templates in the |
There hasn't been any new information posted for some time on this, so I am closing it for now. Feel free to reopen if the proposed solution doesn't work out. |
Left hand nav works fine with sourceMetadata parameters
i.e. having something like
switch (condition)
{
case Condition1:
@Html.MvcSiteMap().Menu(new { name = "Condition1" })
case Condition2:
@Html.MvcSiteMap().Menu(new { name = "Condition2" })
}
the above gives the correct left hand navigation menu.
But when i use the same in SiteMapPath
i.e
switch (condition)
{
case Condition1:
@Html.MvcSiteMap().SiteMapPath(new { name = "Condition1" })
case Condition2:
@Html.MvcSiteMap().SiteMapPath(new { name = "Condition2" })
}
The above always shows the first mvcSiteMapNode from the sitemapFile & not the one depending on the name passed.
Sample sitemap is below:
The thing is when i come to the product page at that time i have a routeValues but when i go to the ProductChild pages then i don't have the routeValues passed.
Any help would be appreciated.
The text was updated successfully, but these errors were encountered: