Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

See which controller and action will be executed from a route?

In ASP .NET Core, how can I given a route (for instance /api/users/28) see which controller will be used, and which action will be used? In this case for instance, it would be UsersController and its Get(int id) action.

I would love if there was a way to access some kind of router that could tell me this, so that I don't have to replicate the internal routing system myself. I just haven't been able to find this using the official documentation on ASP .NET Core routing.

Edit 1 My question is not a duplicate. I'm not looking for options on determining if a route exists - I want to know what exact action and controller will handle it.

Edit 2 Here's what my current code looks like, and what I've tried:

var httpContext = new DefaultHttpContext();
httpContext.Request.Path = "/api/users/28";
httpContext.Request.Method = "GET";

var context = new RouteContext(httpContext);

//throws an exception: AmbiguousActionException: Multiple actions matched.
var bestCandidate = _actionSelector.SelectBestCandidate(context,
    _actionDescriptorCollectionProvider.ActionDescriptors.Items); 
like image 354
Mathias Lykkegaard Lorenzen Avatar asked Sep 04 '17 13:09

Mathias Lykkegaard Lorenzen


People also ask

What is URL RouteUrl?

RouteUrl(String, RouteValueDictionary, String, String) Generates a fully qualified URL for the specified route values by using the specified route name, protocol to use, and host name.

What is MapControllerRoute?

MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app. Run(); MapControllerRoute is used to create a single route. The single route is named default route. Most apps with controllers and views use a route template similar to the default route.

What does APP UseRouting do?

UseRouting adds route matching to the middleware pipeline. This middleware looks at the set of endpoints defined in the app, and selects the best match based on the request. UseEndpoints adds endpoint execution to the middleware pipeline. It runs the delegate associated with the selected endpoint.

How do you resolve an Ambigous route?

We can resolve this error by setting priority of the controllers. Suppose in the application HomeCOntroller defined in the Mvc4_RouteConstraints namespace has high priority than Mvc4_Route namespace controller.


1 Answers

It looks like the IActionSelector only matches the HTTP methods etc. and completely ignores the route templates.

Thanks to Sets answer and https://blog.markvincze.com/matching-route-templates-manually-in-asp-net-core/, I came up with the following solution:

public ManualActionSelector(IActionSelector actionSelector, IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
{
    _actionSelector = actionSelector;
    _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
}

public ActionDescriptor GetMatchingAction(string path, string httpMethod)
{
    var actionDescriptors = _actionDescriptorCollectionProvider.ActionDescriptors.Items;

    // match by route template
    var matchingDescriptors = new List<ActionDescriptor>();
    foreach (var actionDescriptor in actionDescriptors)
    {
        var matchesRouteTemplate = MatchesTemplate(actionDescriptor.AttributeRouteInfo.Template, path);
        if (matchesRouteTemplate)
        {
            matchingDescriptors.Add(actionDescriptor);
        }
    }

    // match action by using the IActionSelector
    var httpContext = new DefaultHttpContext();
    httpContext.Request.Path = path;
    httpContext.Request.Method = httpMethod;
    var routeContext = new RouteContext(httpContext);
    return _actionSelector.SelectBestCandidate(routeContext, matchingDescriptors.AsReadOnly());
}


public bool MatchesTemplate(string routeTemplate, string requestPath)
{
    var template = TemplateParser.Parse(routeTemplate);

    var matcher = new TemplateMatcher(template, GetDefaults(template));
    var values = new RouteValueDictionary();
    return matcher.TryMatch(requestPath, values);
}

// This method extracts the default argument values from the template. From https://blog.markvincze.com/matching-route-templates-manually-in-asp-net-core/
private RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate)
{
    var result = new RouteValueDictionary();

    foreach (var parameter in parsedTemplate.Parameters)
    {
        if (parameter.DefaultValue != null)
        {
            result.Add(parameter.Name, parameter.DefaultValue);
        }
    }

    return result;
}

It first tries to match all routes by the templates. The it calls the IActionSelector, which does the rest. You can use it like this:

var action = GetMatchingAction("/api/users/28", "GET"); // will return null if no matching route found
like image 112
Manuel Allenspach Avatar answered Nov 10 '22 13:11

Manuel Allenspach