Riutilizzo Logica per richieste AJAX e richieste non AJAX



A cura di Giovanni Arcifa,


Riutilizzo Logica per richieste  AJAX e richieste non AJAX

Il pattern Model-View-Controller utilizzato dal Frameword ASP.NET MVC permette una forte separazione dei compiti per garantire che i singoli componenti siano isolati l'uno dall'altro. Analizziamo il codice sotto riportato che mostra un esempio di Controller con 3 action che mi restituiscono una view, una partial view ed un Json.

Esempio 1. Tre modi diversi per recuperare un utente dal nostro Controller UsersController.cs

 

public class UsersController : Controller

    {

        public ActionResult User(long id)

        {

            var db = new DataContext();

            var user = db.Users.Find(id);

            return View("User", user);

        }

 

        [HttpPost]

        public ActionResult JsonUser(long id)

        {

            var db = new DataContext();

            var user = db.Users.Find(id);

            return Json(auction);

        }

 

        public ActionResult PartialUser(long id)

        {

            var db = new DataContext();

            var user = db.Users.Find(id);

            return PartialView("User", user);

        }

    }

 

La logica applicativa MVC se fatta bene non dovrebbe essere legata ad una vista in particolare. Allora perché ho dovuto creare 3 action che eseguono la stessa logica e che si differenziano solo per il modo in cui i dati vengono restituiti al browser??

In seguito vedremo come rendere la logica indipendente dal tipo di ActionResult che dovrà essere restituita.

 

 

Rispondere alle richieste Ajax

Per evitare questa duplicazione di logica e di codice, ASP.NET MVC fornisce il metodo Request.IsAjaxRequest(), che ci permette di sapere se la richiesta è una richiesta AJAX. Possiamo quindi utilizzare queste informazioni per determinare quale formato il richiedente si aspetta di ricevere e quindi quale ActionResult dovremmo scegliere per generare tale risposta. Il metodo Request.IsAjaxRequest () è molto semplice: esso si limita a controllare che nel HTTP header ci sia il valore X-Requested-With: XMLHttpRequest, che viene automaticamente aggiunto dalla maggior parte dei browser e framework AJAX. Quindi se abbiamo bisogno di ingannare ASP.NET MVC per fargli credere che la richiesta sia AJAX sarà sufficiente aggiungere nel HTTP header la voce X-Requested-With: XMLHttpRequest Nel codice sotto illustro l’utilizzo del metodo Request.IsAjaxRequest(). Ho fuso le 2 action viste in precedenza in un'unica action e se la richiesta è una richiesta AJAX, utilizzarò il metodo PartialView() per restituire un PartialViewResult, e in caso contrario, utilizzarò il metodo View() per restituire un ViewResult:

 

    public ActionResult User(long id)

    {

        var db = new DataContext();

        var user = db.Users.Find(id);

        if (Request.IsAjaxRequest())

            return PartialView("User", user);

        return View("User", user);

   }

 

In questo modo con una singola Action sono in grado di rispondere sia a richieste HTTP GET “Normali” che a richieste di tipo AJAX, restituendo la vista appropriata. Utilizzando la stessa logica possiamo gestire le richieste JSON, purtroppo però, ASP.NET non fornisce un metodo utile, come Request.IsAjaxRequest() per determinare se il richiedente si aspetta dati JSON. Tuttavia, con un pò di creatività possiamo noi stessi facilmente implementare questa logica. Inizierò con la soluzione più semplice: l'aggiunta di un parametro personalizzato nella request per indicare che la richiesta aspetta dati JSON in risposta. Ad esempio, possiamo andare alla ricerca di un parametro chiamato format all’interno della request e ogni volta che il valore di questo parametro è "json" restituire un JsonResult:

 

   public ActionResult User(long id)

    {

        var db = new DataContext();

        var user = db.Users.Find(id);

        if (string.Equals(request["format"], "json"))

            return Json(user);

        return View("User", user);

    }

 

In questo modo possiamo richiedere le informazioni di un utente in formato JSON aggiungendo la query string “?format=json” e quindi la richiesta diventerà /users/user/18?format=json . Possiamo anche fare un ulteriore passo avanti , spostando questa logica nel metodo di estensione JsonRequestExtensions, in modo da avere a disposizione un metodo simile al Request.IsAjaxRequest ( ) visto in precedenza. Metodo di estensione :

 

using System;

using System.Web;

public static class JsonRequestExtensions

{

    public static bool IsJsonRequest(this HttpRequestBase request)

    {

        return string.Equals(request["format"], "json");

    }

}

 

Con il metodo di estensione IsJsonRequest , il codice visto in precedenza  può essere ripulito come segue:

 

public ActionResult User(long id)

    {

        var db = new DataContext();

        var user = db.Users.Find(id);

        if (Request.IsJsonRequest())

            return Json(user);

        return View("User", user);

    }

 

 

Applicando la stessa logica tra più action controller

Se uniamo gli approcci fin qui analizzati otteniamo un action molto flessibile che sarà in grado di produrre ActionResult differenti utilizzando la stessa logica dell'applicazione.

Diamo un occhiata alla versione ottimizzata della classe UsersController.cs: Esempio 2. Un unica action che mi restituisce una view, una partial view, o la risposta JSON, a seconda di come viene effettuata la richiesta.

 

public class UsersController : Controller

{

    public ActionResult User(long id)

    {

        var db = new DataContext();

        var user = db.Users.Find(id);

        // Respond to AJAX requests

        if (Request.IsAjaxRequest())

            return PartialView("User", user);

        // Respond to JSON requests

        if (Request.IsJsonRequest())

            return Json(user);

        // Default to a "normal" view with layout

        return View("User", user);

    }

}

 

Il codice scritto è flessibile, ma il fatto che esso sia definito all’interno di una sola action significa che le altre action dello stesso controller non saranno in grado di sfruttare la stessa logica. Fortunatamente, ASP.NET MVC offre il meccanismo perfetto per riutilizzare la logica su più actions del Controller tramite gli action filters. Per spostare questa logica in un action filter in modo da poter essere riutilizzata in altre action del Controller , dobbiamo creare una classe che implementi il tipo System.Web.Mvc.ActionFilterAttribute e sovrascriva il metodo OnActionExecuted(). Il framework ASP.NET MVC chiamerà il metodo OnActionExecuted al completamento del metodo del action.

 

public class MultipleResponseFormatsAttribute : ActionFilterAttribute

{

    public override void OnActionExecuted(ActionExecutedContext filterContext)

    {

        // We will add the logic here

    }

}

 

In questo modo spostiamo la logica dall'action controller user in questa nuova classe in modo da sostituire il risultato dell'action restituito dal controller quando la richiesta è AJAX o JSON:

 

using System;

using System.Web.Mvc;

public class MultipleResponseFormatsAttribute : ActionFilterAttribute

{

    public override void OnActionExecuted(ActionExecutedContext filterContext)

    {

        var request = filterContext.HttpContext.Request;

        var viewResult = filterContext.Result as ViewResult;

        if (viewResult == null)

            return;

        if (request.IsAjaxRequest())

        {

            // Replace result with PartialViewResult

            filterContext.Result = new PartialViewResult

            {

                TempData = viewResult.TempData,

                ViewData = viewResult.ViewData,

                ViewName = viewResult.ViewName,

            };

        }

        else if (Request.IsJsonRequest())

        {

            // Replace result with JsonResult

            filterContext.Result = new JsonResult

            {

                Data = viewResult.Model

            };

        }

    }

}

 

Ora si può facilmente applicare il MultipleResponseFormatsAttribute action filter per qualsiasi action del controller nel vostro sito web, questo permette istantaneamente di restituire una view , una partial view , o la risposta JSON , a seconda di come viene effettuata la richiesta. Affinché essa venga eseguita in corrispondenza di una action, è sufficiente decorare quest'ultima con l'attributo che abbiamo creato

 

[MultipleResponseFormats]

public ActionResult User(long id)

{

    var db = new DataContext();

    var user = db.Users.Find(id);

    return View("User", user);

 

}

Eventualmente è anche possibile specificarla a livello di controller, così che tale impostazione valga per tutte le action che gli appartengono:

 

[MultipleResponseFormats]

public class UserController : Controller

{

    // .. codice qui ..

}