0

I'm trying to implement my own dynamic router, my plan is to pull routes from my database and create a set of dynamic landing pages, my issue is that I'm getting 404 after setting up context.RouteData to my new route data.

I just want to redirect to my LandingPageController and the Index IActionResult everytime I found a route.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;
using System.Collections.Generic;
using System.Linq;

namespace Myproject.Web.Main.Config
{
    public class LandingPageRouter : IRouter
    {
        public VirtualPathData GetVirtualPath(VirtualPathContext context)
        {
            return null;
        }

        public Task RouteAsync(RouteContext context)
        {

            var requestPath = context.HttpContext.Request.Path.Value;
            if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
            {
                requestPath = requestPath.Substring(1);
            }

            var pagefound = GetPages().Any(x => x == requestPath);
            if (pagefound)
            {
                //TODO: Handle querystrings
                var routeData = new RouteData();
                routeData.Values["controller"] = "LandingPage";
                routeData.Values["action"] = "Index";
                context.RouteData = routeData;
            }

            return Task.FromResult(0);
        }

        private IEnumerable<string> GetPages()
        {
            //TODO: pull from database
            return new List<string> { "page-url-title", "another-dynamic-url" };
        }
    }
}

I looked at this answer but it seems outdated some properties in the context doesn't even exist anymore in RC2.

What Am I missing?

Community
  • 1
  • 1
pedrommuller
  • 15,741
  • 10
  • 76
  • 126
  • Please, stop using the term MVC6. You will only spread confusion, when/if a new version of legacy MVC gets released! The former "ASP.NET 5 MVC6" is now named "ASP.NET Core MVC 1.0" to **make it clear** that it's not the next version of ASP.NET MVC5, but **a complete new framework written from scratch**. Also the correct tag is `asp.net-core-mvc`! – Tseng Jun 23 '16 at 05:55
  • Looks like an interesting scenario...could you please give more info on it? So if a request like `/articles/cavaliers-win-nba-title` comes in you want to map to `/articles/10` url and which gets handled by the `articles` controllers and takes in the article id `10` and also when links are generated you want the mapping to happen the other way...am i right? – Kiran Jun 23 '16 at 12:48
  • @KiranChalla I'd like to configure routes at the root level like mydomain.com/name-of-the-article and internally and map those routes to my Landing Page controller, it could be either landingpage/Id or landingpage/name-of-the-article – pedrommuller Jun 23 '16 at 14:26
  • @Tseng yeah I knew that's a totally new version and it's renamed to Core MVC, I just use that name because old questions are written in that way, so my bad, I'll try to remember it in my next question, thanks for the heads up – pedrommuller Jun 23 '16 at 14:29

2 Answers2

2

It doesn't seem like the nicest solution but from my testing it should work for your requirements.


I injected the default MVC IRouter into your LandingPageRouter. Then, once you have updated the route data, just call the default router and pass in the context:

public class LandingPageRouter : IRouter
{
    private readonly IRouter _router;

    public LandingPageRouter(IRouter router)
    {
        _router = router;
    }

    public VirtualPathData GetVirtualPath(VirtualPathContext context)
    {
        return null;
    }

    public Task RouteAsync(RouteContext context)
    {

        var requestPath = context.HttpContext.Request.Path.Value;
        if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
        {
            requestPath = requestPath.Substring(1);
        }

        var pagefound = GetPages().Any(x => x == requestPath);
        if (pagefound)
        {
            //TODO: Handle querystrings
            var routeData = new RouteData();
            routeData.Values["controller"] = "LandingPage";
            routeData.Values["action"] = "Index";
            context.RouteData = routeData;
            return _router.RouteAsync(context);
        }

        return Task.FromResult(0);
    }

    private IEnumerable<string> GetPages()
    {
        //TODO: pull from database
        return new List<string> { "page-url-title", "another-dynamic-url" };
    }
}

Then just insert the default route wherever you are adding your route in Startup.Configure:

app.UseMvc(routes =>
{
    routes.Routes.Add(new LandingPageRouter(routes.DefaultHandler));
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});
Sock
  • 5,323
  • 1
  • 21
  • 32
  • thanks, I ended up with a similar approach of what you have proposed and definitely your answer help me out to point to the right path! – pedrommuller Jun 28 '16 at 00:47
0

Based on this answer and @sock approach, I passed the route builder which is going to pass at the same time the router context

public LandingPageRouter(IRouteBuilder routeBuilder)
{
    _routeBuilder = routeBuilder;
}

public Task RouteAsync(RouteContext context)
{
    var requestPath = context.HttpContext.Request.Path.Value;
    if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
    {
        requestPath = requestPath.Substring(1);
    }

    var pagefound = GetPages().SingleOrDefault(x => x.Name == requestPath);
    if (pagefound!=null)
    {
        //TODO: Handle querystrings
        var routeData = new RouteData();
        routeData.Values["controller"] = "LandingPage";
        routeData.Values["action"] = "Index";
        routeData.Values["id"] = pagefound.Id;

        context.RouteData = routeData;
        return _routeBuilder.DefaultHandler.RouteAsync(context);
    }
    return Task.FromResult(0);
}

Routes configuration in startup.cs

app.UseMvc(routes =>
{
    routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id?}");

    routes.Routes.Add(new LandingPageRouter(routes));
}
pedrommuller
  • 15,741
  • 10
  • 76
  • 126
  • Glad that works, it looks like the answer you linked to also passed in the `DefaultHandler` directly rather than the `IRouteBuilder` as in your answer - I wonder what the implications are for differences between them. Maybe just a question of Tell don't Ask and preference? – Sock Jun 28 '16 at 11:38
  • it was my own post replicated on Github, basically, I implemented the IRouterBuiler since "routes" (as the expression in the app.UseMvc) implements IRoutebuilder, it feels more "natural" to me to pass it as a parameter, but that's just my opinion due to the lack of documentation is hard to tell the difference between both implementations – pedrommuller Jun 28 '16 at 16:41