lunes, 1 de noviembre de 2010

Reutilización de informes en entornos de aplicación orientadas a productos

 

Merece la pena escribir este post, ya que me ha dado algún que otro dolor de cabeza hasta llegar a la solución. Vamos a jugar un ratito a pasar como parámetro la cadena de conexión para visualizar un informe mediante Report Viewer 2010.

 

Generando el informe con Report Builder 3.0

Esta herramienta, ya mencionada en posts anteriores, nos permitirá crear la definición del informe y obtener un archivo en formato RDL (Report Definition Language) se trata de un fichero estándar XML que nos permite cotillear las tripas y ver lo que ocurre en la “sala de máquinas”.

Primeramente tendremos que incluir un nuevo parámetro en el informe al que llamaremos BaseDatos, lo marcaremos como oculto, guardamos y salimos de Report Builder.

 

Modificando el RDL

Partiendo de cualquier informe generado previamente con dicha herramienta, solo nos queda guardar el archivo (en el escritorio de Windows por ejemplo) y usar el bloc de notas para ver en detalle la definición del mismo. Tendremos que localizar al principio del documento la sección ConnectString del elemento DataSource y modificarlo de la siguiente manera:

<?xml version="1.0" encoding="utf-8"?>
<Report xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner"xmlns:cl="http://schemas.microsoft.com/sqlserver/reporting/2010/01/componentdefinition"xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition">
  <AutoRefresh>0</AutoRefresh>
  <DataSources>
    <DataSource Name="GIE">
      <ConnectionProperties>
        <DataProvider>SQL</DataProvider>
        <ConnectString>="Password=XXXX;Persist Security Info=True;User ID=XXXX;Data Source=XXXX;Initial Catalog=" &amp; Parameters!BaseDatos.Value</ConnectString>
      </ConnectionProperties>
      <rd:DataSourceID>2bf79f31-84cc-4a46-b53d-93fbf236f92d</rd:DataSourceID>
    </DataSource>
  </DataSources>

Lo que hemos modificado con respecto al original ha sido la inclusión de la expresión Parameters!BaseDatos.Value embebida en la cadena de conexión delDataSource

Os dejo un enlace de interés donde podréis indagar mas sobre estas cuestiones:http://msdn.microsoft.com/en-us/library/ms156450.aspx#Expressions

Hay otro punto importantísimo y que también me costo un buen rato, no se porque razón la definición del parámetro BaseDatos tiene que ser el primero de la colección, podemos observar también su definición en el propio documento RDL casi al final del archivo:

<ReportParameters>
  <ReportParameter Name="BaseDatos">
    <DataType>String</DataType>
    <Prompt>BaseDatos</Prompt>
    <Hidden>true</Hidden>
  </ReportParameter>
  <ReportParameter Name="IdUsuarioComercial">
    <DataType>Integer</DataType>
    <DefaultValue>
      <DataSetReference>
        <DataSetName>Comerciales</DataSetName>
        <ValueField>IdUsuario</ValueField>
      </DataSetReference>
    </DefaultValue>
    <Prompt>Comercial</Prompt>
    <ValidValues>
      <DataSetReference>
        <DataSetName>Comerciales</DataSetName>
        <ValueField>IdUsuario</ValueField>
        <LabelField>Comercial</LabelField>
      </DataSetReference>
    </ValidValues>
    <MultiValue>true</MultiValue>
  </ReportParameter>

 

Publicación del informe mediante Report Manager

Ya sabéis que Report Manager es la herramienta web que nos permite gestionar y publicar informes cuando nos basamos en RemoteMode, solo tendremos que asegurarnos de indicar que no se necesitan credenciales para la visualización del informe, os dejo la pantalla donde se parametriza:

report_manager

 

Report Viewer en Visual Studio

Os dejo el código a implementar desde Visual Studio con la llamada al informe:

if (!IsPostBack)
{
    ReportViewer1.ProcessingMode = ProcessingMode.Remote;
    ServerReport serverReport = ReportViewer1.ServerReport;
    serverReport.ReportServerUrl = new Uri(Properties.Settings.Default.ReportServer);
    serverReport.ReportPath = Request["ReportPath"].ToString();
    ReportParameter rp = new ReportParameter("BaseDatos",”XXXXX”);
    serverReport.SetParameters(rp);

}

Esto nos permitirá reutilizar informes que compartan diferentes bases de datos con misma estructura, imprescindible cuando se trata por ejemplo de desarrollo de aplicaciones orientadas a productos donde existe un único repositorio de ejecución de aplicación pero diferentes bases de datos (una para cada cliente).

Como inconveniente es que no podéis hacer ingeniería inversa, si abres el informe (ya modificado desde el bloc de notas anteriormente) mediante Report Manager al volverlo a publicar o grabar destroza la expresión de paso de parámetros de la cadena de conexión. Se lo comunicaremos a Redmond para que lo corrijan Guiño

viernes, 2 de julio de 2010

Comunicando Android y Web Services .NET

Android_NET

En esta ocasión el objetivo del post será el de comunicar dos tecnologías totalmente complementarias: Google Android yMicrosoft .NET, para ello nos basaremos en el protocolo SOAP, más concretamente en Web Services desde el lado Microsoft y la creación de un componente personalizado que permitirá consumir y acceder a los datos del modelo de negocio desde un dispositivo Android.

Estudiaremos los casos más sencillos para comunicar un servicio web y un dispositivo móvil, desde la interacción de datos simples hasta estructuras de datos complejas definidos por el desarrollador.

En el ejemplo que vamos a abordar el sistema comprobará un par usuario y contraseña y nos informará si el usuario en cuestión se encuentra registrado dentro del repositorio de members de nuestra aplicación. La arquitectura que entrará en juego será la siguiente, partiremos primeramente de un servicio web que será el encargado de albergar la lógica de comprobación y autenticación de usuario junto con un cliente, en este caso un dispositivo móvil que será el encargado de consumir dicho servicio e informar al usuario del resultado de la operación.

 

Definiendo el Web Services .NET

No profundizaremos demasiado en este aspecto puesto que no es el objetivo del post, partiremos de una clase de tipo “System.Web.Services.WebService” en la que incluiremos un método “EsUsuarioValido” que nos devolverá un objeto de tipo “Usuario” si la autenticación se ha realizado con éxito. Nos debería de quedar algo así:

[WebMethod]
public Usuario EsUsuarioValido(string username, string password)
{
    Usuario reply = null;

    using (Contexto ctx = new Contexto())
    {
        reply = new CADUsuario(ctx).ObtenerUsuario(username, password);
    }

    return reply;
}

La clase Usuario nos debería quedar algo así:

namespace WebService.Entidades
{
    public class Usuario
    {
        …

        private string username;

        public string Username
        {
            get { return username; }
            set { username = value; }
        }
        private string password;

        public string Password
        {
            get { return password; }
            set { password = value; }
        }

        …

    }
}

Ya solo nos quedaría realizar el despliegue y publicarlo en un sitio visible para ser consumido desde cualquier cliente. El siguiente paso que estudiaremos será el desarrollo de la capa del cliente, encargada de consumir y comunicarse con nuestro servicio web, en este caso el dispositivo móvil elegido es Android sobre la versión1.6 aunque también funcionaría en versiones superiores 2.2

 

Consumiendo el servicio web .NET desde Android

Para empezar sera necesario descargar el paquete ksoap2-android de la siguiente dirección: http://code.google.com/p/ksoap2-android/downloads/list, ya solo queda incluirlo en nuestra aplicación Android (http://developer.android.com/guide/index.html, si os queréis iniciar está bastante completa), en el caso de estar trabajando con el IDE Eclipse nos situamos con el botón derecho sobre las propiedades del proyecto y añadimos la referencia bajo la opción Java Build Path.

En la estructura de nuetro proyecto nos encontraremos dos clases, una de ellas será Usuario y otra a la que llamaremos Seguridad. La primera solo la utilizaremos como objeto de transporte de datos y la segunda será la encargada de manejar y contendrá los métodos propios de acceso a la autenticación, actuará de proxy en la comunicación con el servicio web.

Os dejo el ejemplo de la clase de datos Usuario, esto es una lata pero ya sabéis que hablamos de Java así que tendremos que picar algo más de código, Microsoft nos malacostumbra a hacerlo todo con el ratón:

package jcantos.demo.entidades;

import java.util.Hashtable;

import org.ksoap2.serialization.KvmSerializable;
import org.ksoap2.serialization.PropertyInfo;

public class Usuario implements KvmSerializable {
    private int idUsuario;
    private String nombre;
    private String email;
    private String username;
    private String password;

    public Usuario(){
        PI_idUsuario.setName("IdUsuario");
        PI_nombre.setName("Nombre");
        PI_email.setName("Email");
        PI_username.setName("Username");
        PI_password.setName("Password");

        PI_idUsuario.setType(PropertyInfo.INTEGER_CLASS);
        PI_nombre.setType(PropertyInfo.STRING_CLASS);
        PI_email.setType(PropertyInfo.STRING_CLASS);
        PI_username.setType(PropertyInfo.STRING_CLASS);
        PI_password.setType(PropertyInfo.STRING_CLASS);
    }
    public void setIdUsuario(int idUsuario) {
        this.idUsuario = idUsuario;
    }

    public int getIdUsuario() {
        return idUsuario;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getNombre() {
        return nombre;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getEmail() {
        return email;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUsername() {
        return username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword() {
        return password;
    }
    private static PropertyInfo PI_idUsuario = new PropertyInfo();
    private static PropertyInfo PI_nombre = new PropertyInfo();
    private static PropertyInfo PI_email = new PropertyInfo();
    private static PropertyInfo PI_username = new PropertyInfo();
    private static PropertyInfo PI_password = new PropertyInfo();
    private static PropertyInfo[] PI_PROP_ARRAY =
    {
        PI_idUsuario,
        PI_nombre,
        PI_email,
        PI_username,
        PI_password
    };

    @Override
    public Object getProperty(int param) {
        Object object = null;
        switch(param)
        {
            case 0 : object = new Integer(idUsuario);break;
            case 1 : object = nombre;break;
            case 2 : object = email;break;
            case 3 : object = username;break;
            case 4 : object = password;break;
        }
        return object;    }

    @Override
    public int getPropertyCount() {
        return 5;
    }

    @Override
    public void getPropertyInfo(int param, Hashtable arg1, PropertyInfo propertyInfo) {
        switch(param){
            case 0:
                propertyInfo.type = PropertyInfo.INTEGER_CLASS;
                propertyInfo.name = "IdUsuario";
                break;          
            case 1:
                propertyInfo.type = PropertyInfo.STRING_CLASS;
                propertyInfo.name = "Nombre";
                break;          
            case 2:
                propertyInfo.type = PropertyInfo.STRING_CLASS;
                propertyInfo.name = "Email";
                break;          
            case 3:
                propertyInfo.type = PropertyInfo.STRING_CLASS;
                propertyInfo.name = "Username";
                break;          
            case 4:
                propertyInfo.type = PropertyInfo.STRING_CLASS;
                propertyInfo.name = "Password";
                break;          
        }
    }

    @Override
    public void setProperty(int param, Object obj) {
        switch(param)
        {
            case 0  : idUsuario     = ((Integer)obj).intValue(); break;
            case 1  : nombre     = (String)obj; break;
            case 2  : email     = (String)obj; break;
            case 3  : username     = (String)obj; break;
            case 4  : password     = (String)obj; break;
        }  
    }
}

Me parece algo tosco para indicar a una clase que es Serializable, en .NET simplemente marcaríamos como atributo [Serializable] pero bueno “eso es así”. Y ahora os dejo la clase correspondiente al consumo del web services.

package jcantos.demo.utiles;

import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

import android.util.Log;
import jcantos.demo.entidades.*;

public class Seguridad {
    private static final String METHOD_NAME = "EsUsuarioValido";
    private static final String NAMESPACE = "
http://tempuri.org/";
    private static final String URL = "http://www.jcantos.net/webservice.asmx"; //sustituir esta URL por el sitio definitivo
    private static final String SOAP_ACTION = "
http://tempuri.org/EsUsuarioValido";

    private static Usuario usuario=null;
    public static Usuario EsUsuarioValido(String username, String password){
        Usuario reply=null;
        try {
             SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
             request.addProperty("username", username);
             request.addProperty("password", password);
             SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
             envelope.dotNet=true;
             envelope.setOutputSoapObject(request);
             HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
             androidHttpTransport.call(SOAP_ACTION, envelope);
             SoapObject result = (SoapObject)envelope.getResponse();
             if (result!=null){
                    reply=new Usuario();
                 reply.setIdUsuario(Integer.parseInt(result.getProperty("IdUsuario").toString()));
                 reply.setNombre(result.getProperty("Nombre").toString());
                 reply.setEmail(result.getProperty("Email").toString());
                 reply.setUsername(username);
                 reply.setPassword(password);
             }
         } catch (Exception e) {
             Log.e("Servicio_Web",e.getMessage());
         }
        return reply;
    }

    public static void setUsuario(Usuario usuario) {
        Seguridad.usuario = usuario;
    }

    public static Usuario getUsuario() {
        return usuario;
    }
    public static boolean EsLogueado(){
        return Seguridad.usuario!=null;
    }
}

Me parece un tema interesantísimo, imaginaros las posibilidades que se nos abre y el abanico de oportunidades para integrar vuestros cacharritos con aplicaciones ya desarrolladas, no solamente en .NET sino cualquier otro lenguaje JAVA por ejemplo,……¿quién nos quita que pudiésemos elaborar un servicio web a modo de gateway que expusiese aquellos métodos que quisieran ser consumidos por un dispositivo móvil?. Y no solo hablamos a modo de consulta, aquí os dejo un ejemplo en el que podemos actualizar la clave de un usuario una vez que la autenticación se ha realizado con exito, básado en el ejemplo anterior.

Editando y actualizando objetos de negocio desde el dispositivo móvil

Supongamos que un usuario ha realizado una operación de login con éxito y desea cambiar o actualizar su perfil, informando al sistema central del cambio de su correo electrónico junto con la actualización de su nueva contraseña. Para ello podríamos tener una clase tal que esta:

package jcantos.demo.cad;

import jcantos.demo.entidades.Usuario;

import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.PropertyInfo;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

import android.util.Log;

public class CADUsuario {
    private static final String METHOD_NAME = "ActualizarUsuario";
    private static final String NAMESPACE = "
http://tempuri.org/";
    private static final String URL = "http://www.jcantos.net/webservice.asmx";
    private static final String SOAP_ACTION = "http://tempuri.org/ActualizarUsuario";

    public void ActualizarUsuario(Usuario usuario){
        try {
             SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
             PropertyInfo pi=new PropertyInfo();
             pi.setName("usuario");
             pi.setValue(usuario);
             pi.setType(usuario.getClass());
             request.addProperty(pi);
             SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
             envelope.dotNet=true;
             envelope.setOutputSoapObject(request);
             envelope.addMapping(NAMESPACE, "Usuario", usuario.getClass());
             HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
             androidHttpTransport.setXmlVersionTag("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
             androidHttpTransport.debug=false;
             androidHttpTransport.call(SOAP_ACTION, envelope);
             envelope.getResponse();
         } catch (Exception e) {
             Log.e("Servicio_Web",e.getMessage());
         }
    }
}

Como veis las posibilidades son infinitas, así que una vez más que la técnica no coarte las necesidades del cliente. Un abrazo a todos y nos vemos en el próximo post.

jueves, 17 de junio de 2010

Report Viewer 10 y ASP.NET en Visual Studio 2010, paso a paso

 

logo_SQL

Como no podía ser de otra manera nos afrontamos a un nuevo reto, la generación de informes elevada a la máxima expresión, SQL Server 2008 Express R2, Reporting Services, y Visual Studio 2010.

El motivo de este post viene derivado de una necesidad del cliente como alternativa a Crystal Reports y sus costes de licencia asociados. Estudiaremos paso paso como instalar todas las distribuciones necesarias para la ejecución de informes en Visual Studio 2010. Los productos que mencionaremos a continuación son gratuitos con ciertas restricciones que también comentaremos.

Descarga de paquetes

Comenzaremos por la descarga de todos los paquetes necesarios para el desarrollo y la integración de informes con Reporting Services, estos son los siguientes:

Una vez descargados e instalados todos los paquetes, nos disponemos a abrir Visual Studio 2010 y agregamos un nuevo proyecto de aplicación web ASP.NET. Llegado a este momento es importante destacar que VS2010 no incluye la versión 10 de Report Viewer sino la 9, por lo tanto será necesario añadir a nuestro cuadro de herramientas la última versión.

Agregando referencias al proyecto

Creamos una carpeta en nuestro proyecto y la llamamos “librerias”, en esta copiamos tres DLLs que se encuentran ubicadas en el GAC_MSIL del directorio c:\windows\assembly, todas ellas en su versión 10:

  • Microsoft.ReportViewer.Common.dll
  • Microsoft.ReportViewer.ProcessingObjectModel.dll
  • Microsoft.ReportViewer.WebForms.dll

Posteriormente añadimos las referencias a nuestro proyecto web ASP.NET. Este paso es interesante tenerlo en cuenta para realizar un futuro despliegue en el servidor de producción o subir el proyecto a algún repositorio de código fuente, de esta manera tendremos todas las dependencias bien guardaditas en el árbol del proyecto y así evitaremos quebraderos de cabeza.

Remozando Visual Studio 2010

Vamos a ello, nos posicionamos sobre la ventana de cuadro de herramientas y añadimos una nueva ficha a la que podemos llamar “Reports”, de seguido elegimos los elementos y seleccionamos el control ReportViewer deMicrosoft.ReportViewer.WebForms.dll, a partir de este momento ya podemos arrastrar y soltar el control ReportViewer en cualquiera de nuestras páginas aspx. La nueva versión de este control necesita de un ScriptManager para su funcionamiento por lo que podríamos añadirlo a nuestra página Maestra para que estuviera disponible en cualquier momento.

Actualizando referencias

Hay que tener cuidadin porque al arrastrar el control ReportViewer a nuestra página, nos añade una directiva de control pero referenciado a la versión 9, tendremos que cambiarlo a la 10, además es nesesario retocar el web.config en los siguientes aspectos:

  • Actualizar versiones de assemblys de la 9 a la 10
  • Incluir el manejador AXD

Nos debería de quedar algo tal como esto:

<system.web>
<compilation debug="true" targetFramework="4.0">
<assemblies>
<add assembly="Microsoft.ReportViewer.WebForms, Version=10.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="Microsoft.ReportViewer.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="Microsoft.Build.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/></assemblies></compilation>

<httpHandlers>
<add verb="*" path="Reserved.ReportViewerWebControl.axd" type = "Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</httpHandlers>
</system.web>

Generando y publicando el informe con Report Builder 3.0

report_builder

Abrimos Report Builder, y creamos un nuevo informe basado en algunas de las plantillas existentes, como anteriormente hemos instalado la base de datos de ejemplo de AdventureWorks podemos tomar esta como origen de datos, para facilitaros la labor os dejo un RDL de ejemplo generado con esta herramienta. Este informe muestra un historial de precios de todos los productos.

Una vez generado tenemos que publicarlo en Reports Manager en esta dirección:http://localhost/Reports_SQLEXPRESS2008, es una herramienta web que nos instala Reporting Services y que nos permite gestionar todos los informes que vayamos publicando, asignar permisos y organizarlos entre otras cuestiones. Por si queréis cotillear la herramienta se nos instala en Microsoft SQL Server 2008 R2 >> Configuration Tools >> Reporting Services Configuration Manager, aquí podemos cambiar el directorio virtual y muchas más cosas que investigaremos en otro post. Por cierto esta herramienta solo funciona con Internet Explorer, en el resto de navegadores tiene algunos problemillas supongo se solucionarán en próximas versiones.

Para publicar el informe simplemente cargamos el archivo desde la opción habilitada a tal efecto y le ponemos un nombre identificativo, en nuestro casopreciosProductos, este nombre es el que corresponderá a la propiedadServerReport.ReportPath del control ReportViewer

Volvemos al IDE

Ya nos queda menos, vamos a añadir una nueva página donde arrastrar un control de tipo ReportViewer, no olvidamos actualizar las directivas del control a la versión 10 y en el Code Behind de nuestra página incluimos lo siguiente:

protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ReportViewer1.ProcessingMode = ProcessingMode.Remote;
ServerReport serverReport = ReportViewer1.ServerReport;
serverReport.ReportServerUrl = new Uri("http://localhost/ReportServer_SQLEXPRESS2008");
serverReport.ReportPath = "/preciosProductos";
}
}

El valor de ReportServerUrl corresponderá a la propiedad Virtual Directory de Web Service URL definido en Reporting Services Configuration Manager.

Conclusiones

Visto lo visto es una muy buena opción ante Crystal Reports, todo muy bien integrado y sobre todo simple, fácil e intuitivo. Aquí os dejo algunas conclusiones:

  • La versión Express de SQL Server 2008 R2 dispone de 10Gb para nuestra base de datos, lo cual es algo más que sobrado.
  • Reporting Services solo permite trabajar con motores de bases de datos SQL Server, si quisiéramos integrar informes de otros proveedores tendremos que adquirir la licencia superior Standard o Enterprise.
  • La herramienta de generación de informes Report Builder 3.0 está un poco verde, peta errores en el diseñador en algunas ocasiones pero normalmente estable.
  • Integración del control Report Viewer 10 con tecnología AJAX.
  • La potencia de la generación de informes con Report Builder 3.0 es impresionantemente productiva, por ejemplo la inclusión de informes de tablas cruzadas, mapas de color, indicadores, gráficos, de verdad que es para echarle un ratillo y ponernos a jugar con el detenidamente.
  • Por cierto, esto también funciona en Visual Studio 2008.

Aquí os dejo el proyecto de ejemplo para Visual Studio 2010 Express:ReportExample.rar

Related Posts Plugin for WordPress, Blogger...