maartenba / MvcSiteMapProvider

An ASP.NET MVC SiteMapProvider implementation for the ASP.NET MVC framework.
Microsoft Public License
537 stars 220 forks source link

Urls not produced when you use a RouteAttribute instead of using routes.MapRoute() #459

Open johnwc opened 7 years ago

johnwc commented 7 years ago

If I decorate my controllers and actions with the Route attributes built into MVC, in place of manually building out the routes via routes.MapRoute(), the provider doesn't seem to find or use the routes. When using the Route attributes, the Url for each product we build with the dynamic node all have # as the value.

Dynamic Node

foreach(var prod in cat.Products)
{
    var prodNode = new DynamicNode();
    prodNode.Title = prod.Name;
    prodNode.ParentKey = catNode.Key;
    prodNode.Key = string.Format("Product_{0}", prod.Id);
    prodNode.Attributes.Add("visible", false);
    prodNode.Controller = "Product";
    prodNode.Action = "Index";
    prodNode.Route = "ProductInfo";
    prodNode.RouteValues.Add("id", prod.Id);
    prodNode.RouteValues.Add("producttitle", prod.Name);
}

Example not working:

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

            routes.MapMvcAttributeRoutes();
            ...
        }
}

[RoutePrefix("product")]
public class ProductController : Controller
{
    // GET: product/123/pepsi
    [Route("{id}/{producttitle}", Name ="ProductInfo")]
    public ActionResult Index(int id)
    {
        ...
    }
}

Example working:

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

            routes.MapRoute(
                "ProductInfo",
                "product/{id}/{producttitle}",
                new { controller = "Product", action = "Index" }
                );
            ...
        }
}

public class ProductController : Controller
{
    // GET: product/123/pepsi
    public ActionResult Index(int id)
    {
        ...
    }
}
NightOwl888 commented 7 years ago

What happens if you remove/comment the route name for the non-working example?

//prodNode.Route = "ProductInfo";

Attribute Routing support was added as a contribution to MvcSiteMapProvider after MVC 5 was released, and it is possible that some of the features of it aren't fully supported.

johnwc commented 7 years ago

Has no affect, still doesn't work.

johnwc commented 7 years ago

If I call the Url.RouteUrl like so, it works. Is the code on the backend not calling Url.RouteUrl?

var url = new System.Web.Mvc.UrlHelper(HttpContext.Current.Request.RequestContext, System.Web.Routing.RouteTable.Routes);

foreach(var prod in cat.Products)
{
    var prodNode = new DynamicNode();
    prodNode.Title = prod.Name;
    prodNode.ParentKey = catNode.Key;
    prodNode.Key = string.Format("Product_{0}", prod.Id);
    prodNode.Attributes.Add("visible", false);
    prodNode.Controller = "Product";
    prodNode.Action = "Index";
    prodNode.Route = "ProductInfo";
    prodNode.RouteValues.Add("id", prod.Id);
    prodNode.RouteValues.Add("producttitle", prod.Name);
    prodNode.Url = url.RouteUrl("ProductInfo", new { id = prod.Id, producttitle = prod.Name });
}
johnwc commented 7 years ago

Any updates?

johnwc commented 7 years ago

So the issue turns out to be that the CurrentNode is always null when viewing a action that is created via attributes. I think I have traced it down to the FindSiteMapNodeFromMvcRoute method in the SiteMap.cs file. It looks like GetMvcRouteData returns the underlining route when contained in a RouteCollectionRoute, and then that value is passed to the FindSiteMapNodeFromMvcRoute. In FindSiteMapNodeFromMvcRoute it runs through all the routes on the RouteTable.Routes and checks to see if each is equal to the passed in Route. Since the RouteTable.Routes doesn't have the sub route in it's root list, it always equals false. It might need to be changed up so that it doesn't do a straight object == object, and maybe check the route path or something.