How to use ambient route values in ASP.NET Core 3 Razor Pages?

Viewed 884

Using ASP.NET Core 3 Razor Pages, suppose you would like to set up pages with the following URL structure:

http://contoso.com/course/4231/details
http://contoso.com/course/4231/students

where 4231 is the course ID. This can be achieved by using the @page directive:

@page "/course/{courseId}/details"

Now suppose you want to create links between the /details and the /students pages. You then want the courseId parameter to be added to every link, which can be accomplished using the anchor tag helper as follows:

<a asp-page="./students" asp-route-courseid="@RouteData.Values["courseId"]>Students</a>

As far as I've understood it, in ASP.NET Core 2.1 and earlier versions it was not necessary to add the asp-route-* attribute for this purpose, because route values would 'propagate' to other pages, being added automatically to any anchor tag unless manually overridden ("ambient route values", as it was called). This was apparently removed in version 2.2, but I'm not quite sure why.

Having to always remember to manually propagate route values using the above scheme seems like a very error-prone workflow, and could get out of hand quick if you have many route values that always needs to be added to anchor tags.

Is it possible to manually enable ambient route values in later versions of ASP.NET Core, at the very least for individual parameters? Is there any reason not to do this?

Essentially, I'd like for the anchor tag helpers to be relative, so that just linking to ./students from /course/4231/details automatically resolves to /course/4231/details.

2 Answers

Since I managed to find a solution for this, I'm going to answer my own question.

As mentioned in this GitHub issue (credit to Oliver Weichhold), you can override the default anchor tag helper (and form action tag helper) to achieve the desired behaviour of having route values automatically added to links.

I ended up implementing derived versions of the tag helpers that will automatically add route values if they are part of the route template, because that's the behaviour I was looking for. To keep it short, the Process method of the tag helpers looks like this:

public override void Process(TagHelperContext context, TagHelperOutput output)
{
    var routeTemplate = ViewContext.ActionDescriptor.AttributeRouteInfo.Template;

    Regex regex = new Regex(@"\{(\w+)(:.+)?\??\}");
    var matches = regex.Matches(routeTemplate);

    if (matches.Count > 0)
        foreach (var routeValueKvp in ViewContext.HttpContext.Request.RouteValues)
            if (matches.Any(x => x.Groups[1].Value == routeValueKvp.Key))
                RouteValues[routeValueKvp.Key] = routeValueKvp.Value.ToString();

    base.Process(context, output);
}

This solution works, but it's not optimal. One of the reasons is that the custom tag helpers needs to be decorated with the HtmlTargetElement attributes manually, which means that if the base versions of the tag helpers gets extended with new attributes in future releases of the framework, the custom versions will need to be updated manually.

IMO, it is expected. When you redirect to the same action/page, the current ambient route values are being reused.

<a asp-page="details">Studentdetails</a>

It generates url: /course/4231/details.

When you redirect to a different action/page, the ambient values are correctly ignored.

<a asp-page="./students">Students</a>

it will not generate correct url.

Refer to https://github.com/dotnet/aspnetcore/issues/3746

Related