ASP.NET ed i Patterns: ASP.NET MVC (Model, View, Controller) Framework (Parte 1)

Con il rilascio in dicembre  della prima CTP (Customer Technology Preview) delle nuove  ASP.NET 3.5 Extension abbiamo a disposizione la prima versione pubblica del nuovo ASP.NET MVC Framework.

Il MVC (Model, View , Controller) sui cui principi è basato il nuovo ASP.NET MVC Framework è un pattern per sviluppare le componenti di presentation di un'applicazione che ha un ampio utilizzo nel mondo del Web, ne esistono moltissime implementazioni per differenti tecnologie e linguaggi. Il suo successo è essenzialmente dovuto alle caratteristiche implementative che consentono di separare nettamente la logica di controllo e la gestione dei dati del dominio applicativo,  dalla modalità con cui vengono presentate  le informazioni, rendendo più semplice manutenzione e test dell'applicazione.

In  passato abbiamo visto nascere diversi tipi di framework più o meno validi per sviluppare applicazioni ASP.NET con il pattern MVC , la vera differenza del nuovo ASP.NET MVC Framework consiste in una vera e propria integrazione del modello nell'infrastruttura delle pagine dinamiche di .NET.  Il nuovo framework, infatti, trova spazio all'interno del namespace System.Web con uno specifico namespace System.Web.Mvc e si propone di diventare una vera e propria alternativa al classico modello di sviluppo delle pagine web con  .NET, per quei casi in cui la separazione della logica di controllo e degli oggetti del nostro dominio applicativo, dalla presentazione svolgono un ruolo determinate, o dove si vuole utilizza un approccio basato su un maggior livello di test del codice o addirittura uno sviluppo TDD (Test Driven Development). 

 

Overview dell'ASP.NET MVC Framework

L'infrastruttura classica per sviluppare pagine Web con ASP.NET è essenzialmente basata sul pattern "Page Controller" utilizzato come base concettuale di funzionamente dalle principali infrastrutture per lo sviluppo di pagine Web dinamiche. Le richieste  (URL) indirizzate ad ASP.NET vengono gestite dal runtime della piattaforma che assegna l'esecuzione della risorsa richiesta ad una pagina .aspx.  La pagina implementa il suo ciclo di vita gestito con una serie di eventi predefiniti che eseguono il "Page Controller" . In questo modo lo sviluppo dell'applicazione è rapido e semplice, ma si legano in modo determinante gli url richiesti alle pagine che implementano sia alla parte di logica che di presentazione vera e propria, rendendo di fatto più difficili tutte le attività di test, evoluzione e manutenzione dell'applicazione.

In particolare le difficoltà collegate alla costruzione di unit test rappresentano uno dei più significativi limiti portati da questo tipo di pattern. Essendo, infatti, l'implementazione del controller, strettamente legata alla pagina e quindi all'ambiente in cui la pagina vive (HttpContext e tutti gli altri elementi di contesto e funzionamento della pagina e di ASP.NET) , diventa praticamente impossibile sia realizzare uno sviluppo TDD  che gestire dei semplici unit test sulla parte di logica del controller.

Per approfondire l'implementazione del "Page Controller" in ASP.NET vi suggerisco di leggere  Implementing Page Controller in ASP.NET .

 

Nell' ASP.NET MVC Framework vengono realizzati ed applicati i principi base del pattern  MVC ovvero la netta separazione tra i tre elementi fondamentali che concettualmente lo compongono:

- Model: Gestisce il modello di oggetti che rappresentano i dati nel nostro Dominio Applicativo e fornisce la logica per la gestione dei dati. Nelle applicazioni più semplici basate su ASP.NET può essere anche un normale Dataset.

- View: Gestisce le modalità di presentazione dei dati attraverso il model che gli viene assegnato

- Controller: E' l'elemento principale  del pattern, si occupa di gestire  la richiesta e di coordinarne la logica di esecuzione utilizzando Model e selezionando la View da associare per la risposta  .

Esistono diverse sfumature nell'MVC ma nelle implementazioni Web di questo pattern viene utilizzato principalmente  l'MVC con view passiva ed il flusso è genericamente quello rappresentato nel seguente disegno:

 image

 

La richiesta ad uno specifico URL viene assegnata ad un Controller che implementa la logica di esecuzione delle azioni necessarie a fornire la risposta al client. Il Controller utilizza il Model per rappresentare i dati necessari alla risposta, li associa ad una View con cui non è strettamente collegato ed a cui assegna il compito di presentarli al richiedente.

In particolare nella definizione dell'architettura del nuovo framework MVC per ASP.NET  uno dei principali elementi di attenzione è stato posto nel principio di Estendibilità che è anche alla base dell'ottenimento di un'infrastruttura più semplicemente testabile. L'intera architettura, infatti,  fà un ampio uso di interfacce e factory per praticamente l'intera gamma di elementi che la compongono. La costruzione del controller e della view, avviene attraverso delle specifiche factory che, attraverso la sostituzione delle stesse e l'utilizzo di pattern come il Dependency Injection, permettono lo sviluppo di sofisticati ambienti di test per il codice ed una grande flessibilità nell'architettura.

Ulteriore esempio di flessibilità inserito nel framework non propriamente collegato al pattern MVC  è rappresentato dall'engine di routing che consente la mappatura degli URL con la specifica classe Controller che ne deve gestire l'esecuzione e l'indirizzamento della View per produrre la risposta.

Il seguente disegno rappresenta una semplificazione del ciclo di esecuzione di una richiesta all'interno dell'ASP.NET MVC Framework :

 image

 

La richiesta viene intercettata da un HttpModule specifico inserito dal framework MVC (UrlRoutingModule) e indirizzata alla componente che si occupa di  effettuare il routing che, in base all'URL ed alla sua configurazione, individua un oggetto Route che rappresenta il match tra richiesta e il  controller che deve gestirla.

L'oggetto Route utilizza il RouteHandler ad esso associato, assegna l'esecuzione della richiesta ad un controller creandone un'istanza attraverso una specifica factory ed esegue il metodo sul controller associato all'azione specifica che il controller deve eseguire.

Il controller riceve il contesto della richiesta, esegue l'azione e utilizzando il model prepara i dati da passare alla view che viene innescata su richiesta attraverso una specifica factory.

La View costruisce la risposta che viene inviata al browser.

 

Un esempio pratico per la gestione di una richiesta con l' ASP.NET MVC Framework

Proseguiamo nell'illustrare le componenti del framework con un piccolo esempio con il quale vogliamo implementare una semplice applicazione Web per ricercare informazioni sui nostri clienti.  Per il momento trascuriamo il funzionamento dell'infrastruttura di routing su cui torneremo dopo e la utiliziamo con la sua configurazione di default.

Per poter sviluppare con Visual Studio un'applicazione MVC occorre selezionare lo specifico template di progetto Web:

 

image

 

L'organizzazione e gli artifatti prodotti dal template di progetto si presentano come di seguito :

 

 image

 

Vengono create le seguenti cartelle:

\Content   per i contenuti grafici e di stile

\Controllers   per l'inserimento del codice delle classi Controller

\Models per contenere le classi models del progetto

\Views per le classi view , contiene una sotto cartella per ogni controller e una view per ogni action

 

La configurazione di default impostata per il routing associa l'url al controller attraverso la seguente modalità:

 

/[Controller]/[Action]?paramname1=paramvalue1,.....

 

Dove [Controller] è il nome della classe che implementa il controller stesso per la specifica richiesta, [Action] rappresenta l'azione che deve essere eseguita sul controller ed i parametri vengono inseriti nel contesto della richiesta come valori che la action deve utilizzare o come  parametri di input per l'esecuzione. Vedremo successivamente che implementando opportunamente la action è anche possibile passare dei parametri con il formato:

 

/[Controller]/[Action]/[id]

 

Ad esempio nel caso in cui abbiamo una richiesta  in cui l'url è  /Customers/....   con la configurazione di default descritta  verrà ricercata una classe controller di nome CustomersController che sarà la classe che utilizzeremo per gestire le azioni collegate ai nostri clienti.

Per poter implementare una classe come controller occorre implementare l'interfaccia System.Web.MVC.IController che viene usata come contratto con il resto dell'infrastruttura del framework e per ricevere il contesto della richiesta   che viene rappresentato con la classe System.Web.MVC.ControllerContext. L'interfaccia IController ha la seguente struttura:

 

public interface IController
    {
        void Execute(ControllerContext controllerContext);
    }

 

La classe ControllerContext è definita come di seguito :

 

public class ControllerContext : RequestContext
    {
        public ControllerContext(RequestContext requestContext, IController controller);
        public ControllerContext(IHttpContext httpContext, RouteData routeData, IController controller);

        public IController Controller { get; }
    }

 

e contiene le informazioni necessarie al gestire il contesto dell'esecuzione , abbiamo il Controller assegnato e la classe System.Web.MVC.RequestContext  definita come di seguito:

 

public class RequestContext
   {
       public RequestContext(IHttpContext httpContext, RouteData routeData);

       public IHttpContext HttpContext { get; internal set; }
       public RouteData RouteData { get; internal set; }
   }

 

Questa classe contiene in particolare: 

- i dati di routing determinati in base all'url

- il contesto della richiesta ASP.NET gestita con la classica HttoContext

In particolare l'HttpContext viene in questo caso agganciato partendo dall'interfaccia System.Web.IHttpContext per permetterne una sostituzione sia di questa che  delle interfacce IHttpRequest e IHttpResponse in fase di unit test senza dover derivare dalle classi base dell' infrastruttura di  ASP.NET.

 

Per semplificarne la costruzione di un Controller si può derivare dalla classe System.Web.MVC.Controller che oltre al metodo Execute implementa una serie di funzionalità e di metodi helper che permettono di sviluppare concentrandoci sulla logica del controller riducendo al minimo il codice di infrastruttura necessario.

Abbiamo già detto che ogni controller può implementare una o più Action che rappresentano le azioni che possono essere eseguite sul controller e che nella configurazione di default del routing vengono, come sopra descritto, rimappate sull'url richiesto.

Ad esempio  l'url /Customers/List che useremo per avere una lista di tutti i nostri clienti, viene interpretato con esecuzione della action "List" sul Controller CustomersController .

Le Action vengono associate ai metodi marcati con lo specifico attributo System.Web.MVC.Controller .

Nel nostro esempio, quindi, per poter implementare il controller Customers implementeremo la seguente classe:

 

image

 

Ogni controller implementa anche una Action di base che viene nella configurazione di default associata alla action Index e che è quella che viene eseguita in assenza di informazioni di routing relative alla action nell'url come ad esempio nel caso di una URL : /Customers.

Alla nostra action possono  essere passati anche dei parametri che possono essere estratti attraverso l'utilizzo dell'oggetto di contesto della richiesta. Infatti, come abbiamo già visto sopra, quest'ultimo implementa IHttpcontext di ASP.NET e quindi contiene l'oggetto che rappresenta la richiesta implementando IHttpRequest.

Avendo derivato il nostro oggetto dalla classe Controller abbiamo già a disposizione la proprietà Request sulla nostra classe che ci permette di accedere direttamente all'oggetto Request del contesto della richiesta arrivata al controller.

Ad esempio con una richiesta di url in cui vogliamo passare un parametro per paginare i dati estratti ed estrarre la pagina 1 della lista clienti: /Customers/List?page=1  possiamo dall'interno del nostro metodo di action estrarre il parametro nel seguente modo:

 

this.Request["page"];

 

sapendo che lo utilizzeremo come un int :

 

int i = Convert.ToInt32(this.Request["page"]); 

 

E' possibile anche utilizzare un formato per il metodo della action che preveda già direttamente i parametri come ad esempio:

image

 

In questo caso potrà essere usato il formato di URL seguente :  /Customers/List/1 ed il parametro successivo a list verrà direttamente associato al parametro del metodo perchè previsto dalla configurazione di default dell'url routing.

Agendo sulla configurazione del routing, come vedremo, si possono cambiare questi comportamenti nella gestione dei paramentri, aggiungerne ed inserire anche dei validatori sull'url che vengono eseguiti prima di innescare  il controller.

 

Per costruire il Model del nostro esempio, utiliziamo LinqToSql e procediamo a mappare una classe Customers sulla tabella omonima del database Northwind.

LinqToSql ci costruisce l'infrastruttura di classi necessaria derivandola dai metadati del database. Possiamo quindi procedere ad utilizzare le classi che sono state generate dai tool di visual studio ed implementare il segente metodo GetCustomers nel NorthwindDataContext che verrà usato dal controller per estrarre la lista dei clienti:

 

image

 

Terminata l'implementazione del Model e della logica della nostra Action , l'ultimo passo consiste nell'associare il model alla view che dovrà rappresentarlo.

Nella seconda parte del post procederò con il descrivere questa parte dell'implementazione del MVC Framework con qualche approfondimento anche sull'infrastruttura di routing.