RouteHandler vs ControllerFactory

2019-05-30 10:42发布

问题:

new to asp.net mvc (using v3 + razor) and am wondering how to best solve a problem with creating dynamic routes based on a database. Essentially, the main site navigation will be entered into a database and I want to load them up as routes. i.e. - Load Category list from database, then append the routes to the routing engine if possible...

mysite.com/cars mysite.com/televisions mysite.com/computers

etc....

Each category after the slash comes from the db, but, there are regular entries like /about and /contactus that will not be in the database and have been statically entered in the global.asax... my question is:

For the dynamic database URLs should I use a custom RouteHandler or pehaps create a ControllerFactory that will match and handle the requests for the entries loaded from the database. Is it possible to have the DefaultControllerFactory handle the routing if my RouteHandler or CustomControllerFactory don't find the route in the list from the database? Thanks for any help, very first project with this so I'm not sure what the best route is ;) no pun intended...

Update:

Tried using a route constraint that pulls from the database but it conflicts with the default route now... here is my custom constraint and routes:

public class CategoryListConstraint : IRouteConstraint
{
    public CategoryListConstraint()
    {
        var repo = new Repository<Topic>();
        var cats = repo.All();
        var values = new List<string>();
        foreach (var c in cats)
        {
            values.Add(c.URI.Replace("/", "").Replace("?", ""));
        }
        this._values = values.ToArray<string>();
    }

    private string[] _values;

    public bool Match(HttpContextBase httpContext,
      Route route,
      string parameterName,
      RouteValueDictionary values,
      RouteDirection routeDirection)
    {
        // Get the value called "parameterName" from the 
        // RouteValueDictionary called "value"
        string value = values[parameterName].ToString();

        // Return true is the list of allowed values contains 
        // this value.
        return _values.Contains(value);
    }
}

and here are the routes:

            Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                 "Categories",
                 "{category}/{*values}",
                 new { controller = "Category", action = "List" },
                 new CategoryListConstraint()
             );

            Routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );

The home page www.mysite.com loads using the Default route. All the URLs that match the constraint list are loaded by the category route... but if I have the www.mysite.com/admin or www.mysite.com/aboutus these are getting picked up by the Categories route even though the values are not in the constraint list. Confused...

回答1:

What about something like this?

Categories controller:

public ActionResult List(string category)
{
    var products = _repo.Get(category); // however you are getting your data
    return View(products);
}

Routes

routers.MapRoute(
    "About",
    "About",
    new { controller = "Home", action = "About" });

//... other static routes

routes.MapRoute(
    "CategoriesList",
    "{id}",
    new { controller = "Categories", action = "List" },
    new { id = @"\w+" });

The incoming URL is tested against each Route rule to see if it matches - and if a Route rule matches then that rule (and its associated RouteHandler) is the one that is used to process the request (and all subsequent rules are ignored). This means that you want to typically structure your routing Rules in a "most specific to least specific" order

source



回答2:

Found the exact solution I was looking for. Code is below. I managed to avoid using Controller Factories or implementing a custom IRouteHandler by using extending the RouteBase class which worked perfectly and allows me to pass control down to the default mvc route is something specific isn't hit. BTW - constraints ended up not working properly as the broke the controllers associated with the default route (although the default route was getting hit)

public class CustomRoutingEngine : RouteBase 
{
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {                       
        var routeHandler = new MvcRouteHandler();
        var currentRoute = new Route("{controller}/{*URI}", routeHandler);
        var routeData = new RouteData(currentRoute, routeHandler);

        // implement caching here
        var list = GetConstraintList();

        // set your values dynamically here
        routeData.Values["controller"] = "Category";
        // or
        routeData.Values.Add("action", "List");

        // return the route, or null to have it passed to the next routing engine in the list
        var url = Util.StripSlashOnFrontAndBack(httpContext.Request.Path.ToLower()).Split('/')[0];
        if (list.Contains(url))
            return routeData;
        return null;  // have another route handle the routing
    }


    protected List<string> GetConstraintList()
    {
        using (var repo = new RavenRepository<Topic>())
        {
            var tops = repo.Query().Where(x => x.Hidden == false).ToList()
                .Select(x=>x.Name.ToLower());
            List<string> list = new List<string>();
            list.AddRange(tops);
            repo.Dispose();
            return list ?? new List<string>();
        }
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        //implement this to return url's for routes, or null to just pass it on
        return null;
    }
}

Then my register routes method looks like so:

            Routes.Clear();

            // Set Defaults
            Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            AreaRegistration.RegisterAllAreas();                

            routes.Add(new App.Helpers.CustomRoutingEngine());

            Routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );