Skip to content

Feature request: strongly-typed route values in a tag-helpers #35376

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dharmatech opened this issue Aug 16, 2021 · 9 comments
Closed

Feature request: strongly-typed route values in a tag-helpers #35376

dharmatech opened this issue Aug 16, 2021 · 9 comments
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-mvc-razor-views Features related to the Razor view engine for Razor pages and MVC views feature-routing

Comments

@dharmatech
Copy link

dharmatech commented Aug 16, 2021

The Issue

The example project from the Razor Pages with Entity Framework Core in ASP.NET Core - Tutorial includes the following code:

<a asp-page="./Index"
   asp-route-sortOrder="@Model.CurrentSort"
   asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
   asp-route-currentFilter="@Model.CurrentFilter"
   class="btn btn-primary @nextDisabled">
    Next
</a>

Link to those lines in the project.

The route parameter names:

asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"

are not strongly-typed. I.e. there could be a typo in any of the names (sortOrder, pageIndex, currentFilter) and the project will compile and run however the program will not function as expected.

Workaround - use LinkGenerator.GetPathByAction

This stackoverflow post discusses one workaround for the issue. The example there has the following a tag-helper:

    <a class="btn btn-block
       @(category == ViewBag.SelectedCategory 
           ? "btn-primary": "btn-outline-secondary")"
       asp-action="Index" asp-controller="Home"
       asp-route-category="@category"
       asp-route-productPage="1">
        @category
    </a>

It is rewritten in a more strongly-typed manner using LinkGenerator.GetPathByAction as follows:

<a class="btn btn-block @(category == ViewBag.SelectedCategory ? "btn-primary" : "btn-outline-secondary")"
   href="@(
    _linkGenerator.GetPathByAction(
        nameof(HomeController.Index), 
        Regex.Replace(nameof(HomeController), "Controller$", String.Empty),
        new IndexParameters() 
        { 
            Category = category, 
            ProductPage = 1 
        }))">
    @category
</a>

This is more of a demonstration of what is theoretically possible in the form of a quick solution; it's not ideal for end users.

Workaround - R4MVC

Another approach is to use R4MVC.

Here's a video demonstrating R4MVC.

Downsides to R4MVC:

  • Still in pre-release
  • Not built-in to ASP.NET Core

Workaround - object for parameters

If we use a class to represent the parameters to OnGetAsync:

public class OnGetAsyncParameters
{
    public SortOrder? SortOrder { get; set; }
    public string CurrentFilter { get; set; }
    public string SearchString { get; set; }
    public int? PageIndex { get; set; }
}

then instead of:

<a asp-page="./Index"
   asp-route-sortOrder="@Model.CurrentSort"
   asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
   asp-route-currentFilter="@Model.CurrentFilter"
   class="btn btn-primary @nextDisabled">
    Next
</a>

we can use asp-all-route-data and generate a dictionary from an instance of OnGetAsyncParameters:

<a asp-page="./Index"
   asp-all-route-data="to_dict(new OnGetAsyncParameters()
    {
        SortOrder = Model.CurrentSort,
        PageIndex = Model.Students.PageIndex + 1,
        CurrentFilter = Model.CurrentFilter
    })"
   class="btn btn-primary @nextDisabled">
    Next
</a>

Link

where to_dict is the following utility function:

Dictionary<string, string> to_dict(object obj)
{
    return obj
        .GetType()
        .GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
        .ToDictionary(
            prop => prop.Name,
            prop =>
            {
                var val = prop.GetValue(obj, null);

                return val == null ? "" : val.ToString();
            });
}

In this case, OnGetAsync is updated to accept OnGetAsyncParameters:

public async Task OnGetAsync(OnGetAsyncParameters onGetAsyncParameters)

Link

Request

The request is for ASP.NET Core to have route parameters be more strongly-typed in a tag-helpers.

I understand that the priority may be low. I'd just like to request that it at least be on the roadmap. :-)

@javiercn javiercn added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-mvc-razor-views Features related to the Razor view engine for Razor pages and MVC views labels Aug 16, 2021
@SteveSandersonMS SteveSandersonMS added this to the Backlog milestone Aug 16, 2021
@ghost
Copy link

ghost commented Aug 16, 2021

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@MichalSznajder
Copy link

Tag helpers are great but refactoring them is always a pain. Maybe source generators could be used here?

@dharmatech
Copy link
Author

@MichalSznajder

Interestingly, this post from the .NET Blog:

Introducing C# Source Generators

suggests exactly that:

Another capability Source Generators can offer is obviating the use of some “stringly-typed” APIs, such as how ASP.NET Core routing between controllers and razor pages work. With a Source Generator, routing can be strongly typed with the necessary strings being generated as a compile-time detail. This would reduce the amount of times a mistyped string literal leads to a request not hitting the correct controller.

@dharmatech
Copy link
Author

@MichalSznajder,

Tag helpers are great but refactoring them is always a pain.

(I'll get off topic here a bit.)

Yup, this is true. As opposed to Razor syntax, I prefer systems like JSX which look like markup but actually expand into regular JavaScript code. I wish we had something like this in ASP.NET Core. Something like JSX (csx?) which simply expands into C#. This way the page description code is fully in C# and thus you have the full power of the language to perform whatever refactoring you'd like.

For this reason, I like:

https://github.com/giraffe-fsharp/Giraffe

You write your page code in straight F#. So very powerful refactoring. However, it's not as mature as ASP.NET Core. E.g. nothing like tag-helpers there.

@dharmatech
Copy link
Author

As an aside, I've been exploring an approach to getting views that are fully type-safe by having everything expressed in C# instead of Razor syntax. Video I recorded demonstrating this approach:

https://youtu.be/PIfX-u27TLY

@MichalSznajder
Copy link

I think this approach goes a little bit too far for me. I am into type-safe tag helpers not complete overhaul of whole Razor.

Maybe first step would be some extension of IHtmlHelper? Tag helpers use internally IHtmlHelper.

@dharmatech
Copy link
Author

@MichalSznajder,

I think this approach goes a little bit too far for me. I am into type-safe tag helpers not complete overhaul of whole Razor.

Oh, I completely understand that such a radical approach may not be feasible for some folks. :-) Was just showing an approach I've been exploring to work around the lack of type-safety.

@mkArtakMSFT mkArtakMSFT added the enhancement This issue represents an ask for new feature or an enhancement to an existing one label Oct 11, 2022
@mkArtakMSFT mkArtakMSFT modified the milestones: Backlog, .NET 8 Planning Oct 12, 2022
@ghost
Copy link

ghost commented Oct 12, 2022

Thanks for contacting us.

We're moving this issue to the .NET 8 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@mkArtakMSFT
Copy link
Member

Hi. Thanks for contacting us.
We're closing this issue as there was not much community interest in this ask for quite a while now.
You can learn more about our triage process and how we handle issues by reading our Triage Process writeup.

@mkArtakMSFT mkArtakMSFT closed this as not planned Won't fix, can't repro, duplicate, stale Nov 15, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Dec 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-mvc-razor-views Features related to the Razor view engine for Razor pages and MVC views feature-routing
Projects
None yet
Development

No branches or pull requests

8 participants