Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ewerspej/epj.RouteGenerator

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.
https://github.com/ewerspej/epj.RouteGenerator

Last synced: about 1 month ago
JSON representation

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.

Awesome Lists containing this project

README

        

# epj.RouteGenerator

![License](https://img.shields.io/github/license/ewerspej/epj.RouteGenerator)
[![Nuget](https://img.shields.io/nuget/v/epj.RouteGenerator)](https://www.nuget.org/packages/epj.RouteGenerator/)

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](https://www.nuget.org/packages/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:

```c#
[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:

```c#
//
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 allRoutes = new()
{
MainPage,
VolvoPage,
AudiPage,
};

public static ReadOnlyCollection AllRoutes => allRoutes.AsReadOnly();

private static Dictionary routeTypeMap = new()
{
{ MainPage, typeof(RouteGeneratorSample.MainPage) },
{ VolvoPage, typeof(RouteGeneratorSample.Cars.VolvoPage) },
{ AudiPage, typeof(RouteGeneratorSample.Cars.AudiPage) },
};

public static ReadOnlyDictionary RouteTypeMap => routeTypeMap.AsReadOnly();
}
}
```

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

```c#
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 `` 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)`.

```c#
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`:

```c#
//
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 allRoutes = new()
{
MainPage,
VolvoPage,
AudiPage,
SomeOtherRoute,
YetAnotherRoute
};

public static ReadOnlyCollection AllRoutes => allRoutes.AsReadOnly();

private static Dictionary routeTypeMap = new()
{
{ MainPage, typeof(RouteGeneratorSample.MainPage) },
{ VolvoPage, typeof(RouteGeneratorSample.Cars.VolvoPage) },
{ AudiPage, typeof(RouteGeneratorSample.Cars.AudiPage) },
{ YetAnotherRoute, typeof(RouteGeneratorSample.MainPage) },
};

public static ReadOnlyDictionary 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.*

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

Inspired by a comment by [Miguel Delgado](https://github.com/mdelgadov), 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:

```c#
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:

- Videos:
- [No More Magic String Navigation in .NET MAUI Shell with this Plugin!](https://www.youtube.com/watch?v=XNLKyEPWqws) by [Gerald Versluis](https://github.com/jfversluis)
- Blog posts:
- [Introducing Route Generator for .NET](https://blog.ewers-peters.de/introducing-route-generator-for-net)
- [Add automatic route registration to your .NET MAUI app](https://blog.ewers-peters.de/add-automatic-route-registration-to-your-net-maui-app)

# 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 `10.0` 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](https://learn.microsoft.com/dotnet/core/deploying/native-aot#limitations-of-native-aot-deployment). 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](https://github.com/dotnet/maui/issues/18839#issuecomment-1828006233).

# 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