Awesome
Route Generator for .NET
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:
- Videos:
- Blog posts:
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.
<a href="https://www.buymeacoffee.com/ewerspej" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-yellow.png" alt="Buy Me A Coffee" height="41" width="174"></a>