Sempre più spesso, nelle moderne applicazioni web, il codice Javascript di una pagina ha la necessità di accedere ad una webapi. Questo è certamente il più semplice metodo per attivare la pagina caricando informazioni dal server senza necessariamente effettuare il refresh dell'intera pagina. La chiamata può essere necessaria per riempire una semplice dropdowlist il cui contenuto dipende da qualche altro valore impostato in una form, piuttosto che una lista di risultati di una ricerca. Qualunque sia il contenuto, l'operazione è sempre quella di richiemare un metodo di una webapi e in seguito alla risposta deserializzare il json per popolare l'interfaccia utente.

In casi come questi ho trovato molto utile usare una classe proxy, mimando quello che avviene in C# quando si interroga un servizio WCF. La cosa interessante è che le WebApi mettono a disposizione di un ApiExplorer che è in grado di fare l'inspect della api stessa e restituire tutti i dettagli quali i metodi, i loro parametri, tipi di ritorno etc.. Grazie ad esso è possibile scrivere il seguente metodo:

   1: public abstract class ApScriptableController : ApiController
   2: {
   3:     /// <summary>
   4:     /// Il metodo restituisce lo script Javascript che consente di interrogare il controller
   5:     /// </summary>
   6:     /// <returns>Ritorna il </returns>
   7:     [HttpGet]
   8:     public HttpResponseMessage GetScript()
   9:     {
  10:         ApiExplorer explorer = new ApiExplorer(this.Configuration);
  11:  
  12:         StringBuilder builder = new StringBuilder();
  13:  
  14:         builder.Append("var __extends = this.__extends || function (d, b) {");
  15:         builder.Append("    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];");
  16:         builder.Append("    function __() { this.constructor = d; }");
  17:         builder.Append("    __.prototype = b.prototype;");
  18:         builder.Append("    d.prototype = new __();");
  19:         builder.Append("};");
  20:  
  21:         var actions = (from api in explorer.ApiDescriptions
  22:                        where  api.ActionDescriptor.ControllerDescriptor.ControllerType.IsAssignableFrom(this.GetType())
  23:                        orderby api.ActionDescriptor.ActionName ascending
  24:                        select api).ToArray();
  25:  
  26:         if (actions.Count() > 0)
  27:         {
  28:             string controllerName = actions.First().ActionDescriptor.ControllerDescriptor.ControllerName;
  29:             controllerName = controllerName.Substring(0, 1).ToUpper() + controllerName.Remove(0, 1);
  30:  
  31:             builder.AppendFormat("var {0}Proxy = (function (_super) {{", controllerName);
  32:             builder.AppendFormat("    __extends({0}Proxy, _super);", controllerName);
  33:             builder.AppendFormat("    function {0}Proxy(baseUri, accessToken, context) {{", controllerName);
  34:             builder.Append("        _super.call(this, baseUri, accessToken);");
  35:             builder.Append("        this.context = context;");
  36:             builder.Append("    }");
  37:  
  38:             foreach (var item in actions)
  39:             {
  40:                 builder.AppendFormat("    {1}Proxy.prototype.{0} = function (value) {{", item.ActionDescriptor.ActionName, controllerName);
  41:                 builder.AppendFormat("        return _super.prototype.callApi.call(this, '{0}', value, this.context);", item.RelativePath);
  42:                 builder.Append("    };");
  43:             }
  44:  
  45:             builder.AppendFormat("    return {0}Proxy;", controllerName);
  46:             builder.Append("})(sys.net.Proxy);");
  47:         }
  48:  
  49:         var response = new HttpResponseMessage(HttpStatusCode.OK);
  50:         response.Content = new StringContent(builder.ToString(), Encoding.UTF8, "text/javascript");
  51:         return response;
  52:     }
  53: }

Il codice rappresenta una classe base da utilizzare per i controller delle WebApi. Essa implementa un metodo GetScript che non fa altro che ispezionare i metodi della api stessa e genera di conseguenza un codice Javascript che rappresenterà il proxy della WebApi. In coda al metodo, le ultime righe cambiano il content-type della risposta impostandolo e text/javascript. In questo modo sarà possibile fornire l'url della api ad un tag script e di conseguenza far leggere il codice e caricarlo nel browser.

   1: <script src="~/api/Home/getscript"></script>

L'ultimo tassello di questo piccolo puzzle è una classe Typescript che rappresenta la base per il proxy Javascript. Il codice generato infatti fa uso delle funzioni e dei tipi presenti in questo breve snippet.

   1: module sys.net
   2: {
   3:     // represents a base for api responses
   4:     export interface IApiResponseBase 
   5:     {
   6:         HasErrors: boolean
   7:         Message: string;
   8:     }
   9:  
  10:     // represents a successful response from an api
  11:     export interface IApiResponse<T> extends IApiResponseBase
  12:     {
  13:         Result: T;
  14:     }
  15:  
  16:     export class Proxy 
  17:     {
  18:         // creates and initializes the proxy with the given base uri
  19:         constructor(private baseUri: string, private accessToken: string)
  20:         { }
  21:  
  22:         // calls a generic API with the specified route and argument
  23:         public callApi<T>(route: string, argument: any, context?: string): JQueryPromise<T>
  24:         {
  25:             var uri = this.baseUri + route;
  26:  
  27:             return $.Deferred(
  28:                 (deferred: JQueryDeferred<T>) =>
  29:                 {
  30:                     $.ajax({
  31:                         url: uri,
  32:                         type: 'POST',
  33:                         data: JSON.stringify(argument),
  34:                         contentType: 'application/json; charset=utf-8',
  35:                         beforeSend: (xhr) =>
  36:                         {
  37:                             xhr.setRequestHeader('Authorization', 'Bearer ' + this.accessToken);
  38:  
  39:                             if (context != undefined)
  40:                                 xhr.setRequestHeader('X-Tsf-Context', context);
  41:                         }
  42:                     })
  43:                         .done(
  44:                         (retVal: IApiResponse<T>) =>
  45:                         {
  46:                             if (retVal.HasErrors)
  47:                                 deferred.reject(retVal);
  48:                             else
  49:                                 deferred.resolve(retVal.Result);
  50:                         })
  51:                         .fail((err: any) => deferred.reject(this.mapToError(uri, err)));
  52:                 }).promise();
  53:         }
  54:  
  55:         // maps any error information to a common object
  56:         private mapToError(uri: string, err: any): IApiResponseBase
  57:         {
  58:             return <IApiResponseBase>
  59:                 {
  60:                     Message: 'Api "' + uri + '" reported an error: ' + err.statusText,
  61:                     HasErrors: true
  62:                 };
  63:         }
  64:     }
  65: }

Una volta che il codice sia caricato nell'ordine corretto, avremo a disposizione una classe che riporta il nome del controller seguito dalla parola Proxy. Perciò se il controller ha il nome "Home" il proxy sarà "HomeProxy".

   1: var proxy = new HomeProxy(sys.Application.ServiceUri, sys.Application.AccessToken, sys.Application.Context);
   2: proxy.GetValuesForList()
   3:     .done((result) =>
   4:      {
   5:         // TODO: process here
   6:      });

Il proxy fa uso delle promise di jQuery perciò il suo utilizzo è molto semplice e gestisce perfettamente la asincronicità.


Aggiungi Commento