Supportare route multiple nella generazione di URL in Blazor

di Marco De Sanctis, in ASP.NET Core,

Negli script precedenti abbiamo iniziato un breve viaggio nel sopperire a una delle mancanze di Blazor, costruendo un generatore dinamico di URL in base al routing, analogamente a come avviene in ASP.NET MVC. Abbiamo inizialmente impostato la nostra soluzione, con alcune limitazioni (https://www.aspitalia.com/script/1390/Creare-Link-Fortemente-Tipizzati-Blazor.aspx) e poi abbiamo visto come supportare eventuali parametri (https://www.aspitalia.com/script/1391/Generare-URL-Strongly-Typed-Parametri-Blazor.aspx). Con questo script chiuderemo il cerchio, e vedremo come gestire componenti che potenzialmente hanno più di una route definita.

Come sappiamo infatti, dato che Blazor non supporta parametri opzionali o valori di default nel routing, l'unico modo per implementare questi concetti è quello di associare più di una route allo stesso componente:

@page "/counter"
@page "/counter/{start:int}"
@page "/counter/{start:int}/{end:int}"

<h1>Counter</h1>

<p>Current count: @Start</p>

...

Prima di aggiungere questa feature al nostro codice, conviene però effettuare un piccolo refactoring della nostra soluzione precedente, creando una classe ParsedRoute che rappresenta una route della quale abbiamo recuperato il template di base e i parametri:

public class ParsedRoute
{
    public string Template { get; private set; }
    public string[] Parameters { get; private set; }

    public ParsedRoute(string template)
    {
        this.Template = template;

        this.Parameters = Regex.Matches(template, "{(\\w+)(:.+?)?}")
            .Select(x => x.Groups[1].Value)
            .ToArray();
    }

    public string Render(RouteValueDictionary values)
    {
        string url = this.Template;

        foreach (var key in values.Keys)
        {
            url = Regex.Replace(url, $"{{{key}(:.+?)?}}", values[key].ToString(), RegexOptions.IgnoreCase);
        }

        return url;
    }
}

Questa classe può essere costruita a partire da un template, e usa le regular expression per popolare un array con tutti i parametri presenti nella route stessa.

Allo stesso tempo, nel metodo Render incapsula la logica per rimpiazzare i parametri con i valori provenienti da un RouteValueDictionary, tramite la stessa tecnica che abbiamo visto nello script precedente, restituendo la stringa che rappresenta l'URL finale.

Anche la nostra classe RouteHelper a questo punto necessita di alcune modifiche, per incorporare il nuovo oggetto che abbiamo appena creato. In particolare, contiene un metodo privato ParseRoutes in grado di restituire una collection di ParsedRoute a partire da quelle definite nel componente stesso:

public static class RouteHelper
{
    private static IEnumerable<ParsedRoute> ParseRoutes<TComponent>() where TComponent : ComponentBase
    {
        Type type = typeof(TComponent);
        var result = type.GetCustomAttributes(typeof(RouteAttribute), inherit: false)
            .OfType<RouteAttribute>()
            .Select(x => new ParsedRoute(x.Template));

        return result;
    }
    ...
}

Il codice è facilmente comprensibile, grazie all'utilizzo di LINQ: non facciamo altro che individuare tutti gli attributi RouteAttribute associati al componente, e con ognuno di questi, generiamo il corrispondente ParsedRoute sfruttandone il template.

Ora non ci resta che utilizzare questo metodo per generare l'URL richiesto, a partire dai parametri forniti in ingresso:

public static string GetUrl<TComponent>(object parameters = null)
    where TComponent : ComponentBase
{
    var properties = new RouteValueDictionary(parameters);

    var result = ParseRoutes<TComponent>()
        // stesso numero di parametri in input
        .Where(x => x.Parameters.Length == properties.Count)
        // tutti i nomi dei parametri corrispondono all'input
        .Where(x => x.Parameters.All(parameter => properties.ContainsKey(parameter)))
        .Select(x => x.Render(properties))
        .FirstOrDefault();

    return result;
}

Anche questo metodo sfrutta una query LINQ che, almeno all'apparenza, può sembrare leggermente più complessa della precedente. In buona sostanza, ciò che facciamo è, innanzitutto, popolare un RouteValueDictionary con le proprietà dell'oggetto parameters in input, e successivamente cerchiamo, tra tutte le ParsedRoute disponibili, quella che abbia lo stesso numero e nomi di parametri.

Nel caso questa route esista, ne invochiamo il metodo Render visto prima per sostituire i parametri e generare il risultato richiesto. In caso contrario, il metodo comunque non fallisce, ma l'URL restituito avrà valore null.

Grazie a questo nuovo oggetto, ora possiamo selezionare la route desiderata per il componente Counter che abbiamo visto all'inizio dello script, semplicemente specificando i parametri corrispondenti:

<NavLink class="nav-link" href="@(RouteHelper.GetUrl<Counter>(new { Start = 4 }))">
    <span class="oi oi-plus" aria-hidden="true"></span> Counter with Start
</NavLink>
<NavLink class="nav-link" href="@(RouteHelper.GetUrl<Counter>(new { Start = 4, End = 10 }))">
    <span class="oi oi-plus" aria-hidden="true"></span> Counter with Start/End
</NavLink>

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi