0

I have the following implementation of my router:

public class TenantUrlResolverRouter : IRouter
{
    private readonly IRouter _defaultRouter;

    public TenantUrlResolverRouter(IRouter defaultRouter)
    {
        _defaultRouter = defaultRouter;
    }

    public VirtualPathData GetVirtualPath(VirtualPathContext context)
    {
        return _defaultRouter.GetVirtualPath(context);
    }

    public async Task RouteAsync(RouteContext context)
    {
        var oldRouteData = context.RouteData;
        var newRouteData = new RouteData(oldRouteData);
        newRouteData.Values["library"] = "Default";
        try
        {
            context.RouteData = newRouteData;
            await _defaultRouter.RouteAsync(context);
        }
        finally
        {
            if (!context.IsHandled)
            {
                context.RouteData = oldRouteData;
            }
        }
    }
}

Then I define it in Startup.cs:

app.UseMvc(routes =>
        {
            routes.Routes.Add(
                new TenantUrlResolverRouter(routes.DefaultHandler));
            routes.MapRoute(
                name: "default",
                template: "{library=Unknown}/{controller=Home}/{action=Index}/{id?}");
        });

But nothing happens, RouteData.Values in RouteContext always empty, I always have Unknown, while it's need to be Default. That's not the problem of the predefined template, because it even worked without the {library=Unknown} and this {library}/{controller=Home}/{action=Index}/{id?} doesn't work too.

What's the problem with this custom IRouter?

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
Yurii N.
  • 5,455
  • 12
  • 42
  • 66

1 Answers1

2

You are not providing a complete set of route values. In order to route to an action, you need to provide both controller and action.

Also, you don't have a match condition in the route. A match condition will determine whether the incoming request matches the current route. In the built-in routing, the url and constraints are match conditions. However, in a custom route, you need to put an if block to ensure that any request that doesn't match will pass through to the next registered route.

NOTE: This is the most powerful part of custom routing. The built-in routing can only match against URLs. But with custom routing, you can match anything in the request. For example, you could make the route match only for a certain domain or a certain subdomain. You could even make it match things like posted form values or session state.

public async Task RouteAsync(RouteContext context)
{
    var requestPath = context.HttpContext.Request.Path.Value;

    if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
    {
        // Trim the leading slash
        requestPath = requestPath.Substring(1);
    }

    var segments = requestPath.Split('/');

    // Required: Match condition to determine if the incoming request
    // matches this route. If not, you should allow the framework to
    // match another route by doing nothing here.
    if (segments.Length > 0 && segments[0].Equals("libraryname", StringComparison.OrdinalIgnoreCase))
    {
        var oldRouteData = context.RouteData;
        var newRouteData = new RouteData(oldRouteData);
        newRouteData.Values["library"] = segments[0];
        newRouteData.Values["controller"] = segments[1];
        newRouteData.Values["action"] = segments[2];
        try
        {
            context.RouteData = newRouteData;
            await _defaultRouter.RouteAsync(context);
        }
        finally
        {
            if (!context.IsHandled)
            {
                context.RouteData = oldRouteData;
            }
        }
    }
}

See this question for more info about implementing IRouter.

Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • with this implementation, the path changes, but it doesn't affect any changes to pages, i. e. always have `About` page, somehow. And I've read this article, my example is from this article. – Yurii N. Apr 20 '16 at 10:18
  • What do you mean by "changes to pages"? Routing is generally static, unless you do something to make it dynamic such as make it [data-driven](http://stackoverflow.com/questions/32565768/change-route-collection-of-mvc6-after-startup#32586837). Do note that you *could* pass controller and action through the constructor so you can reuse the same route over and over for multiple pages. – NightOwl888 Apr 20 '16 at 10:48
  • "Changes to pages" means that if I'm trying to go to path `/Home/Contact` for example, there is no redirecting to this page, i remain on `/Home/About` (I don't know why this path exactly), but my path in address bar changes to `/Home/Contact`, as it should be. – Yurii N. Apr 20 '16 at 10:53
  • I updated my answer. You have no match condition in the route to determine if the incoming requests matches the route. Therefore, it will always match. The `url` and `constraints` in a normal route are match conditions. But in a custom route, you need to have some kind of `if` statement to allow any non-matching URLs to pass through. – NightOwl888 Apr 20 '16 at 11:15
  • hmmm, but all routes match, I only need to add library name to the first part of the path, for example `/Home/Contact`, `/Home/About`, should be `/LibraryName/Home/Contact`, `/LibraryName/Home/About` respectively. – Yurii N. Apr 20 '16 at 11:38
  • Changed condition to match only on "libraryname" and use the second and third segments as controller and action. – NightOwl888 Apr 20 '16 at 11:55
  • but I don't need any conditions, any route path will match, I just need to insert library name, which I get from database or somewhere else, to the beginning of the route path. – Yurii N. Apr 20 '16 at 12:02
  • You DO need conditions, or any routes that you register after this one (read: default route) won't function. That is, unless you remove all routes except this one from your configuration. – NightOwl888 Apr 20 '16 at 14:31
  • And I wouldn't recommend "getting a library name from the database" inline because routes execute for every request. You should see my other answer about making the URLs data-driven with a cache to prevent excessive database requests. – NightOwl888 Apr 20 '16 at 14:32
  • Ok, I'll get library name from user claims. But I still can't understand, why we need conditions? – Yurii N. Apr 20 '16 at 15:29
  • A route must do 2 tasks: 1) Determine if the route matches (condition) 2) Populate route values (and route data, etc). Without the condition, the route will match *every* request. Since the first match always wins, the routes registered after this one become an unreachable execution path. See [this answer](http://stackoverflow.com/q/35661062/#35674633) to for an explanation. – NightOwl888 Apr 20 '16 at 19:39