Conociendo sobre angular-formly

Revisando sobre las bondades por demás comentadas de AngularJS, me encontré con angular-formly la cual es una librería que tiene como finalidad trasladar mucho del código html a javascript, permitiendo la creación y personalización de plantillas, el uso de validaciones a través de expresiones o incluso ser extendidas mediante la creación de funciones personalizadas, además de una serie de atributos sumamente interesantes. Para tener una idea más clara de lo que se ha mencionado, a continuación muestro como queda reducida la estructura html de la vista unidadForm.html que utilicé en un articulo anterior:

<body ng-app="webApp" ng-controller="MainCtrl as vm">
 <div class="panel panel-primary">
   <div class="panel-heading">Nueva Unidad</div>
     <div class="panel-body">
      <form >
       <formly-form model="vm.unidad" fields="vm.unidadFields">
          <button type="submit" class="btn btn-danger" ng-click="vm.onSubmit()"><i class="glyphicon glyphicon-thumbs-down"></i>Cancelar</button>
          <button type="submit" class="btn btn-primary" ng-click="vm.onSubmit()"><i class="glyphicon glyphicon-thumbs-up"></i>Guardar</button>
       </formly-form>
      </form>
  </div>
</div>

Se puede apreciar como es que el código está en funcionamiento y como el html ha sido trasladado a javascript, ingresando a través del siguiente enlace en JS Bin

Finalmente adjunto unos enlaces donde se puede recabar más información de esta librería:

IV Parte – CRUD con Angularjs + ASP Net 5 Web Api + EF 7

Cuarta  Parte: Elaborando el Cliente en AngularJS

Introducción

En la presente serie de artículos se ha hecho una recopilación de recomendaciones mostrando un modelo de desarrollo a través de una estructura de capas que incluye el uso de Entity Framework 7 Beta 5 con el uso de Clases POCO, DTOs, Repositorios, exposición de funciones a través de un Servicio Web Api y finalmente un cliente Java Script con AngularJS.

Por lo tanto los artículos a incluir en esta serie son los siguientes:

  1. Primera Parte: Creando el Modelo de Datos y DTOs
  2. Segunda Parte: Creando el Contexto y los Repositorios.
  3. Tercera Parte: Creando el Servicio Web API.
  4. Cuarta  Parte: Elaborando el Cliente en AngularJS [Este Artículo]

El proyecto se encuentra publicado Aquí.

Acerca del Proyecto en AngularJS

Con respecto a este último Post, solo se procederá a describir las partes que se consideran más importantes del código, a fin de facilitar su entendimiento, por lo demás esté ha sido publicado en su totalidad a fin de que puedan analizarlo y modificarlo según su criterio. También cabe señalar que este se ha manejado como un Proyecto Web independiente de los Posts anteriores, cuya estructura ha quedado definida de la siguiente manera:

Sin Agregar-Proyecto-02

Configurando la Aplicación

En principio se agregó un elemento denominado  Index.html, donde se hace referencia a los CDN de AngularJS; que en nuestro ejemplo han sido agregados en el body del documento:

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-route.min.js"></script>

Luego procedimos a modificar la etiqueta html del documento insertando la directiva ng-app y colocando un identificador; en nuestro caso webApp, que definirá el ámbito de nuestra aplicación AngularJS :

<html ng-app="webApp" ng-class="{'show': loaded}" ng-init="loaded=true">

Dentro de nuestro documento Index.html, que se erige como el marco principal de esta aplicación, es necesario definir un área donde se inyectarán las sucesivas vistas que componen este proyecto, para tal efecto es necesario usar la directiva ng-view que es el complemento que sirve de enlace para procesar las rutas que serán definidas más adelante.

  <div class="container">
      <div class="ng-view">
      </div>
  </div>

Definiendo la Aplicación AngularJS

Es necesario crear el directorio App y agregar  el documento javascript webApp.js, de aquí en adelante todo documento javascript será inicializado con la directiva use strict, la cual tiene por finalidad forzar el uso de variables declaradas, esto con el objetivo de identificar de manera oportuna errores de tipos no declarados en el proyecto.

"use strict";

var webApp = angular.module('webApp', ['ngRoute', 'ui.bootstrap', 'angularUtils.directives.dirPagination', 'angular-loading-bar']);

webApp.constant("confServer", {
 "apiUrl": "http://localhost:59827/"
});

 webApp.factory("sharedData", function () { return { value: 0 } });

Como se puede apreciar existen tres bloques de código, el primero define un modulo, el cual es el que constituye el punto de inicio de nuestra aplicacion AngularJS, además se agregan ngRoute, ui.bootstrap, etc. que son modulos adicionales que son inyectados dentro de la aplicación, a fin de que puedan ser usados mas adelante ya sea por los servicios o controllers que componen este proyecto. El segundo bloque corresponde a la  definición de una constante, en este caso corresponde a la url donde estarán publicados los servicios Web API que deseamos acceder. Finalmente el tercer bloque es una rutina que nos servira para transferir data entre controladores.

Configurando las rutas del lado del Cliente

AngularJS requiere la configuración de una serie de rutas que hacen posible la navegación dentro del aplicativo, inyectando las vistas requeridas según la ruta solicitada, por ello es necesario agregar un documento javascript y denominarlo clientRoute.js, el cual tendrá la estructura tal como se muestra a continuación.


webApp.config(["$routeProvider", "$locationProvider", "$httpProvider", function ($routeProvider, $locationProvider, $httpProvider) {

 $routeProvider.when("/", {
 templateUrl: "/app/home/html/home.html"
 });

 $routeProvider.when("/unidades", {
 templateUrl: "/app/unidad/html/unidadList.html",
 controller: "unidadController",
 controllerAs: "vm"
 });

 $routeProvider.when("/unidades/unidad", {
 templateUrl: "/app/unidad/html/unidadForm.html",
 controller: "crudUnidadController",
 controllerAs: "vm"
 });

 $routeProvider.when("/unidades/deleteunidad", {
 templateUrl: "/app/unidad/html/deleteUnidadForm.html",
 controller: "crudUnidadController",
 controllerAs: "vm"
 });

 $routeProvider.when("/productos", {
 templateUrl: "/app/producto/html/productoList.html",
 controller: "productoController",
 controllerAs: "vm"
 });

 $routeProvider.when("/productos/producto", {
 templateUrl: "/app/producto/html/productoForm.html",
 controller: "crudProductoController",
 controllerAs: "vm"
 });

 $routeProvider.when("/productos/deleteproducto", {
 templateUrl: "/app/producto/html/deleteProductoForm.html",
 controller: "crudProductoController",
 controllerAs: "vm"
 });

 $routeProvider.when("/about", {
 templateUrl: "/app/home/html/about.html"
 });

 $routeProvider.otherwise({
 redirecTo: '/'
 });

 $locationProvider.html5Mode(true).hashPrefix('!');

}]);

El código mostrado presenta un patrón similar donde apreciamos una ruta consultada en el $routerProvider a traves del método when, el cual en caso de cumplirse la condición define la plantilla a ser inyectada , y según sea el caso incluye el Controller que condiciona su funcionamiento.

Definiendo los Controllers

Los controllers definen el comportamiento de la plantilla que estamos utilizando, estos se encuentran limitados según un determinado ámbito, en este caso la propia plantilla, a continuación observamos el Controller que corresponde a las funciones CRUD de Producto, el cual se denomina crudProductoController, el cual cuenta en su interior con tres funciones Save, Cancel y Destroy, cada una de ellas  hace uso de un servicio que se conecta a nuestro Web API: productoService, adicionalmente existe una llamada directa a GetUnidades que también es parte del mismo servicio, el cual tiene por finalidad cargar la lista de unidades que poblaran el combobox que permite seleccionar la unidad que corresponde al producto.

webApp.controller("crudProductoController", function ($location, sharedData, productoService) {

    var vm = this;
    var unidades = [];

    if (sharedData.value > 0) {
        productoService.getProducto(sharedData.value).then(function (results) {
            vm.producto = results.data;
        },
        function (error) {
            alert(error.data.message);
        });
    }
    else {
        vm.producto = {};
        vm.producto.id = 0;
    }

    function save() {
        var Producto = {
            Id: vm.producto.id,
            Nombre: vm.producto.nombre,
            UnidadId: vm.producto.unidadID,
            Precio: vm.producto.precio
        };

        var promise;

        if (sharedData.value > 0)
            promise = productoService.put(sharedData.value, Producto);
        else
            promise = productoService.post(Producto);

        promise.then(function (pl) {
                    $location.path("/productos");
                },
              function (errorPl) {
                  vm.error = 'Falló al grabar el Producto.', errorPl;
              });
    };

    function destroy() {
        var promise = productoService.delete(sharedData.value);
        promise.then(function (pl) {
                    $location.path("/productos");
                },
              function (errorPl) {
                  $scope.error = 'Falló en la eliminación del Producto', errorPl;
              });
    };

    productoService.getUnidades()
                .then(function (results) {
                    vm.unidades = results.data;
                },
                function (error) {
                    alert(error.data.message);
                });

    function cancel() {
        $location.path("/productos");
    };

    vm.cancel = cancel;
    vm.save = save;
    vm.destroy = destroy;

});

Adicionalmente para el manejo de los productos, se requiere un controller más el cual es denominado productoController, que será responsable de listar lo productos existentes de forma paginada, quién cuenta a su vez con tres funciones definidas edit, destroy y getResultsPage, el primero gestiona la adición o edición de un producto, mientras que el segundo las peticiones de eliminación, finalmente el tercero tiene por finalidad controlar el grupo de productos a mostrar  según sea solicitado.

       webApp.controller('productoController', function ($location, sharedData, productoService) {
    var vm = this;

    vm.productos = [];
    vm.totalUsers = 0;
    vm.usersPerPage = 10;

    getResultsPage(1);

    function edit(Id) {
        sharedData.value = Id;
        $location.path("/productos/producto");
    }

    function destroy(Id) {
        sharedData.value = Id;
        $location.path("/productos/deleteproducto");
    }

    function getResultsPage(newPage) {
        productoService.getPaginado(newPage).then(function (results) {
            vm.productos = results.data.productos;
            vm.totalUsers = results.data.count;
        },
            function (error) {
                alert(error.data.message);
            });
    };

    vm.destroy = destroy;
    vm.edit = edit;
    vm.getResultsPage = getResultsPage;
});

Analizando una Vista

Ahora es momento de analizar una de las vistas gestionada por los controllers; en este caso productoForm.html – la cual es gestionada por  productoController -, en su interior podemos apreciar una serie de directivas que son parte de AngularJS, tal como ng-model, que tiene por finalidad brindar el data binding de doble direccionalidad, una de las características más importantes de este framework, además apreciamos el uso dg-repeat, directiva que nos permite iterar un conjunto de datos, en este caso una lista de unidades que serán mostradas dentro de un combobox. A su vez podemos visualizar el uso de directivas de validación como ng-required que señala que dicho dato es obligatoriamente requerido, y finalmente de directivas que vinculan acciones dentro de la vista con funciones definidas en el controller, tal es el caso de la directiva ng-click la cual vincula dicho evento del botón grabar con la función save() del controller, y la función cancel() con el evento de click del botón cancelar.

<div class="panel panel-primary">

 <div class="panel-heading">Nuevo Producto</div>
 <div class="panel-body">
 <form class="form-horizontal" role="form" id="productoForm" name="productoForm">

 <div class="form-group">
 <label for="id" class="col-sm-3 control-label">ID</label>
 <div class="col-sm-6">
 <input type="text" id="id" name="id" class="form-control" readonly="readonly"
 ng-model="vm.producto.id" placeholder="id" />

 </div>
 </div>
 <div class="form-group">
 <label for="nombre" class="col-sm-3 control-label">Nombre</label>
 <div class="col-sm-6">
 <input type="text" id="nombre" name="nombre" class="form-control"
 ng-model="vm.producto.nombre" ng-required="true" placeholder="nombre" />

 </div>
 </div>
 <div class="form-group">
 <label for="precio" class="col-sm-3 control-label">Precio</label>
 <div class="col-sm-6">
 <input type="text" id="precio" name="precio" class="form-control"
 ng-model="vm.producto.precio" ng-required="true" placeholder="precio" />
 </div>
 </div>

 <div class="form-group">
 <label for="unidad" class="col-sm-3 control-label">Unidad</label>
 <div class="col-md-2">
 <select data-ng-model="vm.producto.unidadID" class="dropdown form-control" required>
 <option data-ng-repeat="unidad in vm.unidades" value="{{unidad.id}}">{{unidad.descripcion}}</option>
 </select>
 </div>
 </div>

 <div class="form-group">
 <div class="col-md-3 col-sm-3 col-sm-offset-3 col-md-offset-3">
 <button id="cancelButton" name="cancelButton" class="btn btn-danger"
 ng-click="vm.cancel()">
 <i class="glyphicon glyphicon-thumbs-down"></i> Cancelar
 </button>
 <button id="submitButton" name="submitButton" class="btn btn-primary"
 ng-click="vm.save()">
 <i class="glyphicon glyphicon-thumbs-up"></i> Guardar
 </button>

 </div>
 </div>
 </form>
 </div>
</div>

A continuación se muestra la vista que gestiona el controller denominado productoController:

Angular Listado Productos

Además mostramos como es la vista que gestiona las altas y bajas de un Producto a través del controller definido como crudProductoController:

Angular Listado Productos

Definiendo el Servicio

Tal como vamos analizando el proyecto basado en AngularJS, este nos brinda la capacidad de limitar las responsabilidades dentro de los diferentes componentes que constituyen el código, así tenemos que los controladores se hacen cargo de la lógica, necesitamos de servicios que se hagan cargo de la invocación a los servicios Web API, en esta caso veremos la funcionalidad de productoService, cuya responsabilidad es establecer la conexión con el Servicio Web API y devolver el resultado de las operaciones celebradas, tal como se aprecia a continuación:


webApp.service("productoService", function ($http, confServer) {

    this.getProductos = function () {
        return $http.get(confServer.apiUrl + "/api/Producto");
    };

    this.getUnidades = function () {
        return $http.get(confServer.apiUrl + "/api/Unidad");
    };

    this.getProducto = function (id) {
        return $http.get(confServer.apiUrl + "/api/Producto/" + id);
    };

    this.getProductoDTO = function (id) {
        return $http.get(confServer.apiUrl + "/api/Producto/dto/" + id);
    };

    this.getPaginado = function (page) {
        return $http.get(confServer.apiUrl + "api/Producto/paging/" + page);
    };

    this.post = function (Producto) {
        var request = $http({
            method: "post",
            url: confServer.apiUrl + "api/Producto/",
            data: Producto
        });
        return request;
    };

    this.put = function (id, Producto) {
        var request = $http({
            method: "put",
            url: confServer.apiUrl + "api/Producto/" + id,
            data: Producto
        });
        return request;
    };

    this.delete = function (id) {
        var request = $http({
            method: "delete",
            url: confServer.apiUrl + "api/Producto/" + id
        });
        return request;
    };
});

Tal como se pudo apreciar el Servicio hace uso de los métodos publicados en nuestra Web API (Get, Post, Put, Delete) a través de una serie de llamadas mediante la función $http, la cual es parte del core de AngularJS y que tiene por finalidad facilitar la comunicación con servicios remotos.

Notas Adicionales:

  • Con respecto a los Controllers y Servicios de gestión de las Unidades, su explicación se ha omitido por tener una naturaleza funcional sumamente similar a los Productos.
  • Se ha obviado la explicación de la mayoría de vistas html debido a que estás no contienen código invasivo que requiera mayor explicación que las usuales directivas propias del código html.

Referencias:

III Parte – CRUD con Angularjs + ASP Net 5 Web Api + EF 7

Tercera Parte: Creando el Servicio Web API

Introducción

En la presente serie de artículos se ha hecho una recopilación de recomendaciones mostrando un modelo de desarrollo a través de una estructura de capas que incluye el uso de Entity Framework 7 Beta 5 con el uso de Clases POCO, DTOs, Repositorios, exposición de funciones a través de un Servicio Web Api y finalmente un cliente Java Script con AngularJS.

Por lo tanto los artículos a incluir en esta serie son los siguientes:

  1. Primera Parte: Creando el Modelo de Datos y DTOs
  2. Segunda Parte: Creando el Contexto y los Repositorios.
  3. Tercera Parte: Creando el Servicio Web API. [Este Artículo]
  4. Cuarta  Parte: Elaborando el Cliente en AngularJS

El proyecto se encuentra publicado Aquí.

Clases Controller

En este paso procedemos a crear el directorio Controllers y agregar tanto la clase ProductoController como UnidadController, ambas heredan de la Clase Controller; a modo de comentario cabe señalar que en ASP Net 5 se ha unificado los Controllers de MVC como los de Web Api, por lo tanto en ambos casos heredan de la misma Clase Controller.

    [Route("api/[controller]")]
 public class ProductoController : Controller
 {
    IProductoRepositorio repo;

    public ProductoController(IProductoRepositorio _repo)
    {
       repo = _repo;
    }

    [HttpGet]
    [Route("paging/{page}")]
    public IActionResult GetPaging(int page)
    {
       ProductsView productoView = repo.GetPaginado(page);
       if (productoView == null)
       {
          return HttpNotFound();
       }
       return new ObjectResult(productoView);
    }

    [HttpGet("{id}")]
     public IActionResult Get(int id)
    {
       ProductoDTO producto = repo.GetProductoDTO(id);
       if (producto == null)
       {
          return HttpNotFound();
       }
       return new ObjectResult(producto);
    }

    [HttpPost]
    public void Post([FromBody]ProductoDTO producto)
    {
      if (!ModelState.IsValid)
         Context.Response.StatusCode = 400; // Bad Request
      else  {
          repo.Add(EfTranslator.TranslateToProducto(producto));
          Context.Response.StatusCode = 201; // Created
      }
    }

 [HttpPut("{id}")]
 public void Put(int id, [FromBody]ProductoDTO producto)
 {
   if (!ModelState.IsValid)
       Context.Response.StatusCode = 400; // Bad Request
   else {
        if (id != producto.Id)
            Context.Response.StatusCode = 400; // Bad Request
        else {
            repo.Update(EfTranslator.TranslateToProducto(producto));
            Context.Response.StatusCode = 200; //OK
        }
   }
 }

 [HttpDelete("{id}")]
 public IActionResult Delete(int id)
 {
    Producto producto = repo.Get(id);

    if (producto == null)
        return HttpNotFound();
    else
         repo.Delete(producto);

   return new HttpStatusCodeResult(204);
 }
}
[Route("api/[controller]")]
 public class UnidadController : Controller
 {
 IUnidadRepositorio repo;

 public UnidadController(IUnidadRepositorio _repo)
 {
 repo = _repo;
 }

 [HttpGet]
 [Route("paging/{page}")]
 public IActionResult GetPaging(int page)
 {
 UnidadView unidadView = repo.GetPaginado(page);
 if (unidadView == null)
 {
 return HttpNotFound();
 }
 return new ObjectResult(unidadView) ;
 }

 [HttpGet]
 public IEnumerable<UnidadDTO> GetAll()
 {
 return repo.GetDtoAll(); ;
 }

 [HttpGet("{id}")]
 public IActionResult Get(int id)
 {
 UnidadDTO unidad = DtoTranslator.TranslateToUnidadDTO(repo.Get(id));
 if (unidad == null)
 {
 return HttpNotFound();
 }
 return new ObjectResult(unidad);
 }

 [HttpPost]
 public void Post([FromBody]UnidadDTO unidad)
 {
 if (!ModelState.IsValid)
 Context.Response.StatusCode = 400;
 else
 {
 repo.Add(EfTranslator.TranslateToUnidad(unidad));
 Context.Response.StatusCode = 201; // Created
 }
 }

 [HttpPut("{id}")]
 public void Put(int id, [FromBody]UnidadDTO unidad)
 {
 if (!ModelState.IsValid)
 Context.Response.StatusCode = 400;
 else
 {
 if (id != unidad.Id)
 Context.Response.StatusCode = 400;
 else
 {

 repo.Update(EfTranslator.TranslateToUnidad(unidad));
 Context.Response.StatusCode = 200; //OK
 }
 }
 }

 [HttpDelete("{id}")]
 public IActionResult Delete(int id)
 {
 Unidad unidad = repo.Get(id);

 if (unidad == null)
 return HttpNotFound();
 else
 repo.Delete(unidad);

 return new HttpStatusCodeResult(204);
 }
 }

Como se puede apreciar ambas clases poseen una similitud en su funcionalidad e implementación, teniendo cada una de ellas una referencia a la Interface de su respectivo repositorio IProductoRepositorio  y IUnidadRepositorio según es el caso, ambas serán instanciadas vía Dependency Injection, a través de la Clase Startup que inicializa el proyecto. Además, tal como se ha podido observar los Servicios Web API implementados exponen las funciones básicas  CRUD, y a su vez de acuerdo a lo que se manifestó al inicio, hemos desacoplado nuestras Clases POCO de los objetos de transferencia de datos, por lo cual los Servicios Web API definidos exportan e importan objetos DTO.

Configurando la Clase Startup

La Clase Startup constituye el punto de entrada nuestra aplicación, la cual se irá mostrando método por método a fin de hacer una breve explicación de su configuración.

Empezando por su constructor el cual recibe un parámetro que lleva dentro de sus propiedades información sobre el aplicativo como la ruta de ubicación del proyecto; cabe señalar que el esquema de configuración ha variado en ASP Net 5, facilitando el uso de archivos .ini, .json, etc., en este caso haremos uso de un archivo config.json, donde primeramente indicamos su ruta de ubicación con la ayuda del parámetro que recibe el constructor, señalamos el nombre del archivo y agregamos las variables. Es necesario hacer recordar que al momento de escribir este Post, ASP Net 5 aun se encuentra en Beta por lo tanto continúan las modificaciones en su estructura, con respecto al parámetro que corresponde a la ruta del proyecto, se han encontrado diversos ejemplos que no se pudieron hacer viables debido al uso distinto de Betas, así que para evitar mayores complicaciones se utilizó y adaptó la más adecuada que  se encontró disponible.

 public class Startup
 {
    public Startup(IHostingEnvironment env)
    {
       Configuration = new Configuration(env.WebRootPath.Substring(0, env.WebRootPath.Length - 8))
             .AddJsonFile("config.json")
             .AddEnvironmentVariables();
 }
  public IConfiguration Configuration { get; private set; }
}

A continuación mostramos la estructura del archivo config.json que se está utilizando, el cual contiene la cadena de conexión de la Base de Datos.

{
  "Data": {
       "DefaultConnection": {
       "Connectionstring": "Server=(localdb)\\ProjectsV12;Database=BDSistema;Trusted_Connection=True;"
      }
   }
}

Configuración de los Servicios

El siguiente paso es configurar los Servicios que se están implementando a través del método ConfigureServices, siendo el primero la configuración de Entity Framework 7, donde indicamos la ruta de la propiedad que está ubicada dentro del archivo de configuración config.json que es utilizada para acceder a la Base de Datos.

 services.AddEntityFramework()
 .AddSqlServer()
 .AddDbContext<SistemaContext>((options =>
 options.UseSqlServer(Configuration.Get("Data:DefaultConnection:ConnectionString"))));

A continuación es necesario configurar Cross-origin resource sharing (CORS), el cual es el mecanismo que posteriormente permitirá acceder a estos servicios desde nuestra aplicación AngularJS, por lo cual se requiere establecer una política, siendo para este ejemplo una Web API de carácter público, se permitirá el acceso desde cualquier origen, método y header, tal como se muestra a continuación.


 services.AddCors();
 services.ConfigureCors(config => config.AddPolicy("Any",
 policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));

Una de las particularidades de AngularJS es el uso de Camel Case en su estructura de escritura, por lo tanto para mantenernos en el standar, procedemos a incluirlo dentro de nuestros parámetros de configuración tal como se muestra a continuación, por lo tanto toda información solicitada a través de los servicios publicados seguirá dicho esquema:


 services.AddMvc().Configure<MvcOptions>(options =>
 {
 options.OutputFormatters
 .Where(x => x.GetType() == typeof(JsonOutputFormatter))
 .Select(f => f as JsonOutputFormatter)
 .First()
 .SerializerSettings
 .ContractResolver = new CamelCasePropertyNamesContractResolver();
 });

 }

Finalmente hacemos uso de Dependency Injection para la instanciación de los repositorios que serán usados en las Clases Controllers, en este caso simplemente indicamos que la Interface IProductoRepositorio será instanciada con la Clase ProductoRepositorio; y de manera análoga IUnidadRepositorio con UnidadRepositorio.


 services.AddScoped<IProductoRepositorio, ProductoRepositorio>();
 services.AddScoped<IUnidadRepositorio, UnidadRepositorio>();

Finalizando con  el Método Configure de la Clase Startup

Finalmente como ultimo paso en el Método Configure, inicializamos la base de Datos haciendo una activación explicita de la instancia, y además procedemos a usar CORS con la política definida y de MVC.

public void Configure(IApplicationBuilder app)
 {
    var sampleData = ActivatorUtilities
                     .CreateInstance<SistemaContextInicialice>(app.ApplicationServices);
        sampleData.InitializeData();
    app.UseCors("Any");
    app.UseMvc();
 }

Comprobación del Servicio Web API

En este caso haciendo uso del Cliente Restful Postman, ejecutamos nuestro proyecto y verificamos la operatividad de los servicios publicados tal como se muestra a continuación:

Api-Producto-12

En este caso hemos hecho referencia al método GET que se encuentra ubicado en la dirección http://localhost:49528/api/Producto/12, proporcionandonos la información del Producto que posee el identificador 12, como se puede apreciar la estructura sigue el estandar Camel Case, y el Producto es mostrado según su equivalente DTO.

Api-Unidad-Paging-1

Finalmente invocamos el método GET que se encuentra ubicado en la dirección http://localhost:49528/api/Unidad/paging/1, el cual nos retorna una lista de objetos  correspondiente a UnidadDTO, además de indicar la cantidad global de los mismos.

Paquetes Utilizados:

  • «Microsoft.AspNet.Mvc»: «6.0.0-beta5-13890»
  • «Microsoft.AspNet.Mvc.Core»: «6.0.0-beta5-13890»
  • «Microsoft.AspNet.Server.IIS»: «1.0.0-beta5-11775»
  • «Microsoft.AspNet.Server.WebListener»: «1.0.0-beta5-12160»
  • «Microsoft.AspNet.StaticFiles»: «1.0.0-beta5-11935»
  • «EntityFramework.SqlServer»: «7.0.0-beta5-13202»
  • «Microsoft.AspNet.Cors»: «1.0.0-beta5-10426»
  • «Microsoft.CSharp»: «4.0.0-beta-22913»,
  • «Microsoft.Framework.ConfigurationModel.Json»: «1.0.0-beta5-11331»
  • «Microsoft.AspNet.Hosting»: «1.0.0-beta5-11820»

Referencias:

II Parte – CRUD con Angularjs + ASP Net 5 Web Api + EF 7

Segunda Parte: Creando el Contexto y los Repositorios

Introducción

En la presente serie de artículos se ha hecho una recopilación de recomendaciones mostrando un modelo de desarrollo a través de una estructura de capas que incluye el uso de Entity Framework 7 Beta 5 con el uso de Clases POCO, DTOs, Repositorios, exposición de funciones a través de un Servicio Web Api y finalmente un cliente Java Script con AngularJS.

Por lo tanto los artículos a incluir en esta serie son los siguientes:

  1. Primera Parte: Creando el Modelo de Datos y DTOs
  2. Segunda Parte: Creando el Contexto y los Repositorios. [Este Artículo]
  3. Tercera Parte: Creando el Servicio Web API.
  4. Cuarta  Parte: Elaborando el Cliente en AngularJS

El proyecto completo se encuentra publicado Aquí.

Agregando un Nuevo Proyecto

Iniciar Visual Studio 2015 y abrir el Proyecto Sistemas.WebModel, desde la opción del menú Archivo, seleccionar Agregar > Nuevo Proyecto. 

En la ventana de dialogo  Nuevo Proyecto, click en Plantillas > Visual C# > Web, y seleccionar Aplicación Web ASP.NET  como proyecto plantilla, nombrarlo como Sistemas.WebApi y dar click en Aceptar.

Sin Agregar-Proyecto-01

Posteriormente se muestra la ventana de dialogo Nuevo Proyecto de ASP.NET y del grupo de Plantillas de vista previa de ASP.Net 5. Seleccionar Web API y dar click en Aceptar.

Sin Agregar-Proyecto-02

Creando el Contexto

Lo que procede ahora es definir el contexto de nuestra base de datos, con el objetivo de mantener nuestras Clases POCO limpias, se hace uso de una declaración explicita del esquema de la Base de Datos a través de Fluent API, tal como se muestra a continuación  :

Crear el directorio Models y agregar la siguiente clase SistemaContext:

public class SistemaContext : DbContext
 {
   public DbSet<Producto> Productos { get; set; }
   public DbSet<Unidad> Unidades { get; set; }

   protected override void OnModelCreating(ModelBuilder modelBuilder)
   {
    modelBuilder.Entity<Producto>().Key(a => a.Id);
    modelBuilder.Entity<Unidad>().Key(a => a.Id);

    modelBuilder.Entity<Producto>().Property(g => g.Id).ForSqlServer(b => b.UseSequence());
    modelBuilder.Entity<Unidad>().Property(a => a.Id).ForSqlServer(b => b.UseSequence());

    modelBuilder.Entity<Producto>().Property(p => p.Precio).ColumnType("Money");
    modelBuilder.Entity<Producto>().Property(p => p.Nombre).MaxLength(80).ColumnType("varchar");

    modelBuilder.Entity<Unidad>().Property(p => p.Descripcion).MaxLength(80).ColumnType("varchar");
 modelBuilder.Entity<Unidad>().Property(p => p.Nemonico).MaxLength(3).ColumnType("varchar");
 }
 }

Básicamente en lo codificado se hace explicito las claves primarias y los tipos  del esquema de datos que se está implementando.

Creando un Repositorio Generico

Con el objetivo de evitar la redundancia de código, se ha codificado una clase genérica, la cual cubre las operaciones básicas de extracción y persistencia de datos.

Crear el directorio Base y primeramente agregar la interface IBaseRepositorio que define los métodos que deberán ser implementados en nuestra clase abstracta BaseRepositorio   :

  public interface IBaseRepositorio<TObject,T>
 {
   TObject Get(int id);
   ICollection<TObject> GetAll();
   TObject Add(TObject t);
   TObject Update(TObject updated);
   void Delete(TObject t);
   int Count();
 }

Luego agregar la clase BaseRepositorio tal como se muestra a continuación:

  public abstract class BaseRepositorio<TObject, T> : IBaseRepositorio<TObject,T> where T : DbContext where TObject : Entity {

 protected T Ctx { get; set; }

 public BaseRepositorio(T ctx)
 {
   Ctx = ctx;
 }

 public TObject Get(int id)
 {
  return Ctx.Set<TObject>().FirstOrDefault(x => x.Id == id);
 }

 public ICollection<TObject> GetAll()
 {
  return Ctx.Set<TObject>().ToList();
 }

 public TObject Add(TObject t)
 {
 Ctx.Set<TObject>().Add(t);
 Ctx.SaveChanges();
 return t;
 }

 public TObject Update(TObject updated)
 {
 if (updated == null)
 return null;

 Ctx.Set<TObject>().Update(updated);
 Ctx.SaveChanges();

 return updated;
 }

 public void Delete(TObject t)
 {
 Ctx.Set<TObject>().Remove(t);
 Ctx.SaveChanges();
 }

 public int Count()
 {
 return Ctx.Set<TObject>().Count();
 }

 public void Dispose()
 {
 Ctx.Dispose();
 }
 }

Como se puede apreciar líneas arriba, la Clase Abstracta BaseRepositorio referencia dos tipos genéricos, TObject el cual tiene por obligación ser descendiente de nuestra Clase Identity (Ejemplo: Producto y Unidad), y el tipo T el cual a su vez es descendiente de DbContext (SistemaContext).

Definiendo las Responsabilidades de los Repositorios

Haciendo un ligero enfoque al patrón de diseño de Inyección de Dependencia, que nos sugiere la alta cohesión y la baja dependencia entre clases, es que se hace un uso intensivo de interfaces, a fin de establecer el vinculo entre ellas a través de sus responsabilidades.

Por lo tanto creamos el directorio Repositorios y  dentro del mismo el directorio Interfaces,  y  procedemos a agregar las Interfaces IProductoRepositorio IUnidadRepositorio según como se muestra :

 public interface IProductoRepositorio : IBaseRepositorio<Producto, SistemaContext>
    {
        ProductoDTO GetProductoDTO(int id);
        IList<ProductoDTO> GetAllProductoDTO();
        ProductsView GetPaginado(int page);
    }
    public interface IUnidadRepositorio : IBaseRepositorio<Unidad, SistemaContext>
    {
        UnidadView GetPaginado(int page);
        List<UnidadDTO> GetDtoAll();
    }

Creando los Repositorios

El siguiente paso es implementar las responsabilidades definidas previamente, para tal efecto es necesario que dentro del directorio Repositorios, se agreguen tanto la clase ProductoRepositorio como la clase UnidadRepositorio:

   public class ProductoRepositorio : BaseRepositorio<Producto, SistemaContext>, IProductoRepositorio
    {
        public ProductoRepositorio(SistemaContext ctx): base(ctx)
        {

        }

        public ProductoDTO GetProductoDTO(int id)
        {
            Producto b = Ctx.Productos
                            .Include(y => y.Unidad)
                            .FirstOrDefault(x => x.Id == id);
            return DtoTranslator.TranslateToProductoDTO(b);
        }

        public IList<ProductoDTO> GetAllProductoDTO()
        {
            var productos = Ctx.Productos
                                .Include(x => x.Unidad)
                                .Select(b => DtoTranslator.TranslateToProductoDTO(b)).ToList();

            return productos;
        }

        public ProductsView GetPaginado(int page)
        {
            var z = Ctx.Productos
                    .Include(x => x.Unidad)
                    .OrderBy(e => e.Nombre)
                    .Skip(10 * (page - 1))
                    .Take(10)
                    .ToList();

            int iTotal = Ctx.Productos.Count();

            ProductsView producto = new ProductsView() {
                                        Productos = z.ToList()
                                                     .Select(b => DtoTranslator.TranslateToProductoDTO(b))
                                                     .ToList(),
                                        Count = iTotal
                                };

            return producto;
        }
    }
 public class UnidadRepositorio : BaseRepositorio<Unidad, SistemaContext>, IUnidadRepositorio
    {
        public UnidadRepositorio(SistemaContext ctx): base(ctx)
        {

        }

        public List<UnidadDTO> GetDtoAll()
        {
            var Unidades = Ctx.Unidades.Select(b => DtoTranslator.TranslateToUnidadDTO(b));
            return Unidades.ToList();
        }

        public UnidadView GetPaginado(int page)
        {
            UnidadView unidad = new UnidadView() {

                Unidades = Ctx.Unidades
                                .OrderBy(e => e.Nemonico)
                                .Skip(10 * (page - 1))
                                .Take(10)
                                .Select(b => DtoTranslator.TranslateToUnidadDTO(b)),
                Count = Ctx.Unidades.Count()
            };

            return unidad;
        }
    }

Como se puede apreciar en ambos repositorios, heredan de la clase BaseRepositorio y de sus respectivas interfaces vinculantes, es aquí donde se hace explicito el tipo de datos que la clase basé será responsable de manejar siendo en un caso Producto y en el otro Unidad, además de especificar el Contexto, en este caso el mismo para ambos SistemaContext.

Inicializando el Contexto de la Base de Datos

Finalmente se procede con la inicialización de la Base de Datos, insertando data de muestra en la Base de Datos, tal como se puede apreciar a través de la clase SistemaContextInicialize :

    public class SistemaContextInicialice
    {
        private SistemaContext _ctx;

        public SistemaContextInicialice(SistemaContext ctx)
        {
            _ctx = ctx;
        }

        public void InitializeData()
        {
            if (_ctx.Database.EnsureCreated()){
                Seed();
            }
        }

        private void Seed()
        {

            if (_ctx.Unidades.Count() == 0){

                var Unidades = new List<Unidad>{
                    new Unidad{Descripcion = "Metros", Nemonico="MTO"},
                    new Unidad{Descripcion = "Kilogramo", Nemonico="KGR"},
                    new Unidad{Descripcion = "Litro", Nemonico="LTO"},
                    new Unidad{Descripcion = "Unidad", Nemonico="UND"},
                };

                Unidades.ForEach(u => _ctx.Unidades.Add(u));

                var Productos = new List<Producto>{
                    new Producto{Nombre = "Cable Electrico", Precio = 10.35M, Unidad = Unidades.Single(a => a.Nemonico == "MTO") },
                    new Producto{Nombre = "Anilina Negra", Precio = 11.35M, Unidad = Unidades.Single(a => a.Nemonico == "KGR") },
                    new Producto{Nombre = "Aceite Ligero", Precio = 14.35M, Unidad = Unidades.Single(a => a.Nemonico == "LTO") },
                    new Producto{Nombre = "Tornillos", Precio = 8.35M, Unidad = Unidades.Single(a => a.Nemonico == "UND") },
                    new Producto{Nombre = "Clavos", Precio = 5.35M, Unidad = Unidades.Single(a => a.Nemonico == "UND") }

                };

                Productos.ForEach(u => _ctx.Productos.Add(u));

                _ctx.SaveChanges();
            }

        }
    }

Como se ha podido apreciar existen dos procesos de validación a fin de evitar errores en la carga de datos, el primero es el método que asegura que la Base de Datos se encuentre creada y el segundo es quien valida que la tabla Unidad se encuentre vacía.

El siguiente artículo tiene por finalidad la construcción del Servicio Web API haciendo uso del esquema de datos y los repositorios, así como también de la configuración de la Clase Startup.

Referencias:

I Parte – CRUD con Angularjs + ASP Net 5 Web Api + EF 7

Primera Parte: Creando el Modelo de Datos y DTOs

Introducción

En la presente serie de artículos se ha hecho una recopilación de recomendaciones mostrando un modelo de desarrollo a través de una estructura de capas que incluye el uso de Entity Framework 7 Beta 5 con el uso de Clases POCO, DTOs, Repositorios, exposición de funciones a través de un Servicio Web Api y finalmente un cliente Java Script con AngularJS.

Por lo tanto los artículos a incluir en esta serie son los siguientes:

  1. Primera Parte: Creando el Modelo de Datos y DTOs [Este Artículo]
  2. Segunda Parte: Creando el Contexto y los Repositorios.
  3. Tercera Parte: Creando el Servicio Web API.
  4. Cuarta  Parte: Elaborando el Cliente en AngularJS

El proyecto se encuentra publicado Aquí.

La estructura de código definida en este tutorial, incluye dos soluciones, la primera consta de dos proyectos; siendo el primer proyecto para el diseño exclusivo del Modelo de Datos (Clases POCO), DTOs e interpretes de traducción entre objetos POCO y DTO; y estando el segundo Proyecto constituido por los Repositorios que hacen uso del  Modelo de Datos y DTOs, así como también de la Web API que expondrá los servcios CRUD. La segunda solución, está destinada a una Aplicación Web que cobija un diseño a través del uso de javascript mediante el Framework AngularJS.

Creando un Nuevo Proyecto

Iniciar Visual Studio 2015. Desde la opción del menú Archivo, seleccionar Nuevo > Proyecto.

En la ventada de dialogo  Nuevo Proyecto, click en Plantillas > Visual C# > Web, y seleccionar Class Library (Package)  como proyecto plantilla. Nombrar el proyecto «Sistemas.WebModel» y dar click en Aceptar.

CreacionProyecto-Paso01

 

Clases POCO

Se ha definido un esquema de datos bastante sencillo, conformado por dos clases: Productos y Unidades, las cuales heredan de una clase abstracta denominada Entity, dicha clase tiene por objetivo facilitar la elaboración de un repositorio genérico.

Crear el directorio Base y agregar la Clase Abstracta Entity, la cual debe constar de una propiedad que se constituirá en el identificador de cada objeto :

public abstract class Entity
{
  public virtual int Id { get; set; }
}

Posteriormente crear el directorio Models y agregar tanto la clase Producto como la clase Unidad según como se muestra a continuación:

    public class Producto : Entity
    {
        public string Nombre { get; set; }
        public decimal Precio { get; set; }

        public int UnidadID { get; set; }

        public virtual Unidad Unidad { get; set; }

        public Producto() { }

        public Producto(int id, string nombre, decimal precio, int unidadID)
        {
            Id = id;
            Nombre = nombre;
            Precio = precio;
            UnidadID = unidadID;
        }
    }
    public class Unidad : Entity
    {
        public string Nemonico { get; set; }
        public string Descripcion { get; set; }

        public Unidad() { }
        public Unidad(int id, string nemonico, string descripcion)
        {
            Id = id;
            Nemonico = nemonico;
            Descripcion = descripcion;
        }
    }

Como se ha podido apreciar, ambas clases heredan de la Clase Entity, la cual tiene por característica principal la propiedad Id que individualiza cada objeto hecho persistente, ambos cuentan con un constructor por defecto y un constructor explicito que será utilizado más adelante en el proceso de traducción de Clases POCO a DTOs y viceversa.

DTOs

La elaboración de estas clases tiene por finalidad desacoplar el modelo de datos de las capas de presentación. En este ejemplo su utilidad puede interpretarse como un proceso redundante, sin embargo en un proyecto real donde la complejidad de las clases del esquema de datos así como de la información transferida entre las capas que componen el proyecto es notoria; se hace necesario establecer un esquema de separación de responsabilidades, a fin de garantizar un adecuado  mantenimiento del proyecto a partir de una más sencilla interpretación de la lectura ordenada del código.

Se procede a crear el directorio DTO y agregar tanto la Clase ProductoDTO como la Clase UnidadDTO según como se muestra a continuación:

    public class ProductoDTO
    {
        public int Id { get; set; }
        public string Nombre { get; set; }
        public decimal Precio { get; set; }
        public int UnidadID { get; set; }
        public string Unidad { get; set; }

        public ProductoDTO() { }

        public ProductoDTO(int id, string nombre, decimal precio, Unidad unidad)
        {
            Id = id;
            Nombre = nombre;
            Precio = precio;
            UnidadID = unidad.Id;
            Unidad = unidad.Nemonico;
        }
    }
    public class UnidadDTO
    {
        public int Id { get; set; }
        public string Nemonico { get; set; }
        public string Descripcion { get; set; }

        public UnidadDTO() {}

        public UnidadDTO(int id, string nemonico, string descripcion)
        {
            Id = id;
            Nemonico = nemonico;
            Descripcion = descripcion;
        }
    }

De manera análoga que las Clases Poco implementadas, las Clases DTO definidas cuentan con un constructor por defecto y otro explicito el cual también servirá para implementar el proceso de traducción entre ambas.

Clases Utilitarias

A continuación vamos a implementar un par de clases que nos servirán de interpretes entre las denominadas Clases POCO y DTO, para mayor información de las mismas ver el proyecto indicado en la primera referencia mostrada al final del artículo.

Crear el directorio Helpers y agregar tanto la clase DtoTranslator como la clase EfTranslator según como se muestra a continuación:

    public static class DtoTranslator
    {
        public static UnidadDTO TranslateToUnidadDTO(Unidad unidad)
        {
            return new UnidadDTO(unidad.Id, unidad.Nemonico, unidad.Descripcion);
        }

        public static ProductoDTO TranslateToProductoDTO(Producto producto)
        {
            return new ProductoDTO(producto.Id, producto.Nombre, producto.Precio, producto.Unidad);
        }

    }
public static class EfTranslator
 {
       public static Unidad TranslateToUnidad(UnidadDTO unidad)
        {
            return new Unidad(unidad.Id, unidad.Nemonico, unidad.Descripcion);
        }

        public static Producto TranslateToProducto(ProductoDTO producto)
        {
            return new Producto(producto.Id, producto.Nombre, producto.Precio, producto.UnidadID);
        }
    }

El desarrollo de estas clases tienen por objetivo hacer de interprete entre las Clases Poco y DTO, en el primer caso transformando de Clases POCO a DTO y en la segunda en viceversa.

Clases de Transferencia de Datos Adicionales

Debemos tener presente que además de las Clases DTO que se han definido, en ciertas circunstancias se requiere información adicional la cual debe ser transferida a las capas superiores, siguiendo nuestro proyecto, enfocamos un caso simple en el que a través de una solicitud de registros esta deba ser expuesta de forma paginada siendo controlado por el servidor la cantidad de envío de los mismos, por lo tanto es lógico inferir que la capa de presentación deberá conocer el total global de los registros existentes, a fin de que posteriormente se pueda controlar la solicitud del grupo de registros requerido.

Ahora es necesario crear el directorio Views y agregar tanto la clase ProductsView como la clase UnidadView según como se muestra a continuación:

    public class ProductsView
    {
        public IList<ProductoDTO> Productos { get; set; }
        public int Count { get; set; }

        public ProductsView() {
            Productos = new List<ProductoDTO>();
            Count = 0;
        }
    }
    public class UnidadView
    {
        public IQueryable<UnidadDTO> Unidades { get; set; }
        public int Count { get; set; }
    }

A este punto hemos concluido con nuestro sencillo esquema de datos, el siguiente paso será la construcción del Contexto de la Base de Datos y los Repositorios.

Paquetes Utilizados:

  • «Microsoft.CSharp»: «4.0.0-beta-22913»
  • «EntityFramework.Core»: «7.0.0-beta5-13266»

Notas Adicionales:

El proyecto ha sido elaborado con ASP Net Beta 5 a fin de actualizar el .Net Runtime se siguieron los siguientes pasos desde dentro del directorio del proyecto:

  1. dnvm upgrade -u -runtime coreclr
  2. dnvm upgrade -u -runtime clr
  3. dnvm upgrade -u -architecture x64 -runtime coreclr
  4. dnvm upgrade -u -architecture x64 -runtime clr
  5. dnvm use 1.0.0-beta5-XXXXX -r clr

Con la finalidad de poder instalar los paquetes de ASP Net 5 que se encuentran en desarrollo, es necesario agregar la ruta https://www.myget.org/F/aspnetvnext/  en los origenes de paquetes que se encuentra ubicado en: Herramientas \ Opciones \ Administrador de Paquetes de Nuget \ Origenes de Paquetes.

Referencias:

Ejemplo de Angularjs + ASP Net 5 Web Api

Se ha desarrollado un sencillo  aplicativo que tiene por objetivo aceptar peticiones de traducción de números a letras a través de una aplicación web de una sola página, se hace uso de Visual Studio 2015 RC y Angularjs.

NumerosLetras

Entre las particularidades es que se está usando ASP Net 5 Beta 5, se ha tomado referencia en la configuración del proyecto los siguientes enlaces:

  1. Getting the ASP.NET 5 samples to work on Windows with the “new” dnvm (and beta 5)
  2. Angular Style Guide

El proyecto se encuentra publicado en Github