Skip to content
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

One action in two sections with same route values. #376

Open
mayorovp opened this issue Dec 24, 2014 · 6 comments
Open

One action in two sections with same route values. #376

mayorovp opened this issue Dec 24, 2014 · 6 comments

Comments

@mayorovp
Copy link

I my project, i have structure like this:

<mvcSiteMapNode controller="Home" action="Index">
    <mvcSiteMapNode controller="Home" action="Table1">
        <mvcSiteMapNode controller="Process" action="Show" typeCode="Type1" preservedRouteParameters="id" />
    </mvcSiteMapNode>

    <mvcSiteMapNode controller="Home" action="Table2">
        <mvcSiteMapNode controller="Process" action="Show" typeCode="Type2" preservedRouteParameters="id" />
    </mvcSiteMapNode>
</mvcSiteMapNode>

But the problem is that there is no route value named 'typeCode', this code must be loaded from database. How to make breadcrumbs work?

@NightOwl888
Copy link
Collaborator

Sorry for the late reply.

If there is no route value named 'typeCode' configured, the URLs will be produced with typeCode appended to the URL as a query string.

/Process/Show/123?typeCode=Type1
/Process/Show/123?typeCode=Type2

However, creating nodes in XML similar to crating ActionLinks in a view. They are, for the most part, static.

If you want your nodes to have database driven values, then you will need to use a dynamic node provider. Have a look at this article.

@mayorovp
Copy link
Author

No, i have no typeCode variable neither in route values dictionary nor in query string parameters.

I have urls like /Process/Show/123 - and i need to make tree like this:

  • /Home/Table1
    • /Process/Show/123
    • /Process/Show/125
  • /Home/Table2
    • /Process/Show/124

I cannot use dynamic node provider because process list changes too frequently.

@NightOwl888
Copy link
Collaborator

Well, if what you are doing is trying to track some CRUD operations, it is better to use preservedRouteParameters anyway.

But in no way does that mean you can't use dynamic node provider if you wanted to keep up with fast changing data. You would just need to use [SiteMapCacheRelease] attribute on every action method that changes the data (assuming your MVC application has full control over the changes).

You have configured it correctly in MvcSiteMapProvider (you don't have 2 nodes that are exactly the same), but keep in mind that MvcSiteMapProvider cannot create the URLs when using preservedRouteParameters - they need to be built from your views. Typically, when browsing database driven items, you would already have them displayed in a list or table, anyway.

But you need to account for the extra parameter when building the hyperlinks to get to the pages. If typeCode has no meaning to you, I would recommend something more meaningful, like parentAction. Or stick with typeCode - it will work the same way.

<mvcSiteMapNode title="Home" controller="Home" action="Index">
    <mvcSiteMapNode title="Table1" controller="Home" action="Table1">
        <mvcSiteMapNode title="dynamic" controller="Process" action="Show" parentAction="Table1" preservedRouteParameters="id" />
    </mvcSiteMapNode>

    <mvcSiteMapNode title="Table2" controller="Home" action="Table2">
        <mvcSiteMapNode title="dynamic" controller="Process" action="Show" parentAction="Table2" preservedRouteParameters="id" />
    </mvcSiteMapNode>
</mvcSiteMapNode>

parentAction doesn't have to mean anything to your application. Its only purpose is to make the URL unique so it will know to match the node below "Table2" when the parentAction is "Table2". It might be better if you adjusted your routes to make it part of the URL instead of a query string, though.

Then when you build the URLs in your view, you need to add the extra parameter to the ActionLink (or RouteLink) when it is displayed in your view.

@Html.ActionLink("123", "Show", "Process", new { id=123, parentAction="Table1" }, null)
@Html.ActionLink("125", "Show", "Process", new { id=125, parentAction="Table1" }, null)

@Html.ActionLink("124", "Show", "Process", new { id=124, parentAction="Table2" }, null)

You need one more thing, though - to fix the title of the node. Since you are using the same node for every "Show" node, you need to update it dynamically according to the current page being viewed. This can be accomplished with the SiteMapTitle attribute. This example just sets the title to the value of "id", but you could set it to anything that makes sense - the value could even come from your database.

public class ProcessController
{
    [SiteMapTitle("id")]
    public ActionResult Show(int id)
    {
        return View();
    }
}

When clicking the first link, your breadcrumb will be:

Home > Table1 > 123

And the last link will be:

Home > Table2 > 124

@mayorovp
Copy link
Author

I found another way (node keys and explicit node selection logic) - but i want do it right way, without any hacks or extra URL parts.

@mayorovp
Copy link
Author

PS It is not a question, it is a feature request

@NightOwl888
Copy link
Collaborator

It is a core limitation of Microsoft's design (which this is based on) that the request must be unique in order to identify which node it matches. The nodes are compared from the top of the SiteMap down to the bottom and the first match always wins.

Since your URLs are unique without using preservedRouteParameters, you can use a dynamic node provider (or ISiteMapNodeProvider) to get exactly the URL scheme you want with absolutely no hacks (assuming you don't put the same node under both Table1 and Table2, anyway). I really don't understand your objection to this unless you have a SiteMap that changes every few seconds.

Using preservedRouteParameters is another option that I once viewed pretty harshly, but is actually a more efficient way to get the job done, especially if you have an enormous number of records or they change rapidly. The problem you have is that when you combine this technique with nesting the same controller and action under separate parent nodes, you fall into a situation where your route combinations are not unique and you have to follow the procedure from Mutiple Navigation Paths to a Single Page.

Do note there are many different ways to make a URL unique. I just use the query string example because it is the simplest to implement. You could also potentially return the results of one action method from another:

public class ProcessController
{
    public ActionResult ShowTable1(int id)
    {
        return View();
    }

    public ActionResult ShowTable2(int id)
    {
        return ShowTable1(id);
    }
}

Or, as I mentioned, you can add a route to make the URLs more eye appealing and search engine friendly:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "ProcessWithParent",
            url: "Home/{parentAction}/Process/{action}/{id}/",
            defaults: new { controller = "Process", action = "Index", id = UrlParameter.Optional }
        );

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

Which would make your URL structure:

  • /Home/Table1
    • /Home/Table1/Process/Show/123
    • /Home/Table1/Process/Show/125
  • /Home/Table2
    • /Home/Table2/Process/Show/124

This also fits in line with the canonical tag that the search engines use, because even they recognize that it is not possible to always host content on a single URL (likely for this very reason).

I spent a lot of time and research contemplating how to add this feature because I required it personally, including Microsoft connect answers to this very problem, and the consensus is that making the URL unique is far preferable to the alternative, which would be to use session state or a cookie to track which parent node the current user navigated from. There are at least 2 problems with that approach:

  1. It requires session state or a cookie to function.
  2. Navigation falls apart if the user doesn't follow a path that you expect, such as navigating directly to the URL without first going to the parent page.

It might work sometimes, but not 100% of the time. It can't work 100% of the time regardless of how good you make it because there is no way to completely control how a user navigates a website.

I am not seeing any other options to accommodate your feature request. If you have any ideas about how this could be implemented, or if you are fine with a feature that will work most of the time but not all of the time, please let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants