miércoles, septiembre 26, 2012

Modelo POCO a partir de ADO.NET Entity Data Model

Si decidimos utilizar Entity Framework para diseñar nuestro modelo de datos, tenemos la opción de utilizar el diseñador gráfico (siguiendo el patrón Database-First o Model-First dependiendo de si partimos de una base de datos existente o no), o crearlo “a pelo” (siguiendo el patrón Code-First). Para más info sobre estos patrones ver el post Database first vs Model first vs Code first.

Ambas formas de trabajar tienen sus gracias y sus inconvenientes:

La primera nos simplificará el trabajo de modelado de entidades y conexión con la base de datos, y además nos aporta una representación gráfica que siempre ayuda.

image

Para ello simplemente tenemos que añadir a nuestro proyecto un elemento de tipo ADO.NET Entity Data Model:

image

La segunda forma de trabajar (sin utilizar el diseñador gráfico que proporciona ADO.NET Entity Data Model) nos dará mayor libertad para crear nuestras entidades y basarnos por ejemplo en un modelo de objetos POCO.

imageimage

Una de las posibilidades más interesantes es partir del modelo gráfico de ADO.NET Entity Data Model y a partir de él generar las entidades POCO y el contexto de BDD. Esto se puede conseguir utilizando un generador de código desde el diseñador:

image

image

Como se ve existen multitud de plantillas entre las que elegir, y no es objetivo de este post explicar cada una de ellas. Para generar un modelo de objetos POCO nos pueden servir por ejemplo ADO.NET POCO Entity Generator y ADO.NET DbContext Generator.

ADO.NET Entity Data Model y Data Annotations

Data Annotations permite añadir validaciones al modelo de datos que luego tendrán reflejo en la vista. A continuación mostraré un ejemplo de aplicación hecha con ASP.NET con una única entidad (Team) definida en el modelo de datos y su controlador y vistas creadas por scaffolding (ver post Cómo crear un CRUD (mantenimiento) básico Web utilizando ASP.Net MVC y Entity Framework).

Supongamos que partimos de una entidad Team definida así:

using System.ComponentModel;
using System;
using System.Collections.Generic;  
namespace PruebaEF
{
    public partial class Team
    {
        public int Id { get; set; }        
        public string Name { get; set; }         
        public string City { get; set; }
    }
}

Si creamos el controlador y las vistas mediante scaffolding y ejecutamos el proyecto tendremos (ojo, tendremos que añadir /teams a la URL inicial).


image


image


Si modificamos la entidad añadiendo Data Annotations podemos conseguir algunas validaciones sin necesidad de modificar ni el controlador ni la vista. Veamos el siguiente ejemplo:



using System.ComponentModel;
using
System.ComponentModel.DataAnnotations;
using System;
using
System.Collections.Generic;

namespace
PruebaEF
{
   
   
public partial class Team
    {
        [DisplayName("Identificador")]
       
public int Id { get; set
; }

        [
DisplayName("Nombre del club"
)] 
        [
StringLength
(10)]
       
public string Name { get; set
; }


        [DisplayName("Ciudad")]
       
public string City { get; set
; }
    }
}


El resultado sería el siguiente (sin necesidad de modificar ni el controlador ni las vistas):


image


image


Pero si las entidades las generamos utilizando ADO.NET Entity Data Model tal como se explica en el post anterior (Modelo POCO a partir de ADO.NET Entity Data Model), cada vez que hagamos una modificación desde el diseñador, perderemos las Data Annotations. ¿Y qué podemos hacer al respecto? Tenemos varias opciones aunque ninguna me parece perfecta:



  1. Utilizar el patrón Database-First / Model-First para crear una primera versión del modelo de datos (tal como se explica en el post anterior (Modelo POCO a partir de ADO.NET Entity Data Model) y a partir de ahí eliminar el fichero edmx y pasar a utilizar el patrón Code-First. Siempre podríamos añadir después un elemento de tipo Diagrama de clase para tener una visión gráfica del modelo ;P
  2. Utilizar el generador de código (desde el diseñador) de ADO.NET POCO Entity Generator With Data Annotations. La pega es que sólo sirve para el patrón Database-First y no se pueden añadir o modificar validaciones en el modelo.
  3. Utilizar el patrón Code-First (ver Code-First Development with Entity Framework 4). Si partimos de una BDD existente, podemos utilizar Reverse Engineer Code First (ver Entity Framework Power Tools) para crear las entidades POCO a partir de los objetos de la BDD. Este sistema tampoco me convence porque crea unas clases para el mapeo mediante Fluent-API.
  4. Crear una clase a banda que contenga las anotaciones e indicarlo a la entidad original mediante el atributo MetadataType (ver Validation with the Data Annotation Validators).
  5. Desde el diseñador de ADO.NET Entity Data Model, como generador de código, utilizar ADO.NET Self-Tracking Entity Generator + Data Annotations, que permite añadir validaciones en el diseñador (propiedad Validations de cada una de las entidades). La pega es que esto no genera clases POCO (que es lo que yo estaba buscando).
  6. Utilizar Portable Extensible Metadata (ver Introducing the Portable Extensible Metadata), aunque con esto tampoco conseguiríamos tener clases POCO con anotaciones.

jueves, septiembre 13, 2012

Cómo consumir un Web Service desde PHP

A continuación un ejemplo sencillo en php que consume un Web Service en este caso de Navision (aunque eso es lo de menos):

<?php
define('USERPWD','192.168.0.150\Administrador:xxx'); // Sustituir xxx por la contraseña del servidor 
include("NTLMStream.php");  
include("NTLMSoapClient.php");
 
stream_wrapper_unregister('http');
stream_wrapper_register('http','NTLMStream'or die("Fallo al registrar protocolo");
$pageURL = 'http://192.168.0.150:7047/dynamicsnav/ws/Autoequip/Codeunit/NEUMALIAWS'; // El Web Service 
$params = array();
$params["param1"] = "2855519MDIAMARISVR"; // param1 es el nombre el primer parámetro.
$params["param2"] = "2";
$params["param3"] = "1024"; 
$client = new NTLMSoapClient($pageURL);
stream_wrapper_restore('http');
$result = $client->ImportPedNeumalia($params); // El método del Web Service 
$numped = $result->return_value;
echo 'Pedido creado: '.$numped;
die();
?>

 
Los módulos php incluídos son:

NTLMStream.php

<?php
class NTLMStream 
{ 
    private $path; 
    private $mode; 
    private $options; 
    private $opened_path; 
    private $buffer; 
    private $pos; 
    /** 
     * Open the stream 
      * 
     * @param unknown_type $path 
     * @param unknown_type $mode 
     * @param unknown_type $options 
     * @param unknown_type $opened_path 
     * @return unknown 
     */ 
    public function stream_open($path, $mode, $options, $opened_path) { 
        $this->path = $path; 
        $this->mode = $mode; 
        $this->options = $options; 
        $this->opened_path = $opened_path; 
        $this->createBuffer($path); 
        return true; 
    } 
    /** 
     * Close the stream 
     * 
     */ 
    public function stream_close() { 
        curl_close($this->ch); 
    } 
    /** 
     * Read the stream 
     * 
     * @param int $count number of bytes to read 
     * @return content from pos to count 
     */ 
    public function stream_read($count) { 
        if(strlen($this->buffer) == 0) { 
            return false; 
        } 
        $read = substr($this->buffer,$this->pos, $count); 
        $this->pos += $count; 
        return $read; 
    } 
    /** 
     * write the stream 
     * 
     * @param int $count number of bytes to read 
     * @return content from pos to count 
     */ 
    public function stream_write($data) { 
        if(strlen($this->buffer) == 0) { 
            return false; 
        } 
        return true; 
    } 
    /** 
     * 
     * @return true if eof else false 
     */ 
    public function stream_eof() { 
        return ($this->pos > strlen($this->buffer)); 
    } 
    /** 
     * @return int the position of the current read pointer 
     */ 
    public function stream_tell() { 
        return $this->pos; 
    } 
    /** 
     * Flush stream data 
     */ 
    public function stream_flush() { 
        $this->buffer = null; 
        $this->pos = null; 
    } 
    /** 
     * Stat the file, return only the size of the buffer 
     * 
     * @return array stat information 
     */ 
    public function stream_stat() { 
        $this->createBuffer($this->path); 
        $stat = array( 
            'size' => strlen($this->buffer), 
        ); 
        return $stat; 
    } 
    /** 
     * Stat the url, return only the size of the buffer 
     * 
     * @return array stat information 
     */ 
    public function url_stat($path, $flags) { 
        $this->createBuffer($path); 
        $stat = array( 
            'size' => strlen($this->buffer), 
        ); 
        return $stat; 
    } 
    /** 
     * Create the buffer by requesting the url through cURL 
     * 
     * @param unknown_type $path 
     */ 
    private function createBuffer($path) { 
        if($this->buffer) { 
            return; 
        } 
        $this->ch = curl_init($path); 
        curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); 
        curl_setopt($this->ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 
        curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM); 
        curl_setopt($this->ch, CURLOPT_USERPWD, USERPWD); 
        $this->buffer = curl_exec($this->ch); 
        $this->pos = 0; 
    } 
}
?>
 
NTLMSoapClient.php

<?php
class NTLMSoapClient extends SoapClient { 
    function __doRequest($request, $location, $action, $version) { 
        $headers = array( 
            'Method: POST', 
            'Connection: Keep-Alive', 
            'User-Agent: PHP-SOAP-CURL', 
            'Content-Type: text/xml; charset=utf-8', 
            'SOAPAction: "'.$action.'"', 
        ); 
        $this->__last_request_headers = $headers; 
        $ch = curl_init($location); 
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 
        curl_setopt($ch, CURLOPT_POST, true ); 
        curl_setopt($ch, CURLOPT_POSTFIELDS, $request); 
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 
        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM); 
        curl_setopt($ch, CURLOPT_USERPWD, USERPWD); 
        $response = curl_exec($ch); 
        return $response; 
    } 
 
    function __getLastRequestHeaders() { 
        return implode("\n", $this->__last_request_headers)."\n"; 
    } 
}
?> 
 
 
NOTA: Debemos asegurarnos que la extensión php_soap está habilitada; de lo contrario se producirá un error similar a este:
 
 

En el caso de tener WAMP como servidor de Apache/PHP/MySQL, esta opción la encontraremos dentro de las PHP_Extensions.