in

DotNetSide

Dot Net South Italy Developers User Group

Articoli

Articoli pubblicati dagli iscritti a .netSide

Model-View-Controller con ASP.NET

Autore: Bruno (DaViL) Fortunato

 

Quando ci si trova di fronte ad un applicazione piuttosto complessa, è di fondamentale importanza tenere presente alcuni aspetti fondamentali tra cui la facilità di manutenzione del codice ed il riutilizzo dello stesso.
Da non sottovalutare nemmeno la separazione fra i componenti che regolano le funzioni di business ed i componenti che implementano la logica di presentazione.
Potrebbe essere necessario, ad esempio, effettuare un restyling grafico (View) in un momento successivo alla distribuzione del software, e questo non dovrebbe in alcun modo richiedere interventi sulle funzioni dell’applicazione (Model).
In pratica, bisogna tenere separato il lavoro del programmatore dal lavoro del grafico.
Per far fronte a queste esigenze si è reso necessario individuare dei patterns di progettazione e fra tutti spicca il Model-View-Controller (da ora MVC)

Come funziona MVC
Il funzionamento del pattern MVC ruota, per l’appunto, intorno a tre oggetti: i Models, le Views ed i Controllers. Questo ci da la possibilità di creare applicazioni estremamente modulari, che è poi il nostro obiettivo principale.
I Models rappresentano il cuore dell’applicazione. In essi sono infatti contenute le funzionalità quali recupero dati, aggiornamento, eliminazione etc.
Le Views, come è facile immaginare, rappresentano l’interfaccia grafica con cui l’utente finale interagisce.
Il Controller infine, contiene la logica di controllo e si occupa di leggere le richieste inviate dagli utenti tramite le View, eseguire i Models appropriati e, se necessario, ripresentare una nuova View con il risultato richiesto.
Come per altri ambienti, quali Java (Struts, ...), Ruby (RubyOnRails) o PHP (CakePHP, ...), anche per ASP.NET esistono dei framework che permettono di utilizzare questo design pattern (Maverick.NET, NWAF per citarne alcuni).
Lo in questo articolo però, vedremo come implementarne uno personalizzato.

Implementazione del controller
Iniziamo con l’implementazione di un Controller. Di questo componente, ne sistono vari tipi: page controller, base page controller etc. come si evince da questo documento. Nella nostra implementazione ne realizzeremo uno centralizzato.
Per realizzare il primo elemento, il MainController, utilizzeremo l’interfaccia IHttpModule che contiene all’interno due metodi che vengono richiamati rispettivamente all’inizio ed alla fine di ogni richiesta:

Init(HttpApplication context);
Dispose();

Il metodo Init riceve un oggetto HttpApplication che contiene eventi e metodi che possiamo intercettare ed utilizzare per instradare le varie richieste ai corrispondenti controllers. L’evento che invece andremo ad utilizzare è HttpApplication.AuthorizeRequest.
Nel nostro esempio, cercheremo di visualizzare dei dati fittizzi creati dal model “Entries” in diverse forme (lista e statistiche) utilizzando, appunto, il pattern MVC.
Aggiungiamo quindi una classe in App_Code e chiamiamola MainController. Questa classe implementerà l’interfaccia IHttpModule, intercetterà l’evento HttpApplication.AuthorizeRequest, identificherà il controller appropriato tramite l’url, e lo invocherà tramite la reflection.

Ecco il codice di App_Code/MainController.cs

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Reflection;

/// <summary>
/// MVC By DaVIL: Main Controller
/// </summary>
public class MainController : IHttpModule
{
   private string[] _sAspExtensions = { ".aspx", ".asmx", ".ashx" };

   public MainController()
   {

   }

   public void Init(HttpApplication application)
   {
      application.AuthorizeRequest += new EventHandler(application_AuthorizeRequest);
   }

   void application_AuthorizeRequest(object sender, EventArgs e)
   {
      HttpApplication application = (HttpApplication)sender;

      //controllo se usare il nostro controller o quello standard asp.net
      if(!IsAspExtension(application.Request.Path))
      {
         //recupero il nome del controller da richiamare grazie alla querystring
         int iStart = application.Request.Path.LastIndexOf("/") + 1;
         string sUtilPath = application.Request.Path.Substring(iStart);

         string sType = sUtilPath;
         string sMethod = "View"; //il metodo di default del controller è View

         //se invece viene specificato un altro metodo ...
         if (sUtilPath.IndexOf(".") != -1)
         {
            string[] sSplit = sUtilPath.Split('.');
            sType = sSplit[0];
            sMethod = sSplit[1];
         }

         //instanzio il tipo
         Type ctrl = Type.GetType("Controllers." + sType);
         if (ctrl == null)
            application.Response.Redirect("default.aspx");
         Object instance = Activator.CreateInstance(ctrl);

         //e richiamo il metodo passandoli l'oggetto application
         MethodInfo mi = ctrl.GetMethod(sMethod);
         if(mi==null)
            application.Response.Redirect("default.aspx");

         mi.Invoke(instance, new object[] { application });
      }
   }

   private bool IsAspExtension(string sPath)
   {
      foreach (string sExt in _sAspExtensions)
      if (sPath.EndsWith(sExt))
         return true;

      return false;
   }

   public void Dispose()
   {
   }
}

Le classi che implementano IHttpModule necessitano una registrazione nel file di configurazione di ASP.NET web.config.
Inserire quindi tra i tag <system.web> le seguenti righe:

<httpModules>
   <add name="MainController" type="MainController" />
</httpModules>

Una volta creato il MainController possiamo passare alla creazione dei controller specifici che per convenzione inseriremo nella cartella App_Code/Controllers sotto il namespace Controllers.
Per la creazione dei controller non esistono regole specifiche in quanto vengono richiamati tramite la reflection. Ogni metodo deve pero contenere come parametro un HttpApplication che viene passato dal MainController durante l’invocazione.
Inseriamo quindi in App_Code/Controllers la classe Entries.cs.
Questa classe contiene due metodi.

View(HttpApplication application);
Statistics(HttpApplication application);

View è per convenzione il metodo predefinito. Utilizzando la reflection infatti, abbiamo reso possibile richiamare dei metodi direttamente dalla barra degli indirizzi del browser con un url simile a http://localhost/mvc/Entries.View .
Il MainController, tramite la reflection, instanziera un oggetto controllore di tipo entries e ne invocherà il metodo View.
Se nessun metodo verrà specificato (ad esempio http://localhost/mvc/Entries), verrà richiamato il metodo predefinito (View).
Nel nostro caso, il metodo View inizializza tramite il Model un data table e la passa al visualizzatore Views/Entries/View.aspx che visualizzerà il contenuto dei dati recuperati tramite il Model
Il secondo metodo (Statistics) invece, visualizzerà delle semplicissime statistiche (il numero di record presenti) utilizzando la stessa fonte dati, recuperata nuovamente tramite il Model. Il visualizzatore utilizzato in questo caso sarà Views/Entries/Statistics.aspx. Per richiamarlo sarà sufficiente inserire nel browser http://localhost/mvc/Entries.Statistics.

Ecco il codice di App_Code/Controllers/Entries.cs

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

/// <summary>
/// Entries Controller
/// </summary>
namespace Controllers
{
   public class Entries
   {
      public Entries()
      {
         //
         // TODO: Add constructor logic here
         //
      }

      public void View(HttpApplication application)
      {
         DataTable dtData = Models.Entries.GetData();
         application.Context.Items["data"] = dtData;
         application.Server.Transfer("Views/Entries/View.aspx");
      }

      public void Statistics(HttpApplication application)
      {
         DataTable dtData = Models.Entries.GetData();
         application.Context.Items["data"] = dtData;
         application.Server.Transfer("Views/Entries/Statistics.aspx");
      }
   }
}

Implementazione del Model
Abbiamo gia detto che i Models si occupano di gestire le funzionalità di business. Come possiamo vedere dal codice in basso, entrambi i metodi del controller Entries richiamano il metodo statico GetData() che, nel nostro esempio, restituisce un DataTable. L’oggetto restituito viene memorizzato nella Items Collection dell’oggetto Context in modo da essere poi passato alla View.
Il controllo viene poi trasferito alla view tramite Server.Transfer().

Ecco il codice di App_Code/Models/Entries.cs

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
/// <summary>
/// Entries Model
/// </summary>
namespace Models
{
   public class Entries
   {
      public Entries()
      {
         //
         // TODO: Add constructor logic here
         //
      }

      public static DataTable GetData()
      {
         DataTable dt = new DataTable();
         dt.Columns.Add("titolo", Type.GetType("System.String"));
         dt.Columns.Add("testo", Type.GetType("System.String"));

         DataRow dr = dt.NewRow();
         dr["titolo"] = "Titolo 1";
         dr["testo"] = "Model-View-Controller 1";
         dt.Rows.Add(dr);

         dr = dt.NewRow();
         dr["titolo"] = "Titolo 2";
         dr["testo"] = "Model-View-Controller 2";
         dt.Rows.Add(dr);

         return dt;
      }
   }
}

La visualizzazione dei dati: le views
Dopo aver creato il Model ed il Controller, dobbiamo pensare al problema della visualizzazione.
Come già detto in precedenza, nel nostro esempio esistono due views: la prima (Views/Entries/View.aspx) visualizza i dati utilizzando un repeater e la seconda (Views/Entries/View.aspx) visualizza in un Label il numero di records presenti. Entrambe utilizzano la stessa fonte dati creata dal Model e passata tramite la Items Collection dell’oggetto Context di HttpApplication.
Ecco il codice di Views/Entries/View.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="View.aspx.cs" Inherits="Views_Entries_View" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
   <title>Entries.View</title>
</head>
<body>
   <form id="form1" runat="server">
      <div>
         <asp:Repeater ID="rptView" runat="server">
            <ItemTemplate>
               <b><%# Eval("titolo") %></b><br />
               <%# Eval("testo") %>
            </ItemTemplate>
            <SeparatorTemplate>
               <hr />
            </SeparatorTemplate>
         </asp:Repeater>
      </div>
   </form>
</body>
</html>

ed il relativo code behind

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class Views_Entries_View : System.Web.UI.Page
{
   protected void Page_Load(object sender, EventArgs e)
   {
      DataTable dt = (DataTable)Context.Items["data"];
      this.rptView.DataSource = dt;
      this.rptView.DataBind();
   }
}

Infine, il codice di Views/Entries/Statistics.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Statistics.aspx.cs" Inherits="Views_Entries_Statistics" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
   <title>Entries.Statistics</title>
</head>
<body>
   <form id="form1" runat="server">
      <div>
         Numbers of entries: <asp:Label ID="lblNumber" runat="server"></asp:Label>
      </div>
   </form>
</body>
</html>

ed il code behind

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class Views_Entries_Statistics : System.Web.UI.Page
{
   protected void Page_Load(object sender, EventArgs e)
   {
      DataTable dt = (DataTable)Context.Items["data"];
      this.lblNumber.Text = dt.Rows.Count.ToString();
   }
}

Conclusioni
Come possiamo notare dagli esempi, in entrambe le views create viene eseguito il casting del DataTable nell’evento Page_Load della pagina. La fonte dati è la medesima, creata richiamando la stessa funzione GetData() in entrambi i casi. Siamo quindi riusciti a riutilizzare del codice e, attraverso il pattern MVC, abbiamo reso l’applicazione modulare separando il lavoro dei vari sviluppatori.
Se in un futuro i dati visualizzati dovessero cambiare, non sarebbe necessario modificare anche le relative viste, e viceversa.

Only published comments... May 10 2006, 08:05 PM by DotNetSide Staff
Filed under:

Comments

 

Blog di Bruno Fortunato said:

Quando ci si trova di fronte ad un applicazione piuttosto complessa,
&#232; di fondamentale importanza tenere...
May 10, 2006 11:29 PM
 

Mighell's blog said:

Oggi girovagavo un po’ senza meta tra i settaggi di Community Server (su cui gira .netSide) e sono andato...
June 4, 2006 1:05 PM
 

Mighell's Blog said:

June 4, 2006 1:06 PM
 

Mighell's blog said:


Oggi girovagavo un po’ senza meta tra i settaggi di Community Server (su cui gira .netSide) e sono...
June 4, 2006 1:09 PM
Powered by Community Server (Commercial Edition), by Telligent Systems