Mostrando entradas con la etiqueta REPORT SERVER. Mostrar todas las entradas
Mostrando entradas con la etiqueta REPORT SERVER. Mostrar todas las entradas

jueves, 23 de julio de 2015

Excluir (Todo) de una consulta MDX generado por SQL DTE para report server

Situación

En un informe Report Server un parámetro utiliza un consulta que MDX que te ha generado el IDE DTE, Te aparece como una jerarquía en árbol donde la raíz es (Todo) y es seleccionable.
Quieres que no aparezca esta opción.

Solución:

Visualiza los data set ocultos, busca el que usa el parámetro, accede a la consulta MDX y cambia al final del código
.ALLMEMBERS ON ROWS
por
.CHILDREN ON ROWS

lunes, 20 de julio de 2015

No se puede establece un valor por defecto para un parametro de fecha en SQL REPORT Server

Síntomas

Intentas establecer un valor por defecto para un parámetro de informe y no te hace caso.
=dateadd(dateinterval.Year,-5,Now)

Solución

Tienes que formatear la fecha:
=cdate(format(dateadd(dateinterval.Year,-5,Now),"dd/MM/yyyy"))

jueves, 19 de febrero de 2015

Problemas Report Viewer web forms 10 con Internet Explorer 11

Síntomas

Algunos informe no se visualizan en Internet Explorer 11 (casualemente son los que en Chrome presentan una pequeña imagen no encontrada)

Solución:

Actualizar a la versión 11 el ReportViewer (SQL 2012) en las referencias del proyecto, los web forms, la caja de herramientas y el web.config.
El último post en esta página puede ser una buena lista de cosas a hacer (yo creo que lo conseguí con menos cosas):
http://stackoverflow.com/questions/21993721/reportviewer-ie-11

sábado, 25 de mayo de 2013

Una regla de color uniforme, redondeada, con cero y colores desplazados (y 5)

ENUNCIADO


En la entrada (1) de esta serie definimos los objetivos: una regla de color personalizada para un mapa generado mediante Reporting Services que fuera uniforme, redondeada, con cero y colores desplazados
En la entrada (2) vimos como podemos cargar un informe en memoria, para modificarlo y volcarlo en un control ReportViewer.
En la entrada (3) modificamos la definición XML del informe dejando pendientes las funciones que cálculan los rangos y colores.
En la entrada (4) calculamos los intervalo numéricos de la regla.


En está última entrada de la serie veremos como obtener una escala de colores con degradados centrados en el rango que contiene el valor cero.

En nuestro caso para simplificar y porque los colores son los mas intuitivos situaremos el centro en el color amarillo y degradaremos hacia el azulo por los valores negativos y hacia el rojo por los valores positivos.

En mi algoritmo el color amarillo se situará a la derecha o a la izquierda del cero según el número de rangos que haya en cada lado. El objetivo es aminorar la perdida de definición en los colores y visualizar siempre los tres colores, lo que podría no ocurrir si el cero estuviera en primera o última posición.

SOLUCIÓN

El algoritmo rellena la lista en tres pasos:
Primero por la parte derecha donde:
  • El azul está en máximos (#FF o 255) y decrece hasta casi #00.
  • El rojo y el verde crecen desde #00 hasta  "casi"  #FF
En este punto entra el amarillo (#FFFF00)
Luego la parte derecha:
  • El azul se mantiene en #00
  • El rojo que empieza en casi #FF deberá terminar en #FF (aunque puede sufrir una perdida de intensidad intermedia).
  • El verde empieza el casi #FF pero termina en #00
Obviamente el "truco" del algoritmo está en calcular:

  • El punto medio donde situamos el amarillo
  • Los pasos para cada color en los degradados derecho e izquierdo.
  • 'Operar' con colores para generar el string hexadecimal.


    ''' 
    ''' Calcula un rango de colores que empieza en azul, pasa por el verde en el valor cero y termina en el rojo
    ''' 
    ''' colección de extremos de rango que contiene el valor cero
    ''' devuelve la lista de colores
    ''' 
    Public Function coloresCero(rango As List(Of Double)) As List(Of String)
        Dim colores As New List(Of String)
        Dim posicionMedio = rango.IndexOf(0) + 1
        If posicionMedio > rango.Count / 2 Then posicionMedio -= 1
        Dim cuentaIniciales = posicionMedio - 1
        Dim cuentaFinales = rango.Count - posicionMedio

        Dim pasoInicial = 255 / cuentaIniciales
        Dim pasoMedio = 255 / (cuentaIniciales + 1)

        For i = 0 To cuentaIniciales - 1
            colores.Add(HexString(pasoMedio * i, pasoMedio * i, 255 - pasoInicial * i))
        Next
        colores.Add(HexString(255, 255, 0))
        Dim pasoFinal = 255 / cuentaFinales
        pasoMedio = 255 / cuentaFinales
        For i = 1 To cuentaFinales
            colores.Add(HexString((pasoFinal * i) + (pasoMedio * (cuentaFinales - i)),
                                  pasoMedio * (cuentaFinales - i), 0))
        Next
        Return colores
    End Function

    Function HexString(r As Integer, g As Integer, b As Integer) As String
        ' cálcula el string hexadecimal de color RGB
      Return "#" + hex(r) + hex(g) + hex(b)
    End Function

    Function hex(v As Integer) As String
        ' Obtiene el hexadecimal de un entero con cero por delante si hace falta
        Dim r = String.Format("{0:x}", v)
        If r.Length > 1 Then
            Return r
        Else
            Return "0" + r
        End If

    End Function

Reconozco que hay un poco de juego fácil en la elección de los colores, pero eran los que me pedían. ¡Más fácil habría sido elegir como central el verde¡.

Una regla de color uniforme, redondeada, con cero y colores desplazados (4)

ENUNCIADO

En la entrada (1) de esta serie definimos los objetivos: una regla de color personalizada para un mapa generado mediante Reporting Services que fuera uniforme, redondeada, con cero y colores desplazados
En la entrada (2) vimos como podemos cargar un informe en memoria, para modificarlo y volcarlo en un control ReportViewer.
En la entrada (3) modificamos la definición XML del informe dejando pendientes las funciones que cálculan los rangos y colores.
Durante la modificación de las reglas de color del informe utilizamos una función rangosMapa que desde una sentencia MDX devuelve la lista de valores doubles para construir los rangos de la regla.

Dicho de otra forma, los números que vemos aquí:

Recordemos que estos números deben satisfacer las condiciones:

  1. Cada rango de color debe contener aproximadamente al misma cantidad de países.
  2. Los extremos de cada rango deben redondearse con ceros a la derecha sin afectar la condición anterior.
  3. Si los valores inferior y exterior tienen distinto signo dos rangos de color deben tener como extremo el cero, con mínima afección sobre el requisito 1.

SOLUCIÓN

En nuestro ejemplo los datos provederán de un servidor SQL Server Analisys Services por lo que necesitaremos la librería Microsoft.AnalysisServices.adomdClient 
Imports Microsoft.AnalysisServices.AdomdClient

Dividimos nuestro trabajo en dos pasos: evaluar la sentencia MDX y calcular los rangos:

Public Function RangosMapa (s As String) As List(Of Double, numRangos as Integer)
    Dim lista As List(Of Double)= EvaluaMDX(s)
    Dim Rangos as List (Of Double)=RangosRedondos(lista, NumRangos, 0, True)
    Return (Rangos)
End Function

Evaluar la expresión MDX y almacenamos los resultados en una lista de doubles no tiene gran secreto:

 Public Function EvaluaMDX(s As String) As List(Of Double)
        Dim lista As New List(Of Double)
        Dim cn As New AdomdConnection(My.Settings.SSASConexion)
        cn.Open()
        Dim cmd As AdomdCommand = cn.CreateCommand()
        cmd.CommandText = evaluaSentencia(s)
        Dim lector = cmd.ExecuteReader()
        Do While lector.Read
            lista.Add(lector.GetDecimal(4)) 'el valor está en la posición 4
        Loop
        lector.Close()
        cn.Close()
        cn.Dispose()
        Return (lista)
End Function

El procedimiento siguiente será crear la lista de rangos.

El truco está en ordenar la lista y luego la recorrerla en saltos con un paso calculado desde el total de valores y numero de intervalos que queramos. En cada salto extremos el valor y lo añadimos a la lista de rangos Obtendríamos así una la lista que satisfacerla  la condición 1 más arriba..

Para el caso de que la lista contenga el cero (condición 2), el procedimiento es ligeramente distinto. Trabajamos con dos listas (positivos y negativos) y aplicamos el algoritmo anterior a cada uno de ellos con un numero de rangos en cada lista proporcional a la posición del cero.

Esta lista tendrá valores extraídos de la lista por ejemplo 9457,89. La siguiente tarea será redondear a un múltiplo de 10 sin alterar los valores incluidos en cada rango, por ejemplo 9000,00. pero deberemos hacer evitando afectar el número de valores entre los nuevos extremos.
Veamos el código que quizás sea lo mas simple:
 
   ''' 
    ''' Desde una lista de valores decimales obtiene otra lista de valores decimales que reparte de forma uniforme los valores.
    ''' 
    ''' Lista original
    ''' Número de rangos en los que se repartirá
    ''' número decimales
    ''' Lista de rangos
    ''' 
    Public Function RangosRedondos(lista As List(Of Double), numRangos As Integer, decimales As Integer, redondear As Boolean) As List(Of Double)
        Dim rangos As List(Of Double)
        lista.Sort() 'ordenamos
        'obtención del rango variable 
        If lista(0) <= 0 And lista(lista.Count - 1) >= 0 Then
            'cuando hay un cero deberá aparecer en un extremo de los rangos
            rangos = repartoConCero(lista, numRangos)
        Else
            rangos = reparto(lista, numRangos)
        End If
        'ahora vamos redondeando sin alterar el reparto
        If redondear Then rangos = RedondeoLista(rangos, lista, decimales)
        'y para terminar nos aseguramos de que los valores extremos esten incluidos
        rangos(0) -= 1
        rangos(rangos.Count - 1) += 1
        Return rangos
    End Function

    ''' 
    ''' Crea una lista de rangos que contienen aproximandamente la misma cantidad de elementos
    ''' 
    ''' lista ordenada de números decimales desde donde se genera
    ''' numero de rangos
    ''' lista con numrangos + 1 valores decimales que definen los rangos
    ''' 
    Private Function reparto(lista As List(Of Double), numRangos As Integer) As List(Of Double)
        Dim paso As Decimal = lista.Count / numRangos
        Dim rangos As New List(Of Double)
        rangos.Add(lista(0))
        For i = 1 To numRangos - 1
            rangos.Add(lista(Math.Round(i * paso, System.MidpointRounding.AwayFromZero)))
        Next
        rangos.Add(lista(lista.Count - 1))
        Return rangos
    End Function

    ''' 
    ''' Reparte los valores de una lista decimal que cruza el valor cero entre una serie de rangos.
    ''' Dos rangos compartiran el extremo con valor cero
    ''' 
    ''' lista ordenada de números decimales desde donde se genera
    ''' numero de rangos
    ''' lista con numrangos + 1 valores decimales que definen los rangos
    ''' 
    Private Function repartoConCero(lista As List(Of Double), numRangos As Integer) As List(Of Double)
        Dim Negativos = (From v In lista Where v < 0).ToList
        Dim Positivos = (From v In lista Where v >= 0).ToList
        Dim rangoNegativos = reparto(Negativos, numRangos * Negativos.Count / lista.Count)
        Dim rangoPositivos = reparto(Positivos, numRangos * Positivos.Count / lista.Count)
        If (-rangoNegativos(rangoNegativos.Count - 1)) < rangoPositivos(0) Then
            'el mayor número negativo está mas próximo a cero que el menor positivo
            rangoNegativos(rangoNegativos.Count - 1) = 0
            rangoPositivos.RemoveAt(0)
        Else
            rangoPositivos(0) = 0
            rangoNegativos.RemoveAt(rangoNegativos.Count - 1)
        End If
        Dim resultado As New List(Of Double)
        resultado.AddRange(rangoNegativos)
        resultado.AddRange(rangoPositivos)
        Return resultado
    End Function

    ''' 
    ''' Redondea todo lo posible los valores del rango.
    ''' 
    ''' rango a redondear
    ''' lista que controla el rango
    ''' número de decimales a conservar
    ''' 
    ''' 
    Private Function RedondeoLista(rangos As List(Of Double), lista As List(Of Double), decimales As Integer) As List(Of Double)
        For i = 1 To rangos.Count - 2
            Dim limiteRango = rangos(i)
            'calculamos la diferencia entre el último valor en el rango anterior y el primero del siguiente
            Dim difSupInf = (From v In lista Where v >= limiteRango).Min - (From v In lista Where v < limiteRango).Max
            'calculamos el primer digito no significativo mediante la parte entera del logaritmo en base 10
            Dim factorDigitos = 10 ^ Math.Floor(Math.Log10(difSupInf) + decimales - 1) 'le sumamos los decimales (ver linea siguiente)
            Dim factorDecimales = 10 ^ decimales
            limiteRango *= factorDecimales 'estamos trasladando los decimales a la parte entera.
            limiteRango = Math.Floor(limiteRango / factorDigitos) * factorDigitos
            rangos(i) = limiteRango / factorDecimales
        Next
        Return rangos
    End Function

Antes de terminar llamar la atención sobre dos parámetros que no he comentado:

  • decimales: que permite establecer el número de decimales con los que trabajamos en el redondeo.
  • redondeo: por si en un momento preferimos obtener la lista sin redondeo.
La próxima y última entrada de esta serie definirá una lista de colores para el rango calculado si este contuviera el "cero".

Una regla de color uniforme, redondeada, con cero y colores desplazados (2)

ENUNCIADO:

En la primera entrada de esta serie presentamos nuestro objetivo: Personalizar la regla de colores en un mapa incluido en un informe SSRS de forma que los valores y colores cumplieran ciertos requisitos.

El diseñador de informes de MS (BIDS) no nos permite definir reglas de color automáticas para nuestros requisitos, deberemos utilizar intervalos y colores personalizados.

Dado que esta personalización depende de los valores de la consulta no puede estar establecida en el informe, y deberemos modificar este cada vez que vayamos a visualizarlo.

En esta entrada vamos a ver como cargar un informe desde el servidor SSRS en nuestro programa cliente, modificar el XML e inyectar el resultado en un control ReportViewer que finalmente solicitará al servidor el procesado.

SOLUCIÓN:

Partimos de que hemos creado una página web que contiene un control ReportViewer llamado Visor y que está configurado para remoteProcessing.
Usaremos la libreria ReportingServices2010 nos permite recuperar la definición de un informe  mediante los servicios Web de reporting services:

Imports mapasAduanas.Microsoft.SqlServer.ReportingServices2010

El esquema de nuestro proceso será:
 
Sub Main()
 Dim informe as byte()=cargaInforme(nombre as string) 'cargamos el informe
 Dim informeXML as system.XML.linq.xDocument= bytesToXML(informe)  'lo pasamos a XML
 informeXML=parametrizaMapa(informeXML, sentenciaMDX,10)  'Personalizamos e informe
 informe = XMLtoBytes(InformeXML)  'Volvemos a array de bytes
 Visor.ServerReport.LoadReportDefinition(informe) 'lo cargamos en el visor de informes.
 Visor.ServerReport.Refresh()
End Sub 

Deberemos importar la librería de acceso proxy al SSRS
Imports mapasAduanas.Microsoft.SqlServer.ReportingServices2010

Para poder cargar el informe:

    Private Function CargaInforme(nombre As String) As byte()
        Dim SSRS As New ReportingService2010
        SSRS.Credentials = (New MyReportServerCredentials).NetworkCredentials
        Dim informe = SSRS.GetItemDefinition(My.Settings.pathMapas + nombre)
        SSRS = Nothing
        Return doc
    End Function

Para conectarnos necesitamos suministrar una credenciales utilizando la clase myReportServerCredentials descrita en Autenticación Reporting Services.

La función getItemDefinition y  nuetra función CargaInforme nos devuelven una matriz de bytes que deberemos convertir en un documento system.XML.link.xDocument para poder editarla:
    Private Function bytesToXML(informe As byte()) As xDocument
        Dim stream = New System.IO.MemoryStream(informe)
        Dim lector = System.Xml.XmlReader.Create(stream)
        Dim doc As XDocument = XDocument.Load(lector, LoadOptions.None)
        Return doc
    End Function

Cuando hayamos terminado de personalizar este documento deberemos volverlo a convertir en matriz de bytes.

   Private Function XMLBytes(doc As XDocument) As System.IO.Stream
        Dim stream As New System.IO.MemoryStream
        Dim escritor = System.Xml.XmlWriter.Create(stream)
        doc.Save(escritor)
        escritor.Close()
        stream.Position = 0
        Return stream
    End Function

En la siguiente entrada editaremos el XML del informe usando linq-to-xml.

viernes, 24 de mayo de 2013

Una regla de color uniforme, redondeada, con cero y colores desplazados (1)

ENUNCIADO

Desde una consulta que puede devolver valores negativos y positivos para todos los países del mundo quieres generar un mapa geográfico cuyos colores reflejen estos valores con una regla de color basada en tres colores: color inferior, color medio y color superior con degradados desde el color medio al inferior y desde el color medio al superior.
Hasta aquí todo se puede resolver con los mapas del servidor de informes del Microsoft (SSRS). Pero además deben satisfacerse los siguientes requisitos:
  1. Uniforme: Cada rango de color debe contener aproximadamente la misma cantidad de países.
  2. Redondeada: Los extremos de cada rango deben redondearse con ceros a la derecha sin afectar la condición anterior.
  3. Con cero: Si los valores inferior y exterior tienen distinto signo dos rangos de color deben tener como extremo el cero, con mínima afección sobre el requisito 1.
  4. Colores desplazados: En caso de visualizarse el cero el color medio se situara en un rango que contenga este valor entre sus extremos.
Veamos con un ejemplo que ilustra una caída de ventas mundial en los inicios de la crisis del 2008:




Fíjate en los números redondeados y en el cero que desplaza el color amarillo a la derecha de la barra.

UN COMENTARIO PREVIO

Este entrada reemplaza a algunas que escribí anteriormente, pero en esta nueva versión he mejorado los algoritmos y voy a redactarlo mas claramente. De hecho dedicaré cuatro entradas a presentar la solución.
Comencemos.

SOLUCIÓN

El mapa lo visualizaremos mediante un control reportViewer de  WindowsForms o de WebForms configurado para informes de servidor SSRS (remote processing).

Aunque partiremos de un informe diseñado BIDS y plublicado en el SSRS, el visor no utilizará este informe directamente. En su lugar nuestra aplicación cargará la definición XML, la modificará en memoria y la cargará en el visor de informes que solicitará el procesado al SSRS.

Las modificaciones consistirán en personalizar las reglas de color de color de la regla tanto en lo que atañe a los rangos de valor (extremo superior e inferior para cada uno) como en los colores aplicados en cada rango.

Para personalizar los rangos numéricos utilizaremos una función que nos devolverá un lista de "Double" que contendrá los extremos de estos rangos según los requisitos 1, 2, 3 dichos arriba. A esta función dedicaremos la tercera entrada. Otra función nos devolverá una lisa de colores centrada en el rango de cambio de signo.

Dedicaremos cuatro entradas a estos trabajos

Comentario:

Antes de seguir quería decir que desarrollar una aplicación para visualizar magnitudes sobre mapas me ha recordado una vez más que cuando ves resultados estadísticos tienes que estar muy atento a lo que te muestran y como te lo muestran.

Por ejemplo el mapa mas arriba visualizaba las  variaciones de ventas interanuales  entre octubre 2009 y octubre 2008.
Si en lugar del acumulado mostráramos el acumulado interanual de ventas en octubre de 2009, podriamos decir ¿ Crisis? ¿Qué crisis?:







viernes, 17 de mayo de 2013

Repetir las filas de encabezados en informes Microsoft

Enunciado

¿Cómo hacer que se repitan las filas de encabezado en tablix de report server?

Respuesta

En el panel de grupos (abajo) activa la vista avanzada
(La vista avanzada se consigue desplegando una flecha mínima a la derecha del panel que incluye los titulos grupo de filas y grupos de columnas ¡!)
Entonces veras unos grupos de filas (static) que corresponden a tus encabezados (si hay varios).
Selecciona uno.
Cambia en el panel de propiedades a la derecha:
KeepWithGroup: After
RepeatOnNewPage: True

viernes, 10 de mayo de 2013

Generar miniaturas de archivos TIF, JPG, PDF,...

Enunciado

Desarrollas una aplicación donde los usuarios deben subir archivos varios gráficos en diversos formatos gráficos (JPG, TIF,...etc), así como archivos PDF.
Quieres devolverles un informe en PDF con los archivos y las informaciones asociadas.
Sin embargo no los informes de Microsoft no permiten el uso de muchos de los tipos de archivos o estos tienen excesivo peso lo que repercute en la generación y peso mismo del informe.

Solución

Genera las miniaturas en el momento en que suban el archivo, para ello utiliza http://imageresizing.net que puedes instalar mediante NuGet.
Para usarla lee esta página: http://imageresizing.net/docs/howto/upload-and-resize
Para que pueda leer y procesar PDFs añádele el plugin PdfRenderer.
Para instalar este último plugin usa NuGet pero además debes editar el archivo web.config.

  
    
..... sigue el web config

El código será el mismo que para generar las miniaturas desde archivos gráficos. El plugin le añade la capacidad de leer PDFs.

Comentarios

Es una librería perfecta que tiene muchas mas aplicaciones. Es tan simple de usar que al principio no te lo crees.

viernes, 13 de julio de 2012

Descargar un informe desde Report Server en PDF

Enunciado 

Usando Visual Basic.NET, descarga un informe desde Report Server que puede ser utilizado para mandarlo por correo o generar una descarga por web.

Solución

Es tan simple como esto:
Private Function solicitaInformeSSRS(idRespuesta As Integer) As Byte()
        Dim informe As New ServerReport
        With informe
            .ReportServerUrl = New Uri(My.Settings.urlServerReport)
            .ReportPath = My.Settings.SSRSEncuestaPromocion
            .ReportServerCredentials = New MyReportServerCredentials
            .SetParameters(New ReportParameter("idRespuesta", idRespuesta))
            .Refresh()
            Return .Render("PDF")
        End With
End Function
Hay que utilizar la clase  MyReportServerCredentials que puedes encontare en mi post Autentificación Reporting Services

Usos:

Una vez descargado el array de byte, puedes utilizarlo para adjuntarlo en un email como un adjunto:

 msj.Attachments.Add(New Attachment(New System.IO.MemoryStream(informe),"informe.pdf")) 
O entregarlo en el stream de salida de una página (descarga del PDF):
Response.Clear()
Response.ContentType = "Application/PDF"
Response.AddHeader("content-disposition", String.Format("attachment; filename={0}.PDF", "archivo.PDF"))
Response.BinaryWrite(bytes)

Comentario:

Usar informes del servidor resulta mas simple, y no digamos ya si existen subinformes, ver Subinformes RDLC en Report cargado dinámicamente.

domingo, 17 de junio de 2012

Una escala de colores "redonda" para los mapas en Reporting Services

Enunciado

Las formas automáticas para definir la escala de colores en los mapas de reporting services tienen un defecto: los máximos y mínimos de cada rango pueden ser algo así como:

  • De 567 a 1002
  • De 1002 a 3141    
  • De 3141 a 9999
  • De 9999 a 20010
En este post copio un código que genera una escala:

  • De 500 a 1000
  • De 1000 a 3000    
  • De 3000 a 10000    
  • De 10000 a 30000
Cada color contiene "mas o menos" la misma cantidad de elementos.

Solución

Hay que hacer varias cosas:
  1. Evaluar la sentencia del informe (en mi código una sentencia MDX, pero no es necesario podría ser SQL)
  2. Para que haya la misma cantidad de países en cada color usamos percentiles. En el código que muestro hay diez colores, por tanto uso deciles (uso un código similar al de mi post Cuartiles en SSAS, pero es aplicable con otras formas de obtener los datos, i.e. SQL)
  3. A medida que los obtenemos redondeamos el dígito mas alto esto es de 9.999.999 "redondeamos a 10.000.000 y lo guardamos.
  4. Cuando terminamos, comprobamos si hay dos valores iguales y lo resolvemos(lo puede haber provocado el redondeo). Por ejemplo 10000 a 20000,  20000 a 20000, 20000 a 30000 pasaría a ser 10000 a 20000 20000 a 25000 25000 a 30000.
  5. Modificamos la definición del informe (ver mi post sobre cómo hacerlo)
Bien, este es el código:
Public Function RangosMapa(s As String) As List(Of Double)
        Dim r As New List(Of Double)
        Dim isov As String()
        If Ambito = "M" Then
            isov = IsoEsriMundo 'lista de ISO2 paises del mundo
        Else
            isov = IsoEsriEuropa 'Lista de ISO2 paises de Europa
        End If
        Dim lista As New List(Of Double)

        'Calculamos la sentencia en el informe
        Dim cn As New AdomdConnection(My.Settings.SSASConexion)
        cn.Open()
        Dim cmd As AdomdCommand = cn.CreateCommand()
        cmd.CommandText = evaluaSentencia(s)

        Dim lector = cmd.ExecuteReader()
        Do While lector.Read
            If isov.Contains(lector.GetString(0)) Then lista.Add(lector.GetDecimal(4))
        Loop
        lector.Close()
        cn.Close()
        cn.Dispose()

        'recorremos lo deciles
        lista.Sort()
        r.Add(Piso(lista(0)))
        Dim paso = lista.Count / 10
        For i = paso To lista.Count - 2 Step paso
            Dim valor = lista(Math.Round(i) - 1)
            Dim redondo = Redondeo(valor)
            If redondo > r(r.Count - 1) Then
                r.Add(Redondeo(lista(Math.Round(i) - 1)))
            Else
                r.Add(Techo(valor))
            End If
        Next
        r.Add(Techo(lista(lista.Count - 1)))
        'Si hay valores duplicados partimos por la mitad.
        Dim cambio As Boolean
        Do
            cambio = False
            For i = 1 To r.Count - 1
                If r(i) = r(i - 1) Then
                    r(i) = (r(i - 1) + r(i + 1)) / 2
                    cambio = True
                End If
            Next
        Loop Until Not cambio
        Return r
    End Function
'Funciones de redondeo
    Public Function CalculoFactor10(n As Double) As Integer
        Return Math.Pow(10, Math.Round(Math.Log10(n)))
    End Function
    Public Function Piso(n As Double) As Double
        If RedondeoSuperior Then
            Dim factor10 = CalculoFactor10(n)
            Return Math.Floor(n / factor10) * factor10
        Else
            Return Math.Floor(n)
        End If

    End Function

    Public Function Techo(n As Double) As Double
        If RedondeoSuperior Then
            Dim factor10 = CalculoFactor10(n)
            Return Math.Ceiling(n / factor10) * factor10
        Else
            Return Math.Ceiling(n)
        End If
    End Function

    Public Function Redondeo(n As Double) As Double
        If RedondeoSuperior Then
            Dim factor10 = CalculoFactor10(n)
            Return Math.Round(n / factor10) * factor10
        Else
            Return Math.Round(n)
        End If
    End Function
'parametrización del mapa
 Public Function ParametrizaMapa(informe As XDocument, rangosMapa As List(Of Double)) As XDocument
        Dim df = informe.Root.Name.Namespace
        Dim mapa = informe.Root.Element(
            df + "ReportSections").Element(
            df + "ReportSection").Element(
            df + "Body").Element(
            df + "ReportItems").Element(
            df + "Map")
        Dim capa = mapa.Element(df + "MapLayers").Element(df + "MapPolygonLayer")
        capa.Element(df + "MapPolygons").ReplaceNodes(Poligonos.Nodes)
        Dim reglasColor = capa.Element(df + "MapPolygonRules").Element(df + "MapColorRangeRule")
        Dim culturaUSA = New System.Globalization.CultureInfo("en-US")
        reglasColor.Element(df + "DistributionType").Value = "Custom"
        Dim i = 0
        For Each elemento In reglasColor.Element(df + "MapBuckets").Elements
            elemento.Element(df + "StartValue").Value = rangosMapa(i).ToString(culturaUSA.NumberFormat)
            elemento.Element(df + "EndValue").Value = rangosMapa(i + 1).ToString(culturaUSA.NumberFormat)
                i += 1
        Next
        Return informe
    End Function
Si alguien necesita mas detalles, estoy a su disposición

Autentificación Reporting Services en VB.NET

Enunciado

Construir una clase para autentificarse ante un servidor de informes de Microsoft (SSRS) puede ser útil para:
  1. Utilizar el visor de informes con Informes de servidor (processingMode=Remote)
  2. Utilizar servicios web para Cargar y modificar la definición de informes

Solución

Crearemos la siguiente clase:>

Imports System.Net
Imports System.Security.Principal
Imports Microsoft.Reporting.WebForms
 _
Public NotInheritable Class MyReportServerCredentials
    Implements IReportServerCredentials

    Public ReadOnly Property ImpersonationUser() As WindowsIdentity _
            Implements IReportServerCredentials.ImpersonationUser
        Get

            'Use the default windows user.  Credentials will be
            'provided by the NetworkCredentials property.
            Return Nothing

        End Get
    End Property

    Public ReadOnly Property NetworkCredentials() As ICredentials _
            Implements IReportServerCredentials.NetworkCredentials
        Get

            'Read the user information from the web.config file.  
            'By reading the information on demand instead of storing 
            'it, the credentials will not be stored in session, 
            'reducing the vulnerable surface area to the web.config 
            'file, which can be secured with an ACL.

            'User name
            Dim userName As String = My.Settings.usuario

            If (String.IsNullOrEmpty(userName)) Then
                Throw New Exception("Missing user name from web.config file")
            End If

            'Password
            Dim password As String = My.Settings.password

            If (String.IsNullOrEmpty(password)) Then
                Throw New Exception("Missing password from web.config file")
            End If

            'Domain
            Dim domain As String = My.Settings.dominio

            If (String.IsNullOrEmpty(domain)) Then
                Throw New Exception("Missing domain from web.config file")
            End If

            Return New NetworkCredential(userName, password, domain)

        End Get
    End Property

    Public Function GetFormsCredentials(ByRef authCookie As Cookie, _
                                        ByRef userName As String, _
                                        ByRef password As String, _
                                        ByRef authority As String) _
                                        As Boolean _
            Implements IReportServerCredentials.GetFormsCredentials

        authCookie = Nothing
        userName = Nothing
        password = Nothing
        authority = Nothing

        'Not using form credentials
        Return False

    End Function

End Class

Para usarla en el visor de informes:

Protected Sub Page_Init(ByVal sender As Object, _
                              ByVal e As System.EventArgs) _
                              Handles Me.Init
        Visor.ServerReport.ReportServerUrl = New Uri(My.Settings.SSRS)
        Visor.ServerReport.ReportServerCredentials = _
            New MyReportServerCredentials()
    End Sub

Para usarla en los web Services

        Dim SSRS As New ReportingService2010
        SSRS.Credentials = (New MyReportServerCredentials).NetworkCredentials
        Dim informe = SSRS.GetItemDefinition(My.Settings.mapas + nombre)
        SSRS = Nothing

Cargar y modificar un informe de Report Server

Enunciado

En ocasiones puede resultar interesante leer un informe definido en el servidor de  Microsoft Reporting Services, modificar la definición y volver subirlo al servidor para visualizar en un webform Report viewer.
Redactaré mas adelante algunos ejemplos de aplicación, por el momento.

Solución

El problema se divide en tres partes:

  1. Leer la definición del informe.
  2. Modificar el XML de esta definición mediante Linq to XML
  3. Establecer la definición modificada en el report Viewer.

Leer la definición del informe desde el servidor


Hay que acceder a los servicios web de Reporting Services, para ello se crea una un clase proxy mediante WSDL.EXE y una clase con las credenciales necesarias para autentificarse.
Una vez hecho esto es relativamente fácil.
 Private Function CargaInforme(nombre As String) As XDocument
        Dim SSRS As New ReportingService2010
        SSRS.Credentials = (New MyReportServerCredentials).NetworkCredentials
        Dim informe = SSRS.GetItemDefinition(My.Settings.mapas + nombre)
        SSRS = Nothing
        Dim stream = New System.IO.MemoryStream(informe)
        Dim lector = System.Xml.XmlReader.Create(stream)
        Dim doc As XDocument = XDocument.Load(lector, LoadOptions.None)
        Return doc
    End Function

En breve, el servicio veb me devuelve un array de bytes, que traslado a un memory stream que a su vez me permite crear un xdocument.

Modificar la definición de informe

Esto es lo mas fácil, aunque a veces engorroso. Por ejemplo vamos a cambiar la definición del Dataset "datos" en el informe.
Dim df = doc.Root.Name.Namespace
            Dim xdatos = (From f In doc.Root.Element(df + "DataSets").Elements(df + "DataSet")).First(
            Function(x As XElement) x.Attribute("Name") = "Datos").Element(df + "Query").Element(df + "CommandText")
            xdatos.SetValue(s)
S es un string con la sentencia SQL, MDX o lo que quiera que utilices, por supuesto tiene que se coherente con el resto del informe en lo que atañe a nombres de campo, parametros,...etc

Visualizar el informe modificado en Report Viewer

Para hacerlo necesitaremos convertir nuestro Xdocument que contiene la definición en un stream:
 Private Function XMLBytes(doc As XDocument) As System.IO.Stream
        Dim stream As New System.IO.MemoryStream
        Dim escritor = System.Xml.XmlWriter.Create(stream)
        doc.Save(escritor)
        escritor.Close()
        stream.Position = 0
        Return stream
    End Function
Lo que sigue es asignar este stream al visor de informes, establecer los parámetros que hagan falta y refrescar
Visor.ServerReport.LoadReportDefinition(e.Informe)
Visor.ServerReport.SetParameters(New ReportParameter("miParametro", suValor))
Visor.ServerReport.Refresh()

miércoles, 31 de agosto de 2011

Desaparece la ventana de datos del informe en visual studio

Sintomas:
Probablemente por error cierras la ventana de datos de un informe RDLC (donde estan el exporador de servidores y las herramientas). No encuentras como abrirla de nuevo.
Solución:
Cuando estas en el informe fijate bien en el menú ver abajo del todo. Ahí tienes la opción.
¿por qué no está en el menú ventana? Misterios.