GitXplorerGitXplorer
e

epj.RouteGenerator

public
35 stars
3 forks
0 issues

Commits

List of commits on branch main.
Unverified
170819e29db0cfa344b0d1c14d2e700458ed51ce

bumped version to 1.0.2

eewerspej committed 3 months ago
Verified
c0b6af8fb076b0e641598814721c7df3eb3dd5e8

Nullables support (#7)

eewerspej committed 3 months ago
Verified
03055f3a23908cc1532a1ba97efc9b41ec11b7d9

Update FUNDING.yml

eewerspej committed 6 months ago
Verified
238812cbe7b3c58aca3466248fcbb3f988bf7162

Fixed typo in README.md

eewerspej committed 9 months ago
Verified
32297d1cce6b2c784b15899f93425cbf9b108aa7

Create FUNDING.yml

eewerspej committed a year ago
Unverified
756d1001a7f202902b1c2970f706e502098b0a73

updated README

eewerspej committed a year ago

README

The README file for this repository.

Route Generator for .NET

License Nuget

Tired of manually specifying route identifiers and fixing typos in the route-based navigation of your .NET app? This source generator will take away that pain.

Introduction

Route Generator is a C# source generator that generates a static Routes class for your .NET app containing all route identifiers for your string-based route navigation.

Although the sample project is using .NET MAUI, this generator can also be used with other .NET technologies since it targets .NET Standard 2.0.

Basic Usage

First, add the epj.RouteGenerator nuget package to your target project that contains the classes (i.e. pages) from which routes should be automatically generated.

Then, use the [AutoRoutes] attribute from the epj.RouteGenerator namespace on one of the classes at the root of your application. This must be within the same project and namespace containing the pages.

You must provide a suffix argument which represents the naming convention for your routes to the attribute, e.g. "Page" like above. It doesn't have to be "Page", it depends on the naming convention you use for pages in your app. If all your page classes end in "Site", then you would pass "Site" to the attribute. This suffix is used in order to identify all classes that should be included as routes in the generated Routes class based on their class name.

When using .NET MAUI, I recommend to use the attribute to decorate the MauiProgram class:

[AutoRoutes("Page")]
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();

        // ...

        return builder.Build();
    }
}

The source generator will then pick up all pages that end in the specified suffix and generate the Routes class with these identifiers within the same root namespace as the entry point:

// <auto-generated/>
using System.Collections.ObjectModel;

namespace RouteGeneratorSample
{
    public static class Routes
    {
        public const string MainPage = "MainPage";
        public const string VolvoPage = "VolvoPage";
        public const string AudiPage = "AudiPage";
    
        private static List<string> allRoutes = new()
        {
            MainPage,
            VolvoPage,
            AudiPage,
        };
        
        public static ReadOnlyCollection<string> AllRoutes => allRoutes.AsReadOnly();

        private static Dictionary<string, Type> routeTypeMap = new()
        {
            { MainPage, typeof(RouteGeneratorSample.MainPage) },
            { VolvoPage, typeof(RouteGeneratorSample.Cars.VolvoPage) },
            { AudiPage, typeof(RouteGeneratorSample.Cars.AudiPage) },
        };
        
        public static ReadOnlyDictionary<string, Type> RouteTypeMap => routeTypeMap.AsReadOnly();
    }
}

Now, you can use these identifiers for your navigation without having to worry about typos in your string-based route navigation:

await Shell.Current.GoToAsync($"/{Routes.AudiPage}");

If the AudiPage would ever get renamed to some other class name, you would instantly notice, because the compiler wouldn't find the Routes.AudiPage symbol anymore and emit an error, letting you know that it has changed. When using verbatim strings instead of an identifier like this, you wouldn't notice this change until the app either crashes or stops behaving the way it should.

Extra Routes

There may be situations where you need to be able to specify extra routes, e.g. when a route doesn't follow the specified naming convention using the suffix or when a routes is defined in a different way (in MAUI or Xamarin.Forms this could be using a <ShellContent> element in XAML).

For situations like these, the Route Generator exposes a second attribute called [ExtraRoute] and it takes a single argument representing the name of the route. You should not pass null, empty strings, any whitespace or special characters. Duplicates will be ignored.

If an extra route is specified whose name doesn't match any existing class name, you will have to provide a type to the attribute in order to include it in the generated Routes.RouteTypeMap dictionary using typeof(SomeClass).

namespace RouteGeneratorSample;

[AutoRoutes("Page")]
[ExtraRoute("SomeFaulty!Route")] // invalid, will emit warning EXR001 and will be ignored
[ExtraRoute("YetAnotherRoute", typeof(MainPage))]
[ExtraRoute("YetAnotherRoute")] // duplicate, will emit warning EXR002 and will be ignored
[ExtraRoute("SomeOtherRoute")] // valid, but no corresponding type available, will emit warning EXR003
[ExtraRoute(null)] // will be ignored
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        
        // ...

        return builder.Build();
    }
}

This would then result in the following Routes:

// <auto-generated/>
using System.Collections.ObjectModel;

namespace RouteGeneratorSample
{
    public static class Routes
    {
        public const string MainPage = "MainPage";
        public const string VolvoPage = "VolvoPage";
        public const string AudiPage = "AudiPage";
        public const string SomeOtherRoute = "SomeOtherRoute";
        public const string YetAnotherRoute = "YetAnotherRoute";
    
        private static List<string> allRoutes = new()
        {
            MainPage,
            VolvoPage,
            AudiPage,
            SomeOtherRoute,
            YetAnotherRoute
        };
        
        public static ReadOnlyCollection<string> AllRoutes => allRoutes.AsReadOnly();

        private static Dictionary<string, Type> routeTypeMap = new()
        {
            { MainPage, typeof(RouteGeneratorSample.MainPage) },
            { VolvoPage, typeof(RouteGeneratorSample.Cars.VolvoPage) },
            { AudiPage, typeof(RouteGeneratorSample.Cars.AudiPage) },
            { YetAnotherRoute, typeof(RouteGeneratorSample.MainPage) },
        };
        
        public static ReadOnlyDictionary<string, Type> RouteTypeMap => routeTypeMap.AsReadOnly();
    }
}

Note: If you don't provide a type to the [ExtraRoute] attribute and the specified route doesn't match any existing class name, the Routes.RouteTypeMap dictionary will not contain an entry for that route. Above, this is the case for the "SomeOtherRoute" route.

Ignore specific routes

Starting with v1.0.4, there is an [IgnoreRoute] attribute that can be used to exclude specific classes from being included in the generated Routes class. This can be useful if you have a class that matches the naming convention but should not be included as a route for some reason (e.g. non-abstract base classes):

using epj.RouteGenerator;

namespace RouteGeneratorSample;

[IgnoreRoute]
public class SomeIgnorableRoute { }

Also, abstract classes are now ignored by default:

namespace RouteGeneratorSample;

// will be ignored by default
public abstract class BaseRoute { }

// will be ignored by default
public abstract class TypedBaseRoute<T> { }

Route registration (e.g. in .NET MAUI)

Inspired by a comment by Miguel Delgado, version 1.0.1 introduced a new Routes.RouteTypeMap dictionary that maps route names to their respective Type. This can be used to register routes like this:

foreach (var route in Routes.RouteTypeMap)
{
    Routing.RegisterRoute(route.Key, route.Value);
}

Since this library is not MAUI-specific, I will not add such a utility method directly to this library. However, as mentioned below, automatic registration could be handled in a MAUI-specific layer.

Resources

The Route Generator is featured in the following resources:

Future Ideas

  • Platform-specific layer(s), e.g. epj.RouteGenerator.Maui
    • Automatic route registration
    • Automatic registration of Pages and ViewModels as services
    • Generation of route-specific extensions or methods (e.g. Shell.Current.GoToMyAwesomePage(params))

Remarks

C# version compatibility

This source generator only works with C# 10.0 or higher. If you are using .NET 5.0 or below, you will need to specify <LangVersion>10.0</LangVersion> in your project file.

The source generator is compatible with nullable reference types, the [ExtraRoute] attribute uses a Type? property. Please let me know if you run into problems with this.

Native AOT support

While Native AOT is still experimental in .NET 8.0 (e.g., it's not supported for Android yet and even iOS still is experiencing some hiccups), the latest version of Route Generator should technically be AOT-compatible. However, I cannot test this properly while there are still issues. Full Native AOT support will probably only be available with .NET 9.0 or higher according to this issue on GitHub.

Support

You can support this project by starring it on GitHub, sharing it with others or contributing to it. If you have any questions, feedback or ideas, feel free to open an issue or reach out to me.

Additionally, you can support me by buying me a coffee or by becoming a sponsor.

Buy Me A Coffee