Controladores WebApi Dinámicos

Que chulo que está el WebApi este y el MVC y el DDD con cada cosa en su sitio y la inyección de dependencias y los contenedores de inversión de control y separar las cosas en assemblies y que mi capa de aplicación pueda trabajar con web y con desktop y el AOP y los atributos y también el MVVM y al Angular y el binding y yo que sé que más … que guay, que chulo está todo … pero COÑO! es que para que mi base de datos se entere de que un usuario ha hecho click en marcar como favorito un cliente para verlo en su pantalla de bienvenida de la aplicación tengo que hacerme una directiva Angular que me muestre una estrellita en html y pillar el click, ejecutar un método en el controlador, llamar al servicio de favoritos de Angular, realizar una llamada al servidor, que llega al controller selector de WebApi que usa StructureMap para resolverlo y resolver sus dependencias que antes he configurado, se instancia el controlador, se selecciona la acción con el action selector, se llama al método correspondiente, este llama al servicio de aplicación, este llama a los servicios de negocio que por fin usa un repositorio para hacer constar a mi dominio de que Juan, el contable de Estructuras Arjoma, quiere tener como favorito a su cliente Decoraciones Maltasan, si, por que aunque nos hayamos perdido por el camino esto iba de eso, de poner un flag a un cliente o un cliente al flag … Que bonito es diseñarlo todo y que encaje, pero que coñazo es programarlo.

<script>
    $(estrellita).click(function() {         
        $.post("/api/favoritos", $("idCliente").val());     
    });
</script>
public class Favoritos : ApiController
{
    public void Post(int idCliente)
    {
        using (var db = new ApplicationDbContext())
        {
            db.Favoritos.Add(db.Clientes.Get(idCliente));
        }
    }
}

Está claro, no hay que olvidar las reglas pero … hay que romperlas, por supuesto si las rompes que sea cumpliendo alguna regla …

Esta vez vamos a huir (con “i” latina, esta vez si) de escribir controladores WebApi. Hace tiempo cuando vi por primera vez el tema de web api me pregunté si podría meterme por “ahí en medio” para, igual que MVC o WebApi elegían el controlador, seleccionar un servicio de aplicación y llamarlo directamente, para que hacer escala en los controladores?? me preguntaba. Empecé a ver IXXXDescriptors, IXXXSelectors, IXXXInvokers, IXXXYYYYYrs, … y bueno, sin mucho tiempo para eso lo aparqué. La idea siempre me rondó la cabeza e incluso llegué a hacerme un controlador base con get, put, post y delete con generics y poder instanciar el controlador genérico según los parámetros de “route”, pero no todo era REST así que no lo evolucioné mucho.

Hace poco, unos meses, encontré un proyecto en la web que está muy muy chulo, se llama ASP.NET Boilerplate que es de un turco “mu simpático”, el tal Halil. La cosa es que este Boilerplate es algo más que un punto de partida, es un Framework completo para realizar aplicaciones de cualquier escala que da para escribir varios artículos. Lo que más me llamo la atención fue que implementaba los controladores WebApi dinámicos.

La fiesta se puede ver en Web.Api/WebApi/Controllers/Dynamic. En el inicio de tu aplicación puedes exponer tus servicios de aplicación de la siguiente forma:

DynamicApiControllerBuilder.For&lt;ICustomerAppService>("pepitopalotes/customer").Build();
DynamicApiControllerBuilder.For&lt;IInvoiceAppService>("pepitopalotes/invoice").Build();
DynamicApiControllerBuilder.For&lt;IDeliveryNoteAppService>("pepitopalotes/deliverynote").Build();

Lo que hace el dynamic api controller builder, es crearse un DynamicApiControllerInfo por cada servicio de aplicación, le asocia un nombre y recorre los métodos de la interface pasada para “recolectarlos” creando una colección un DynamicApiActionInfo. Luego registra en IoC un DynamicApiController que no contiene ningún código adicional, y usando un “proxy” usa un interceptor para pillar todas las llamadas a ese controlador. Ójo! aquí está la clave para que esto funcione tan limpiamente, el “proxy”, lo que hace es generar un proxy para el DynamicApiController, pero la clave está en que lo extiende con el interface T, el interface de nuestro servicio de aplicación, si no lo hiciera nos va a decir que el método que se quiere ejecutar no se encuentra en el objeto destino.

Como configuración inicial también crea un ruta especifica para estos controladores:

GlobalConfiguration.Configuration.Routes.MapHttpRoute(
                name: "AbpDynamicWebApi",
                routeTemplate: "api/services/{*serviceNameWithAction}"
                );

Una vez montado todo, lo que hace es usar un HttpControllerSelector, que hereda de DefaultHttpControllerSelector, y usa los parámetros de la ruta para ver si se quiere acceder a sus controladores dinámicos o no:

public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
    if (request != null)
    {
        var routeData = request.GetRouteData();
        if (routeData != null)
        {
            string serviceNameWithAction;
            if (routeData.Values.TryGetValue("serviceNameWithAction", out serviceNameWithAction) && DynamicApiServiceNameHelper.IsValidServiceNameWithAction(serviceNameWithAction))
            {
                var serviceName = DynamicApiServiceNameHelper.GetServiceNameInServiceNameWithAction(serviceNameWithAction);
                var controllerInfo = DynamicApiControllerManager.FindOrNull(serviceName);
                if (controllerInfo != null)
                {
                    var controllerDescriptor = new DynamicHttpControllerDescriptor(_configuration, controllerInfo.ServiceName, controllerInfo.Type, controllerInfo.Filters);
                    controllerDescriptor.Properties["__AbpDynamicApiControllerInfo"] = controllerInfo;
                    return controllerDescriptor;
                }
            }
        }
    }
 
    return base.SelectController(request);
}

También intercepta el HttpControllerActionSelector para determinar según los valores de la ruta que método del servicio de aplicación es el que va a ejecutar. Una vez que se ejecuta el interceptor entra en juego …

/// &lt;summary>
/// Interceptor dynamic controllers.
/// It handles method calls to a dynmaic generated api controller and 
/// calls underlying proxied object.
/// &lt;/summary>
/// &lt;typeparam name="T">Type of the proxied object&lt;/typeparam>
public class AbpDynamicApiControllerInterceptor&lt;T> : IInterceptor
{
    /// &lt;summary>
    /// Real object instance to call it's methods when dynamic controller's methods are called.
    /// &lt;/summary>
    private readonly T _proxiedObject;
 
    /// &lt;summary>
    /// Creates a new AbpDynamicApiControllerInterceptor object.
    /// &lt;/summary>
    /// 
    public AbpDynamicApiControllerInterceptor(T proxiedObject)
    {
        _proxiedObject = proxiedObject;
    }
 
    /// &lt;summary>
    /// Intercepts method calls of dynamic api controller
    /// &lt;/summary>
    /// 
    public void Intercept(IInvocation invocation)
    {
        //If method call is for generic type (T)...
        if (typeof(T).IsAssignableFrom(invocation.Method.DeclaringType))
        {
            //Call real object's method
            try
            {
                invocation.ReturnValue = invocation.Method.Invoke(_proxiedObject, invocation.Arguments);
            }
            catch (TargetInvocationException targetInvocation)
            {
                if (targetInvocation.InnerException != null)
                {
                    throw targetInvocation.InnerException;
                }
 
                throw;
            }
        }
        else
        {
            //Call api controller's methods as usual.
            invocation.Proceed();
        }
    }
}

Estaba cantado ¿no?, el interceptor mira si el método que se va a invocar está declarado en el servicio de aplicación, si no lo está lo invoca sin más, pero si está declarado en nuestro servicio de aplicación lo que hace es llamarlo.

Básicamente lo que hay que hacer es:

  • Crear una entrada en la tabla de rutas para no colisionar con la normal de WebApi.
  • Decidir que servicios de aplicación expones.
  • Crear un controller selector para identificar los request que van a tus controladores dinámicos.
  • Crear un action selector para casar el request con tus métodos declarados en tu servicio de aplicación.
  • Interceptar las llamadas a los métodos de tu controlador dinámico.
  • Determinar si invocas al controlador o al servicio de aplicación que expones.

Además como lo tiene hecho Halil Kalkan permite añadir filtros WebApi en la a las acciones, de tal modo que podemos aprovechar la potencia de los filtros en las acciones. También funcionan los model binders, la clave de que tanto los filtros como los model binders funcionen es que define ControllersDescriptors y ActionDescriptors para cada controlador y acción, de forma que no rompe el pipeline de WebApi, y todo se ejecuta como si el controlador estuviera codificado.

Yo para mi proyecto básicamente me lo he fusilado, vamos que lo he pillado y lo he integrado para que funcione con el resto de mi sistema, es cierto que le he añadido cosas, por ejemplo cuando mi servicio de aplicación implementa una interface que tiene métodos básicos para CRUD, lo que hago es que habilito el acceso RESTful, pero uso un route distinto, entonces en el controller builder decido si el controlador que registro para el servicio de aplicación hereda de DynamicApiController o de DynamicRestControler. Luego en el controller selector tengo en cuenta esta nueva ruta y en el antion selector dejo pasar la petición si va por rest, para que sea WebApi quien decida en función del verbo del request. Así pues si puedo hacer estas peticiones:

POST http://server/api/service/pepitopalotes/invoice/removeAll
POST http://server/api/service/pepitopalotes/invoice/getAllOfThisMonth

GET http://server/api/rest/pepitopalotes/invoice
GET http://server/api/rest/pepitopalotes/invoice/2239
POST http://server/api/rest/pepitopalotes/invoice 
DELETE http://server/api/rest/pepitopalotes/invoice/2239

Las dos primeras van por la ruta “dynamicServices” y llaman a los metodos “removeAll()” y “getAllOfThisMonth” y las otras van por la ruta “dynamicRest” y efectúan las correspondientes acciones.

No podemos negar que que está chulo y nos ahorra trabajo, con esto puedes romper tu relación con los controladores WebApi, puedes crear una api RESTful sin programar ni un solo controlador WebApi.

ASP.NET Boilerplate también te brinda la posibilidad de ahorrarte el código cliente, ya sea con JQuery o con Angular, echar un vistazo a esto, para cada uno de los controladores y cada una de las acciones genera el javascripts necesario para invocar a los servicios. Pero no te los genera para que lo incluyas en tu carpeta script, no. Lo que hace es pone a tu disposición un controlador al que puedes llamar para descargar los scripts, él te los genera y ya los tienes para poder usarlos en el cliente. Esta parte no la he usado todavía por que en mi proyecto estoy usando mgCrud, pero no lo descarto para un futuro uso.

Bien en definitiva hemos visto como no escribir controladores sin ser muy chapuzas y sin perder características que nos brinda el framework de WebApi, y en cuanto a ASP.NET Boilerplate creo que es digno de perder un poco de tiempo en ver como está hecha la librería y las cosas que tiene. Si puedo dedicaré un post para hablar de ello.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

Demuestra que no eres un bot *