daviddotcs / safe-routing

A C# source generator for ASP.NET Core razor pages and MVC controllers which produces strongly-typed identifiers for routes.
MIT License
31 stars 2 forks source link

Ideas for generated routes #147

Open austins opened 5 months ago

austins commented 5 months ago

Awesome project! Would these features be possible in the realm of source generators?

💡 Idea 1: Handler method type Assuming the handler method name conventions can't be changed in ASP.NET, if the method name starts with OnGet, add a property on the generated IPageRouteValues implementation, e.g. public HttpMethod Method => HttpMethod.Get; (or the HttpMethods.Get for a string type). Same for OnPost, etc.

One use case would be creating a tag helper for HTMX, which adds attributes for URLs like hx-get, hx-post. The tag helper can take an attribute such as hx-for-route and then map it to the attribute with the corresponding method along with any query/route values.

💡 Idea 2 (maybe): Relative path Currently, there is a way to get PageName, but not the "relative path".

I wrote an extension method for use outside of views. Not sure if it'll be useful within the source generated class or not. It looks like:

    public static string GetPagePath(this IPageRouteValues routeValues)
    {
        const string indexPagePath = "/Index";
        if (routeValues.PageName == indexPagePath)
        {
            return "/";
        }

        var pagePath = routeValues.PageName;
        if (routeValues.PageName.EndsWith(indexPagePath, StringComparison.Ordinal))
        {
            pagePath = routeValues.PageName[..routeValues.PageName.LastIndexOf(
                indexPagePath,
                StringComparison.Ordinal)];
        }

        return pagePath.ToLowerInvariant(); // Maybe not everyone has the lowercase routes settings enabled?
    }

Example mappings of page name -> relative path would be:

daviddotcs commented 5 months ago

Thanks for your feedback and suggestions!

I'm happy to add the HTTP method as a property for page routes. It wouldn't work as well for controllers since each method on a controller could potentially handle multiple HTTP methods, so I'll only be adding it to IPageRouteValues.

I haven't decided whether I'll expose it as an HttpMethod or string as yet. There's no HttpMethod.Patch in .NET Standard 2.0 which makes supporting that a little clumsy, so I'm leaning towards having it be a string.

Regarding the relative paths, is that something you can achieve with the .Url(...) extensions or are you wanting to get those outside the context of a request and thus don't have an IUrlHelper instance? E.g.;

var route = Routes.Pages.Index.Get();
var path = route.Url(Url); // "/"

A method of getting the URL outside of a request is likely not something I'll include in this library because the @page directive can be used within a razor file to make its route no longer correlate to the PageName value—something to keep in mind with your GetPagePath extension method. Unfortunately source generators don't have access to the output of other source generators, so I can't get route information from the output of the razor source generator to be able to determine URLs in a reliable manner at build time (without re-implementing parts of the razor source generator).

austins commented 5 months ago

Thank you! Yeah, that sounds good. I think a string is good as long as it's the same casing as the HttpMethods constants to make comparisons easier.

For controllers, I haven't seen verb overloading done much, but you're right that it's possible. Is this how it's done in the MVC controllers?

[HttpGet("someaction")]
[HttpPost("someaction")]
public IActionResult SomeAction() { return View(); }

Would it be possible to have an array of HTTP method strings?

austins commented 5 months ago

Update about idea 2 and the extension method I posted. I got rid of my extension method. If a route path is needed during app startup builder for configuration, IConfigureNamedOptions can be used and LinkGenerator injected there. In integration tests, I'm able to get the LinkGenerator instance from the services collection and use that with SafeRouting's Path() extension.

I opted for LinkGenerator instead of UrlHelper since that requires an action context, but I'm not too familiar with either to say if they would generate different results.

daviddotcs commented 5 months ago

Yes, you can use any number of attributes that inherit from Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute to make an action handle the designated HTTP method(s). And yes, I could have a property or method yielding a collection of HTTP methods, though it's making me wonder whether the structure of controller routes should be closer to that of the page routes, e.g.; Routes.Controllers.Account_Index.Get() instead of Routes.Controllers.Account.Index(). That way both could conform to the same interface with an HttpMethod property returning a single value. I'm not sure that's a change I want to make at the moment though without a good use case for controllers.

That's great that you were able to use the LinkGenerator extensions for your needs. I forgot that I added those!

I should have the HttpMethod property ready to go for page routes within the next few days. I ended up going with strongly typed rather than strings since it isn't too difficult to conditionally work around the limited API in .NET Standard 2.0.