facebook

Blog

Resta aggiornato

Convertiamo le Partial View di ASP.NET MVC con i View Component di ASP.NET Core
Kestrel, lanciami i componenti!
martedì 26 Marzo 2019

Durante il lavoro di porting effettuato sul nostro CMS “WebRight” da ASP.NET MVC 5.0 a ASP.NET Core, mi sono imbattuto in un bel problema: la gestione delle widget di WebRight. Infatti, queste erano gestite utilizzando delle Partial View che venivano renderizzate all’interno delle view tramite l’helper HTML.RenderAction. Purtroppo, quest’ultimo non è più utilizzabile in ASP.NET Core.

Niente paura comunque: per gestire questi casi d’uso, ASP.NET Core introduce i View Components, ovvero una nuova feature che permette di renderizzare HTML da una pagina Razor. Questi sono generati da una classe C# che estende la classe base ViewComponent, e sono tipicamente associati ad un file Razor per generare il markup che ci serve.

L’idea è di raccontarvi come scrivere dei View Component, usando Visual Studio 2017 e ASP.NET CORE, e come poi abbiamo portato questa nuova funzionalità in WebRight.

Il primo passo è creare una nuova Web Application ASP.NET CORE

Quando ci viene chiesto il tipo di Web App da creare, scegliamo Applicazione Web, che ci creerà un semplice website utilizzando le nuove ASP.NET Core Razor pages.

Il template scelto crea per noi una struttura di cartelle ben definita, all’interno della quale c’è la directory Pages. In questa creeremo una sottocartella dove saranno presenti i nostri View Component. 

Creiamo a questo punto una cartella TestViewComponent, che conterrà un file C# e una Razor View Page che andranno a comporre il nostro View Component, e che chiameremo TestViewComponent.cs e Default.cshtml: 

Implementiamo il nostro View Component (la nostra classe base), che avrà un costruttore vuoto e un methodo Invoke con solo un parametro.

using Microsoft.AspNetCore.Mvc;
 
namespace ViewComponentsExample.Pages.Components.TestViewComponent
{
    public class TestViewComponentViewComponent : ViewComponent
    {
        public TestViewComponentViewComponent() { }
        public IViewComponentResult Invoke(string nome)
        {
            return View("Default", nome);
        }
    }
}

La nostra view Razor sarà molto semplice, formata da solo due righe di codice. La prima ci dice di che tipo sarà il modello che verrà passato alla view, e la seconda renderizzerà il modello in un tag h2

@model string
<h2>@Model</h2>

A questo punto abbiamo un View Component completo. Per utilizzarlo, possiamo usare una pagina Razor, proprio come facevamo con HTML.RenderAction, quindi creiamo una nuova Razor View che chiamiamo TestViewComponentPage

Possiamo visualizzare il nostro View Component in due modi: il primo consiste nel richiamare la classe con il metodo InvokeAsync e il secondo è un tag HTML custom.

Il primo metodo che vediamo è InvokeAsync: a questo dobbiamo passare in input un primo parametro con il nome del component (nome della classe senza il suffisso ViewComponent) e il secondo parametro è un tipo anonimo. Questa è la nostra pagina completa:

@page
@model ViewComponentsExample.Pages.TestViewComponentPageModel
 
<body>
    <h1>Demo component Async</h1>
    @await Component.InvokeAsync("TestViewComponent", new {
    nome = "Mio primo componente"
    })
</body>

Se avviamo l’applicazione, ecco il risultato di quanto scritto fino ad ora:

Come già anticipato, esiste un altro modo per poter utilizzare i View Component all’interno delle nostre pagine Razor: possiamo creare un tag HTML custom da inserire nella pagina, funzionalità di ASP.NET Core che è chiamata Tag Helpers.

Diamo uno sguardo a come utilizzare i Tag Helper per invocare il nostro View Component. Ecco di seguito come cambia la nostra pagina Razor rispetto alla precedente.

@page
@model ViewComponentsExample.Pages.TestViewComponentPageModel
 
@addTagHelper *, ViewComponentsExample
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Demo component con Tag helper</title>
</head>
<body>
    <h1>Demo component con Tag helper</h1>
    <vc:test-view-component nome="mio componente con tag helper">
    </vc:test-view-component>
</body>
</html>

Tutto quello che ho dovuto fare è stato aggiungere la riga @addTagHelper*, ViewComponentsExample che semplicemente dice di utilizzare tutti i Tag Helper definiti ovunque nel mio assembly. A questo punto, posso referenziare il mio component nel body HTML con il prefisso vc per i view component. Il nome del tag non è altro che il nome del nostro View Component diviso in Kebab casing (test-view-component). La parte migliore di questo approccio è che non ho dovuto fare alcuna modifica al codice della mia classe per permettere al Tag Helper di funzionare!

Come abbiamo usato tutto questo in WebRight? La nostra precedente implementazione gestiva le widget direttamente dal controller unico presente nel core dell’applicazione, utilizzando questo semplice codice:

[HttpGet]
[Route("widget/{widget_name}", Name = "WebRightWidgetDefaultGet")]
public PartialViewResult Widget(string widget_name, WebRightWidgetArguments arguments)
{
    IWebRightWidget widget = this.widgetFactory.GetWidgetByName(widget_name);
 
    return PartialView(
        string.Format("_{0}{1}", widget_name, arguments.ViewVersionName),
        widget.GetViewModel(getCurrentCultureName(), arguments.Parameters));
}

Questo approccio ci permetteva di scrivere degli html helper, in cui andavamo a specificare il nome della widget da richiamare e passare gli eventuali parametri che potessero servire allo scopo della widget. Ecco un esempio di codice di un helper che richiama la widget del Menu:

public static MvcHtmlString Menu(this HtmlHelper html, string categoryName, string viewVersionName = "")
{
    return html.Action(
        "Widget", 
        "WebRightCms", 
        new
        {
            widget_name = "WebRightMenuWidget",
            arguments = new WebRightWidgetArguments
            {
                ViewVersionName = viewVersionName,
                Parameters = new object[] { categoryName }
            }
        });
}

Queste due sezioni di codice presentate ci permettevano di renderizzare una widget in modo semplice in qualunque Page della nostra applicazione:

@Html.Menu("mainNavigationMenu")

Come abbiamo modificato questo comportamento per utilizzare i nuovi View Component di ASP.NET Core? Di seguito il nostro View Component, che va a sostituire l’action Widget del controller che vi ho descritto:

public class WidgetViewComponent : ViewComponent
{
    private readonly IWebRightWidgetFactory widgetFactory = null;
 
    public WidgetViewComponent(IWebRightWidgetFactory wf)
    {
        this.widgetFactory = wf;
    }
 
    public async Task<IViewComponentResult> InvokeAsync(string name, string culture = null, string viewVersionName = null, object[] parameters = null)
    {
        IWebRightWidget widget = this.widgetFactory.GetWidgetByName(name);
        return View($"{name + viewVersionName}", widget.GetViewModel(culture, parameters));
    }
}

Come vedete, la classe è relativamente semplice: ha un metodo InvokeAsync che viene richiamato con dei parametri (alcuni di questi erano quelli utilizzati sulla action Widget del controller).

Quello che ci manca adesso è sostituire l’utilizzo degli Html Helper. Anche questa modifica risulta abbastanza semplice: tutto quello che serve è richiamare il metodo InvokeAsync all’interno di una nostra pagina Razor, in modo da renderizzare la widget che ci serve. Ecco un esempio di utilizzo:

<h1>TEST PAGE ANCORA</h1>
 
@await Component.InvokeAsync("Widget", new { name = "TestWidget" })

Come spiegato precedentemente, preferisco l’approccio con i TagHelper per renderizzare i ViewComponent. Facciamo, quindi, un’altra piccola modifica alla nostra page, che diventerà:

<h1>TEST PAGE ANCORA</h1>
 
<vc:widget name="TestWidget"></vc:widget>

Per inizializzare i TagHelper non ho fatto altro che aggiungere questa riga nella pagina Layout dell’applicazione:

@addTagHelper *, WebRight

Con questo concludiamo la panoramica sull’utilizzo dei View Component, spero di aver stimolato la vostra curiosità su questa funzionalità di ASP.NET Core.

Alla prossima!