Question

I'm building a site using NopCommerce 2.30. Out of the box, the product URLs look like this:

/p/16/build-your-own-computer/

I've made some changes to get the URLs looking like this:

/build-your-own-computer/

I've made a change to a Route to stop it from using the Id parameter. However, all the links across the site are still passing the Id parameter through the Url.RouteUrl method:

@Url.RouteUrl("Product", new { productId = Model.Id, SeName = Model.SeName })

This results in the following URL:

/build-your-own-computer/?productId=16

I could just go through and remove the productId parameter from the method call. However, some plugins also add links containing the productId, which I'm unable to change.

Is there a way I can override the Url.RouteUrl method to do a check for requests to the product Route and then remove the productId?

Was it helpful?

Solution 2

I've managed to resolve my issue using a slightly modified solution of this: http://erraticdev.blogspot.com/2011/03/removing-route-values-from-linksurls-in.html

Robert's solution didn't include namespaces so I made a few adjustments below:

public static class CustomRouteExtensions
{

    public static Route MapRoute(this RouteCollection routes, string name, string url, string excludeRouteValueNames, object defaults)
    {
        return MapRoute(routes, name, url, excludeRouteValueNames, defaults, null, null);
    }

    public static Route MapRoute(this RouteCollection routes, string name, string url, string excludeRouteValueNames, object defaults, string[] namespaces)
    {
        return MapRoute(routes, name, url, excludeRouteValueNames, defaults, null, namespaces);
    }

    public static Route MapRoute(this RouteCollection routes, string name, string url, string excludeRouteValueNames, object defaults, object constraints)
    {
        return MapRoute(routes, name, url, excludeRouteValueNames, defaults, constraints, null);
    }

    public static Route MapRoute(this RouteCollection routes, string name, string url, string excludeRouteValueNames, object defaults, object constraints, string[] namespaces)
    {
        if (routes == null)
            throw new ArgumentNullException("routes");

        if (url == null)
            throw new ArgumentNullException("url");

        Route item = new CustomRoute(url, new MvcRouteHandler(), excludeRouteValueNames)
        {
            Defaults = new RouteValueDictionary(defaults),
            Constraints = new RouteValueDictionary(constraints),
            DataTokens = new RouteValueDictionary()
        };

        if ((namespaces != null) && (namespaces.Length > 0))
            item.DataTokens["Namespaces"] = namespaces;

        routes.Add(name, item);

        return item;
    }

}

My custom Route then looks like this:

public class CustomRoute : Route
{

    #region Properties

    public ReadOnlyCollection<string> ExcludedRouteValuesNames { get; private set; }

    #endregion

    #region Constructor

    public CustomRoute(string url, IRouteHandler routeHandler, string commaSeparatedRouteValueNames)
        : this(url, routeHandler, (commaSeparatedRouteValueNames ?? string.Empty).Split(','))
    {
    }

    public CustomRoute(string url, IRouteHandler routeHandler, params string[] excludeRouteValuesNames)
        : base(url, routeHandler)
    {
        ExcludedRouteValuesNames = new ReadOnlyCollection<string>(excludeRouteValuesNames.Select(val => val.Trim()).ToList());
    }

    #endregion

    #region Route overrides

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        if (requestContext == null)
            throw new ArgumentNullException("requestContext");

        // create new route data and include only non-excluded values
        var excludedRouteData = new RouteData(this, this.RouteHandler);

        // add route values
        requestContext.RouteData.Values
            .Where(pair => !this.ExcludedRouteValuesNames.Contains(pair.Key, StringComparer.OrdinalIgnoreCase))
            .ToList()
            .ForEach(pair => excludedRouteData.Values.Add(pair.Key, pair.Value));

        // add data tokens
        requestContext.RouteData.DataTokens
            .ToList()
            .ForEach(pair => excludedRouteData.DataTokens.Add(pair.Key, pair.Value));

        // intermediary request context
        var currentContext = new RequestContext(new HttpContextWrapper(HttpContext.Current), excludedRouteData);

        // create new URL route values and include only none-excluded values
        var excludedRouteValues = new RouteValueDictionary(
            values
                .Where(v => !this.ExcludedRouteValuesNames.Contains(v.Key, StringComparer.OrdinalIgnoreCase))
                .ToDictionary(pair => pair.Key, pair => pair.Value)
        );

        var result = base.GetVirtualPath(currentContext, excludedRouteValues);

        return result;
    }

    #endregion

}

My new Route.MapRoute looks like this:

routes.MapRoute("Product", 
                "{SeName}", 
                "productIdd", 
                new { controller = "Catalog", action = "UrlType" }, 
                new[] { "Nop.Plugin.FreshEgg.Seo.Controllers" });

OTHER TIPS

There is no need to override RouteUrl. It takes in a collection of key/value pairs. Just modify the route so it no longer expects the id and remove the id from the @Url.RouteUrl call. You will need to create a route that accepts URLs like the following:

/p/{productName}

You can do that in your Global.asax:

routes.MapRoute(
    "Products",
    "/p/{productName}",
    new { controller = "Products", action = "Details" }
);

Now you would generate a route like this instead:

@Url.RouteUrl("Products", new { productName = Model.SeName })

Now in your controller you have to additional work to find a product from a name instead of an id. It will probably look something like this now instead:

 public ActionResult Details(string productName)
 {
     var product = ProductRepository.FindByName(productName);

     return View(product);
 }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top