70

Basically I have a CMS backend I built using ASP.NET MVC and now I'm moving on to the frontend site and need to be able to load pages from my CMS database, based on the route entered.

So if the user enters example.com/students/information, MVC would look in the pages table to see if a page exists that has a permalink that matches students/information, if so it would redirect to the page controller and then load the page data from the database and return it to the view for display.

So far I have tried to have a catch all route, but it only works for two URL segments, so /students/information, but not /students/information/fall. I can't find anything online on how to accomplish this, so I though I would ask here, before I find and open source ASP.NET MVC CMS and dissect the code.

Here is the route configuration I have so far, but I feel there is a better way to do this.

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

        // Default route to handle core pages
        routes.MapRoute(null,"{controller}/{action}/{id}",
                        new { action = "Index", id = UrlParameter.Optional },
                        new { controller = "Index" }
        );

        // CMS route to handle routing to the PageController to check the database for the route.

        var db = new MvcCMS.Models.MvcCMSContext();
        //var page = db.CMSPages.Where(p => p.Permalink == )
        routes.MapRoute(
            null,
            "{*.}",
            new { controller = "Page", action = "Index" }
        );
    }

If anybody can point me in the right direction on how I would go about loading CMS pages from the database, with up to three URL segments, and still be able to load core pages, that have a controller and action predefined.

2 Answers 2

130

You can use a constraint to decide whether to override the default routing logic.

public class CmsUrlConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        var db = new MvcCMS.Models.MvcCMSContext();
        if (values[parameterName] != null)
        {
            var permalink = values[parameterName].ToString();
            return db.CMSPages.Any(p => p.Permalink == permalink);
        }
        return false;
    }
}

use it in route definition like,

routes.MapRoute(
    name: "CmsRoute",
    url: "{*permalink}",
    defaults: new {controller = "Page", action = "Index"},
    constraints: new { permalink = new CmsUrlConstraint() }
);

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

Now if you have an 'Index' action in 'Page' Controller like,

public ActionResult Index(string permalink)
{
    //load the content from db with permalink
    //show the content with view
}
  1. all urls will be caught by the first route and be verified by the constraint.
  2. if the permalink exists in db the url will be handled by Index action in Page controller.
  3. if not the constraint will fail and the url will fallback to default route(i dont know if you have any other controllers in the project and how you will decide your 404 logic).

EDIT

To avoid re querying the cms page in the Index action in Page controller, one can use the HttpContext.Items dictionary, like

in the constraint

var db = new MvcCMS.Models.MvcCMSContext();
if (values[parameterName] != null)
{
    var permalink = values[parameterName].ToString();
    var page =  db.CMSPages.Where(p => p.Permalink == permalink).FirstOrDefault();
    if(page != null)
    {
        HttpContext.Items["cmspage"] = page;
        return true;
    }
    return false;
}
return false;

then in the action,

public ActionResult Index(string permalink)
{
    var page = HttpContext.Items["cmspage"] as CMSPage;
    //show the content with view
}
Sign up to request clarification or add additional context in comments.

12 Comments

Awesome it worked great, just had to add a check if (values[parameterName] != null), but otherwise perfect! Thank You :)
Hello @shakib, what about the performance if there are 50K items. As far as i know there is a route table caching or something like that but in any case it checks from db.
@BarbarosAlp as you said "in any case it checks from db", the best i can think of is to optimize the db querying.
@BarbarosAlp You could also create some kind of flat file that would be loaded in the memory of the app that would represent some sort of "cache" of your database route table. Then you'll just need to implement some sort of "create cache" and "flush cache" action.
@jallen its been a while, but you can use HttpContext.Items dictionary to avoid re querying the cms page in the action.
|
0

I use simpler approach that doesn't require any custom router handling. Simply create a single/global Controller that handles a few optional parameters, then process those parameters as you like:

//Route all traffic through this controller with the base URL being the domain
[Route("")]
[ApiController]
public class ValuesController : ControllerBase
{
    //GET api/values
    [HttpGet("{a1?}/{a2?}/{a3?}/{a4?}/{a5?}")]
    public ActionResult<IEnumerable<string>> Get(string a1 = "", string a2 = "", string a3 = "", string a4 = "", string a5 = "")
    {
        //Custom logic processing each of the route values
        return new string[] { a1, a2, a3, a4, a5 };
    }
}

Sample output at example.com/test1/test2/test3

["test1","test2","test3","",""]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.